Update UI

Refactor and added new functioms
This commit is contained in:
AlphaX-Projects 2024-05-08 13:16:23 +02:00
parent 940f9f82f8
commit fa29ff4c43
357 changed files with 82113 additions and 83085 deletions

View File

@ -26,8 +26,8 @@ Easiest way to install the lastest required packages on Linux is via nvm.
``` source ~/.profile ``` (For Debian based distro) <br/> ``` source ~/.profile ``` (For Debian based distro) <br/>
``` source ~/.bashrc ``` (For Fedora / CentOS) <br/> ``` source ~/.bashrc ``` (For Fedora / CentOS) <br/>
``` nvm ls-remote ``` (Fetch list of available versions) <br/> ``` nvm ls-remote ``` (Fetch list of available versions) <br/>
``` nvm install v18.17.1 ``` (LTS: Hydrogen supported by Electron V27) <br/> ``` nvm install v20.11.1 ``` (LTS: Iron supported by Electron V30) <br/>
``` npm --location=global install npm@10.5.0 ``` <br/> ``` npm --location=global install npm@10.7.0 ``` <br/>
Adding via binary package mirror will only work if you have set the package path. You can do a node or java build via ports instead by downloading ports with portsnap fetch method. Adding via binary package mirror will only work if you have set the package path. You can do a node or java build via ports instead by downloading ports with portsnap fetch method.

View File

@ -1,28 +1,23 @@
const path = require('path') const path = require('path')
const uiCore = require('./core/ui-core.js') const uiCore = require('./core/ui-core.js')
const config = require('./config/config.js')
const pluginsController = require('./plugins/default-plugins.js')
const generateBuildConfig = uiCore('generate_build_config') const generateBuildConfig = uiCore('generate_build_config')
const build = uiCore('build') const build = uiCore('build')
const config = require('./config/config.js')
const pluginsController = require('./plugins/default-plugins.js')
const buildDefalutPlugins = pluginsController('build') const buildDefalutPlugins = pluginsController('build')
srcConfig = { srcConfig = {
...config.build, ...config.build,
options: { options: {
...config.build.options, ...config.build.options,
outputDir: path.join(__dirname, '/builtWWW'), outputDir: path.join(__dirname, '/builtWWW'),
sassOutputDir: path.join(__dirname, '/builtWWW/styles.bundle.css'), sassOutputDir: path.join(__dirname, '/builtWWW/styles.bundle.css')
} }
} }
const { buildConfig, inlineConfigs } = generateBuildConfig(srcConfig) const { buildConfig, inlineConfigs } = generateBuildConfig(srcConfig)
build(buildConfig.options, buildConfig.outputs, buildConfig.outputOptions, buildConfig.inputOptions, inlineConfigs) build(buildConfig.options, buildConfig.outputs, buildConfig.outputOptions, buildConfig.inputOptions, inlineConfigs).then(() => {
.then(() => { console.log("Building and Bundling Plugins")
console.log("Building and Bundling Plugins");
buildDefalutPlugins() buildDefalutPlugins()
}) })

View File

@ -1,14 +1,19 @@
let config = require('./default.config.js') let config = require('./default.config.js')
let userConfig = {} let userConfig = {}
try { try {
userConfig = require('./customConfig.js') userConfig = require('./customConfig.js')
} catch (e) { } catch (e) {
console.warn(e) console.warn(e)
console.warn('Error loading user config') console.warn('Error loading user config')
} }
const checkKeys = (storeObj, newObj) => { const checkKeys = (storeObj, newObj) => {
for (const key in newObj) { for (const key in newObj) {
if (!Object.prototype.hasOwnProperty.call(storeObj, key)) return if (!Object.prototype.hasOwnProperty.call(storeObj, key)) {
return
}
if (typeof newObj[key] === 'object') { if (typeof newObj[key] === 'object') {
storeObj[key] = checkKeys(storeObj[key], newObj[key]) storeObj[key] = checkKeys(storeObj[key], newObj[key])
@ -16,6 +21,7 @@ const checkKeys = (storeObj, newObj) => {
storeObj[key] = newObj[key] storeObj[key] = newObj[key]
} }
} }
return storeObj return storeObj
} }

View File

@ -4,10 +4,4 @@ const styles = require('./styles.config.js')
const build = require('./build.config.js') const build = require('./build.config.js')
const user = require('./user.config.js') const user = require('./user.config.js')
module.exports = { module.exports = { coin, styles, build, user, crypto }
coin,
styles,
build,
user,
crypto
}

View File

@ -1,5 +1,4 @@
const uiCore = require('../core/ui-core.js') const uiCore = require('../core/ui-core.js')
const defaultConfig = uiCore('default_config') const defaultConfig = uiCore('default_config')
module.exports = defaultConfig module.exports = defaultConfig

View File

@ -1,10 +1,11 @@
const user = require('./default.config.js').user const user = require('./default.config.js').user
module.exports = { module.exports = {
node: 0, // set to mainnet node: 0, // set to mainnet
server: { server: {
primary: { primary: {
port: 12388, // set as default UI port port: 12388, // set as default UI port
address: '0.0.0.0', // can specify an IP for a fixed bind address: '0.0.0.0' // can specify an IP for a fixed bind
}, }
}, }
} }

View File

@ -37,4 +37,5 @@ const styles = {
] ]
} }
} }
module.exports = styles module.exports = styles

View File

@ -3,11 +3,11 @@ const path = require('path')
const user = { const user = {
node: 0, node: 0,
nodeSettings: { nodeSettings: {
pingInterval: 30 * 1000, pingInterval: 30 * 1000
}, },
server: { server: {
writeHosts: { writeHosts: {
enabled: true, enabled: true
}, },
relativeTo: path.join(__dirname, '../'), relativeTo: path.join(__dirname, '../'),
primary: { primary: {
@ -16,27 +16,28 @@ const user = {
port: 12388, port: 12388,
directory: './src/', directory: './src/',
page404: './src/404.html', page404: './src/404.html',
host: '0.0.0.0', host: '0.0.0.0'
}, }
}, },
tls: { tls: {
enabled: false, enabled: false,
options: { options: {
key: '', key: '',
cert: '', cert: ''
}, }
}, },
constants: { constants: {
pollingInterval: 30 * 1000, // How long between checking for new unconfirmed transactions and new blocks (in milliseconds). pollingInterval: 30 * 1000, // How long between checking for new unconfirmed transactions and new blocks (in milliseconds).
workerURL: '/build/worker.js', workerURL: '/build/worker.js'
}, },
// Notification Settings (All defaults to true) // Notification Settings (All defaults to true)
notifications: { notifications: {
q_chat: { q_chat: {
playSound: true, playSound: true,
showNotification: true, showNotification: true
},
},
} }
}
}
module.exports = user module.exports = user

View File

@ -10,6 +10,7 @@ const checkKeys = (storeObj, newObj) => {
storeObj[key] = newObj[key] storeObj[key] = newObj[key]
} }
} }
return storeObj return storeObj
} }

View File

@ -6,6 +6,7 @@ html {
--plugback: #ffffff; --plugback: #ffffff;
--border: #d0d6de; --border: #d0d6de;
--border2: #dde2e8; --border2: #dde2e8;
--border3: #080808;
--copybutton: #707584; --copybutton: #707584;
--chat-group: #080808; --chat-group: #080808;
--chat-bubble: #9f9f9f0a; --chat-bubble: #9f9f9f0a;
@ -83,6 +84,7 @@ html[theme="dark"] {
--plugback: #0f1a2e; --plugback: #0f1a2e;
--border: #0b305e; --border: #0b305e;
--border2: #0b305e; --border2: #0b305e;
--border3: #767676;
--copybutton: #d0d6de; --copybutton: #d0d6de;
--chat-group: #ffffff; --chat-group: #ffffff;
--chat-bubble: #9694941a; --chat-bubble: #9694941a;

View File

@ -945,7 +945,16 @@
"gchange56": "Group Name To Search", "gchange56": "Group Name To Search",
"gchange57": "Private Group Name Not Found", "gchange57": "Private Group Name Not Found",
"gchange58": "Note that group name must be an exact match.", "gchange58": "Note that group name must be an exact match.",
"gchange59": "Show / Hide Ticker" "gchange59": "Show / Hide Ticker",
"gchange60": "Please enter an group name",
"gchange61": "Please enter an description",
"gchange62": "Are you sure to update this group?",
"gchange63": "On pressing confirm, the update group request will be sent!",
"gchange64": "Current Owner / New Owner",
"gchange65": "Only replace this address if you want to transfer the group!",
"gchange66": "Invalid Owner / New Owner Address",
"gchange67": "Group Update Successful!",
"gchange68": "Set Group Avatar"
}, },
"puzzlepage": { "puzzlepage": {
"pchange1": "Puzzles", "pchange1": "Puzzles",

View File

@ -1,11 +1,9 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en-us"> <html lang="en-us">
<head> <head>
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover"> <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="Description" content="Qortal Platform UI"> <meta name="Description" content="Qortal Platform UI">
<link rel="apple-touch-icon" sizes="57x57" href="/img/favicon/apple-icon-57x57.png"> <link rel="apple-touch-icon" sizes="57x57" href="/img/favicon/apple-icon-57x57.png">
<link rel="apple-touch-icon" sizes="60x60" href="/img/favicon/apple-icon-60x60.png"> <link rel="apple-touch-icon" sizes="60x60" href="/img/favicon/apple-icon-60x60.png">
<link rel="apple-touch-icon" sizes="72x72" href="/img/favicon/apple-icon-72x72.png"> <link rel="apple-touch-icon" sizes="72x72" href="/img/favicon/apple-icon-72x72.png">
@ -20,37 +18,30 @@
<link rel="icon" type="image/png" sizes="96x96" href="/img/favicon/favicon-96x96.png"> <link rel="icon" type="image/png" sizes="96x96" href="/img/favicon/favicon-96x96.png">
<link rel="icon" type="image/png" sizes="16x16" href="/img/favicon/favicon-16x16.png"> <link rel="icon" type="image/png" sizes="16x16" href="/img/favicon/favicon-16x16.png">
<link rel="manifest" href="/img/favicon/manifest.json"> <link rel="manifest" href="/img/favicon/manifest.json">
<meta name="msapplication-TileColor" content="var(--white)"> <meta name="msapplication-TileColor" content="var(--white)">
<meta name="msapplication-TileImage" content="/img/favicon/ms-icon-144x144.png"> <meta name="msapplication-TileImage" content="/img/favicon/ms-icon-144x144.png">
<meta name="theme-color" content="var(--white)"> <meta name="theme-color" content="var(--white)">
<style> <style>
html { html {
--scrollbarBG: #a1a1a1; --scrollbarBG: #a1a1a1;
--thumbBG: #6a6c75; --thumbBG: #6a6c75;
overflow: hidden; overflow: hidden;
} }
*::-webkit-scrollbar { *::-webkit-scrollbar {
width: 11px; width: 11px;
} }
* { * {
scrollbar-width: thin; scrollbar-width: thin;
scrollbar-color: var(--thumbBG) var(--scrollbarBG); scrollbar-color: var(--thumbBG) var(--scrollbarBG);
} }
*::-webkit-scrollbar-track { *::-webkit-scrollbar-track {
background: var(--scrollbarBG); background: var(--scrollbarBG);
} }
*::-webkit-scrollbar-thumb { *::-webkit-scrollbar-thumb {
background-color: var(--thumbBG); background-color: var(--thumbBG);
border-radius: 6px; border-radius: 6px;
border: 3px solid var(--scrollbarBG); border: 3px solid var(--scrollbarBG);
} }
html, html,
body { body {
margin: 0; margin: 0;
@ -63,8 +54,6 @@
<link rel="stylesheet" href="/font/material-icons.css"> <link rel="stylesheet" href="/font/material-icons.css">
<link rel="stylesheet" href="/font/switch-theme.css"> <link rel="stylesheet" href="/font/switch-theme.css">
<title>Qortal UI</title> <title>Qortal UI</title>
<script> <script>
const checkTheme = localStorage.getItem('qortalTheme') const checkTheme = localStorage.getItem('qortalTheme')
if (checkTheme === 'dark') { if (checkTheme === 'dark') {
@ -119,21 +108,14 @@
</script> </script>
</head> </head>
<body> <body>
<app-styles></app-styles> <app-styles></app-styles>
<main> <main>
<noscript> <noscript>
You need to enable JavaScript to run this app. 😞 You need to enable JavaScript to run this app. 😞
</noscript> </noscript>
<main-app id="main-app"></main-app> <main-app id="main-app"></main-app>
</main> </main>
<script type="module" src="/build/es6/main.js"></script> <script type="module" src="/build/es6/main.js"></script>
</body> </body>
</html> </html>

View File

@ -23,6 +23,7 @@ function serverFactory(routes, address, port, tls) {
await this.server.start() await this.server.start()
delete this.startServer delete this.startServer
return this.server return this.server
} catch (e) { } catch (e) {
console.error(e) console.error(e)

View File

@ -12,7 +12,6 @@ const routesOptions = {
} }
const createRoutes = config => [ const createRoutes = config => [
{ {
method: 'GET', method: 'GET',
path: '/img/{param*}', path: '/img/{param*}',
@ -97,6 +96,7 @@ const createRoutes = config => [
delete response.config.user.tls delete response.config.user.tls
delete response.config.build delete response.config.build
return JSON.stringify(response) return JSON.stringify(response)
}, },
options: routesOptions options: routesOptions

View File

@ -1,5 +1,4 @@
const path = require('path') const path = require('path')
const createCommonRoutes = require('./createCommonRoutes.js') const createCommonRoutes = require('./createCommonRoutes.js')
const createPrimaryRoutes = (config, plugins) => { const createPrimaryRoutes = (config, plugins) => {
@ -131,7 +130,7 @@ const createPrimaryRoutes = (config, plugins) => {
return response return response
}, },
options: routesOptions options: routesOptions
}, }
) )

View File

@ -5,18 +5,16 @@ const createPrimaryRoutes = require('./routes/createPrimaryRoutes.js')
const createServer = (config, plugins) => { const createServer = (config, plugins) => {
this.start = async function () { this.start = async function () {
const primaryServer = new ServerFactory(createPrimaryRoutes(config, plugins), config.user.server.primary.host, config.user.server.primary.port, config.user.tls.enabled ? config.user.tls.options : void 0) const primaryServer = new ServerFactory(createPrimaryRoutes(config, plugins), config.user.server.primary.host, config.user.server.primary.port, config.user.tls.enabled ? config.user.tls.options : void 0)
primaryServer.startServer() primaryServer.startServer().then(server => {
.then(server => {
console.log(`Qortal UI Server started at ${server.info.uri} and listening on ${server.info.address}`) console.log(`Qortal UI Server started at ${server.info.uri} and listening on ${server.info.address}`)
}) }).catch(e => {
.catch(e => {
console.error(e) console.error(e)
}) })
} }
return this return this
} }
const serverExports = { const serverExports = {
createServer createServer
} }

View File

@ -1,54 +1,55 @@
import * as api from 'qortal-ui-crypto' import * as api from 'qortal-ui-crypto'
import mykey from './functional-components/mykey-page.js' import mykey from './functional-components/mykey-page'
'use strict'
export const checkApiKey = async (nodeConfig) => { export const checkApiKey = async (nodeConfig) => {
let selectedNode = nodeConfig.knownNodes[nodeConfig.node]
let selectedNode = nodeConfig.knownNodes[nodeConfig.node]; let apiKey = selectedNode.apiKey
let apiKey = selectedNode.apiKey;
// Attempt to generate an API key // Attempt to generate an API key
const generateUrl = "/admin/apikey/generate"; const generateUrl = '/admin/apikey/generate'
let generateRes = await api.request(generateUrl, { let generateRes = await api.request(generateUrl, {
method: "POST" method: 'POST'
}); })
if (generateRes != null && generateRes.error == null && generateRes.length >= 8) { if (generateRes != null && generateRes.error == null && generateRes.length >= 8) {
console.log("Generated API key"); console.log('Generated API key')
apiKey = generateRes;
apiKey = generateRes
// Store the generated API key // Store the generated API key
selectedNode.apiKey = apiKey; selectedNode.apiKey = apiKey
nodeConfig.knownNodes[nodeConfig.node] = selectedNode; nodeConfig.knownNodes[nodeConfig.node] = selectedNode
localStorage.setItem('myQortalNodes', JSON.stringify(nodeConfig.knownNodes)); localStorage.setItem('myQortalNodes', JSON.stringify(nodeConfig.knownNodes))
} } else {
else { console.log("Unable to generate API key")
console.log("Unable to generate API key");
} }
// Now test the API key // Now test the API key
let testResult = await testApiKey(apiKey); let testResult = await testApiKey(apiKey)
if (testResult === true) { if (testResult === true) {
console.log("API key test passed"); console.log('API key test passed')
} } else {
else { console.log('API key test failed')
console.log("API key test failed");
mykey.show(); mykey.show()
this.dispatchEvent( this.dispatchEvent(
new CustomEvent('disable-tour', { new CustomEvent('disable-tour', {
bubbles: true, bubbles: true,
composed: true composed: true
}), })
); )
} }
} }
export const testApiKey = async (apiKey) => { export const testApiKey = async (apiKey) => {
const testUrl = "/admin/apikey/test?apiKey=" + apiKey; const testUrl = '/admin/apikey/test?apiKey=' + apiKey
let testRes = await api.request(testUrl, {
method: "GET"
});
return testRes === true;
let testRes = await api.request(testUrl, {
method: 'GET'
})
return testRes === true
} }

View File

@ -1,5 +1,3 @@
import WebWorker from 'web-worker:./computePowWorkerFile.js'; import WebWorker from 'web-worker:./computePowWorkerFile.js'
// You can add any initialization or configuration for the Web Worker here export default WebWorker
export default WebWorker;

View File

@ -1,6 +1,9 @@
import {css, html, LitElement} from 'lit' import { html, LitElement } from 'lit'
import { connect } from 'pwa-helpers' import { connect } from 'pwa-helpers'
import {store} from '../store.js' import { store } from '../store'
import { appInfoStyles } from '../styles/core-css'
// Multi language support
import { translate } from '../../translate' import { translate } from '../../translate'
class AppInfo extends connect(store)(LitElement) { class AppInfo extends connect(store)(LitElement) {
@ -14,49 +17,7 @@ class AppInfo extends connect(store)(LitElement) {
} }
static get styles() { static get styles() {
return css` return [appInfoStyles]
* {
--mdc-theme-primary: rgb(3, 169, 244);
--paper-input-container-focus-color: var(--mdc-theme-primary);
}
.normal {
--mdc-theme-primary: rgb(3, 169, 244);
}
#profileInMenu {
flex: 0 0 100px;
padding:12px;
border-top: 1px solid var(--border);
background: var(--sidetopbar);
}
.info {
margin: 0;
font-size: 14px;
font-weight: 100;
display: inline-block;
width: 100%;
padding-bottom: 8px;
color: var(--black);
}
.blue {
color: #03a9f4;
margin: 0;
font-size: 14px;
font-weight: 200;
display: inline;
}
.black {
color: var(--black);
margin: 0;
font-size: 14px;
font-weight: 200;
display: inline;
}
`
} }
constructor() { constructor() {
@ -74,7 +35,6 @@ class AppInfo extends connect(store)(LitElement) {
${this._renderCoreVersion()} ${this._renderCoreVersion()}
<span class="info">${translate("appinfo.blockheight")}: ${this.nodeInfo.height ? this.nodeInfo.height : ''} <span class=${this.cssStatus}>${this._renderStatus()}</span></span> <span class="info">${translate("appinfo.blockheight")}: ${this.nodeInfo.height ? this.nodeInfo.height : ''} <span class=${this.cssStatus}>${this._renderStatus()}</span></span>
<span class="info">${translate("appinfo.peers")}: ${this.nodeInfo.numberOfConnections ? this.nodeInfo.numberOfConnections : ''} <span class="info">${translate("appinfo.peers")}: ${this.nodeInfo.numberOfConnections ? this.nodeInfo.numberOfConnections : ''}
<a id="pageLink"></a>
</div> </div>
` `
} }

File diff suppressed because it is too large Load Diff

View File

@ -1,25 +1,10 @@
import {css, html, LitElement} from 'lit' import { html, LitElement } from 'lit'
import { connect } from 'pwa-helpers' import { connect } from 'pwa-helpers'
import {store} from '../store.js' import { store } from '../store'
class MyElement extends connect(store)(LitElement) { class MyElement extends connect(store)(LitElement) {
static get properties () {
return {
}
}
static get styles () {
return css``
}
render () { render () {
return html` return html`<style></style>`
<style>
</style>
`
}
stateChanged (state) {
} }
} }

View File

@ -1,11 +1,13 @@
import {css, html, LitElement} from 'lit' import { html, LitElement } from 'lit'
import {store} from '../../store'
import { connect } from 'pwa-helpers' import { connect } from 'pwa-helpers'
import {translate} from '../../../translate' import { store } from '../../store'
import { parentEpml } from '../show-plugin' import { parentEpml } from '../show-plugin'
import { syncIndicator2Styles } from '../../styles/core-css'
import '@material/mwc-icon' import '@material/mwc-icon'
// Multi language support
import {translate} from '../../../translate'
class SyncIndicator extends connect(store)(LitElement) { class SyncIndicator extends connect(store)(LitElement) {
static get properties() { static get properties() {
return { return {
@ -18,6 +20,10 @@ class SyncIndicator extends connect(store)(LitElement) {
} }
} }
static get styles() {
return [syncIndicator2Styles]
}
constructor() { constructor() {
super() super()
this.blocksBehind = 0 this.blocksBehind = 0
@ -32,64 +38,6 @@ class SyncIndicator extends connect(store)(LitElement) {
this.hasOpened = false this.hasOpened = false
} }
static get styles() {
return css`
* {
--mdc-theme-text-primary-on-background: var(--black);
box-sizing: border-box;
}
:host {
box-sizing: border-box;
position: fixed;
bottom: 50px;
right: 25px;
z-index: 50000;
}
.parent {
width: 360px;
padding: 10px;
border-radius: 8px;
border: 1px solid var(--black);
display: flex;
align-items: center;
gap: 10px;
user-select: none;
background: var(--white);
}
.row {
display: flex;
gap: 10px;
width: 100%;
}
.column {
display: flex;
flex-direction: column;
gap: 10px;
width: 100%;
}
.bootstrap-button {
font-family: Roboto, sans-serif;
font-size: 16px;
color: var(--mdc-theme-primary);
background-color: transparent;
padding: 8px 10px;
border-radius: 5px;
border: none;
transition: all 0.3s ease-in-out;
}
.bootstrap-button:hover {
cursor: pointer;
background-color: #03a8f475;
}
`
}
render() { render() {
return html` return html`
${!this.hasCoreRunning ? html` ${!this.hasCoreRunning ? html`
@ -225,7 +173,7 @@ class SyncIndicator extends connect(store)(LitElement) {
this.dispatchEvent( this.dispatchEvent(
new CustomEvent('open-welcome-modal-sync', { new CustomEvent('open-welcome-modal-sync', {
bubbles: true, bubbles: true,
composed: true, composed: true
}) })
) )
} }
@ -257,4 +205,4 @@ class SyncIndicator extends connect(store)(LitElement) {
} }
} }
customElements.define('sync-indicator', SyncIndicator) window.customElements.define('sync-indicator', SyncIndicator)

View File

@ -1,16 +1,19 @@
import {css, html, LitElement} from 'lit'; import { html, LitElement } from 'lit'
import {driver} from 'driver.js'; import { connect } from 'pwa-helpers'
import 'driver.js/dist/driver.css'; import { store } from '../../store'
import '@material/mwc-icon'; import { setNewTab } from '../../redux/app/app-actions'
import '@polymer/paper-spinner/paper-spinner-lite.js'; import { tourComponentStyles } from '../../styles/core-css'
import '@vaadin/tooltip'; import { driver } from 'driver.js'
import '@material/mwc-button'; import 'driver.js/dist/driver.css'
import {get, translate} from '../../../translate'; import './tour.css'
import '@polymer/paper-dialog/paper-dialog.js'; import '@material/mwc-button'
import {setNewTab} from '../../redux/app/app-actions.js'; import '@material/mwc-icon'
import {store} from '../../store.js'; import '@polymer/paper-dialog/paper-dialog.js'
import {connect} from 'pwa-helpers'; import '@polymer/paper-spinner/paper-spinner-lite.js'
import './tour.css'; import '@vaadin/tooltip'
// Multi language support
import { get, translate } from '../../../translate'
class TourComponent extends connect(store)(LitElement) { class TourComponent extends connect(store)(LitElement) {
static get properties() { static get properties() {
@ -18,219 +21,87 @@ class TourComponent extends connect(store)(LitElement) {
getElements: { attribute: false }, getElements: { attribute: false },
dialogOpenedCongrats: { type: Boolean }, dialogOpenedCongrats: { type: Boolean },
hasViewedTour: { type: Boolean }, hasViewedTour: { type: Boolean },
disableTour: {type: Boolean} disableTour: { type: Boolean },
}; nodeUrl: { type: String },
address: { type: String }
}
}
static get styles() {
return [tourComponentStyles]
} }
constructor() { constructor() {
super(); super()
this.dialogOpenedCongrats = false; this.dialogOpenedCongrats = false
this._controlOpenWelcomeModal = this._controlOpenWelcomeModal = this._controlOpenWelcomeModal.bind(this)
this._controlOpenWelcomeModal.bind(this); this.hasName = false
this.hasName = false; this.nodeUrl = ''
this.nodeUrl = this.getNodeUrl(); this.address = ''
this.myNode = this.getMyNode();
this._disableTour = this._disableTour.bind(this) this._disableTour = this._disableTour.bind(this)
this.disableTour = false this.disableTour = false
} }
static get styles() { render() {
return css` return html`
* { <!-- Profile read-view -->
--mdc-theme-primary: rgb(3, 169, 244); ${this.dialogOpenedCongrats && this.hasViewedTour ? html`
--mdc-theme-secondary: var(--mdc-theme-primary); <paper-dialog class="full-info-wrapper" ?opened="${this.dialogOpenedCongrats}">
--mdc-theme-surface: var(--white); <h3>Congratulations!</h3>
--mdc-dialog-content-ink-color: var(--black); <div style="display:flex;gap:15px;justify-content:center;margin-top:10px">
box-sizing: border-box; ${translate("tour.tour13")}
color: var(--black); </div>
background: var(--white); <div style="display:flex;gap:15px;justify-content:center;margin-top:10px">
${translate("tour.tour14")}
</div>
<div class="accept-button" @click=${this.visitQtube}>
${translate("tour.tour15")}
</div>
<div style="width:100%;display:flex;justify-content:center;margin-top:10px">
<div class="close-button" @click=${() => { this.onClose() }}>
${translate("general.close")}
</div>
</div>
</paper-dialog>
` : ''}
`
} }
:host {
box-sizing: border-box;
position: fixed;
bottom: 25px;
right: 25px;
z-index: 50000;
}
.full-info-wrapper {
width: 100%;
min-width: 600px;
max-width: 600px;
text-align: center;
background: var(--white);
border: 1px solid var(--black);
border-radius: 15px;
padding: 25px;
box-shadow: 0px 10px 15px rgba(0, 0, 0, 0.1);
display: block !important;
}
.buttons {
display: inline;
}
.accept-button {
font-family: Roboto, sans-serif;
letter-spacing: 0.3px;
font-weight: 300;
padding: 8px 5px;
border-radius: 3px;
text-align: center;
color: var(--black);
transition: all 0.3s ease-in-out;
display: flex;
align-items: center;
gap: 10px;
font-size: 18px;
justify-content: center;
outline: 1px solid var(--black);
}
.accept-button:hover {
cursor: pointer;
background-color: #03a8f485;
}
.close-button {
font-family: Roboto, sans-serif;
letter-spacing: 0.3px;
font-weight: 300;
padding: 8px 5px;
border-radius: 3px;
text-align: center;
color: #f44336;
transition: all 0.3s ease-in-out;
display: flex;
align-items: center;
gap: 10px;
font-size: 18px;
width:auto;
}
.close-button:hover {
cursor: pointer;
background-color: #f4433663;
}
`;
}
_controlOpenWelcomeModal() {
this.isSynced = true
const seenWelcomeSync = JSON.parse(
localStorage.getItem('welcome-sync') || 'false'
);
if (this.hasName) return;
if (seenWelcomeSync) return;
if(!this.hasViewedTour) return
this.dialogOpenedCongrats = true;
}
openWelcomeModal() {
this.dispatchEvent(
new CustomEvent('send-tour-finished', {
bubbles: true,
composed: true,
})
);
const seenWelcomeSync = JSON.parse(
localStorage.getItem('welcome-sync') || 'false'
);
if (this.hasName) return;
if (seenWelcomeSync) return;
if(!this.isSynced) return
this.dialogOpenedCongrats = true;
}
_disableTour(){
this.disableTour = true
driver.reset()
}
connectedCallback() {
super.connectedCallback();
window.addEventListener(
'open-welcome-modal-sync',
this._controlOpenWelcomeModal
);
window.addEventListener(
'disable-tour',
this._disableTour
);
}
disconnectedCallback() {
window.removeEventListener(
'open-welcome-modal-sync',
this._controlOpenWelcomeModal
);
window.addEventListener(
'disable-tour',
this._disableTour
);
super.disconnectedCallback();
}
getNodeUrl() {
const myNode =
window.parent.reduxStore.getState().app.nodeConfig.knownNodes[
window.parent.reduxStore.getState().app.nodeConfig.node
]
return myNode.protocol + '://' + myNode.domain + ':' + myNode.port
}
getMyNode() {
return window.parent.reduxStore.getState().app.nodeConfig.knownNodes[
window.parent.reduxStore.getState().app.nodeConfig.node
]
}
async getName(recipient) {
try {
const endpoint = `${this.nodeUrl}/names/address/${recipient}`;
const res = await fetch(endpoint);
const getNames = await res.json();
if (Array.isArray(getNames) && getNames.length > 0) {
return getNames[0].name;
} else {
return '';
}
} catch (error) {
return '';
}
}
async firstUpdated() { async firstUpdated() {
this.getNodeUrl()
this.address = store.getState().app.selectedAddress.address this.address = store.getState().app.selectedAddress.address
const hasViewedTour = JSON.parse(
localStorage.getItem(`hasViewedTour-${this.address}`) || 'false' const hasViewedTour = JSON.parse(localStorage.getItem(`hasViewedTour-${this.address}`) || 'false')
); const name = await this.getName(this.address)
const name = await this.getName(this.address);
if (name) { if (name) {
this.hasName = true; this.hasName = true
} }
this.hasViewedTour = hasViewedTour;
this.hasViewedTour = hasViewedTour
if (!hasViewedTour) { if (!hasViewedTour) {
try { try {
if (name) { if (name) {
this.hasViewedTour = true; this.hasViewedTour = true
this.hasName = true; this.hasName = true
localStorage.setItem(`hasViewedTour-${this.address}`, JSON.stringify(true)) localStorage.setItem(`hasViewedTour-${this.address}`, JSON.stringify(true))
} }
} catch (error) { } catch (error) {
console.log({ error }); console.log({ error })
} }
} }
await new Promise((res) => { await new Promise((res) => {
setTimeout(() => { setTimeout(() => {
res(); res()
}, 1000); }, 1000)
}); })
if (!this.hasViewedTour && this.disableTour !== true) { if (!this.hasViewedTour && this.disableTour !== true) {
const elements = this.getElements(); const elements = this.getElements()
let steps = [
{ let steps = [{
popover: { popover: {
title: get("tour.tour6"), title: get("tour.tour6"),
description: ` description: `
@ -238,22 +109,24 @@ class TourComponent extends connect(store)(LitElement) {
<img style="height:40px;width:auto;margin:15px 0px;" src="/img/qort.png" /> <img style="height:40px;width:auto;margin:15px 0px;" src="/img/qort.png" />
</div> </div>
<div style="display:flex;gap:15px;align-items:center;margin-top:15px;"> <div style="display:flex;gap:15px;align-items:center;margin-top:15px;">
<div style="height:6px;width:6px;border-radius:50%;background:var(--black)"></div> <p style="margin:0px;padding:0px">${get("tour.tour7")}</p> <div style="height:6px;width:6px;border-radius:50%;background:var(--black)"></div>
<p style="margin:0px;padding:0px">${get("tour.tour7")}</p>
</div> </div>
<div style="display:flex;gap:15px;align-items:center;margin-top:15px;"> <div style="display:flex;gap:15px;align-items:center;margin-top:15px;">
<div style="height:6px;width:6px;border-radius:50%;background:var(--black)"></div> <p style="margin:0px;padding:0px">${get("tour.tour8")}</p> <div style="height:6px;width:6px;border-radius:50%;background:var(--black)"></div>
<p style="margin:0px;padding:0px">${get("tour.tour8")}</p>
</div> </div>
<div style="display:flex;gap:15px;align-items:center;margin-top:15px;margin-bottom:30px"> <div style="display:flex;gap:15px;align-items:center;margin-top:15px;margin-bottom:30px">
<div style="height:6px;width:6px;border-radius:50%;background:var(--black)"></div> <p style="margin:0px;padding:0px">${get("tour.tour9")}</p> <div style="height:6px;width:6px;border-radius:50%;background:var(--black)"></div>
<p style="margin:0px;padding:0px">${get("tour.tour9")}</p>
</div> </div>
`, `
// ... other options }
}, }]
},
]; const step2 = elements['core-sync-status-id']
const step2 = elements['core-sync-status-id']; const step3 = elements['tab']
const step3 = elements['tab']; const step4 = elements['checklist']
const step4 = elements['checklist'];
if (step2) { if (step2) {
steps.push({ steps.push({
@ -277,10 +150,11 @@ class TourComponent extends connect(store)(LitElement) {
<p style="margin:0px;padding:0px">${get("tour.tour4")}</p> <p style="margin:0px;padding:0px">${get("tour.tour4")}</p>
</div> </div>
`, `
},
});
} }
})
}
if (step3) { if (step3) {
steps.push({ steps.push({
element: step3, element: step3,
@ -288,31 +162,26 @@ class TourComponent extends connect(store)(LitElement) {
title: 'Tab View', title: 'Tab View',
description: ` description: `
<div style="display:flex;gap:15px;align-items:center;margin-top:15px;margin-bottom:30px"> <div style="display:flex;gap:15px;align-items:center;margin-top:15px;margin-bottom:30px">
<p style="margin:0px;padding:0px">${get("tour.tour10")} <p style="margin:0px;padding:0px">${get("tour.tour10")}</p>
</p>
</div> </div>
<div style="display:flex;gap:15px;align-items:center;margin-top:15px;"> <div style="display:flex;gap:15px;align-items:center;margin-top:15px;">
<span><img src="/img/addplugin.webp" style="height: 36px; width: 36px; padding-top: 4px;" /></span> <span><img src="/img/addplugin.webp" style="height: 36px; width: 36px; padding-top: 4px;" /></span>
<p style="margin:0px;padding:0px">You can also bookmark other Q-Apps and Plugins by clicking on the ${get( <p style="margin:0px;padding:0px">
'tabmenu.tm19' You can also bookmark other Q-Apps and Plugins by clicking on the ${get('tabmenu.tm19')} button
)} button</p> </p>
</div> </div>
`, `
},
});
} }
})
}
if (step4) { if (step4) {
steps.push( steps.push({ element: step4, popover: { title: get("tour.tour11"), description: get("tour.tour12")}})
{ this.hasViewedTour
element: step4,
popover: {
title: get("tour.tour11"),
description: get("tour.tour12"),
},
} }
);this.hasViewedTour
} let currentStepIndex = 0
let currentStepIndex = 0;
const driverObj = driver({ const driverObj = driver({
popoverClass: 'driverjs-theme', popoverClass: 'driverjs-theme',
showProgress: true, showProgress: true,
@ -321,25 +190,93 @@ class TourComponent extends connect(store)(LitElement) {
allowClose: false, allowClose: false,
onDestroyed: () => { onDestroyed: () => {
localStorage.setItem(`hasViewedTour-${this.address}`, JSON.stringify(true)) localStorage.setItem(`hasViewedTour-${this.address}`, JSON.stringify(true))
this.hasViewedTour = true; this.hasViewedTour = true
this.openWelcomeModal(); this.openWelcomeModal()
} }
}); })
driverObj.drive(); driverObj.drive()
} else { } else {
this.dispatchEvent( this.dispatchEvent(
new CustomEvent('send-tour-finished', { new CustomEvent('send-tour-finished', {
bubbles: true, bubbles: true,
composed: true, composed: true
}) })
); )
}
}
_controlOpenWelcomeModal() {
this.isSynced = true
const seenWelcomeSync = JSON.parse(localStorage.getItem('welcome-sync') || 'false')
if (this.hasName) return
if (seenWelcomeSync) return
if (!this.hasViewedTour) return
this.dialogOpenedCongrats = true
}
openWelcomeModal() {
this.dispatchEvent(
new CustomEvent('send-tour-finished', {
bubbles: true,
composed: true
})
)
const seenWelcomeSync = JSON.parse(localStorage.getItem('welcome-sync') || 'false')
if (this.hasName) return
if (seenWelcomeSync) return
if (!this.isSynced) return
this.dialogOpenedCongrats = true
}
_disableTour() {
this.disableTour = true
driver.reset()
}
connectedCallback() {
super.connectedCallback()
window.addEventListener('open-welcome-modal-sync', this._controlOpenWelcomeModal)
window.addEventListener('disable-tour', this._disableTour)
}
disconnectedCallback() {
window.removeEventListener('open-welcome-modal-sync', this._controlOpenWelcomeModal)
window.addEventListener('disable-tour', this._disableTour)
super.disconnectedCallback()
}
getNodeUrl() {
const myNode = store.getState().app.nodeConfig.knownNodes[store.getState().app.nodeConfig.node]
const myNodeUrl = myNode.protocol + '://' + myNode.domain + ':' + myNode.port
this.nodeUrl = myNodeUrl
}
async getName(recipient) {
try {
const endpoint = `${this.nodeUrl}/names/address/${recipient}`
const res = await fetch(endpoint)
const getNames = await res.json()
if (Array.isArray(getNames) && getNames.length > 0) {
return getNames[0].name
} else {
return ''
}
} catch (error) {
return ''
} }
} }
visitQtube() { visitQtube() {
this.onClose(); this.onClose()
const query = `?service=APP&name=Q-Tube`; const query = `?service=APP&name=Q-Tube`
store.dispatch( store.dispatch(
setNewTab({ setNewTab({
url: `qdn/browser/index.html${query}`, url: `qdn/browser/index.html${query}`,
@ -350,59 +287,16 @@ class TourComponent extends connect(store)(LitElement) {
page: `qdn/browser/index.html${query}`, page: `qdn/browser/index.html${query}`,
title: 'Q-Tube', title: 'Q-Tube',
menus: [], menus: [],
parent: false, parent: false
}, }
}) })
); )
} }
onClose() { onClose() {
localStorage.setItem(`welcome-sync-${this.address}`, JSON.stringify(true)) localStorage.setItem(`welcome-sync-${this.address}`, JSON.stringify(true))
this.dialogOpenedCongrats = false; this.dialogOpenedCongrats = false
}
render() {
return html`
<!-- Profile read-view -->
${this.dialogOpenedCongrats && this.hasViewedTour
? html`
<paper-dialog
class="full-info-wrapper"
?opened="${this.dialogOpenedCongrats}"
>
<h3>Congratulations!</h3>
<div
style="display:flex;gap:15px;justify-content:center;margin-top:10px"
>
${translate("tour.tour13")}
</div>
<div
style="display:flex;gap:15px;justify-content:center;margin-top:10px"
>
${translate("tour.tour14")}
</div>
<div
class="accept-button"
@click=${this.visitQtube}
>
${translate("tour.tour15")}
</div>
<div style="width:100%;display:flex;justify-content:center;margin-top:10px">
<div
class="close-button"
@click=${()=> {
this.onClose()
}}
>
${translate("general.close")}
</div>
</div>
</paper-dialog>
`
: ''}
`;
} }
} }
customElements.define('tour-component', TourComponent);
window.customElements.define('tour-component', TourComponent)

View File

@ -1,10 +1,11 @@
import {css, html, LitElement} from 'lit' import { html, LitElement } from 'lit'
import {translate} from '../../translate'
import isElectron from 'is-electron' import isElectron from 'is-electron'
import '@polymer/paper-icon-button/paper-icon-button.js' import '@polymer/paper-icon-button/paper-icon-button.js'
import '@polymer/iron-icons/iron-icons.js' import '@polymer/iron-icons/iron-icons.js'
// Multi language support
import { translate } from '../../translate'
class CheckForUpdate extends LitElement { class CheckForUpdate extends LitElement {
static get properties() { static get properties() {
return { return {
@ -17,11 +18,6 @@ class CheckForUpdate extends LitElement {
this.theme = localStorage.getItem('qortalTheme') ? localStorage.getItem('qortalTheme') : 'light' this.theme = localStorage.getItem('qortalTheme') ? localStorage.getItem('qortalTheme') : 'light'
} }
static styles = [
css`
`
]
render() { render() {
return html` return html`
${this.renderUpdateButton()} ${this.renderUpdateButton()}
@ -29,6 +25,7 @@ class CheckForUpdate extends LitElement {
} }
firstUpdated() { firstUpdated() {
// ...
} }
renderUpdateButton() { renderUpdateButton() {

View File

@ -1,14 +1,10 @@
import { Sha256 } from 'asmcrypto.js' import { Sha256 } from 'asmcrypto.js'
function sbrk(size, heap) { function sbrk(size, heap) {
let brk = 512 * 1024 // stack top let brk = 512 * 1024 // stack top
let old = brk let old = brk
brk += size brk += size
if (brk > heap.length) throw new Error('heap exhausted')
if (brk > heap.length)
throw new Error('heap exhausted')
return old return old
} }
@ -20,63 +16,43 @@ self.addEventListener('message', async e => {
}) })
const memory = new WebAssembly.Memory({ initial: 256, maximum: 256 }) const memory = new WebAssembly.Memory({ initial: 256, maximum: 256 })
const heap = new Uint8Array(memory.buffer) const heap = new Uint8Array(memory.buffer)
const computePow = async (chatBytes, path, difficulty) => { const computePow = async (chatBytes, path, difficulty) => {
let response = null let response = null
await new Promise((resolve, reject) => { await new Promise((resolve, reject) => {
const _chatBytesArray = Object.keys(chatBytes).map(function (key) { return chatBytes[key]; })
const _chatBytesArray = Object.keys(chatBytes).map(function (key) { return chatBytes[key]; }); const chatBytesArray = new Uint8Array(_chatBytesArray)
const chatBytesArray = new Uint8Array(_chatBytesArray); const chatBytesHash = new Sha256().process(chatBytesArray).finish().result
const chatBytesHash = new Sha256().process(chatBytesArray).finish().result; const hashPtr = sbrk(32, heap)
const hashPtr = sbrk(32, heap); const hashAry = new Uint8Array(memory.buffer, hashPtr, 32)
const hashAry = new Uint8Array(memory.buffer, hashPtr, 32); hashAry.set(chatBytesHash)
hashAry.set(chatBytesHash); const workBufferLength = 8 * 1024 * 1024
const workBufferPtr = sbrk(workBufferLength, heap)
const workBufferLength = 8 * 1024 * 1024;
const workBufferPtr = sbrk(workBufferLength, heap);
const importObject = { const importObject = {
env: { env: {
memory: memory memory: memory
}, }
}; }
function loadWebAssembly(filename, imports) { function loadWebAssembly(filename, imports) {
// Fetch the file and compile it // Fetch the file and compile it
return fetch(filename) return fetch(filename)
.then(response => response.arrayBuffer()) .then(response => response.arrayBuffer())
.then(buffer => WebAssembly.compile(buffer)) .then(buffer => WebAssembly.compile(buffer))
.then(module => { .then(module => {
// Create the instance. // Create the instance.
return new WebAssembly.Instance(module, importObject); return new WebAssembly.Instance(module, importObject)
}); })
} }
loadWebAssembly(path) loadWebAssembly(path)
.then(wasmModule => { .then(wasmModule => {
response = { response = {
nonce: wasmModule.exports.compute2(hashPtr, workBufferPtr, workBufferLength, difficulty), nonce: wasmModule.exports.compute2(hashPtr, workBufferPtr, workBufferLength, difficulty),
chatBytesArray chatBytesArray
} }
resolve() resolve()
});
}) })
})
return response return response
} }

View File

@ -1,40 +1,25 @@
import { Sha256 } from 'asmcrypto.js' import { Sha256 } from 'asmcrypto.js'
function sbrk(size, heap) { function sbrk(size, heap) {
let brk = 512 * 1024 // stack top let brk = 512 * 1024 // stack top
let old = brk let old = brk
brk += size brk += size
if (brk > heap.length) throw new Error('heap exhausted')
if (brk > heap.length)
throw new Error('heap exhausted')
return old return old
} }
self.addEventListener('message', async e => { self.addEventListener('message', async e => {
const response = await computePow(e.data.convertedBytes, e.data.path) const response = await computePow(e.data.convertedBytes, e.data.path)
postMessage(response) postMessage(response)
}) })
const memory = new WebAssembly.Memory({ initial: 256, maximum: 256 }) const memory = new WebAssembly.Memory({ initial: 256, maximum: 256 })
const heap = new Uint8Array(memory.buffer) const heap = new Uint8Array(memory.buffer)
const computePow = async (convertedBytes, path) => { const computePow = async (convertedBytes, path) => {
let response = null let response = null
await new Promise((resolve, reject) => { await new Promise((resolve, reject) => {
const _convertedBytesArray = Object.keys(convertedBytes).map( const _convertedBytesArray = Object.keys(convertedBytes).map(
function (key) { function (key) {
return convertedBytes[key] return convertedBytes[key]
@ -50,7 +35,6 @@ const hashAry = new Uint8Array(
hashPtr, hashPtr,
32 32
) )
hashAry.set(convertedBytesHash) hashAry.set(convertedBytesHash)
const difficulty = 14 const difficulty = 14
const workBufferLength = 8 * 1024 * 1024 const workBufferLength = 8 * 1024 * 1024
@ -58,23 +42,19 @@ const workBufferPtr = sbrk(
workBufferLength, workBufferLength,
heap heap
) )
const importObject = { const importObject = {
env: { env: {
memory: memory memory: memory
}, }
}; }
function loadWebAssembly(filename, imports) { function loadWebAssembly(filename, imports) {
return fetch(filename) return fetch(filename)
.then(response => response.arrayBuffer()) .then(response => response.arrayBuffer())
.then(buffer => WebAssembly.compile(buffer)) .then(buffer => WebAssembly.compile(buffer))
.then(module => { .then(module => {
return new WebAssembly.Instance(module, importObject); return new WebAssembly.Instance(module, importObject)
}); })
} }
loadWebAssembly(path) loadWebAssembly(path)
.then(wasmModule => { .then(wasmModule => {
response = { response = {
@ -82,11 +62,7 @@ loadWebAssembly(path)
} }
resolve() resolve()
});
}) })
})
return response return response
} }

View File

@ -1,42 +1,182 @@
import {html, LitElement} from 'lit'; import { html, LitElement } from 'lit'
import '@material/mwc-icon'; import { connect } from 'pwa-helpers'
import {store} from '../../store'; import { store } from '../../store'
import {connect} from 'pwa-helpers'; import { parentEpml } from '../show-plugin'
import '@vaadin/tooltip'; import { setCoinBalances } from '../../redux/app/app-actions'
import {parentEpml} from '../show-plugin';
import {setCoinBalances} from '../../redux/app/app-actions';
class CoinBalancesController extends connect(store)(LitElement) { class CoinBalancesController extends connect(store)(LitElement) {
static get properties() { static get properties() {
return { return {
coinList: { type: Object }, coinList: { type: Object }
}; }
} }
constructor() { constructor() {
super(); super();
this.coinList = {} this.coinList = {}
this.nodeUrl = this.getNodeUrl(); this.nodeUrl = this.getNodeUrl()
this.myNode = this.getMyNode(); this.myNode = this.getMyNode()
this.fetchBalance = this.fetchBalance.bind(this) this.fetchBalance = this.fetchBalance.bind(this)
this._updateCoinList = this._updateCoinList.bind(this) this._updateCoinList = this._updateCoinList.bind(this)
this.stop = false this.stop = false
} }
getNodeUrl() { render() {
const myNode = return html``
store.getState().app.nodeConfig.knownNodes[ }
store.getState().app.nodeConfig.node
]
getNodeUrl() {
const myNode = store.getState().app.nodeConfig.knownNodes[store.getState().app.nodeConfig.node]
return myNode.protocol + '://' + myNode.domain + ':' + myNode.port return myNode.protocol + '://' + myNode.domain + ':' + myNode.port
} }
getMyNode() { getMyNode() {
return store.getState().app.nodeConfig.knownNodes[ return store.getState().app.nodeConfig.knownNodes[store.getState().app.nodeConfig.node]
store.getState().app.nodeConfig.node
]
} }
async updateQortWalletBalance() {
let qortAddress = store.getState().app.selectedAddress.address
await parentEpml.request('apiCall', {
url: `/addresses/balance/${qortAddress}?apiKey=${this.myNode.apiKey}`,
}).then((res) => {
this.qortWalletBalance = res
store.dispatch(
setCoinBalances({
type: 'qort',
fullValue: Number(res)
})
)
}).catch(() => {
console.log('error')
})
}
async updateBtcWalletBalance() {
let _url = `/crosschain/btc/walletbalance?apiKey=${this.myNode.apiKey}`
let _body = store.getState().app.selectedAddress.btcWallet.derivedMasterPublicKey
await parentEpml.request('apiCall', {
url: _url,
method: 'POST',
body: _body
}).then((res) => {
if (isNaN(Number(res))) {
//...
} else {
this.btcWalletBalance = (Number(res) / 1e8).toFixed(8)
store.dispatch(
setCoinBalances({
type: 'btc',
fullValue: Number(res)
})
)
}
}).catch(() => {
console.log('error')
})
}
async updateLtcWalletBalance() {
let _url = `/crosschain/ltc/walletbalance?apiKey=${this.myNode.apiKey}`
let _body = store.getState().app.selectedAddress.ltcWallet.derivedMasterPublicKey
await parentEpml.request('apiCall', {
url: _url,
method: 'POST',
body: _body
}).then((res) => {
if (isNaN(Number(res))) {
//...
} else {
this.ltcWalletBalance = (Number(res) / 1e8).toFixed(8)
store.dispatch(
setCoinBalances({
type: 'ltc',
fullValue: Number(res)
})
)
}
}).catch(() => {
console.log('error')
})
}
async updateDogeWalletBalance() {
let _url = `/crosschain/doge/walletbalance?apiKey=${this.myNode.apiKey}`
let _body = store.getState().app.selectedAddress.dogeWallet.derivedMasterPublicKey
await parentEpml.request('apiCall', {
url: _url,
method: 'POST',
body: _body
}).then((res) => {
if (isNaN(Number(res))) {
//...
} else {
this.dogeWalletBalance = (Number(res) / 1e8).toFixed(8)
store.dispatch(
setCoinBalances({
type: 'doge',
fullValue: Number(res)
})
)
}
}).catch(() => {
console.log('error')
})
}
async updateDgbWalletBalance() {
let _url = `/crosschain/dgb/walletbalance?apiKey=${this.myNode.apiKey}`
let _body = store.getState().app.selectedAddress.dgbWallet.derivedMasterPublicKey
await parentEpml.request('apiCall', {
url: _url,
method: 'POST',
body: _body
}).then((res) => {
if (isNaN(Number(res))) {
//...
} else {
this.dgbWalletBalance = (Number(res) / 1e8).toFixed(8)
store.dispatch(
setCoinBalances({
type: 'dgb',
fullValue: Number(res)
})
)
}
}).catch(() => {
console.log('error')
})
}
async updateRvnWalletBalance() {
let _url = `/crosschain/rvn/walletbalance?apiKey=${this.myNode.apiKey}`
let _body = store.getState().app.selectedAddress.rvnWallet.derivedMasterPublicKey
await parentEpml.request('apiCall', {
url: _url,
method: 'POST',
body: _body
}).then((res) => {
if (isNaN(Number(res))) {
//...
} else {
this.rvnWalletBalance = (Number(res) / 1e8).toFixed(8)
store.dispatch(
setCoinBalances({
type: 'rvn',
fullValue: Number(res)
})
)
}
}).catch(() => {
console.log('error')
})
}
async updateArrrWalletBalance() { async updateArrrWalletBalance() {
let _url = `/crosschain/arrr/walletbalance?apiKey=${this.myNode.apiKey}` let _url = `/crosschain/arrr/walletbalance?apiKey=${this.myNode.apiKey}`
@ -56,150 +196,7 @@ class CoinBalancesController extends connect(store)(LitElement) {
type: 'arrr', type: 'arrr',
fullValue: Number(res) fullValue: Number(res)
}) })
); )
}
}).catch(()=> {
console.log('error')
})
}
async updateQortWalletBalance() {
let qortAddress = store.getState().app.selectedAddress.address
await parentEpml.request('apiCall', {
url: `/addresses/balance/${qortAddress}?apiKey=${this.myNode.apiKey}`,
}).then((res) => {
this.qortWalletBalance = res
store.dispatch(
setCoinBalances({
type: 'qort',
fullValue: Number(res)
})
);
}).catch(()=> {
console.log('error')
})
}
async updateRvnWalletBalance() {
let _url = `/crosschain/rvn/walletbalance?apiKey=${this.myNode.apiKey}`
let _body = store.getState().app.selectedAddress.rvnWallet.derivedMasterPublicKey
await parentEpml.request('apiCall', {
url: _url,
method: 'POST',
body: _body,
}).then((res) => {
if (isNaN(Number(res))) {
//...
} else {
this.rvnWalletBalance = (Number(res) / 1e8).toFixed(8)
store.dispatch(
setCoinBalances({
type: 'rvn',
fullValue: Number(res)
})
);
}
}).catch(()=> {
console.log('error')
})
}
async updateDgbWalletBalance() {
let _url = `/crosschain/dgb/walletbalance?apiKey=${this.myNode.apiKey}`
let _body = store.getState().app.selectedAddress.dgbWallet.derivedMasterPublicKey
await parentEpml.request('apiCall', {
url: _url,
method: 'POST',
body: _body,
}).then((res) => {
if (isNaN(Number(res))) {
//...
} else {
this.dgbWalletBalance = (Number(res) / 1e8).toFixed(8)
store.dispatch(
setCoinBalances({
type: 'dgb',
fullValue: Number(res)
})
);
}
}).catch(()=> {
console.log('error')
})
}
async updateDogeWalletBalance() {
let _url = `/crosschain/doge/walletbalance?apiKey=${this.myNode.apiKey}`
let _body = store.getState().app.selectedAddress.dogeWallet.derivedMasterPublicKey
await parentEpml.request('apiCall', {
url: _url,
method: 'POST',
body: _body,
}).then((res) => {
if (isNaN(Number(res))) {
//...
} else {
this.dogeWalletBalance = (Number(res) / 1e8).toFixed(8)
store.dispatch(
setCoinBalances({
type: 'doge',
fullValue: Number(res)
})
);
}
}).catch(()=> {
console.log('error')
})
}
async updateBtcWalletBalance() {
let _url = `/crosschain/btc/walletbalance?apiKey=${this.myNode.apiKey}`
let _body = store.getState().app.selectedAddress.btcWallet.derivedMasterPublicKey
await parentEpml.request('apiCall', {
url: _url,
method: 'POST',
body: _body,
}).then((res) => {
if (isNaN(Number(res))) {
//...
} else {
this.btcWalletBalance = (Number(res) / 1e8).toFixed(8)
store.dispatch(
setCoinBalances({
type: 'btc',
fullValue: Number(res)
})
);
}
}).catch(()=> {
console.log('error')
})
}
async updateLtcWalletBalance() {
let _url = `/crosschain/ltc/walletbalance?apiKey=${this.myNode.apiKey}`
let _body = store.getState().app.selectedAddress.ltcWallet.derivedMasterPublicKey
await parentEpml.request('apiCall', {
url: _url,
method: 'POST',
body: _body,
}).then((res) => {
if (isNaN(Number(res))) {
//...
} else {
this.ltcWalletBalance = (Number(res) / 1e8).toFixed(8)
store.dispatch(
setCoinBalances({
type: 'ltc',
fullValue: Number(res)
})
);
} }
}).catch(() => { }).catch(() => {
console.log('error') console.log('error')
@ -209,16 +206,17 @@ class CoinBalancesController extends connect(store)(LitElement) {
_updateCoinList(event) { _updateCoinList(event) {
const copyCoinList = { ...this.coinList } const copyCoinList = { ...this.coinList }
const coin = event.detail const coin = event.detail
if (!copyCoinList[coin]) { if (!copyCoinList[coin]) {
try { try {
if(coin === 'ltc'){ if (coin === 'qort') {
this.updateLtcWalletBalance()
} else if(coin === 'qort'){
this.updateQortWalletBalance() this.updateQortWalletBalance()
} else if(coin === 'doge'){
this.updateDogeWalletBalance()
} else if (coin === 'btc') { } else if (coin === 'btc') {
this.updateBtcWalletBalance() this.updateBtcWalletBalance()
} else if (coin === 'ltc') {
this.updateLtcWalletBalance()
} else if (coin === 'doge') {
this.updateDogeWalletBalance()
} else if (coin === 'dgb') { } else if (coin === 'dgb') {
this.updateDgbWalletBalance() this.updateDgbWalletBalance()
} else if (coin === 'rvn') { } else if (coin === 'rvn') {
@ -226,28 +224,25 @@ class CoinBalancesController extends connect(store)(LitElement) {
} else if (coin === 'arrr') { } else if (coin === 'arrr') {
this.updateArrrWalletBalance() this.updateArrrWalletBalance()
} }
} catch (error) { } catch (error) { }
}
copyCoinList[coin] = Date.now() + 120000
}
}
copyCoinList[coin] = Date.now() + 120000;
this.coinList = copyCoinList this.coinList = copyCoinList
this.requestUpdate() this.requestUpdate()
} }
async fetchCoins(arrayOfCoins) { async fetchCoins(arrayOfCoins) {
const getCoinBalances = (arrayOfCoins || []).map( const getCoinBalances = (arrayOfCoins || []).map(async (coin) => {
async (coin) => { if (coin === 'qort') {
if(coin === 'ltc'){
await this.updateLtcWalletBalance()
} else if(coin === 'qort'){
await this.updateQortWalletBalance() await this.updateQortWalletBalance()
} else if(coin === 'doge'){
await this.updateDogeWalletBalance()
} else if (coin === 'btc') { } else if (coin === 'btc') {
await this.updateBtcWalletBalance() await this.updateBtcWalletBalance()
} else if (coin === 'ltc') {
await this.updateLtcWalletBalance()
} else if (coin === 'doge') {
await this.updateDogeWalletBalance()
} else if (coin === 'dgb') { } else if (coin === 'dgb') {
await this.updateDgbWalletBalance() await this.updateDgbWalletBalance()
} else if (coin === 'rvn') { } else if (coin === 'rvn') {
@ -257,16 +252,17 @@ class CoinBalancesController extends connect(store)(LitElement) {
} }
}) })
await Promise.all(getCoinBalances); await Promise.all(getCoinBalances)
} }
async fetchBalance() { async fetchBalance() {
try { try {
let arrayOfCoins = [] let arrayOfCoins = []
const copyObject = { ...this.coinList } const copyObject = { ...this.coinList }
const currentDate = Date.now() const currentDate = Date.now()
const array = Object.keys(this.coinList) const array = Object.keys(this.coinList)
for (const key of array) { for (const key of array) {
const item = this.coinList[key] const item = this.coinList[key]
@ -276,11 +272,15 @@ class CoinBalancesController extends connect(store)(LitElement) {
arrayOfCoins.push(key) arrayOfCoins.push(key)
} }
} }
if (!this.stop) { if (!this.stop) {
this.stop = true this.stop = true
await this.fetchCoins(arrayOfCoins) await this.fetchCoins(arrayOfCoins)
this.stop = false this.stop = false
} }
this.coinList = copyObject this.coinList = copyObject
} catch (error) { } catch (error) {
this.stop = false this.stop = false
@ -288,33 +288,16 @@ class CoinBalancesController extends connect(store)(LitElement) {
} }
connectedCallback() { connectedCallback() {
super.connectedCallback(); super.connectedCallback()
this.intervalID = setInterval(this.fetchBalance, 45000); this.intervalID = setInterval(this.fetchBalance, 45000)
window.addEventListener( window.addEventListener('ping-coin-controller-with-coin', this._updateCoinList)
'ping-coin-controller-with-coin',
this._updateCoinList
);
} }
disconnectedCallback() { disconnectedCallback() {
if (this.intervalID) { clearInterval(this.intervalID) }
super.disconnectedCallback(); window.removeEventListener('ping-coin-controller-with-coin', this._updateCoinList)
window.removeEventListener( super.disconnectedCallback()
'ping-coin-controller-with-coin',
this._updateCoinList
);
if(this.intervalID){
clearInterval(this.intervalID);
}
}
render() {
return html``;
} }
} }
customElements.define('coin-balances-controller', CoinBalancesController); window.customElements.define('coin-balances-controller', CoinBalancesController)

View File

@ -1,11 +1,13 @@
import {css, html, LitElement} from 'lit' import { html, LitElement } from 'lit'
import { connect } from 'pwa-helpers'
import { store } from '../../store'
import { get } from '../../../translate' import { get } from '../../../translate'
import '@material/mwc-icon' import { chatSideNavHeadsStyles } from '../../styles/core-css'
import '@vaadin/tooltip';
import './friend-item-actions' import './friend-item-actions'
import '@material/mwc-icon'
import '@vaadin/tooltip'
class ChatSideNavHeads extends LitElement { class ChatSideNavHeads extends connect(store)(LitElement) {
static get properties() { static get properties() {
return { return {
selectedAddress: { type: Object }, selectedAddress: { type: Object },
@ -21,55 +23,7 @@ class ChatSideNavHeads extends LitElement {
} }
static get styles() { static get styles() {
return css` return [chatSideNavHeadsStyles]
:host {
width: 100%;
}
ul {
list-style-type: none;
}
li {
padding: 10px 2px 10px 5px;
cursor: pointer;
width: 100%;
display: flex;
box-sizing: border-box;
font-size: 14px;
transition: 0.2s background-color;
}
li:hover {
background-color: var(--lightChatHeadHover);
}
.active {
background: var(--menuactive);
border-left: 4px solid #3498db;
}
.img-icon {
font-size:40px;
color: var(--chat-group);
}
.status {
color: #92959e;
}
.clearfix {
display: flex;
align-items: center;
}
.clearfix:after {
visibility: hidden;
display: block;
font-size: 0;
content: " ";
clear: both;
height: 0;
}
`
} }
constructor() { constructor() {
@ -78,7 +32,6 @@ class ChatSideNavHeads extends LitElement {
this.config = { this.config = {
user: { user: {
node: { node: {
} }
} }
} }
@ -89,66 +42,52 @@ class ChatSideNavHeads extends LitElement {
this.imageFetches = 0 this.imageFetches = 0
} }
createImage(imageUrl) {
const imageHTMLRes = new Image();
imageHTMLRes.src = imageUrl;
imageHTMLRes.style= "width:30px; height:30px; float: left; border-radius:50%; font-size:14px";
imageHTMLRes.onclick= () => {
this.openDialogImage = true;
}
imageHTMLRes.onload = () => {
this.isImageLoaded = true;
}
imageHTMLRes.onerror = () => {
if (this.imageFetches < 4) {
setTimeout(() => {
this.imageFetches = this.imageFetches + 1;
imageHTMLRes.src = imageUrl;
}, 500);
} else {
this.isImageLoaded = false
}
};
return imageHTMLRes;
}
render() { render() {
let avatarImg = "" let avatarImg = ''
if (this.chatInfo.name) { if (this.chatInfo.name) {
const myNode = window.parent.reduxStore.getState().app.nodeConfig.knownNodes[window.parent.reduxStore.getState().app.nodeConfig.node]; const myNode = store.getState().app.nodeConfig.knownNodes[store.getState().app.nodeConfig.node]
const nodeUrl = myNode.protocol + '://' + myNode.domain + ':' + myNode.port; const nodeUrl = myNode.protocol + '://' + myNode.domain + ':' + myNode.port
const avatarUrl = `${nodeUrl}/arbitrary/THUMBNAIL/${this.chatInfo.name}/qortal_avatar?async=true&apiKey=${myNode.apiKey}`; const avatarUrl = `${nodeUrl}/arbitrary/THUMBNAIL/${this.chatInfo.name}/qortal_avatar?async=true`
avatarImg = this.createImage(avatarUrl) avatarImg = this.createImage(avatarUrl)
} }
return html` return html`
<li style="display:flex; justify-content: space-between; align-items: center" @click=${(e) => { <li
style="display:flex; justify-content: space-between; align-items: center"
@click=${(e) => {
const target = e.target const target = e.target
const popover = const popover = this.shadowRoot.querySelector('friend-item-actions');
this.shadowRoot.querySelector('friend-item-actions');
if (popover) { if (popover) {
popover.openPopover(target); popover.openPopover(target);
} }
}} class="clearfix" id=${`friend-item-parent-${this.chatInfo.name}`}> }}
class="clearfix" id=${`friend-item-parent-${this.chatInfo.name}`}
>
<div style="display:flex; flex-grow: 1; align-items: center"> <div style="display:flex; flex-grow: 1; align-items: center">
${this.isImageLoaded ? html`${avatarImg}` : html``} ${this.isImageLoaded ? html`${avatarImg}` : html``}
${!this.isImageLoaded && !this.chatInfo.name && !this.chatInfo.groupName ${!this.isImageLoaded && !this.chatInfo.name && !this.chatInfo.groupName ?
? html`<mwc-icon class="img-icon">account_circle</mwc-icon>` html`
: html``} <mwc-icon class="img-icon">account_circle</mwc-icon>
${!this.isImageLoaded && this.chatInfo.name `
? html`<div : html``
}
${!this.isImageLoaded && this.chatInfo.name ?
html`
<div
style="width:30px; height:30px; float: left; border-radius:50%; background: ${this.activeChatHeadUrl === this.chatInfo.url style="width:30px; height:30px; float: left; border-radius:50%; background: ${this.activeChatHeadUrl === this.chatInfo.url
? "var(--chatHeadBgActive)" ? "var(--chatHeadBgActive)"
: "var(--chatHeadBg)"}; color: ${this.activeChatHeadUrl === : "var(--chatHeadBg)"}; color: ${this.activeChatHeadUrl === this.chatInfo.url
this.chatInfo.url
? "var(--chatHeadTextActive)" ? "var(--chatHeadTextActive)"
: "var(--chatHeadText)"}; font-weight:bold; display: flex; justify-content: center; align-items: center; text-transform: capitalize" : "var(--chatHeadText)"}; font-weight:bold; display: flex; justify-content: center; align-items: center; text-transform: capitalize"
> >
${this.chatInfo.name.charAt(0)} ${this.chatInfo.name.charAt(0)}
</div>` </div>
: ""} ` : ''
${!this.isImageLoaded && this.chatInfo.groupName }
? html`<div ${!this.isImageLoaded && this.chatInfo.groupName ?
html`
<div
style="width:30px; height:30px; float: left; border-radius:50%; background: ${this.activeChatHeadUrl === this.chatInfo.url style="width:30px; height:30px; float: left; border-radius:50%; background: ${this.activeChatHeadUrl === this.chatInfo.url
? "var(--chatHeadBgActive)" ? "var(--chatHeadBgActive)"
: "var(--chatHeadBg)"}; color: ${this.activeChatHeadUrl === this.chatInfo.url : "var(--chatHeadBg)"}; color: ${this.activeChatHeadUrl === this.chatInfo.url
@ -156,8 +95,9 @@ class ChatSideNavHeads extends LitElement {
: "var(--chatHeadText)"}; font-weight:bold; display: flex; justify-content: center; align-items: center; text-transform: capitalize" : "var(--chatHeadText)"}; font-weight:bold; display: flex; justify-content: center; align-items: center; text-transform: capitalize"
> >
${this.chatInfo.groupName.charAt(0)} ${this.chatInfo.groupName.charAt(0)}
</div>` </div>
: ""} ` : ''
}
<div> <div>
<div class="name"> <div class="name">
<span style="float:left; padding-left: 8px; color: var(--chat-group);"> <span style="float:left; padding-left: 8px; color: var(--chat-group);">
@ -165,24 +105,25 @@ class ChatSideNavHeads extends LitElement {
? this.chatInfo.groupName ? this.chatInfo.groupName
: this.chatInfo.name !== undefined : this.chatInfo.name !== undefined
? (this.chatInfo.alias || this.chatInfo.name) ? (this.chatInfo.alias || this.chatInfo.name)
: this.chatInfo.address.substr(0, 15)} : this.chatInfo.address.substr(0, 15)
}
</span> </span>
</div> </div>
</div> </div>
</div> </div>
<div style="display:flex; align-items: center"> <div style="display:flex; align-items: center">
${this.chatInfo.willFollow ? html` ${this.chatInfo.willFollow ?
html`
<mwc-icon id="willFollowIcon" style="color: var(--black)">connect_without_contact</mwc-icon> <mwc-icon id="willFollowIcon" style="color: var(--black)">connect_without_contact</mwc-icon>
<vaadin-tooltip <vaadin-tooltip
for="willFollowIcon" for="willFollowIcon"
position="top" position="top"
hover-delay=${200} hover-delay=${200}
hide-delay=${1} hide-delay=${1}
text=${get('friends.friend11')}> text=${get('friends.friend11')}
</vaadin-tooltip> ></vaadin-tooltip>
` : ''} ` : ''
}
</div> </div>
</li> </li>
<friend-item-actions <friend-item-actions
@ -197,25 +138,67 @@ class ChatSideNavHeads extends LitElement {
` `
} }
firstUpdated() {
// ...
}
createImage(imageUrl) {
const imageHTMLRes = new Image()
imageHTMLRes.src = imageUrl
imageHTMLRes.style = "width:30px; height:30px; float: left; border-radius:50%; font-size:14px"
imageHTMLRes.onclick = () => {
this.openDialogImage = true
}
imageHTMLRes.onload = () => {
this.isImageLoaded = true
}
imageHTMLRes.onerror = () => {
if (this.imageFetches < 4) {
setTimeout(() => {
this.imageFetches = this.imageFetches + 1
imageHTMLRes.src = imageUrl
}, 500)
} else {
this.isImageLoaded = false
}
}
return imageHTMLRes
}
shouldUpdate(changedProperties) { shouldUpdate(changedProperties) {
if (changedProperties.has('activeChatHeadUrl')) { if (changedProperties.has('activeChatHeadUrl')) {
return true return true
} }
if (changedProperties.has('chatInfo')) { if (changedProperties.has('chatInfo')) {
return true return true
} }
return !!changedProperties.has('isImageLoaded');
return !!changedProperties.has('isImageLoaded')
} }
getUrl(chatUrl) { getUrl(chatUrl) {
this.setActiveChatHeadUrl(chatUrl) this.setActiveChatHeadUrl(chatUrl)
} }
// Standard functions
getApiKey() {
const coreNode = store.getState().app.nodeConfig.knownNodes[store.getState().app.nodeConfig.node]
return coreNode.apiKey
}
isEmptyArray(arr) {
if (!arr) { return true }
return arr.length === 0
}
round(number) {
return (Math.round(parseFloat(number) * 1e8) / 1e8).toFixed(8)
}
} }
window.customElements.define('chat-side-nav-heads', ChatSideNavHeads) window.customElements.define('chat-side-nav-heads', ChatSideNavHeads)

View File

@ -1,10 +1,11 @@
import {css, html, LitElement} from 'lit'; import { html, LitElement } from 'lit'
import { connect } from 'pwa-helpers'
import { store } from '../../store'
import { translate, } from '../../../translate' import { translate, } from '../../../translate'
import '@material/mwc-button'; import { addFriendsModalStyles } from '../../styles/core-css'
import '@material/mwc-dialog'; import '@material/mwc-button'
import '@material/mwc-checkbox'; import '@material/mwc-checkbox'
import {connect} from 'pwa-helpers'; import '@material/mwc-dialog'
import {store} from '../../store';
import '@polymer/paper-spinner/paper-spinner-lite.js' import '@polymer/paper-spinner/paper-spinner-lite.js'
class AddFriendsModal extends connect(store)(LitElement) { class AddFriendsModal extends connect(store)(LitElement) {
@ -23,273 +24,30 @@ class AddFriendsModal extends connect(store)(LitElement) {
mySelectedFeeds: { type: Array }, mySelectedFeeds: { type: Array },
availableFeeedSchemas: { type: Array }, availableFeeedSchemas: { type: Array },
isLoadingSchemas: { type: Boolean } isLoadingSchemas: { type: Boolean }
};
} }
constructor() {
super();
this.isOpen = false;
this.isLoading = false;
this.alias = '';
this.willFollow = true;
this.notes = '';
this.nodeUrl = this.getNodeUrl();
this.myNode = this.getMyNode();
this.mySelectedFeeds = [];
this.availableFeeedSchemas = [];
this.isLoadingSchemas= false;
} }
static get styles() { static get styles() {
return css` return [addFriendsModalStyles]
* {
--mdc-theme-primary: rgb(3, 169, 244);
--mdc-theme-secondary: var(--mdc-theme-primary);
--mdc-theme-surface: var(--white);
--mdc-dialog-content-ink-color: var(--black);
--mdc-dialog-min-width: 400px;
--mdc-dialog-max-width: 1024px;
box-sizing:border-box;
}
.input {
width: 90%;
outline: 0;
border-width: 0 0 2px;
border-color: var(--mdc-theme-primary);
background-color: transparent;
padding: 10px;
font-family: Roboto, sans-serif;
font-size: 15px;
color: var(--chat-bubble-msg-color);
box-sizing: border-box;
} }
.input::selection { constructor() {
background-color: var(--mdc-theme-primary); super()
color: white; this.isOpen = false
} this.isLoading = false
this.alias = ''
.input::placeholder { this.willFollow = true
opacity: 0.6; this.notes = ''
color: var(--black); this.nodeUrl = this.getNodeUrl()
} this.myNode = this.getMyNode()
this.mySelectedFeeds = []
.modal-button { this.availableFeeedSchemas = []
font-family: Roboto, sans-serif;
font-size: 16px;
color: var(--mdc-theme-primary);
background-color: transparent;
padding: 8px 10px;
border-radius: 5px;
border: none;
transition: all 0.3s ease-in-out;
}
.modal-button-red {
font-family: Roboto, sans-serif;
font-size: 16px;
color: #f44336;
background-color: transparent;
padding: 8px 10px;
border-radius: 5px;
border: none;
transition: all 0.3s ease-in-out;
}
.modal-button-red:hover {
cursor: pointer;
background-color: #f4433663;
}
.modal-button:hover {
cursor: pointer;
background-color: #03a8f475;
}
.checkbox-row {
position: relative;
display: flex;
align-items: center;
align-content: center;
font-family: Montserrat, sans-serif;
font-weight: 600;
color: var(--black);
}
.modal-overlay {
display: block;
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
background-color: rgba(
0,
0,
0,
0.5
); /* Semi-transparent backdrop */
z-index: 1000;
}
.modal-content {
position: fixed;
top: 50vh;
left: 50vw;
transform: translate(-50%, -50%);
background-color: var(--mdc-theme-surface);
width: 80vw;
max-width: 600px;
padding: 20px;
box-shadow: rgba(0, 0, 0, 0.1) 0px 4px 6px;
z-index: 1001;
border-radius: 5px;
display: flex;
flex-direction:column;
}
.modal-overlay.hidden {
display: none;
}
.avatar {
width: 36px;
height: 36px;
display: flex;
align-items: center;
}
.app-name {
display: flex;
gap: 20px;
align-items: center;
width: 100%;
cursor: pointer;
padding: 5px;
border-radius: 5px;
margin-bottom: 10px;
}
.inner-content {
display: flex;
flex-direction: column;
max-height: 75vh;
flex-grow: 1;
overflow: auto;
}
.inner-content::-webkit-scrollbar-track {
background-color: whitesmoke;
border-radius: 7px;
}
.inner-content::-webkit-scrollbar {
width: 12px;
border-radius: 7px;
background-color: whitesmoke;
}
.inner-content::-webkit-scrollbar-thumb {
background-color: rgb(180, 176, 176);
border-radius: 7px;
transition: all 0.3s ease-in-out;
}
`;
}
firstUpdated() {}
getNodeUrl() {
const myNode =
store.getState().app.nodeConfig.knownNodes[
window.parent.reduxStore.getState().app.nodeConfig.node
];
return myNode.protocol + '://' + myNode.domain + ':' + myNode.port;
}
getMyNode() {
return store.getState().app.nodeConfig.knownNodes[
window.parent.reduxStore.getState().app.nodeConfig.node
];
}
clearFields() {
this.alias = '';
this.willFollow = true;
this.notes = '';
}
addFriend() {
this.onSubmit({
name: this.userSelected.name,
alias: this.alias,
notes: this.notes,
willFollow: this.willFollow,
mySelectedFeeds: this.mySelectedFeeds
});
this.clearFields();
this.onClose();
}
removeFriend() {
this.onSubmit(
{
name: this.userSelected.name,
alias: this.alias,
notes: this.notes,
willFollow: this.willFollow,
mySelectedFeeds: this.mySelectedFeeds
},
true
);
this.clearFields();
this.onClose();
}
async updated(changedProperties) {
if (
changedProperties &&
changedProperties.has('editContent') &&
this.editContent
) {
this.userSelected = {
name: this.editContent.name ?? '',
};
this.notes = this.editContent.notes ?? '';
this.willFollow = this.editContent.willFollow ?? true;
this.alias = this.editContent.alias ?? '';
this.requestUpdate()
}
if (
changedProperties &&
changedProperties.has('isOpen') && this.isOpen
) {
await this.getAvailableFeedSchemas()
}
}
async getAvailableFeedSchemas() {
try {
this.isLoadingSchemas= true
const url = `${this.nodeUrl}/arbitrary/resources/search?service=DOCUMENT&identifier=ui_schema_feed&prefix=true`;
const res = await fetch(url);
const data = await res.json();
if (data.error === 401) {
this.availableFeeedSchemas = [];
} else {
this.availableFeeedSchemas = data.filter(
(item) => item.identifier === 'ui_schema_feed'
);
}
this.userFoundModalOpen = true;
} catch (error) {} finally {
this.isLoadingSchemas = false this.isLoadingSchemas = false
} }
}
render() { render() {
return html` return html`
<div class="modal-overlay ${this.isOpen ? '' : 'hidden'}"> <div class="modal-overlay ${this.isOpen ? '' : 'hidden'}">
<div class="modal-content"> <div class="modal-content">
<div class="inner-content"> <div class="inner-content">
<div style="text-align:center"> <div style="text-align:center">
@ -331,9 +89,7 @@ class AddFriendsModal extends connect(store)(LitElement) {
id="name" id="name"
class="input" class="input"
?disabled=${true} ?disabled=${true}
value=${this.userSelected value=${this.userSelected ? this.userSelected.name : ''}
? this.userSelected.name
: ''}
/> />
</div> </div>
<div style="height:15px"></div> <div style="height:15px"></div>
@ -375,67 +131,42 @@ class AddFriendsModal extends connect(store)(LitElement) {
<p>${translate('friends.friend16')}</p> <p>${translate('friends.friend16')}</p>
</div> </div>
<div> <div>
${this.isLoadingSchemas ? html` ${this.isLoadingSchemas ?
html`
<div style="width:100%;display: flex; justify-content:center"> <div style="width:100%;display: flex; justify-content:center">
<paper-spinner-lite active></paper-spinner-lite> <paper-spinner-lite active></paper-spinner-lite>
</div> </div>
` : ''} ` : ''
}
${this.availableFeeedSchemas.map((schema) => { ${this.availableFeeedSchemas.map((schema) => {
const isAlreadySelected = this.mySelectedFeeds.find( const isAlreadySelected = this.mySelectedFeeds.find((item) => item.name === schema.name);
(item) => item.name === schema.name
);
let avatarImgApp; let avatarImgApp;
const avatarUrl2 = `${this.nodeUrl}/arbitrary/THUMBNAIL/${schema.name}/qortal_avatar?async=true&apiKey=${this.myNode.apiKey}`; const avatarUrl2 = `${this.nodeUrl}/arbitrary/THUMBNAIL/${schema.name}/qortal_avatar?async=true&apiKey=${this.myNode.apiKey}`;
avatarImgApp = html`<img avatarImgApp = html`<img src="${avatarUrl2}" style="max-width:100%; max-height:100%;" onerror="this.onerror=null; this.src='/img/incognito.png';"/>`;
src="${avatarUrl2}"
style="max-width:100%; max-height:100%;"
onerror="this.onerror=null; this.src='/img/incognito.png';"
/>`;
return html` return html`
<div <div
class="app-name" class="app-name"
style="background:${isAlreadySelected ? 'lightblue' : ''}" style="background:${isAlreadySelected ? 'lightblue' : ''}"
@click=${() => { @click=${() => {
const copymySelectedFeeds = [ const copymySelectedFeeds = [...this.mySelectedFeeds];
...this.mySelectedFeeds, const findIndex = copymySelectedFeeds.findIndex((item) => item.name === schema.name);
];
const findIndex =
copymySelectedFeeds.findIndex(
(item) =>
item.name === schema.name
);
if (findIndex === -1) { if (findIndex === -1) {
if (this.mySelectedFeeds.length > 4) return if (this.mySelectedFeeds.length > 4) return
copymySelectedFeeds.push({ copymySelectedFeeds.push({name: schema.name, identifier: schema.identifier, service: schema.service});
name: schema.name, this.mySelectedFeeds = copymySelectedFeeds;
identifier: schema.identifier,
service: schema.service,
});
this.mySelectedFeeds =
copymySelectedFeeds;
} else { } else {
this.mySelectedFeeds = this.mySelectedFeeds = copymySelectedFeeds.filter((item) => item.name !== schema.name);
copymySelectedFeeds.filter(
(item) =>
item.name !==
schema.name
);
} }
}} }}
> >
<div class="avatar">${avatarImgApp}</div> <div class="avatar">${avatarImgApp}</div>
<span <span style="color:${isAlreadySelected ? 'var(--white)' : 'var(--black)'};font-size:16px">${schema.name}</span>
style="color:${isAlreadySelected ? 'var(--white)': 'var(--black)'};font-size:16px"
>${schema.name}</span
>
</div> </div>
`; `
})} })}
</div> </div>
</div> </div>
<div <div style="display:flex;justify-content:space-between;align-items:center;margin-top:20px">
style="display:flex;justify-content:space-between;align-items:center;margin-top:20px"
>
<button <button
class="modal-button-red" class="modal-button-red"
?disabled="${this.isLoading}" ?disabled="${this.isLoading}"
@ -447,36 +178,118 @@ class AddFriendsModal extends connect(store)(LitElement) {
> >
${translate('general.close')} ${translate('general.close')}
</button> </button>
${this.editContent ${this.editContent ?
? html` html`
<button <button ?disabled="${this.isLoading}" class="modal-button-red" @click=${() => {this.removeFriend();}}>
?disabled="${this.isLoading}"
class="modal-button-red"
@click=${() => {
this.removeFriend();
}}
>
${translate('friends.friend14')} ${translate('friends.friend14')}
</button> </button>
` ` : ''
: ''} }
<button ?disabled="${this.isLoading}" class="modal-button" @click=${() => {this.addFriend();}}>
<button ${this.editContent ? translate('friends.friend10') : translate('friends.friend2')}
?disabled="${this.isLoading}"
class="modal-button"
@click=${() => {
this.addFriend();
}}
>
${this.editContent
? translate('friends.friend10')
: translate('friends.friend2')}
</button> </button>
</div> </div>
</div> </div>
</div> </div>
`; `
}
firstUpdated() {
// ...
}
getNodeUrl() {
const myNode = store.getState().app.nodeConfig.knownNodes[store.getState().app.nodeConfig.node]
return myNode.protocol + '://' + myNode.domain + ':' + myNode.port
}
getMyNode() {
return store.getState().app.nodeConfig.knownNodes[store.getState().app.nodeConfig.node]
}
clearFields() {
this.alias = ''
this.willFollow = true
this.notes = ''
}
addFriend() {
this.onSubmit({
name: this.userSelected.name,
alias: this.alias,
notes: this.notes,
willFollow: this.willFollow,
mySelectedFeeds: this.mySelectedFeeds
})
this.clearFields()
this.onClose()
}
removeFriend() {
this.onSubmit(
{
name: this.userSelected.name,
alias: this.alias,
notes: this.notes,
willFollow: this.willFollow,
mySelectedFeeds: this.mySelectedFeeds
},
true
)
this.clearFields()
this.onClose()
}
async updated(changedProperties) {
if (changedProperties && changedProperties.has('editContent') && this.editContent) {
this.userSelected = { name: this.editContent.name ?? '' }
this.notes = this.editContent.notes ?? ''
this.willFollow = this.editContent.willFollow ?? true
this.alias = this.editContent.alias ?? ''
this.requestUpdate()
}
if (changedProperties && changedProperties.has('isOpen') && this.isOpen) {
await this.getAvailableFeedSchemas()
} }
} }
customElements.define('add-friends-modal', AddFriendsModal); async getAvailableFeedSchemas() {
try {
this.isLoadingSchemas = true
const url = `${this.nodeUrl}/arbitrary/resources/search?service=DOCUMENT&identifier=ui_schema_feed&prefix=true`
const res = await fetch(url)
const data = await res.json()
if (data.error === 401) {
this.availableFeeedSchemas = []
} else {
this.availableFeeedSchemas = data.filter((item) => item.identifier === 'ui_schema_feed')
}
this.userFoundModalOpen = true
} catch (error) {
} finally {
this.isLoadingSchemas = false
}
}
// Standard functions
getApiKey() {
const coreNode = store.getState().app.nodeConfig.knownNodes[store.getState().app.nodeConfig.node]
return coreNode.apiKey
}
isEmptyArray(arr) {
if (!arr) { return true }
return arr.length === 0
}
round(number) {
return (Math.round(parseFloat(number) * 1e8) / 1e8).toFixed(8)
}
}
window.customElements.define('add-friends-modal', AddFriendsModal)

View File

@ -1,16 +1,17 @@
import {css, html, LitElement} from 'lit'; import { html, LitElement } from 'lit'
import axios from 'axios'; import { connect } from 'pwa-helpers'
import '@material/mwc-menu'; import { store } from '../../store'
import '@material/mwc-list/mwc-list-item.js'; import { RequestQueueWithPromise } from '../../../../plugins/plugins/utils/classes'
import {RequestQueueWithPromise} from '../../../../plugins/plugins/utils/queue'; import { avatarComponentStyles } from '../../styles/core-css'
import '../../../../plugins/plugins/core/components/TimeAgo'; import axios from 'axios'
import {connect} from 'pwa-helpers'; import ShortUniqueId from 'short-unique-id'
import {store} from '../../store'; import '../../../../plugins/plugins/core/components/TimeAgo'
import ShortUniqueId from 'short-unique-id'; import '@material/mwc-menu'
import '@material/mwc-list/mwc-list-item.js'
const requestQueue = new RequestQueueWithPromise(3); const requestQueue = new RequestQueueWithPromise(3)
const requestQueueRawData = new RequestQueueWithPromise(3); const requestQueueRawData = new RequestQueueWithPromise(3)
const requestQueueStatus = new RequestQueueWithPromise(3); const requestQueueStatus = new RequestQueueWithPromise(3)
export class AvatarComponent extends connect(store)(LitElement) { export class AvatarComponent extends connect(store)(LitElement) {
static get properties() { static get properties() {
@ -18,284 +19,210 @@ export class AvatarComponent extends connect(store)(LitElement) {
resource: { type: Object }, resource: { type: Object },
isReady: { type: Boolean }, isReady: { type: Boolean },
status: { type: Object }, status: { type: Object },
name: { type: String }, name: { type: String }
}; }
} }
static get styles() { static get styles() {
return css` return [avatarComponentStyles]
* {
--mdc-theme-text-primary-on-background: var(--black);
box-sizing: border-box;
}
:host {
width: 100%;
box-sizing: border-box;
}
img {
width: 100%;
max-height: 30vh;
border-radius: 5px;
cursor: pointer;
position: relative;
}
.smallLoading,
.smallLoading:after {
border-radius: 50%;
width: 2px;
height: 2px;
}
.defaultSize {
width: 100%;
height: 160px;
}
.parent-feed-item {
position: relative;
display: flex;
background-color: var(--chat-bubble-bg);
flex-grow: 0;
flex-direction: column;
align-items: flex-start;
justify-content: center;
border-radius: 5px;
padding: 12px 15px 4px 15px;
min-width: 150px;
width: 100%;
box-sizing: border-box;
cursor: pointer;
font-size: 16px;
}
.avatar {
width: 36px;
height: 36px;
border-radius: 50%;
overflow: hidden;
display: flex;
align-items: center;
}
.avatarApp {
width: 30px;
height: 30px;
border-radius: 50%;
overflow: hidden;
display: flex;
align-items: center;
}
.feed-item-name {
user-select: none;
color: #03a9f4;
margin-bottom: 5px;
}
.app-name {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
}
mwc-menu {
position: absolute;
}
`;
} }
constructor() { constructor() {
super(); super()
this.resource = { this.resource = {
identifier: '', identifier: '',
name: '', name: '',
service: '', service: ''
}; }
this.status = { this.status = {
status: '', status: ''
};
this.isReady = false;
this.nodeUrl = this.getNodeUrl();
this.myNode = this.getMyNode();
this.isFetching = false;
this.uid = new ShortUniqueId();
} }
getNodeUrl() { this.isReady = false
const myNode = this.nodeUrl = this.getNodeUrl()
window.parent.reduxStore.getState().app.nodeConfig.knownNodes[ this.myNode = this.getMyNode()
window.parent.reduxStore.getState().app.nodeConfig.node this.isFetching = false
]; this.uid = new ShortUniqueId()
return myNode.protocol + '://' + myNode.domain + ':' + myNode.port;
}
getMyNode() {
return window.parent.reduxStore.getState().app.nodeConfig.knownNodes[
window.parent.reduxStore.getState().app.nodeConfig.node
];
}
getApiKey() {
const myNode =
window.parent.reduxStore.getState().app.nodeConfig.knownNodes[
window.parent.reduxStore.getState().app.nodeConfig.node
];
return myNode.apiKey;
}
async fetchResource() {
try {
if (this.isFetching) return;
this.isFetching = true;
await axios.get(
`${this.nodeUrl}/arbitrary/resource/properties/${this.resource.service}/${this.resource.name}/${this.resource.identifier}?apiKey=${this.myNode.apiKey}`
);
this.isFetching = false;
} catch (error) {
this.isFetching = false;
}
}
async fetchVideoUrl() {
await this.fetchResource();
}
async getRawData() {
const url = `${this.nodeUrl}/arbitrary/${this.resource.service}/${this.resource.name}/${this.resource.identifier}?apiKey=${this.myNode.apiKey}`;
return await requestQueueRawData.enqueue(() => {
return axios.get(url);
});
// const response2 = await fetch(url, {
// method: 'GET',
// headers: {
// 'Content-Type': 'application/json'
// }
// })
// const responseData2 = await response2.json()
// return responseData2
}
updateDisplayWithPlaceholders(display, resource, rawdata) {
const pattern = /\$\$\{([a-zA-Z0-9_\.]+)\}\$\$/g;
for (const key in display) {
const value = display[key];
display[key] = value.replace(pattern, (match, p1) => {
if (p1.startsWith('rawdata.')) {
const dataKey = p1.split('.')[1];
if (rawdata[dataKey] === undefined) {
console.error('rawdata key not found:', dataKey);
}
return rawdata[dataKey] || match;
} else if (p1.startsWith('resource.')) {
const resourceKey = p1.split('.')[1];
if (resource[resourceKey] === undefined) {
console.error('resource key not found:', resourceKey);
}
return resource[resourceKey] || match;
}
return match;
});
}
}
async fetchStatus() {
let isCalling = false;
let percentLoaded = 0;
let timer = 24;
const response = await requestQueueStatus.enqueue(() => {
return axios.get(
`${this.nodeUrl}/arbitrary/resource/status/${this.resource.service}/${this.resource.name}/${this.resource.identifier}?apiKey=${this.myNode.apiKey}`
);
});
if (response && response.data && response.data.status === 'READY') {
this.status = response.data;
return;
}
const intervalId = setInterval(async () => {
if (isCalling) return;
isCalling = true;
const data = await requestQueue.enqueue(() => {
return axios.get(
`${this.nodeUrl}/arbitrary/resource/status/${this.resource.service}/${this.resource.name}/${this.resource.identifier}?apiKey=${this.myNode.apiKey}`
);
});
const res = data.data;
isCalling = false;
if (res.localChunkCount) {
if (res.percentLoaded) {
if (
res.percentLoaded === percentLoaded &&
res.percentLoaded !== 100
) {
timer = timer - 5;
} else {
timer = 24;
}
if (timer < 0) {
clearInterval(intervalId);
}
percentLoaded = res.percentLoaded;
}
this.status = res;
if (this.status.status === 'DOWNLOADED') {
await this.fetchResource();
}
}
// check if progress is 100% and clear interval if true
if (res.status === 'READY') {
clearInterval(intervalId);
this.status = res;
this.isReady = true;
}
}, 5000); // 1 second interval
}
async _fetchImage() {
try {
await this.fetchVideoUrl();
await this.fetchStatus();
} catch (error) {
/* empty */
}
}
firstUpdated() {
this._fetchImage();
} }
render() { render() {
return html` return html`
<div> <div>
${this.status.status !== 'READY' ${this.status.status !== 'READY' ?
? html` html`
<mwc-icon style="user-select:none;" <mwc-icon style="user-select:none;">account_circle</mwc-icon>
>account_circle</mwc-icon ` : ''
> }
` ${this.status.status === 'READY' ?
: ''} html`
${this.status.status === 'READY' <div style="height: 24px;width: 24px;overflow: hidden;">
? html`
<div
style="height: 24px;width: 24px;overflow: hidden;"
>
<img <img
src="${this src="${this.nodeUrl}/arbitrary/THUMBNAIL/${this.name}/qortal_avatar?async=true&apiKey=${this.myNode.apiKey}"
.nodeUrl}/arbitrary/THUMBNAIL/${this
.name}/qortal_avatar?async=true&apiKey=${this
.myNode.apiKey}"
style="width:100%; height:100%;border-radius:50%" style="width:100%; height:100%;border-radius:50%"
onerror="this.onerror=null; this.src='/img/incognito.png';" onerror="this.onerror=null; this.src='/img/incognito.png';"
/> />
</div> </div>
` ` : ''
: ''} }
</div> </div>
`; `
}
firstUpdated() {
this._fetchImage()
}
getNodeUrl() {
const myNode = store.getState().app.nodeConfig.knownNodes[store.getState().app.nodeConfig.node]
return myNode.protocol + '://' + myNode.domain + ':' + myNode.port
}
getMyNode() {
return store.getState().app.nodeConfig.knownNodes[store.getState().app.nodeConfig.node]
}
async fetchResource() {
try {
if (this.isFetching) return
this.isFetching = true
await axios.get(
`${this.nodeUrl}/arbitrary/resource/properties/${this.resource.service}/${this.resource.name}/${this.resource.identifier}?apiKey=${this.myNode.apiKey}`
)
this.isFetching = false
} catch (error) {
this.isFetching = false
} }
} }
customElements.define('avatar-component', AvatarComponent); async fetchVideoUrl() {
await this.fetchResource()
}
async getRawData() {
const url = `${this.nodeUrl}/arbitrary/${this.resource.service}/${this.resource.name}/${this.resource.identifier}?apiKey=${this.myNode.apiKey}`
return await requestQueueRawData.enqueue(() => {
return axios.get(url)
})
}
updateDisplayWithPlaceholders(display, resource, rawdata) {
const pattern = /\$\$\{([a-zA-Z0-9_\.]+)\}\$\$/g
for (const key in display) {
const value = display[key]
display[key] = value.replace(pattern, (match, p1) => {
if (p1.startsWith('rawdata.')) {
const dataKey = p1.split('.')[1]
if (rawdata[dataKey] === undefined) {
console.error('rawdata key not found:', dataKey)
}
return rawdata[dataKey] || match
} else if (p1.startsWith('resource.')) {
const resourceKey = p1.split('.')[1]
if (resource[resourceKey] === undefined) {
console.error('resource key not found:', resourceKey)
}
return resource[resourceKey] || match
}
return match
})
}
}
async fetchStatus() {
let isCalling = false
let percentLoaded = 0
let timer = 24
const response = await requestQueueStatus.enqueue(() => {
return axios.get(
`${this.nodeUrl}/arbitrary/resource/status/${this.resource.service}/${this.resource.name}/${this.resource.identifier}?apiKey=${this.myNode.apiKey}`
)
})
if (response && response.data && response.data.status === 'READY') {
this.status = response.data
return
}
const intervalId = setInterval(async () => {
if (isCalling) return
isCalling = true
const data = await requestQueue.enqueue(() => {
return axios.get(
`${this.nodeUrl}/arbitrary/resource/status/${this.resource.service}/${this.resource.name}/${this.resource.identifier}?apiKey=${this.myNode.apiKey}`
)
})
const res = data.data
isCalling = false
if (res.localChunkCount) {
if (res.percentLoaded) {
if (res.percentLoaded === percentLoaded && res.percentLoaded !== 100) {
timer = timer - 5
} else {
timer = 24
}
if (timer < 0) {
clearInterval(intervalId)
}
percentLoaded = res.percentLoaded
}
this.status = res
if (this.status.status === 'DOWNLOADED') {
await this.fetchResource()
}
}
// check if progress is 100% and clear interval if true
if (res.status === 'READY') {
clearInterval(intervalId)
this.status = res
this.isReady = true
}
}, 5000) // 5 second interval
}
async _fetchImage() {
try {
await this.fetchVideoUrl()
await this.fetchStatus()
} catch (error) {
/* empty */
}
}
// Standard functions
getApiKey() {
const coreNode = store.getState().app.nodeConfig.knownNodes[store.getState().app.nodeConfig.node]
return coreNode.apiKey
}
isEmptyArray(arr) {
if (!arr) { return true }
return arr.length === 0
}
round(number) {
return (Math.round(parseFloat(number) * 1e8) / 1e8).toFixed(8)
}
}
window.customElements.define('avatar-component', AvatarComponent)

View File

@ -1,196 +1,76 @@
import {css, html, LitElement} from 'lit'; import { html, LitElement } from 'lit'
import {connect} from 'pwa-helpers'; import { connect } from 'pwa-helpers'
import { store } from '../../store'
import '@vaadin/item'; import { setNewTab } from '../../redux/app/app-actions'
import '@vaadin/list-box'; import { get } from '../../../translate'
import '@polymer/paper-icon-button/paper-icon-button.js'; import { beginnerChecklistStyles } from '../../styles/core-css'
import '@polymer/iron-icons/iron-icons.js'; import ShortUniqueId from 'short-unique-id'
import {store} from '../../store.js'; import '../notification-view/popover'
import {setNewTab} from '../../redux/app/app-actions.js'; import '../../../../plugins/plugins/core/components/TimeAgo'
import '@material/mwc-icon'; import '@material/mwc-icon'
import {get} from '../../../translate'; import '@polymer/paper-icon-button/paper-icon-button.js'
import '../../../../plugins/plugins/core/components/TimeAgo.js'; import '@polymer/iron-icons/iron-icons.js'
import '../notification-view/popover.js'; import '@vaadin/item'
import ShortUniqueId from 'short-unique-id'; import '@vaadin/list-box'
class BeginnerChecklist extends connect(store)(LitElement) { class BeginnerChecklist extends connect(store)(LitElement) {
static properties = { static get properties() {
return {
notifications: { type: Array }, notifications: { type: Array },
showChecklist: { type: Boolean }, showChecklist: { type: Boolean },
theme: { type: String, reflect: true },
isSynced: { type: Boolean }, isSynced: { type: Boolean },
hasName: { type: Boolean }, hasName: { type: Boolean },
hasTourFinished: { type: Boolean }, hasTourFinished: { type: Boolean },
}; theme: { type: String, reflect: true }
}
}
static get styles() {
return [beginnerChecklistStyles]
}
constructor() { constructor() {
super(); super()
this.showChecklist = false; this.showChecklist = false
this.initialFetch = false; this.initialFetch = false
this.theme = localStorage.getItem('qortalTheme') this.isSynced = false
? localStorage.getItem('qortalTheme') this.hasName = null
: 'light'; this.nodeUrl = this.getNodeUrl()
this.isSynced = false; this.myNode = this.getMyNode()
this.hasName = null; this.hasTourFinished = null
this.nodeUrl = this.getNodeUrl(); this._controlTourFinished = this._controlTourFinished.bind(this)
this.myNode = this.getMyNode(); this.uid = new ShortUniqueId()
this.hasTourFinished = null; this.theme = localStorage.getItem('qortalTheme') ? localStorage.getItem('qortalTheme') : 'light'
this._controlTourFinished = this._controlTourFinished.bind(this);
this.uid = new ShortUniqueId();
}
_controlTourFinished() {
this.hasTourFinished = true;
}
firstUpdated() {
this.address = store.getState().app.selectedAddress.address;
this.hasTourFinished = JSON.parse(
localStorage.getItem(`hasViewedTour-${this.address}`) || 'null'
);
}
connectedCallback() {
super.connectedCallback();
window.addEventListener(
'send-tour-finished',
this._controlTourFinished
);
}
disconnectedCallback() {
window.removeEventListener(
'send-tour-finished',
this._controlTourFinished
);
super.disconnectedCallback();
}
getNodeUrl() {
const myNode =
window.parent.reduxStore.getState().app.nodeConfig.knownNodes[
window.parent.reduxStore.getState().app.nodeConfig.node
];
return myNode.protocol + '://' + myNode.domain + ':' + myNode.port;
}
getMyNode() {
return window.parent.reduxStore.getState().app.nodeConfig.knownNodes[
window.parent.reduxStore.getState().app.nodeConfig.node
];
}
async getName(recipient) {
try {
if (!recipient) return '';
const endpoint = `${this.nodeUrl}/names/address/${recipient}`;
const res = await fetch(endpoint);
const getNames = await res.json();
this.hasName = Array.isArray(getNames) && getNames.length > 0;
} catch (error) {
return '';
}
}
stateChanged(state) {
if (
state.app.nodeStatus &&
state.app.nodeStatus.syncPercent !== this.syncPercentage
) {
this.syncPercentage = state.app.nodeStatus.syncPercent;
if (
!this.hasAttempted &&
state.app.selectedAddress &&
state.app.nodeStatus.syncPercent === 100
) {
this.hasAttempted = true;
this.getName(state.app.selectedAddress.address);
}
}
if (
state.app.accountInfo &&
state.app.accountInfo.names.length &&
state.app.nodeStatus &&
state.app.nodeStatus.syncPercent === 100 &&
this.hasName === false &&
this.hasAttempted &&
state.app.accountInfo &&
state.app.accountInfo.names &&
state.app.accountInfo.names.length > 0
) {
this.hasName = true;
}
}
handleBlur() {
setTimeout(() => {
if (!this.shadowRoot.contains(document.activeElement)) {
this.showChecklist = false;
}
}, 0);
} }
render() { render() {
return this.hasName === false || this.hasTourFinished === false return this.hasName === false || this.hasTourFinished === false ?
? html` html`
<div class="layout"> <div class="layout">
<popover-component <popover-component for="popover-checklist" message=${get('tour.tour16')}></popover-component>
for="popover-checklist" <div id="popover-checklist" @click=${() => this._toggleChecklist()}>
message=${get('tour.tour16')} <mwc-icon id="checklist-general-icon" style=${`color: ${!this.hasName ? 'red' : 'var(--black)'}; cursor:pointer;user-select:none`}>
></popover-component> checklist
<div </mwc-icon>
id="popover-checklist" <vaadin-tooltip for="checklist-general-icon" position="bottom" hover-delay=${400} hide-delay=${1} text=${get('tour.tour16')}></vaadin-tooltip>
@click=${() => this._toggleChecklist()}
>
<mwc-icon
id="checklist-general-icon"
style=${`color: ${
!this.hasName ? 'red' : 'var(--black)'
}; cursor:pointer;user-select:none`}
>checklist</mwc-icon
>
<vaadin-tooltip
for="checklist-general-icon"
position="bottom"
hover-delay=${400}
hide-delay=${1}
text=${get('tour.tour16')}
>
</vaadin-tooltip>
</div> </div>
<div id="checklist-panel" class="popover-panel" style="visibility:${this.showChecklist ? 'visibile' : 'hidden'}" tabindex="0" @blur=${this.handleBlur}>
<div
id="checklist-panel"
class="popover-panel"
style="visibility:${this.showChecklist
? 'visibile'
: 'hidden'}"
tabindex="0"
@blur=${this.handleBlur}
>
<div class="list"> <div class="list">
<div class="task-list-item"> <div class="task-list-item">
<p>Are you synced?</p> <p>Are you synced?</p>
${this.syncPercentage === 100 ${this.syncPercentage === 100 ?
? html` html`
<mwc-icon <mwc-icon id="checklist-general-icon" style="color: green; user-select:none">
id="checklist-general-icon" task_alt
style="color: green; user-select:none" </mwc-icon>
>task_alt</mwc-icon
>
` `
: html` : html`
<mwc-icon <mwc-icon id="checklist-general-icon" style="color: red; user-select:none">
id="checklist-general-icon" radio_button_unchecked
style="color: red; user-select:none" </mwc-icon>
>radio_button_unchecked</mwc-icon `
> }
`}
</div> </div>
<div <div
class="task-list-item" class="task-list-item"
style="cursor:pointer" style="cursor:pointer"
@ -206,139 +86,129 @@ class BeginnerChecklist extends connect(store)(LitElement) {
title: 'Name Registration', title: 'Name Registration',
icon: 'vaadin:user-check', icon: 'vaadin:user-check',
mwcicon: 'manage_accounts', mwcicon: 'manage_accounts',
pluginNumber: pluginNumber: 'plugin-qCmtXAQmtu',
'plugin-qCmtXAQmtu',
menus: [], menus: [],
parent: false, parent: false
}, },
openExisting: true, openExisting: true
}) })
); );
this.handleBlur(); this.handleBlur();
}} }}
> >
<p>Do you have a name registered?</p> <p>Do you have a name registered?</p>
${this.hasName ${this.hasName ?
? html` html`
<mwc-icon <mwc-icon id="checklist-general-icon" style="color: green; user-select:none">
id="checklist-general-icon" task_alt
style="color: green; user-select:none" </mwc-icon>
>task_alt</mwc-icon ` : html`
> <mwc-icon id="checklist-general-icon" style="color: red; user-select:none">
radio_button_unchecked
</mwc-icon>
` `
: html` }
<mwc-icon
id="checklist-general-icon"
style="color: red; user-select:none"
>radio_button_unchecked</mwc-icon
>
`}
</div> </div>
</div> </div>
</div> </div>
</div> </div>
` `
: ''; : ''
}
firstUpdated() {
this.address = store.getState().app.selectedAddress.address
this.hasTourFinished = JSON.parse(localStorage.getItem(`hasViewedTour-${this.address}`) || 'null')
}
_controlTourFinished() {
this.hasTourFinished = true
}
connectedCallback() {
super.connectedCallback()
window.addEventListener('send-tour-finished', this._controlTourFinished)
}
disconnectedCallback() {
window.removeEventListener('send-tour-finished', this._controlTourFinished)
super.disconnectedCallback()
}
getNodeUrl() {
const myNode = store.getState().app.nodeConfig.knownNodes[store.getState().app.nodeConfig.node]
return myNode.protocol + '://' + myNode.domain + ':' + myNode.port
}
getMyNode() {
return store.getState().app.nodeConfig.knownNodes[store.getState().app.nodeConfig.node]
}
async getName(recipient) {
try {
if (!recipient) return ''
const endpoint = `${this.nodeUrl}/names/address/${recipient}`
const res = await fetch(endpoint)
const getNames = await res.json()
this.hasName = Array.isArray(getNames) && getNames.length > 0
} catch (error) {
return ''
}
}
stateChanged(state) {
if (state.app.nodeStatus && state.app.nodeStatus.syncPercent !== this.syncPercentage) {
this.syncPercentage = state.app.nodeStatus.syncPercent
if (!this.hasAttempted && state.app.selectedAddress && state.app.nodeStatus.syncPercent === 100) {
this.hasAttempted = true
this.getName(state.app.selectedAddress.address)
}
}
if (state.app.accountInfo &&
state.app.accountInfo.names.length && state.app.nodeStatus && state.app.nodeStatus.syncPercent === 100 &&
this.hasName === false && this.hasAttempted && state.app.accountInfo && state.app.accountInfo.names &&
state.app.accountInfo.names.length > 0
) {
this.hasName = true
}
}
handleBlur() {
setTimeout(() => {
if (!this.shadowRoot.contains(document.activeElement)) {
this.showChecklist = false
}
}, 0)
} }
_toggleChecklist() { _toggleChecklist() {
this.showChecklist = !this.showChecklist; this.showChecklist = !this.showChecklist
if (this.showChecklist) { if (this.showChecklist) {
requestAnimationFrame(() => { requestAnimationFrame(() => {
this.shadowRoot.getElementById('checklist-panel').focus(); this.shadowRoot.getElementById('checklist-panel').focus()
}); })
} }
} }
static styles = css` // Standard functions
.layout { getApiKey() {
display: flex; const coreNode = store.getState().app.nodeConfig.knownNodes[store.getState().app.nodeConfig.node]
flex-direction: column; return coreNode.apiKey
align-items: center;
position: relative;
} }
.count { isEmptyArray(arr) {
position: absolute; if (!arr) { return true }
top: -5px; return arr.length === 0
right: -5px;
font-size: 12px;
background-color: red;
color: white;
border-radius: 50%;
width: 16px;
height: 16px;
display: flex;
align-items: center;
justify-content: center;
} }
.nocount { round(number) {
display: none; return (Math.round(parseFloat(number) * 1e8) / 1e8).toFixed(8)
}
} }
.popover-panel { window.customElements.define('beginner-checklist', BeginnerChecklist)
position: absolute;
width: 200px;
padding: 10px;
background-color: var(--white);
border: 1px solid var(--black);
border-radius: 4px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
top: 40px;
max-height: 350px;
overflow: auto;
scrollbar-width: thin;
scrollbar-color: #6a6c75 #a1a1a1;
}
.popover-panel::-webkit-scrollbar {
width: 11px;
}
.popover-panel::-webkit-scrollbar-track {
background: #a1a1a1;
}
.popover-panel::-webkit-scrollbar-thumb {
background-color: #6a6c75;
border-radius: 6px;
border: 3px solid #a1a1a1;
}
.list {
display: flex;
flex-direction: column;
gap: 15px;
}
.task-list-item {
display: flex;
gap: 15px;
justify-content: space-between;
align-items: center;
}
.checklist-item {
padding: 5px;
border-bottom: 1px solid;
display: flex;
justify-content: space-between;
cursor: pointer;
transition: 0.2s all;
}
.checklist-item:hover {
background: var(--nav-color-hover);
}
p {
font-size: 16px;
color: var(--black);
margin: 0px;
padding: 0px;
}
`;
}
customElements.define('beginner-checklist', BeginnerChecklist);

View File

@ -1,39 +1,24 @@
import { Sha256 } from 'asmcrypto.js' import { Sha256 } from 'asmcrypto.js'
function sbrk(size, heap) { function sbrk(size, heap) {
let brk = 512 * 1024 // stack top let brk = 512 * 1024 // stack top
let old = brk let old = brk
brk += size brk += size
if (brk > heap.length) throw new Error('heap exhausted')
if (brk > heap.length)
throw new Error('heap exhausted')
return old return old
} }
self.addEventListener('message', async e => { self.addEventListener('message', async e => {
const response = await computePow(e.data.convertedBytes, e.data.path) const response = await computePow(e.data.convertedBytes, e.data.path)
postMessage(response) postMessage(response)
}) })
const memory = new WebAssembly.Memory({ initial: 256, maximum: 256 }) const memory = new WebAssembly.Memory({ initial: 256, maximum: 256 })
const heap = new Uint8Array(memory.buffer) const heap = new Uint8Array(memory.buffer)
const computePow = async (convertedBytes, path) => { const computePow = async (convertedBytes, path) => {
let response = null let response = null
await new Promise((resolve, reject) => { await new Promise((resolve, reject) => {
const _convertedBytesArray = Object.keys(convertedBytes).map( const _convertedBytesArray = Object.keys(convertedBytes).map(
function (key) { function (key) {
return convertedBytes[key] return convertedBytes[key]
@ -49,7 +34,6 @@ const hashAry = new Uint8Array(
hashPtr, hashPtr,
32 32
) )
hashAry.set(convertedBytesHash) hashAry.set(convertedBytesHash)
const difficulty = 14 const difficulty = 14
const workBufferLength = 8 * 1024 * 1024 const workBufferLength = 8 * 1024 * 1024
@ -57,23 +41,19 @@ const workBufferPtr = sbrk(
workBufferLength, workBufferLength,
heap heap
) )
const importObject = { const importObject = {
env: { env: {
memory: memory memory: memory
}, }
}; }
function loadWebAssembly(filename, imports) { function loadWebAssembly(filename, imports) {
return fetch(filename) return fetch(filename)
.then(response => response.arrayBuffer()) .then(response => response.arrayBuffer())
.then(buffer => WebAssembly.compile(buffer)) .then(buffer => WebAssembly.compile(buffer))
.then(module => { .then(module => {
return new WebAssembly.Instance(module, importObject); return new WebAssembly.Instance(module, importObject)
}); })
} }
loadWebAssembly(path) loadWebAssembly(path)
.then(wasmModule => { .then(wasmModule => {
response = { response = {
@ -81,11 +61,7 @@ loadWebAssembly(path)
} }
resolve() resolve()
});
}) })
})
return response return response
} }

View File

@ -1,7 +1,8 @@
import {css, html, LitElement} from 'lit' import { html, LitElement } from 'lit'
import {store} from '../../store'
import { connect } from 'pwa-helpers' import { connect } from 'pwa-helpers'
import { store } from '../../store'
import { translate } from '../../../translate' import { translate } from '../../../translate'
import { coreSyncStatusStyles } from '../../styles/core-css'
class CoreSyncStatus extends connect(store)(LitElement) { class CoreSyncStatus extends connect(store)(LitElement) {
static get properties() { static get properties() {
@ -12,6 +13,10 @@ class CoreSyncStatus extends connect(store)(LitElement) {
} }
} }
static get styles() {
return [coreSyncStatusStyles]
}
constructor() { constructor() {
super() super()
this.nodeInfos = [] this.nodeInfos = []
@ -19,69 +24,6 @@ class CoreSyncStatus extends connect(store)(LitElement) {
this.theme = localStorage.getItem('qortalTheme') ? localStorage.getItem('qortalTheme') : 'light' this.theme = localStorage.getItem('qortalTheme') ? localStorage.getItem('qortalTheme') : 'light'
} }
static get styles() {
return css`
.lineHeight {
line-height: 33%;
}
.tooltip {
display: inline-block;
position: relative;
text-align: left;
}
.tooltip .bottom {
min-width: 200px;
max-width: 250px;
top: 35px;
left: 50%;
transform: translate(-50%, 0);
padding: 10px 10px;
color: var(--black);
background-color: var(--white);
font-weight: normal;
font-size: 13px;
border-radius: 8px;
position: absolute;
z-index: 99999999;
box-sizing: border-box;
box-shadow: 0 1px 8px rgba(0,0,0,0.5);
border: 1px solid var(--black);
visibility: hidden;
opacity: 0;
transition: opacity 0.8s;
}
.tooltip:hover .bottom {
visibility: visible;
opacity: 1;
}
.tooltip .bottom i {
position: absolute;
bottom: 100%;
left: 50%;
margin-left: -12px;
width: 24px;
height: 12px;
overflow: hidden;
}
.tooltip .bottom i::after {
content: '';
position: absolute;
width: 12px;
height: 12px;
left: 50%;
transform: translate(-50%,50%) rotate(45deg);
background-color: var(--white);
border: 1px solid var(--black);
box-shadow: 0 1px 8px rgba(0,0,0,0.5);
}
`
}
render() { render() {
return html` return html`
<div id="core-sync-status-id"> <div id="core-sync-status-id">
@ -221,6 +163,20 @@ class CoreSyncStatus extends connect(store)(LitElement) {
// ... // ...
} }
// Standard functions
getApiKey() {
const coreNode = store.getState().app.nodeConfig.knownNodes[store.getState().app.nodeConfig.node]
return coreNode.apiKey
} }
customElements.define('core-sync-status', CoreSyncStatus) isEmptyArray(arr) {
if (!arr) { return true }
return arr.length === 0
}
round(number) {
return (Math.round(parseFloat(number) * 1e8) / 1e8).toFixed(8)
}
}
window.customElements.define('core-sync-status', CoreSyncStatus)

View File

@ -1,19 +1,19 @@
import {css, html, LitElement} from 'lit'; import { html, LitElement } from 'lit'
import { connect } from 'pwa-helpers'
import { store } from '../../store'
import { setNewTab } from '../../redux/app/app-actions'
import { RequestQueueWithPromise } from '../../../../plugins/plugins/utils/classes'
import { translate, } from '../../../translate' import { translate, } from '../../../translate'
import { feedItemStyles } from '../../styles/core-css'
import axios from 'axios' import axios from 'axios'
import '@material/mwc-menu'; import ShortUniqueId from 'short-unique-id'
import '@material/mwc-list/mwc-list-item.js'
import {RequestQueueWithPromise} from '../../../../plugins/plugins/utils/queue';
import '../../../../plugins/plugins/core/components/TimeAgo' import '../../../../plugins/plugins/core/components/TimeAgo'
import {connect} from 'pwa-helpers'; import '@material/mwc-menu'
import {store} from '../../store'; import '@material/mwc-list/mwc-list-item.js'
import {setNewTab} from '../../redux/app/app-actions';
import ShortUniqueId from 'short-unique-id';
const requestQueue = new RequestQueueWithPromise(3);
const requestQueueRawData = new RequestQueueWithPromise(3);
const requestQueueStatus = new RequestQueueWithPromise(3);
const requestQueue = new RequestQueueWithPromise(3)
const requestQueueRawData = new RequestQueueWithPromise(3)
const requestQueueStatus = new RequestQueueWithPromise(3)
export class FeedItem extends connect(store)(LitElement) { export class FeedItem extends connect(store)(LitElement) {
static get properties() { static get properties() {
@ -24,127 +24,15 @@ export class FeedItem extends connect(store)(LitElement) {
feedItem: { type: Object }, feedItem: { type: Object },
appName: { type: String }, appName: { type: String },
link: { type: String } link: { type: String }
}; }
} }
static get styles() { static get styles() {
return css` return [feedItemStyles]
* {
--mdc-theme-text-primary-on-background: var(--black);
box-sizing: border-box;
}
:host {
width: 100%;
box-sizing: border-box;
}
img {
width:100%;
max-height:30vh;
border-radius: 5px;
cursor: pointer;
position: relative;
}
.smallLoading,
.smallLoading:after {
border-radius: 50%;
width: 2px;
height: 2px;
}
.smallLoading {
border-width: 0.8em;
border-style: solid;
border-color: rgba(3, 169, 244, 0.2) rgba(3, 169, 244, 0.2)
rgba(3, 169, 244, 0.2) rgb(3, 169, 244);
font-size: 30px;
position: relative;
text-indent: -9999em;
transform: translateZ(0px);
animation: 1.1s linear 0s infinite normal none running loadingAnimation;
}
.defaultSize {
width: 100%;
height: 160px;
}
.parent-feed-item {
position: relative;
display: flex;
background-color: var(--chat-bubble-bg);
flex-grow: 0;
flex-direction: column;
align-items: flex-start;
justify-content: center;
border-radius: 5px;
padding: 12px 15px 4px 15px;
min-width: 150px;
width: 100%;
box-sizing: border-box;
cursor: pointer;
font-size: 16px;
}
.avatar {
width: 36px;
height: 36px;
border-radius:50%;
overflow: hidden;
display:flex;
align-items:center;
}
.avatarApp {
width: 30px;
height: 30px;
border-radius:50%;
overflow: hidden;
display:flex;
align-items:center;
}
.feed-item-name {
user-select: none;
color: #03a9f4;
margin-bottom: 5px;
}
.app-name {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
}
mwc-menu {
position: absolute;
}
@-webkit-keyframes loadingAnimation {
0% {
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
-webkit-transform: rotate(360deg);
transform: rotate(360deg);
}
}
@keyframes loadingAnimation {
0% {
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
-webkit-transform: rotate(360deg);
transform: rotate(360deg);
}
}
`;
} }
constructor() { constructor() {
super(); super()
this.resource = { this.resource = {
identifier: "", identifier: "",
name: "", name: "",
@ -159,33 +47,76 @@ export class FeedItem extends connect(store)(LitElement) {
this.hasCalledWhenDownloaded = false this.hasCalledWhenDownloaded = false
this.isFetching = false this.isFetching = false
this.uid = new ShortUniqueId() this.uid = new ShortUniqueId()
this.observer = new IntersectionObserver(entries => { this.observer = new IntersectionObserver(entries => {
for (const entry of entries) { for (const entry of entries) {
if (entry.isIntersecting && this.status.status !== 'READY') { if (entry.isIntersecting && this.status.status !== 'READY') {
this._fetchImage(); this._fetchImage()
// Stop observing after the image has started loading // Stop observing after the image has started loading
this.observer.unobserve(this); this.observer.unobserve(this)
} }
} }
}); })
this.feedItem = null this.feedItem = null
} }
getNodeUrl(){
const myNode = window.parent.reduxStore.getState().app.nodeConfig.knownNodes[window.parent.reduxStore.getState().app.nodeConfig.node]
render() {
let avatarImg
const avatarUrl = `${this.nodeUrl}/arbitrary/THUMBNAIL/${this.resource.name}/qortal_avatar?async=true`
avatarImg = html`<img src="${avatarUrl}" style="width:100%; height:100%;" onerror="this.onerror=null; this.src='/img/incognito.png';" />`
let avatarImgApp
const avatarUrl2 = `${this.nodeUrl}/arbitrary/THUMBNAIL/${this.appName}/qortal_avatar?async=true`
avatarImgApp = html`<img src="${avatarUrl2}" style="width:100%; height:100%;" onerror="this.onerror=null; this.src='/img/incognito.png';" />`
return html`
<div
class=${[`image-container`, this.status.status !== 'READY' ? 'defaultSize' : '', this.status.status !== 'READY' ? 'hideImg' : '',].join(' ')}
style=" box-sizing: border-box;"
>
${this.status.status !== 'READY' ?
html`
<div style="display:flex;flex-direction:column;width:100%;height:100%;justify-content:center;align-items:center; box-sizing: border-box;">
<div class=${`smallLoading`}></div>
<p style="color: var(--black)">
${`${Math.round(this.status.percentLoaded || 0).toFixed(0)}% `}${translate('chatpage.cchange94')}
</p>
</div>
`
: ''
}
${this.status.status === 'READY' && this.feedItem ?
html`
<div class="parent-feed-item" style="position:relative" @click=${this.goToFeedLink}>
<div style="display:flex;gap:10px;margin-bottom:5px">
<div class="avatar">${avatarImg}</div>
<span class="feed-item-name">${this.resource.name}</span>
</div>
<div>
<p>${this.feedItem.title}</p>
</div>
<div class="app-name">
<div class="avatarApp">${avatarImgApp}</div>
<message-time timestamp=${this.resource.created}></message-time>
</div>
</div>
`
: ''
}
</div>
`
}
firstUpdated() {
this.observer.observe(this)
}
getNodeUrl() {
const myNode = store.getState().app.nodeConfig.knownNodes[store.getState().app.nodeConfig.node]
return myNode.protocol + '://' + myNode.domain + ':' + myNode.port return myNode.protocol + '://' + myNode.domain + ':' + myNode.port
} }
getMyNode(){
return window.parent.reduxStore.getState().app.nodeConfig.knownNodes[window.parent.reduxStore.getState().app.nodeConfig.node]
}
getApiKey() { getMyNode() {
const myNode = return store.getState().app.nodeConfig.knownNodes[store.getState().app.nodeConfig.node]
window.parent.reduxStore.getState().app.nodeConfig.knownNodes[
window.parent.reduxStore.getState().app.nodeConfig.node
];
return myNode.apiKey;
} }
async fetchResource() { async fetchResource() {
@ -201,89 +132,79 @@ getMyNode(){
} }
async fetchVideoUrl() { async fetchVideoUrl() {
await this.fetchResource() await this.fetchResource()
} }
async getRawData() { async getRawData() {
const url = `${this.nodeUrl}/arbitrary/${this.resource.service}/${this.resource.name}/${this.resource.identifier}?apiKey=${this.myNode.apiKey}` const url = `${this.nodeUrl}/arbitrary/${this.resource.service}/${this.resource.name}/${this.resource.identifier}?apiKey=${this.myNode.apiKey}`
return await requestQueueRawData.enqueue(() => { return await requestQueueRawData.enqueue(() => {
return axios.get(url) return axios.get(url)
}) })
// const response2 = await fetch(url, {
// method: 'GET',
// headers: {
// 'Content-Type': 'application/json'
// }
// })
// const responseData2 = await response2.json()
// return responseData2
} }
updateDisplayWithPlaceholders(display, resource, rawdata) { updateDisplayWithPlaceholders(display, resource, rawdata) {
const pattern = /\$\$\{([a-zA-Z0-9_\.]+)\}\$\$/g; const pattern = /\$\$\{([a-zA-Z0-9_\.]+)\}\$\$/g
for (const key in display) { for (const key in display) {
const value = display[key]; const value = display[key]
display[key] = value.replace(pattern, (match, p1) => { display[key] = value.replace(pattern, (match, p1) => {
if (p1.startsWith('rawdata.')) { if (p1.startsWith('rawdata.')) {
const dataKey = p1.split('.')[1]; const dataKey = p1.split('.')[1]
if (rawdata[dataKey] === undefined) { if (rawdata[dataKey] === undefined) {
console.error("rawdata key not found:", dataKey); console.error("rawdata key not found:", dataKey)
} }
return rawdata[dataKey] || match; return rawdata[dataKey] || match
} else if (p1.startsWith('resource.')) { } else if (p1.startsWith('resource.')) {
const resourceKey = p1.split('.')[1]; const resourceKey = p1.split('.')[1]
if (resource[resourceKey] === undefined) { if (resource[resourceKey] === undefined) {
console.error("resource key not found:", resourceKey); console.error("resource key not found:", resourceKey)
} }
return resource[resourceKey] || match; return resource[resourceKey] || match
} }
return match; return match
}); })
} }
} }
async fetchStatus() { async fetchStatus() {
let isCalling = false let isCalling = false
let percentLoaded = 0 let percentLoaded = 0
let timer = 24 let timer = 24
const response = await requestQueueStatus.enqueue(() => { const response = await requestQueueStatus.enqueue(() => {
return axios.get(`${this.nodeUrl}/arbitrary/resource/status/${this.resource.service}/${this.resource.name}/${this.resource.identifier}?apiKey=${this.myNode.apiKey}`) return axios.get(`${this.nodeUrl}/arbitrary/resource/status/${this.resource.service}/${this.resource.name}/${this.resource.identifier}?apiKey=${this.myNode.apiKey}`)
}) })
if (response && response.data && response.data.status === 'READY') { if (response && response.data && response.data.status === 'READY') {
const rawData = await this.getRawData() const rawData = await this.getRawData()
const object = { const object = {
...this.resource.schema.display ...this.resource.schema.display
} }
this.updateDisplayWithPlaceholders(object, {}, rawData.data) this.updateDisplayWithPlaceholders(object, {}, rawData.data)
this.feedItem = object this.feedItem = object
this.status = response.data this.status = response.data
return return
} }
const intervalId = setInterval(async () => { const intervalId = setInterval(async () => {
if (isCalling) return if (isCalling) return
isCalling = true isCalling = true
const data = await requestQueue.enqueue(() => { const data = await requestQueue.enqueue(() => {
return axios.get(`${this.nodeUrl}/arbitrary/resource/status/${this.resource.service}/${this.resource.name}/${this.resource.identifier}?apiKey=${this.myNode.apiKey}`) return axios.get(`${this.nodeUrl}/arbitrary/resource/status/${this.resource.service}/${this.resource.name}/${this.resource.identifier}?apiKey=${this.myNode.apiKey}`)
}); })
const res = data.data const res = data.data
isCalling = false isCalling = false
if (res.localChunkCount) { if (res.localChunkCount) {
if (res.percentLoaded) { if (res.percentLoaded) {
if ( if (res.percentLoaded === percentLoaded && res.percentLoaded !== 100) {
res.percentLoaded === percentLoaded &&
res.percentLoaded !== 100
) {
timer = timer - 5 timer = timer - 5
} else { } else {
timer = 24 timer = 24
@ -300,12 +221,14 @@ getMyNode(){
isCalling = false isCalling = false
this.fetchResource() this.fetchResource()
}, 25000) }, 25000)
return return
} }
percentLoaded = res.percentLoaded percentLoaded = res.percentLoaded
} }
this.status = res this.status = res
if (this.status.status === 'DOWNLOADED') { if (this.status.status === 'DOWNLOADED') {
await this.fetchResource() await this.fetchResource()
} }
@ -323,7 +246,7 @@ getMyNode(){
this.status = res this.status = res
this.isReady = true this.isReady = true
} }
}, 5000) // 1 second interval }, 5000) // 5 second interval
} }
async _fetchImage() { async _fetchImage() {
@ -333,13 +256,6 @@ getMyNode(){
} catch (error) { /* empty */ } } catch (error) { /* empty */ }
} }
firstUpdated(){
this.observer.observe(this);
}
async goToFeedLink() { async goToFeedLink() {
try { try {
let newQuery = this.link let newQuery = this.link
@ -380,14 +296,13 @@ getMyNode(){
} }
} }
async extractComponents(url) { async extractComponents(url) {
if (!url.startsWith("qortal://")) { if (!url.startsWith("qortal://")) {
return null return null
} }
url = url.replace(/^(qortal\:\/\/)/, "") url = url.replace(/^(qortal\:\/\/)/, "")
if (url.includes("/")) { if (url.includes("/")) {
let parts = url.split("/") let parts = url.split("/")
const service = parts[0].toUpperCase() const service = parts[0].toUpperCase()
@ -426,84 +341,20 @@ getMyNode(){
return null return null
} }
// Standard functions
getApiKey() {
const coreNode = store.getState().app.nodeConfig.knownNodes[store.getState().app.nodeConfig.node]
return coreNode.apiKey
render() {
let avatarImg
const avatarUrl = `${this.nodeUrl}/arbitrary/THUMBNAIL/${this.resource.name}/qortal_avatar?async=true&apiKey=${this.myNode.apiKey}`;
avatarImg = html`<img
src="${avatarUrl}"
style="width:100%; height:100%;"
onerror="this.onerror=null; this.src='/img/incognito.png';"
/>`;
let avatarImgApp
const avatarUrl2 = `${this.nodeUrl}/arbitrary/THUMBNAIL/${this.appName}/qortal_avatar?async=true&apiKey=${this.myNode.apiKey}`;
avatarImgApp = html`<img
src="${avatarUrl2}"
style="width:100%; height:100%;"
onerror="this.onerror=null; this.src='/img/incognito.png';"
/>`;
return html`
<div
class=${[
`image-container`,
this.status.status !== 'READY'
? 'defaultSize'
: '',
this.status.status !== 'READY'
? 'hideImg'
: '',
].join(' ')}
style=" box-sizing: border-box;"
>
${
this.status.status !== 'READY'
? html`
<div
style="display:flex;flex-direction:column;width:100%;height:100%;justify-content:center;align-items:center; box-sizing: border-box;"
>
<div
class=${`smallLoading`}
></div>
<p style="color: var(--black)">${`${Math.round(this.status.percentLoaded || 0
).toFixed(0)}% `}${translate('chatpage.cchange94')}</p>
</div>
`
: ''
} }
${this.status.status === 'READY' && this.feedItem ? html`
<div class="parent-feed-item" style="position:relative" @click=${this.goToFeedLink}>
<div style="display:flex;gap:10px;margin-bottom:5px">
<div class="avatar">
${avatarImg}</div> <span class="feed-item-name">${this.resource.name}</span>
</div>
<div>
<p>${this.feedItem.title}</p>
</div>
<div class="app-name">
<div class="avatarApp">
${avatarImgApp}
</div>
<message-time
timestamp=${this
.resource
.created}
></message-time>
</div>
</div>
` : ''}
</div>
`
isEmptyArray(arr) {
if (!arr) { return true }
return arr.length === 0
}
round(number) {
return (Math.round(parseFloat(number) * 1e8) / 1e8).toFixed(8)
} }
} }
customElements.define('feed-item', FeedItem); window.customElements.define('feed-item', FeedItem)

View File

@ -1,146 +1,42 @@
// popover-component.js import { html, LitElement } from 'lit'
import {css, html, LitElement} from 'lit'; import { connect } from 'pwa-helpers'
import {createPopper} from '@popperjs/core'; import { store } from '../../store'
import '@material/mwc-icon'; import { createPopper } from '@popperjs/core'
import { setNewTab, setSideEffectAction } from '../../redux/app/app-actions'
import { translate } from '../../../translate' import { translate } from '../../../translate'
import {store} from '../../store'; import { friendItemActionsStyles } from '../../styles/core-css'
import {connect} from 'pwa-helpers'; import ShortUniqueId from 'short-unique-id'
import {setNewTab, setSideEffectAction} from '../../redux/app/app-actions'; import '@material/mwc-icon'
import ShortUniqueId from 'short-unique-id';
export class FriendItemActions extends connect(store)(LitElement) { export class FriendItemActions extends connect(store)(LitElement) {
static styles = css`
:host {
display: none;
position: absolute;
background-color: var(--white);
border: 1px solid #ddd;
padding: 8px;
z-index: 10;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
color: var(--black);
max-width: 250px;
}
.close-icon {
cursor: pointer;
float: right;
margin-left: 10px;
color: var(--black);
}
.send-message-button {
font-family: Roboto, sans-serif;
letter-spacing: 0.3px;
font-weight: 300;
padding: 8px 5px;
border-radius: 3px;
text-align: center;
color: var(--mdc-theme-primary);
transition: all 0.3s ease-in-out;
display: flex;
align-items: center;
gap: 10px;
}
.send-message-button:hover {
cursor: pointer;
background-color: #03a8f485;
}
.action-parent {
display: flex;
flex-direction: column;
width: 100%;
}
div[tabindex='0']:focus {
outline: none;
}
`;
static get properties() { static get properties() {
return { return {
for: { type: String, reflect: true }, for: { type: String, reflect: true },
message: { type: String }, message: { type: String },
openEditFriend: { attribute: false }, openEditFriend: { attribute: false },
name: { type: String }, name: { type: String },
closeSidePanel: { attribute: false, type: Object }, closeSidePanel: { attribute: false, type: Object }
}; }
}
static get styles() {
return [friendItemActionsStyles]
} }
constructor() { constructor() {
super(); super()
this.message = ''; this.message = ''
this.nodeUrl = this.getNodeUrl(); this.nodeUrl = this.getNodeUrl()
this.uid = new ShortUniqueId(); this.uid = new ShortUniqueId()
this.getUserAddress = this.getUserAddress.bind(this); this.getUserAddress = this.getUserAddress.bind(this)
}
getNodeUrl() {
const myNode =
store.getState().app.nodeConfig.knownNodes[
window.parent.reduxStore.getState().app.nodeConfig.node
]
return myNode.protocol + '://' + myNode.domain + ':' + myNode.port
}
firstUpdated() {
// We'll defer the popper attachment to the openPopover() method to ensure target availability
}
attachToTarget(target) {
if (!this.popperInstance && target) {
this.popperInstance = createPopper(target, this, {
placement: 'bottom',
});
}
}
openPopover(target) {
this.attachToTarget(target);
this.style.display = 'block';
setTimeout(() => {
this.shadowRoot.getElementById('parent-div').focus();
}, 50);
}
closePopover() {
this.style.display = 'none';
if (this.popperInstance) {
this.popperInstance.destroy();
this.popperInstance = null;
}
this.requestUpdate();
}
handleBlur() {
setTimeout(() => {
this.closePopover();
}, 0);
}
async getUserAddress() {
try {
const url = `${this.nodeUrl}/names/${this.name}`;
const res = await fetch(url);
const result = await res.json();
if (result.error === 401) {
return '';
} else {
return result.owner;
}
} catch (error) {
return '';
}
} }
render() { render() {
return html` return html`
<div id="parent-div" tabindex="0" @blur=${this.handleBlur}> <div id="parent-div" tabindex="0" @blur=${this.handleBlur}>
<span class="close-icon" @click="${this.closePopover}" <span class="close-icon" @click="${this.closePopover}">
><mwc-icon style="color: var(--black)" <mwc-icon style="color: var(--black)">close</mwc-icon>
>close</mwc-icon </span>
></span
>
<div class="action-parent"> <div class="action-parent">
<div <div
class="send-message-button" class="send-message-button"
@ -164,16 +60,15 @@ export class FriendItemActions extends connect(store)(LitElement) {
myPlugObj: { myPlugObj: {
url: 'q-chat', url: 'q-chat',
domain: 'core', domain: 'core',
page: 'messaging/q-chat/index.html', page: 'q-chat/index.html',
title: 'Q-Chat', title: 'Q-Chat',
icon: 'vaadin:chat', icon: 'vaadin:chat',
mwcicon: 'forum', mwcicon: 'forum',
pluginNumber: 'plugin-qhsyOnpRhT', pluginNumber: 'plugin-qhsyOnpRhT',
menus: [], menus: [],
parent: false, parent: false
}, },
openExisting: true
openExisting: true,
}) })
); );
store.dispatch( store.dispatch(
@ -181,8 +76,8 @@ export class FriendItemActions extends connect(store)(LitElement) {
type: 'openPrivateChat', type: 'openPrivateChat',
data: { data: {
address, address,
name: this.name, name: this.name
}, }
}) })
); );
this.closePopover(); this.closePopover();
@ -208,9 +103,9 @@ export class FriendItemActions extends connect(store)(LitElement) {
icon: 'vaadin:mailbox', icon: 'vaadin:mailbox',
mwcicon: 'mail_outline', mwcicon: 'mail_outline',
menus: [], menus: [],
parent: false, parent: false
}, },
openExisting: true, openExisting: true
}) })
); );
this.closePopover(); this.closePopover();
@ -223,26 +118,89 @@ export class FriendItemActions extends connect(store)(LitElement) {
<div <div
class="send-message-button" class="send-message-button"
@click="${() => { @click="${() => {
const customEvent = new CustomEvent( const customEvent = new CustomEvent('open-visiting-profile', { detail: this.name });
'open-visiting-profile',
{
detail: this.name,
}
);
window.dispatchEvent(customEvent); window.dispatchEvent(customEvent);
this.closePopover(); this.closePopover();
this.closeSidePanel(); this.closeSidePanel();
}}" }}"
> >
<mwc-icon style="color: var(--black)" <mwc-icon style="color: var(--black)">person</mwc-icon>
>person</mwc-icon
>
${translate('profile.profile18')} ${translate('profile.profile18')}
</div> </div>
</div> </div>
</div> </div>
`; `
}
firstUpdated() {
// ...
}
getNodeUrl() {
const myNode = store.getState().app.nodeConfig.knownNodes[store.getState().app.nodeConfig.node]
return myNode.protocol + '://' + myNode.domain + ':' + myNode.port
}
attachToTarget(target) {
if (!this.popperInstance && target) {
this.popperInstance = createPopper(target, this, {
placement: 'bottom'
})
} }
} }
customElements.define('friend-item-actions', FriendItemActions); openPopover(target) {
this.attachToTarget(target)
this.style.display = 'block'
setTimeout(() => {
this.shadowRoot.getElementById('parent-div').focus()
}, 50)
}
closePopover() {
this.style.display = 'none'
if (this.popperInstance) {
this.popperInstance.destroy()
this.popperInstance = null
}
this.requestUpdate()
}
handleBlur() {
setTimeout(() => {
this.closePopover()
}, 0)
}
async getUserAddress() {
try {
const url = `${this.nodeUrl}/names/${this.name}`
const res = await fetch(url)
const result = await res.json()
if (result.error === 401) {
return ''
} else {
return result.owner
}
} catch (error) {
return ''
}
}
// Standard functions
getApiKey() {
const coreNode = store.getState().app.nodeConfig.knownNodes[store.getState().app.nodeConfig.node]
return coreNode.apiKey
}
isEmptyArray(arr) {
if (!arr) { return true }
return arr.length === 0
}
round(number) {
return (Math.round(parseFloat(number) * 1e8) / 1e8).toFixed(8)
}
}
window.customElements.define('friend-item-actions', FriendItemActions)

View File

@ -1,18 +1,17 @@
import {html, LitElement} from 'lit'; import { html, LitElement } from 'lit'
import '@material/mwc-icon'; import { store } from '../../store'
import './friends-view' import { connect } from 'pwa-helpers'
import {friendsViewStyles} from './friends-view-css';
import {connect} from 'pwa-helpers';
import {store} from '../../store';
import './feed-item'
import { translate } from '../../../translate' import { translate } from '../../../translate'
import { friendsViewStyles } from '../../styles/core-css'
import './feed-item'
import './friends-view'
import '@material/mwc-icon'
import '@polymer/paper-spinner/paper-spinner-lite.js' import '@polymer/paper-spinner/paper-spinner-lite.js'
const perEndpointCount = 20
const totalDesiredCount = 100
const maxResultsInMemory = 300
const perEndpointCount = 20;
const totalDesiredCount = 100;
const maxResultsInMemory = 300;
class FriendsFeed extends connect(store)(LitElement) { class FriendsFeed extends connect(store)(LitElement) {
static get properties() { static get properties() {
return { return {
@ -21,46 +20,92 @@ class FriendsFeed extends connect(store)(LitElement) {
isLoading: { type: Boolean }, isLoading: { type: Boolean },
hasFetched: { type: Boolean }, hasFetched: { type: Boolean },
mySelectedFeeds: { type: Array } mySelectedFeeds: { type: Array }
};
} }
}
static get styles() {
return [friendsViewStyles]
}
constructor() { constructor() {
super() super()
this.feed = [] this.feed = []
this.feedToRender = [] this.feedToRender = []
this.nodeUrl = this.getNodeUrl(); this.nodeUrl = this.getNodeUrl()
this.myNode = this.getMyNode(); this.myNode = this.getMyNode()
this.endpoints = [] this.endpoints = []
this.endpointOffsets = [] // Initialize offsets for each endpoint to 0 this.endpointOffsets = [] // Initialize offsets for each endpoint to 0
this.loadAndMergeData = this.loadAndMergeData.bind(this) this.loadAndMergeData = this.loadAndMergeData.bind(this)
this.hasInitialFetch = false this.hasInitialFetch = false
this.observerHandler = this.observerHandler.bind(this); this.observerHandler = this.observerHandler.bind(this)
this.elementObserver = this.elementObserver.bind(this) this.elementObserver = this.elementObserver.bind(this)
this.mySelectedFeeds = [] this.mySelectedFeeds = []
this.getSchemas = this.getSchemas.bind(this) this.getSchemas = this.getSchemas.bind(this)
this.hasFetched = false this.hasFetched = false
this._updateFeeds = this._updateFeeds.bind(this) this._updateFeeds = this._updateFeeds.bind(this)
} }
static get styles() { render() {
return [friendsViewStyles]; return html`
<div class="container">
<div id="viewElement" class="container-body" style=${"position: relative"}>
${this.isLoading ? html`
<div style="width:100%;display: flex; justify-content:center">
<paper-spinner-lite active></paper-spinner-lite>
</div>
` : ''}
${this.hasFetched && !this.isLoading && this.feed.length === 0 ? html`
<div style="width:100%;display: flex; justify-content:center">
<p>${translate('friends.friend17')}</p>
</div>
` : ''}
${this.feedToRender.map((item) => {
return html`
<feed-item
.resource=${item}
appName=${'Q-Blog'}
link=${item.link}
>
</feed-item>
`
})}
<div id="downObserver"></div>
</div>
</div>
`
} }
async firstUpdated() {
this.viewElement = this.shadowRoot.getElementById('viewElement')
this.downObserverElement = this.shadowRoot.getElementById('downObserver')
this.elementObserver()
try {
await new Promise((res) => {
setTimeout(() => {
res()
}, 5000)
})
if (this.mySelectedFeeds.length === 0) {
await this.getEndpoints()
await this.loadAndMergeData()
}
this.getFeedOnInterval()
} catch (error) {
console.log(error)
}
}
getNodeUrl() { getNodeUrl() {
const myNode = const myNode = store.getState().app.nodeConfig.knownNodes[store.getState().app.nodeConfig.node]
store.getState().app.nodeConfig.knownNodes[
window.parent.reduxStore.getState().app.nodeConfig.node
]
return myNode.protocol + '://' + myNode.domain + ':' + myNode.port return myNode.protocol + '://' + myNode.domain + ':' + myNode.port
} }
getMyNode() { getMyNode() {
return store.getState().app.nodeConfig.knownNodes[ return store.getState().app.nodeConfig.knownNodes[store.getState().app.nodeConfig.node]
window.parent.reduxStore.getState().app.nodeConfig.node
]
} }
_updateFeeds(event) { _updateFeeds(event) {
@ -71,7 +116,8 @@ class FriendsFeed extends connect(store)(LitElement) {
connectedCallback() { connectedCallback() {
super.connectedCallback() super.connectedCallback()
window.addEventListener('friends-my-selected-feeds-event', this._updateFeeds) } window.addEventListener('friends-my-selected-feeds-event', this._updateFeeds)
}
disconnectedCallback() { disconnectedCallback() {
window.removeEventListener('friends-my-selected-feeds-event', this._updateFeeds) window.removeEventListener('friends-my-selected-feeds-event', this._updateFeeds)
@ -81,97 +127,73 @@ class FriendsFeed extends connect(store)(LitElement) {
async getSchemas() { async getSchemas() {
this.mySelectedFeeds = JSON.parse(localStorage.getItem('friends-my-selected-feeds') || "[]") this.mySelectedFeeds = JSON.parse(localStorage.getItem('friends-my-selected-feeds') || "[]")
const schemas = this.mySelectedFeeds const schemas = this.mySelectedFeeds
const getAllSchemas = (schemas || []).map( const getAllSchemas = (schemas || []).map(
async (schema) => { async (schema) => {
try { try {
const url = `${this.nodeUrl}/arbitrary/${schema.service}/${schema.name}/${schema.identifier}`; const url = `${this.nodeUrl}/arbitrary/${schema.service}/${schema.name}/${schema.identifier}`
const res = await fetch(url) const res = await fetch(url)
const data = await res.json() const data = await res.json()
if (data.error) return false if (data.error) return false
return data return data
} catch (error) { } catch (error) {
console.log(error); console.log(error)
return false return false
} }
} }
); )
const res = await Promise.all(getAllSchemas);
const res = await Promise.all(getAllSchemas)
return res.filter((item) => !!item) return res.filter((item) => !!item)
} }
getFeedOnInterval() { getFeedOnInterval() {
let interval = null; let interval = null
let stop = false; let stop = false
const getAnswer = async () => {
const getAnswer = async () => {
if (!stop) { if (!stop) {
stop = true; stop = true
try { try {
await this.reFetchFeedData() await this.reFetchFeedData()
} catch (error) { } } catch (error) { }
stop = false; stop = false
}
} }
};
interval = setInterval(getAnswer, 900000);
interval = setInterval(getAnswer, 900000)
} }
async getEndpoints() { async getEndpoints() {
const dynamicVars = { const dynamicVars = { }
}
const schemas = await this.getSchemas() const schemas = await this.getSchemas()
const friendList = JSON.parse(localStorage.getItem('friends-my-friend-list') || "[]") const friendList = JSON.parse(localStorage.getItem('friends-my-friend-list') || "[]")
const names = friendList.map(friend => `name=${friend.name}`).join('&'); const names = friendList.map(friend => `name=${friend.name}`).join('&')
if (names.length === 0) { if (names.length === 0) {
this.endpoints = [] this.endpoints = []
this.endpointOffsets = Array(this.endpoints.length).fill(0); this.endpointOffsets = Array(this.endpoints.length).fill(0)
return return
} }
const baseurl = `${this.nodeUrl}/arbitrary/resources/search?reverse=true&mode=ALL&exactmatchnames=true&${names}` const baseurl = `${this.nodeUrl}/arbitrary/resources/search?reverse=true&mode=ALL&exactmatchnames=true&${names}`
let formEndpoints = [] let formEndpoints = []
schemas.forEach((schema) => { schemas.forEach((schema) => {
const feedData = schema.feed[0] const feedData = schema.feed[0]
if (feedData) { if (feedData) {
const copyFeedData = { ...feedData } const copyFeedData = { ...feedData }
const fullUrl = constructUrl(baseurl, copyFeedData.search, dynamicVars); const fullUrl = constructUrl(baseurl, copyFeedData.search, dynamicVars)
if (fullUrl) { if (fullUrl) {
formEndpoints.push({ formEndpoints.push({
url: fullUrl, schemaName: schema.name, schema: copyFeedData url: fullUrl, schemaName: schema.name, schema: copyFeedData
}) })
} }
} }
}) })
this.endpoints = formEndpoints this.endpoints = formEndpoints
this.endpointOffsets = Array(this.endpoints.length).fill(0); this.endpointOffsets = Array(this.endpoints.length).fill(0)
}
async firstUpdated(){
this.viewElement = this.shadowRoot.getElementById('viewElement');
this.downObserverElement =
this.shadowRoot.getElementById('downObserver');
this.elementObserver();
try {
await new Promise((res)=> {
setTimeout(() => {
res()
}, 5000);
})
if(this.mySelectedFeeds.length === 0){
await this.getEndpoints()
await this.loadAndMergeData();
}
this.getFeedOnInterval()
} catch (error) {
console.log(error)
}
} }
getMoreFeed() { getMoreFeed() {
@ -190,115 +212,112 @@ this.getFeedOnInterval()
} }
} }
elementObserver() { elementObserver() {
const options = { const options = {
rootMargin: '0px', rootMargin: '0px',
threshold: 1, threshold: 1
}; }
// identify an element to observe // identify an element to observe
const elementToObserve = this.downObserverElement; const elementToObserve = this.downObserverElement
// passing it a callback function // passing it a callback function
const observer = new IntersectionObserver( const observer = new IntersectionObserver(
this.observerHandler, this.observerHandler,
options options
); )
// call `observe()` on that MutationObserver instance, // call `observe()` on that MutationObserver instance,
// passing it the element to observe, and the options object // passing it the element to observe, and the options object
observer.observe(elementToObserve); observer.observe(elementToObserve)
} }
observerHandler(entries) { observerHandler(entries) {
if (!entries[0].isIntersecting) { if (!entries[0].isIntersecting) {
} else { } else {
if (this.feedToRender.length < 20) { if (this.feedToRender.length < 20) {
return; return
} }
this.getMoreFeed(); this.getMoreFeed()
} }
} }
async fetchDataFromEndpoint(endpointIndex, count) { async fetchDataFromEndpoint(endpointIndex, count) {
const offset = this.endpointOffsets[endpointIndex]; const offset = this.endpointOffsets[endpointIndex]
const url = `${this.endpoints[endpointIndex].url}&limit=${count}&offset=${offset}`; const url = `${this.endpoints[endpointIndex].url}&limit=${count}&offset=${offset}`
const res = await fetch(url) const res = await fetch(url)
const data = await res.json() const data = await res.json()
return data.map((i) => { return data.map((i) => {
return { return {
...this.endpoints[endpointIndex], ...this.endpoints[endpointIndex],
...i ...i
} }
}) })
} }
async initialLoad() { async initialLoad() {
let results = []; let results = []
let totalFetched = 0; let totalFetched = 0
let i = 0; let i = 0
let madeProgress = true; let madeProgress = true
let exhaustedEndpoints = new Set(); let exhaustedEndpoints = new Set()
while (totalFetched < totalDesiredCount && madeProgress) { while (totalFetched < totalDesiredCount && madeProgress) {
madeProgress = false; madeProgress = false
this.isLoading = true this.isLoading = true
for (i = 0; i < this.endpoints.length; i++) { for (i = 0; i < this.endpoints.length; i++) {
if (exhaustedEndpoints.has(i)) { if (exhaustedEndpoints.has(i)) {
continue; continue
} }
const remainingCount = totalDesiredCount - totalFetched; const remainingCount = totalDesiredCount - totalFetched
// If we've already reached the desired count, break // If we've already reached the desired count, break
if (remainingCount <= 0) { if (remainingCount <= 0) {
break; break;
} }
let fetchCount = Math.min(perEndpointCount, remainingCount); let fetchCount = Math.min(perEndpointCount, remainingCount)
let data = await this.fetchDataFromEndpoint(i, fetchCount); let data = await this.fetchDataFromEndpoint(i, fetchCount)
// Increment the offset for this endpoint by the number of items fetched // Increment the offset for this endpoint by the number of items fetched
this.endpointOffsets[i] += data.length; this.endpointOffsets[i] += data.length
if (data.length > 0) { if (data.length > 0) {
madeProgress = true; madeProgress = true
} }
if (data.length < fetchCount) { if (data.length < fetchCount) {
exhaustedEndpoints.add(i); exhaustedEndpoints.add(i)
} }
results = results.concat(data); results = results.concat(data)
totalFetched += data.length; totalFetched += data.length
} }
if (exhaustedEndpoints.size === this.endpoints.length) { if (exhaustedEndpoints.size === this.endpoints.length) {
break; break
} }
} }
this.isLoading = false this.isLoading = false
this.hasFetched = true; this.hasFetched = true
// Trim the results if somehow they are over the totalDesiredCount // Trim the results if somehow they are over the totalDesiredCount
return results.slice(0, totalDesiredCount); return results.slice(0, totalDesiredCount)
} }
trimDataToLimit(data, limit) { trimDataToLimit(data, limit) {
return data.slice(0, limit); return data.slice(0, limit)
} }
mergeData(newData, existingData) { mergeData(newData, existingData) {
const existingIds = new Set(existingData.map(item => item.identifier)); // Assume each item has a unique 'id' const existingIds = new Set(existingData.map(item => item.identifier)) // Assume each item has a unique 'id'
const uniqueNewData = newData.filter(item => !existingIds.has(item.identifier)); const uniqueNewData = newData.filter(item => !existingIds.has(item.identifier))
return uniqueNewData.concat(existingData); return uniqueNewData.concat(existingData)
} }
async addExtraData(data) { async addExtraData(data) {
let newData = [] let newData = []
for (let item of data) { for (let item of data) {
@ -310,11 +329,13 @@ this.getFeedOnInterval()
} }
} }
let newResource = { let newResource = {
identifier: newItem.identifier, identifier: newItem.identifier,
service: newItem.service, service: newItem.service,
name: newItem.name name: newItem.name
} }
if (newItem.schema) { if (newItem.schema) {
const resource = newItem const resource = newItem
@ -325,51 +346,49 @@ this.getFeedOnInterval()
} }
} }
return newData return newData
} }
async reFetchFeedData() { async reFetchFeedData() {
// Resetting offsets to start fresh. // Resetting offsets to start fresh.
this.endpointOffsets = Array(this.endpoints.length).fill(0); this.endpointOffsets = Array(this.endpoints.length).fill(0)
await this.getEndpoints() await this.getEndpoints()
const oldIdentifiers = new Set(this.feed.map(item => item.identifier)); const oldIdentifiers = new Set(this.feed.map(item => item.identifier))
const newData = await this.initialLoad(); const newData = await this.initialLoad()
// Filter out items that are already in the feed // Filter out items that are already in the feed
const trulyNewData = newData.filter(item => !oldIdentifiers.has(item.identifier)); const trulyNewData = newData.filter(item => !oldIdentifiers.has(item.identifier))
if (trulyNewData.length > 0) { if (trulyNewData.length > 0) {
// Adding extra data and merging with old data // Adding extra data and merging with old data
const enhancedNewData = await this.addExtraData(trulyNewData); const enhancedNewData = await this.addExtraData(trulyNewData)
// Merge new data with old data immutably // Merge new data with old data immutably
this.feed = [...enhancedNewData, ...this.feed]; this.feed = [...enhancedNewData, ...this.feed]
this.feed = this.removeDuplicates(this.feed) this.feed = this.removeDuplicates(this.feed)
this.feed.sort((a, b) => new Date(b.created) - new Date(a.created)); // Sort by timestamp, most recent first this.feed.sort((a, b) => new Date(b.created) - new Date(a.created)) // Sort by timestamp, most recent first
this.feed = this.trimDataToLimit(this.feed, maxResultsInMemory); // Trim to the maximum allowed in memory this.feed = this.trimDataToLimit(this.feed, maxResultsInMemory) // Trim to the maximum allowed in memory
this.feedToRender = this.feed.slice(0, 20); this.feedToRender = this.feed.slice(0, 20)
this.hasInitialFetch = true; this.hasInitialFetch = true
const created = trulyNewData[0].created; const created = trulyNewData[0].created
let value = localStorage.getItem('lastSeenFeed'); let value = localStorage.getItem('lastSeenFeed')
if (((+value || 0) < created)) { if (((+value || 0) < created)) {
this.setHasNewFeed(true); this.setHasNewFeed(true)
} }
} }
} }
removeDuplicates(array) { removeDuplicates(array) {
const seenIds = new Set(); const seenIds = new Set()
return array.filter(item => { return array.filter(item => {
if (!seenIds.has(item.identifier)) { if (!seenIds.has(item.identifier)) {
seenIds.add(item.identifier); seenIds.add(item.identifier)
return true; return true
} }
return false; return false
}); })
} }
async loadAndMergeData() { async loadAndMergeData() {
let allData = this.feed let allData = this.feed
const newData = await this.initialLoad(); const newData = await this.initialLoad();
@ -390,87 +409,53 @@ this.getFeedOnInterval()
} }
} }
// Standard functions
getApiKey() {
const coreNode = store.getState().app.nodeConfig.knownNodes[store.getState().app.nodeConfig.node]
return coreNode.apiKey
render() {
return html`
<div class="container">
<div id="viewElement" class="container-body" style=${"position: relative"}>
${this.isLoading ? html`
<div style="width:100%;display: flex; justify-content:center">
<paper-spinner-lite active></paper-spinner-lite>
</div>
` : ''}
${this.hasFetched && !this.isLoading && this.feed.length === 0 ? html`
<div style="width:100%;display: flex; justify-content:center">
<p>${translate('friends.friend17')}</p>
</div>
` : ''}
${this.feedToRender.map((item) => {
return html`<feed-item
.resource=${item}
appName=${'Q-Blog'}
link=${item.link}
></feed-item>`;
})}
<div id="downObserver"></div>
</div>
</div>
`;
} }
isEmptyArray(arr) {
if (!arr) { return true }
return arr.length === 0
} }
customElements.define('friends-feed', FriendsFeed); round(number) {
return (Math.round(parseFloat(number) * 1e8) / 1e8).toFixed(8)
}
}
window.customElements.define('friends-feed', FriendsFeed)
export function substituteDynamicVar(value, dynamicVars) { export function substituteDynamicVar(value, dynamicVars) {
if (typeof value !== 'string') return value; if (typeof value !== 'string') return value
const pattern = /\$\$\{([a-zA-Z0-9_]+)\}\$\$/g // Adjusted pattern to capture $${name}$$ with curly braces
const pattern = /\$\$\{([a-zA-Z0-9_]+)\}\$\$/g; // Adjusted pattern to capture $${name}$$ with curly braces
return value.replace(pattern, (match, p1) => { return value.replace(pattern, (match, p1) => {
return dynamicVars[p1] !== undefined ? dynamicVars[p1] : match; return dynamicVars[p1] !== undefined ? dynamicVars[p1] : match
}); })
} }
export function constructUrl(base, search, dynamicVars) { export function constructUrl(base, search, dynamicVars) {
let queryStrings = []; let queryStrings = []
for (const [key, value] of Object.entries(search)) { for (const [key, value] of Object.entries(search)) {
const substitutedValue = substituteDynamicVar(value, dynamicVars); const substitutedValue = substituteDynamicVar(value, dynamicVars)
queryStrings.push(`${key}=${encodeURIComponent(substitutedValue)}`); queryStrings.push(`${key}=${encodeURIComponent(substitutedValue)}`)
} }
return queryStrings.length > 0 ? `${base}&${queryStrings.join('&')}` : base
return queryStrings.length > 0 ? `${base}&${queryStrings.join('&')}` : base;
} }
export function replacePlaceholders(template, resource, customParams) { export function replacePlaceholders(template, resource, customParams) {
const dataSource = { resource, customParams }; const dataSource = { resource, customParams }
return template.replace(/\$\$\{(.*?)\}\$\$/g, (match, p1) => { return template.replace(/\$\$\{(.*?)\}\$\$/g, (match, p1) => {
const keys = p1.split('.'); const keys = p1.split('.')
let value = dataSource; let value = dataSource
for (let key of keys) { for (let key of keys) {
if (value[key] !== undefined) { if (value[key] !== undefined) {
value = value[key]; value = value[key]
} else { } else {
return match; // Return placeholder unchanged return match // Return placeholder unchanged
} }
} }
return value; return value
}); })
} }

View File

@ -1,10 +1,13 @@
import {css, html, LitElement} from 'lit' import { html, LitElement } from 'lit'
import '@material/mwc-icon' import { connect } from 'pwa-helpers'
import './friends-side-panel.js' import { store } from '../../store'
import '@vaadin/tooltip'
import { translate } from '../../../translate' import { translate } from '../../../translate'
import { friendsSidePanelParentStyles } from '../../styles/core-css'
import './friends-side-panel'
import '@material/mwc-icon'
import '@vaadin/tooltip'
class FriendsSidePanelParent extends LitElement { class FriendsSidePanelParent extends connect(store)(LitElement) {
static get properties() { static get properties() {
return { return {
isOpen: { type: Boolean }, isOpen: { type: Boolean },
@ -12,45 +15,16 @@ class FriendsSidePanelParent extends LitElement {
} }
} }
static get styles() {
return [friendsSidePanelParentStyles]
}
constructor() { constructor() {
super() super()
this.isOpen = false this.isOpen = false
this.hasNewFeed = false this.hasNewFeed = false
} }
static styles = css`
.header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 16px;
border-bottom: 1px solid #e0e0e0;
}
.content {
padding: 16px;
}
.close {
visibility: hidden;
position: fixed;
z-index: -100;
right: -1000px;
}
.parent-side-panel {
transform: translateX(100%); /* start from outside the right edge */
transition: transform 0.3s ease-in-out;
}
.parent-side-panel.open {
transform: translateX(0); /* slide in to its original position */
}
`
setHasNewFeed(val){
this.hasNewFeed = val
}
render() { render() {
return html` return html`
<mwc-icon <mwc-icon
@ -62,7 +36,8 @@ class FriendsSidePanelParent extends LitElement {
this.hasNewFeed = false this.hasNewFeed = false
this.shadowRoot.querySelector("friends-side-panel").selected = 'feed' this.shadowRoot.querySelector("friends-side-panel").selected = 'feed'
} }
}} style="color: ${this.hasNewFeed ? 'green' : 'var(--black)'}; cursor:pointer;user-select:none" }}
style="color: ${this.hasNewFeed ? 'green' : 'var(--black)'}; cursor:pointer;user-select:none"
> >
group group
</mwc-icon> </mwc-icon>
@ -72,12 +47,33 @@ class FriendsSidePanelParent extends LitElement {
hover-delay=${400} hover-delay=${400}
hide-delay=${1} hide-delay=${1}
text=${translate('friends.friend12')} text=${translate('friends.friend12')}
> ></vaadin-tooltip>
</vaadin-tooltip>
<friends-side-panel .setHasNewFeed=${(val) => this.setHasNewFeed(val)} ?isOpen=${this.isOpen} .setIsOpen=${(val) => this.isOpen = val}></friends-side-panel> <friends-side-panel .setHasNewFeed=${(val) => this.setHasNewFeed(val)} ?isOpen=${this.isOpen} .setIsOpen=${(val) => this.isOpen = val}></friends-side-panel>
` `
} }
firstUpdated() {
// ...
} }
customElements.define('friends-side-panel-parent', FriendsSidePanelParent) setHasNewFeed(val) {
this.hasNewFeed = val
}
// Standard functions
getApiKey() {
const coreNode = store.getState().app.nodeConfig.knownNodes[store.getState().app.nodeConfig.node]
return coreNode.apiKey
}
isEmptyArray(arr) {
if (!arr) { return true }
return arr.length === 0
}
round(number) {
return (Math.round(parseFloat(number) * 1e8) / 1e8).toFixed(8)
}
}
window.customElements.define('friends-side-panel-parent', FriendsSidePanelParent)

View File

@ -1,10 +1,13 @@
import {css, html, LitElement} from 'lit'; import { html, LitElement } from 'lit'
import '@material/mwc-icon'; import { connect } from 'pwa-helpers'
import { store } from '../../store'
import { translate } from '../../../translate'
import { friendsSidePanelStyles } from '../../styles/core-css'
import './friends-view' import './friends-view'
import './friends-feed' import './friends-feed'
import {translate} from '../../../translate' import '@material/mwc-icon'
class FriendsSidePanel extends LitElement { class FriendsSidePanel extends connect(store)(LitElement) {
static get properties() { static get properties() {
return { return {
setIsOpen: { attribute: false }, setIsOpen: { attribute: false },
@ -13,7 +16,11 @@ class FriendsSidePanel extends LitElement {
setHasNewFeed: { attribute: false }, setHasNewFeed: { attribute: false },
closeSidePanel: { attribute: false, type: Object }, closeSidePanel: { attribute: false, type: Object },
openSidePanel: { attribute: false, type: Object } openSidePanel: { attribute: false, type: Object }
}; }
}
static get styles() {
return [friendsSidePanelStyles]
} }
constructor() { constructor() {
@ -21,102 +28,6 @@ class FriendsSidePanel extends LitElement {
this.selected = 'friends' this.selected = 'friends'
this.closeSidePanel = this.closeSidePanel.bind(this) this.closeSidePanel = this.closeSidePanel.bind(this)
this.openSidePanel = this.openSidePanel.bind(this) this.openSidePanel = this.openSidePanel.bind(this)
}
static styles = css`
:host {
display: block;
position: fixed;
top: 55px;
right: 0px;
width: 420px;
max-width: 95%;
height: calc(100vh - 55px);
background-color: var(--white);
border-left: 1px solid rgb(224, 224, 224);
z-index: 1;
transform: translateX(100%); /* start from outside the right edge */
transition: transform 0.3s ease-in-out;
}
:host([isOpen]) {
transform: unset; /* slide in to its original position */
}
.header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 16px;
border-bottom: 1px solid #e0e0e0;
}
.content {
padding: 16px;
display: flex;
flex-direction: column;
flex-grow: 1;
overflow: auto;
}
.content::-webkit-scrollbar-track {
background-color: whitesmoke;
border-radius: 7px;
}
.content::-webkit-scrollbar {
width: 12px;
border-radius: 7px;
background-color: whitesmoke;
}
.content::-webkit-scrollbar-thumb {
background-color: rgb(180, 176, 176);
border-radius: 7px;
transition: all 0.3s ease-in-out;
}
.parent {
display: flex;
flex-direction: column;
height: 100%;
}
.active {
font-size: 16px;
background: var(--black);
color: var(--white);
padding: 5px;
border-radius: 2px;
cursor: pointer;
}
.default {
font-size: 16px;
color: var(--black);
padding: 5px;
border-radius: 2px;
cursor: pointer;
}
.default-content {
visibility: hidden;
position: absolute;
z-index: -50;
}
`;
refreshFeed(){
this.shadowRoot.querySelector('friends-feed').refresh()
}
closeSidePanel(){
this.setIsOpen(false)
}
openSidePanel(){
this.setIsOpen(true)
} }
render() { render() {
@ -128,14 +39,13 @@ class FriendsSidePanel extends LitElement {
<span @click=${() => this.selected = 'feed'} class="${this.selected === 'feed' ? 'active' : 'default'}">${translate('friends.friend13')}</span> <span @click=${() => this.selected = 'feed'} class="${this.selected === 'feed' ? 'active' : 'default'}">${translate('friends.friend13')}</span>
</div> </div>
<div style="display:flex;gap:15px;align-items:center"> <div style="display:flex;gap:15px;align-items:center">
<mwc-icon @click=${()=> { <mwc-icon @click=${() => { this.refreshFeed(); }} style="color: var(--black); cursor:pointer;">
this.refreshFeed() refresh
}} style="color: var(--black); cursor:pointer;">refresh</mwc-icon> </mwc-icon>
<mwc-icon style="cursor:pointer" @click=${()=> { <mwc-icon style="cursor:pointer" @click=${() => { this.setIsOpen(false); }}>
this.setIsOpen(false) close
}}>close</mwc-icon> </mwc-icon>
</div> </div>
</div> </div>
<div class="content"> <div class="content">
<div class="${this.selected === 'friends' ? 'active-content' : 'default-content'}"> <div class="${this.selected === 'friends' ? 'active-content' : 'default-content'}">
@ -144,15 +54,41 @@ class FriendsSidePanel extends LitElement {
<div class="${this.selected === 'feed' ? 'active-content' : 'default-content'}"> <div class="${this.selected === 'feed' ? 'active-content' : 'default-content'}">
<friends-feed .setHasNewFeed=${(val) => this.setHasNewFeed(val)}></friends-feed> <friends-feed .setHasNewFeed=${(val) => this.setHasNewFeed(val)}></friends-feed>
</div> </div>
</div> </div>
</div> </div>
</div> `
`;
} }
firstUpdated() {
// ...
} }
customElements.define('friends-side-panel', FriendsSidePanel); refreshFeed() {
this.shadowRoot.querySelector('friends-feed').refresh()
}
closeSidePanel() {
this.setIsOpen(false)
}
openSidePanel() {
this.setIsOpen(true)
}
// Standard functions
getApiKey() {
const coreNode = store.getState().app.nodeConfig.knownNodes[store.getState().app.nodeConfig.node]
return coreNode.apiKey
}
isEmptyArray(arr) {
if (!arr) { return true }
return arr.length === 0
}
round(number) {
return (Math.round(parseFloat(number) * 1e8) / 1e8).toFixed(8)
}
}
window.customElements.define('friends-side-panel', FriendsSidePanel)

View File

@ -1,182 +0,0 @@
import {css} from 'lit'
export const friendsViewStyles = css`
* {
box-sizing: border-box;
}
.top-bar-icon {
cursor: pointer;
height: 18px;
width: 18px;
transition: 0.2s all;
}
.top-bar-icon:hover {
color: var(--black);
}
.modal-button {
font-family: Roboto, sans-serif;
font-size: 16px;
color: var(--mdc-theme-primary);
background-color: transparent;
padding: 8px 10px;
border-radius: 5px;
border: none;
transition: all 0.3s ease-in-out;
}
.close-row {
width: 100%;
display: flex;
justify-content: flex-end;
height: 50px;
flex:0
}
.container-body {
width: 100%;
display: flex;
flex-direction: column;
flex-grow: 1;
margin-top: 5px;
padding: 0px 6px;
box-sizing: border-box;
align-items: center;
gap: 10px;
}
.container-body::-webkit-scrollbar-track {
background-color: whitesmoke;
border-radius: 7px;
}
.container-body::-webkit-scrollbar {
width: 6px;
border-radius: 7px;
background-color: whitesmoke;
}
.container-body::-webkit-scrollbar-thumb {
background-color: rgb(180, 176, 176);
border-radius: 7px;
transition: all 0.3s ease-in-out;
}
.container-body::-webkit-scrollbar-thumb:hover {
background-color: rgb(148, 146, 146);
cursor: pointer;
}
p {
color: var(--black);
margin: 0px;
padding: 0px;
word-break: break-all;
}
.container {
display: flex;
width: 100%;
flex-direction: column;
height: 100%;
}
.chat-right-panel-label {
font-family: Montserrat, sans-serif;
color: var(--group-header);
padding: 5px;
font-size: 13px;
user-select: none;
}
.group-info {
display: flex;
flex-direction: column;
justify-content: flex-start;
gap: 10px;
}
.group-name {
font-family: Raleway, sans-serif;
font-size: 20px;
color: var(--chat-bubble-msg-color);
text-align: center;
user-select: none;
}
.group-description {
font-family: Roboto, sans-serif;
color: var(--chat-bubble-msg-color);
letter-spacing: 0.3px;
font-weight: 300;
font-size: 14px;
margin-top: 15px;
word-break: break-word;
user-select: none;
}
.group-subheader {
font-family: Montserrat, sans-serif;
font-size: 14px;
color: var(--chat-bubble-msg-color);
}
.group-data {
font-family: Roboto, sans-serif;
letter-spacing: 0.3px;
font-weight: 300;
font-size: 14px;
color: var(--chat-bubble-msg-color);
}
.search-results-div {
position: absolute;
top: 25px;
right: 25px;
}
.name-input {
width: 100%;
outline: 0;
border-width: 0 0 2px;
border-color: var(--mdc-theme-primary);
background-color: transparent;
padding: 10px;
font-family: Roboto, sans-serif;
font-size: 15px;
color: var(--chat-bubble-msg-color);
box-sizing: border-box;
}
.name-input::selection {
background-color: var(--mdc-theme-primary);
color: white;
}
.name-input::placeholder {
opacity: 0.9;
color: var(--black);
}
.search-field {
width: 100%;
position: relative;
}
.search-icon {
position: absolute;
right: 3px;
color: var(--chat-bubble-msg-color);
transition: hover 0.3s ease-in-out;
background: none;
border-radius: 50%;
padding: 6px 3px;
font-size: 21px;
}
.search-icon:hover {
cursor: pointer;
background: #d7d7d75c;
}
`

View File

@ -1,22 +1,20 @@
import {html, LitElement} from 'lit'; import { html, LitElement } from 'lit'
import {connect} from 'pwa-helpers'; import { store } from '../../store'
import { connect } from 'pwa-helpers'
import '@material/mwc-button'; import { parentEpml } from '../show-plugin'
import '@material/mwc-dialog'; import { translate } from '../../../translate'
import '@polymer/paper-spinner/paper-spinner-lite.js'; import { friendsViewStyles } from '../../styles/core-css'
import '@polymer/paper-progress/paper-progress.js'; import './add-friends-modal'
import '@material/mwc-icon'; import './ChatSideNavHeads'
import '../../../../plugins/plugins/core/components/ChatSearchResults'
import '@material/mwc-button'
import '@material/mwc-dialog'
import '@material/mwc-icon'
import '@polymer/paper-spinner/paper-spinner-lite.js'
import '@polymer/paper-progress/paper-progress.js'
import '@vaadin/icon' import '@vaadin/icon'
import '@vaadin/icons' import '@vaadin/icons'
import '@vaadin/button'; import '@vaadin/button'
import './ChatSideNavHeads';
import '../../../../plugins/plugins/core/components/ChatSearchResults'
import './add-friends-modal'
import {translate,} from '../../../translate'
import {store} from '../../store';
import {friendsViewStyles} from './friends-view-css';
import {parentEpml} from '../show-plugin';
class FriendsView extends connect(store)(LitElement) { class FriendsView extends connect(store)(LitElement) {
static get properties() { static get properties() {
@ -38,279 +36,35 @@ class FriendsView extends connect(store)(LitElement) {
refreshFeed: { attribute: false }, refreshFeed: { attribute: false },
closeSidePanel: { attribute: false, type: Object }, closeSidePanel: { attribute: false, type: Object },
openSidePanel: { attribute: false, type: Object } openSidePanel: { attribute: false, type: Object }
};
} }
}
static get styles() { static get styles() {
return [friendsViewStyles]; return [friendsViewStyles]
} }
constructor() { constructor() {
super(); super()
this.error = false; this.error = false
this.observerHandler = this.observerHandler.bind(this); this.observerHandler = this.observerHandler.bind(this)
this.viewElement = ''; this.viewElement = ''
this.downObserverElement = ''; this.downObserverElement = ''
this.myAddress = this.myAddress = store.getState().app.selectedAddress.address
window.parent.reduxStore.getState().app.selectedAddress.address; this.errorMessage = ''
this.errorMessage = ''; this.successMessage = ''
this.successMessage = ''; this.friendList = []
this.friendList = []; this.userSelected = {}
this.userSelected = {}; this.isLoading = false
this.isLoading = false;
this.userFoundModalOpen = false this.userFoundModalOpen = false
this.userFound = []; this.userFound = []
this.nodeUrl = this.getNodeUrl(); this.nodeUrl = this.getNodeUrl()
this.myNode = this.getMyNode(); this.myNode = this.getMyNode()
this.isOpenAddFriendsModal = false this.isOpenAddFriendsModal = false
this.editContent = null this.editContent = null
this.addToFriendList = this.addToFriendList.bind(this) this.addToFriendList = this.addToFriendList.bind(this)
this._updateFriends = this._updateFriends.bind(this) this._updateFriends = this._updateFriends.bind(this)
this._updateFeed = this._updateFeed.bind(this) this._updateFeed = this._updateFeed.bind(this)
this._addFriend = this._addFriend.bind(this) this._addFriend = this._addFriend.bind(this)
}
getNodeUrl() {
const myNode =
store.getState().app.nodeConfig.knownNodes[
window.parent.reduxStore.getState().app.nodeConfig.node
]
return myNode.protocol + '://' + myNode.domain + ':' + myNode.port
}
getMyNode() {
return store.getState().app.nodeConfig.knownNodes[
window.parent.reduxStore.getState().app.nodeConfig.node
]
}
getMoreFriends() {}
firstUpdated() {
this.viewElement = this.shadowRoot.getElementById('viewElement');
this.downObserverElement =
this.shadowRoot.getElementById('downObserver');
this.elementObserver();
this.mySelectedFeeds = JSON.parse(localStorage.getItem('friends-my-selected-feeds') || "[]")
this.friendList = JSON.parse(localStorage.getItem('friends-my-friend-list') || "[]")
}
_updateFriends(event) {
this.friendList = event.detail
}
_updateFeed(event) {
this.mySelectedFeeds = event.detail
this.requestUpdate()
}
_addFriend(event){
const name = event.detail;
const findFriend = this.friendList.find((friend)=> friend.name === name)
if(findFriend){
this.editContent = {...findFriend, mySelectedFeeds: this.mySelectedFeeds}
this.userSelected = findFriend;
} else {
this.userSelected = {
name
};
}
this.isOpenAddFriendsModal = true
this.openSidePanel()
}
connectedCallback() {
super.connectedCallback()
window.addEventListener('friends-my-friend-list-event', this._updateFriends)
window.addEventListener('friends-my-selected-feeds-event', this._updateFeed)
window.addEventListener('add-friend', this._addFriend)
}
disconnectedCallback() {
window.removeEventListener('friends-my-friend-list-event', this._updateFriends)
window.removeEventListener('friends-my-selected-feeds-event', this._updateFeed)
window.removeEventListener('add-friend', this._addFriend)
super.disconnectedCallback()
}
elementObserver() {
const options = {
root: this.viewElement,
rootMargin: '0px',
threshold: 1,
};
// identify an element to observe
const elementToObserve = this.downObserverElement;
// passing it a callback function
const observer = new IntersectionObserver(
this.observerHandler,
options
);
// call `observe()` on that MutationObserver instance,
// passing it the element to observe, and the options object
observer.observe(elementToObserve);
}
observerHandler(entries) {
if (!entries[0].isIntersecting) {
} else {
if (this.friendList.length < 20) {
return;
}
this.getMoreFriends();
}
}
async userSearch() {
const nameValue = this.shadowRoot.getElementById('sendTo').value
if(!nameValue) {
this.userFound = []
this.userFoundModalOpen = true
return;
}
try {
const url = `${this.nodeUrl}/names/${nameValue}`
const res = await fetch(url)
const result = await res.json()
if (result.error === 401) {
this.userFound = []
} else {
this.userFound = [
result
];
}
this.userFoundModalOpen = true;
} catch (error) {
// let err4string = get("chatpage.cchange35");
// parentEpml.request('showSnackBar', `${err4string}`)
}
}
getApiKey() {
const apiNode = window.parent.reduxStore.getState().app.nodeConfig.knownNodes[window.parent.reduxStore.getState().app.nodeConfig.node]
return apiNode.apiKey
}
async myFollowName(name) {
let items = [
name
]
let namesJsonString = JSON.stringify({ "items": items })
return await parentEpml.request('apiCall', {
url: `/lists/followedNames?apiKey=${this.getApiKey()}`,
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: `${namesJsonString}`
})
}
async unFollowName(name) {
let items = [
name
]
let namesJsonString = JSON.stringify({ "items": items })
return await parentEpml.request('apiCall', {
url: `/lists/followedNames?apiKey=${this.getApiKey()}`,
method: 'DELETE',
headers: {
'Content-Type': 'application/json'
},
body: `${namesJsonString}`
})
}
async addToFriendList(val, isRemove){
const copyVal = {...val}
delete copyVal.mySelectedFeeds
if(isRemove){
this.friendList = this.friendList.filter((item)=> item.name !== copyVal.name)
}else if(this.editContent){
const findFriend = this.friendList.findIndex(item=> item.name === copyVal.name)
if(findFriend !== -1){
const copyList = [...this.friendList]
copyList[findFriend] = copyVal
this.friendList = copyList
}
} else {
this.friendList = [...this.friendList, copyVal]
}
if(!copyVal.willFollow || isRemove) {
await this.unFollowName(copyVal.name)
} else if(copyVal.willFollow){
await this.myFollowName(copyVal.name)
}
this.setMySelectedFeeds(val.mySelectedFeeds)
await new Promise((res)=> {
setTimeout(()=> {
res()
},50)
})
this.userSelected = {};
this.shadowRoot.getElementById('sendTo').value = ''
this.isLoading = false;
this.isOpenAddFriendsModal = false
this.editContent = null
this.setMyFriends(this.friendList)
if(!isRemove && this.friendList.length === 1){
this.refreshFeed()
}
}
setMyFriends(friendList){
localStorage.setItem('friends-my-friend-list', JSON.stringify(friendList));
const tempSettingsData= JSON.parse(localStorage.getItem('temp-settings-data') || "{}")
const newTemp = {
...tempSettingsData,
userLists: {
data: [friendList],
timestamp: Date.now()
}
}
localStorage.setItem('temp-settings-data', JSON.stringify(newTemp));
this.dispatchEvent(
new CustomEvent('temp-settings-data-event', {
bubbles: true,
composed: true
}),
);
}
setMySelectedFeeds(mySelectedFeeds){
this.mySelectedFeeds = mySelectedFeeds
const tempSettingsData= JSON.parse(localStorage.getItem('temp-settings-data') || "{}")
const newTemp = {
...tempSettingsData,
friendsFeed: {
data: mySelectedFeeds,
timestamp: Date.now()
}
}
localStorage.setItem('temp-settings-data', JSON.stringify(newTemp));
localStorage.setItem('friends-my-selected-feeds', JSON.stringify(mySelectedFeeds));
}
openEditFriend(val){
this.isOpenAddFriendsModal = true
this.userSelected = val
this.editContent = {...val, mySelectedFeeds: this.mySelectedFeeds}
}
onClose(){
this.isLoading = false;
this.isOpenAddFriendsModal = false
this.editContent = null
this.userSelected = {}
} }
render() { render() {
@ -331,22 +85,20 @@ class FriendsView extends connect(store)(LitElement) {
this.userSearch() this.userSearch()
} }
}} }}
/> >
<vaadin-icon <vaadin-icon
@click=${this.userSearch} @click=${this.userSearch}
slot="icon" slot="icon"
icon="vaadin:search" icon="vaadin:search"
class="search-icon"> class="search-icon"
>
</vaadin-icon> </vaadin-icon>
</div> </div>
<div class="search-results-div"> <div class="search-results-div">
<chat-search-results <chat-search-results
.onClickFunc=${(result) => { .onClickFunc=${(result) => {
this.userSelected = result; this.userSelected = result;
this.isOpenAddFriendsModal = true this.isOpenAddFriendsModal = true
this.userFound = []; this.userFound = [];
this.userFoundModalOpen = false; this.userFoundModalOpen = false;
}} }}
@ -356,21 +108,21 @@ class FriendsView extends connect(store)(LitElement) {
}} }}
.searchResults=${this.userFound} .searchResults=${this.userFound}
?isOpen=${this.userFoundModalOpen} ?isOpen=${this.userFoundModalOpen}
?loading=${this.isLoading}> ?loading=${this.isLoading}
>
</chat-search-results> </chat-search-results>
</div> </div>
${this.friendList.map((item) => { ${this.friendList.map((item) => {
return html`<chat-side-nav-heads return html`
<chat-side-nav-heads
activeChatHeadUrl="" activeChatHeadUrl=""
.setActiveChatHeadUrl=${(val) => { .setActiveChatHeadUrl=${(val) => { }}
}}
.chatInfo=${item} .chatInfo=${item}
.openEditFriend=${(val) => this.openEditFriend(val)} .openEditFriend=${(val) => this.openEditFriend(val)}
.closeSidePanel=${this.closeSidePanel} .closeSidePanel=${this.closeSidePanel}
></chat-side-nav-heads>`; >
</chat-side-nav-heads>
`
})} })}
<div id="downObserver"></div> <div id="downObserver"></div>
</div> </div>
@ -387,8 +139,271 @@ class FriendsView extends connect(store)(LitElement) {
.mySelectedFeeds=${this.mySelectedFeeds} .mySelectedFeeds=${this.mySelectedFeeds}
> >
</add-friends-modal> </add-friends-modal>
`; `
}
firstUpdated() {
this.viewElement = this.shadowRoot.getElementById('viewElement')
this.downObserverElement = this.shadowRoot.getElementById('downObserver')
this.elementObserver()
this.mySelectedFeeds = JSON.parse(localStorage.getItem('friends-my-selected-feeds') || "[]")
this.friendList = JSON.parse(localStorage.getItem('friends-my-friend-list') || "[]")
}
getNodeUrl() {
const myNode = store.getState().app.nodeConfig.knownNodes[store.getState().app.nodeConfig.node]
return myNode.protocol + '://' + myNode.domain + ':' + myNode.port
}
getMyNode() {
return store.getState().app.nodeConfig.knownNodes[store.getState().app.nodeConfig.node]
}
getMoreFriends() { }
_updateFriends(event) {
this.friendList = event.detail
}
_updateFeed(event) {
this.mySelectedFeeds = event.detail
this.requestUpdate()
}
_addFriend(event) {
const name = event.detail;
const findFriend = this.friendList.find((friend) => friend.name === name)
if (findFriend) {
this.editContent = { ...findFriend, mySelectedFeeds: this.mySelectedFeeds }
this.userSelected = findFriend
} else {
this.userSelected = {
name
} }
} }
customElements.define('friends-view', FriendsView); this.isOpenAddFriendsModal = true
this.openSidePanel()
}
connectedCallback() {
super.connectedCallback()
window.addEventListener('friends-my-friend-list-event', this._updateFriends)
window.addEventListener('friends-my-selected-feeds-event', this._updateFeed)
window.addEventListener('add-friend', this._addFriend)
}
disconnectedCallback() {
window.removeEventListener('friends-my-friend-list-event', this._updateFriends)
window.removeEventListener('friends-my-selected-feeds-event', this._updateFeed)
window.removeEventListener('add-friend', this._addFriend)
super.disconnectedCallback()
}
elementObserver() {
const options = {
root: this.viewElement,
rootMargin: '0px',
threshold: 1
}
// identify an element to observe
const elementToObserve = this.downObserverElement
// passing it a callback function
const observer = new IntersectionObserver(
this.observerHandler,
options
)
// call `observe()` on that MutationObserver instance,
// passing it the element to observe, and the options object
observer.observe(elementToObserve)
}
observerHandler(entries) {
if (!entries[0].isIntersecting) {
} else {
if (this.friendList.length < 20) {
return;
}
this.getMoreFriends()
}
}
async userSearch() {
const nameValue = this.shadowRoot.getElementById('sendTo').value
if (!nameValue) {
this.userFound = []
this.userFoundModalOpen = true
return;
}
try {
const url = `${this.nodeUrl}/names/${nameValue}`
const res = await fetch(url)
const result = await res.json()
if (result.error === 401) {
this.userFound = []
} else {
this.userFound = [
result
]
}
this.userFoundModalOpen = true;
} catch (error) {
// let err4string = get("chatpage.cchange35")
// parentEpml.request('showSnackBar', `${err4string}`)
}
}
async myFollowName(name) {
let items = [
name
]
let namesJsonString = JSON.stringify({ "items": items })
return await parentEpml.request('apiCall', {
url: `/lists/followedNames?apiKey=${this.getApiKey()}`,
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: `${namesJsonString}`
})
}
async unFollowName(name) {
let items = [
name
]
let namesJsonString = JSON.stringify({ "items": items })
return await parentEpml.request('apiCall', {
url: `/lists/followedNames?apiKey=${this.getApiKey()}`,
method: 'DELETE',
headers: {
'Content-Type': 'application/json'
},
body: `${namesJsonString}`
})
}
async addToFriendList(val, isRemove) {
const copyVal = { ...val }
delete copyVal.mySelectedFeeds
if (isRemove) {
this.friendList = this.friendList.filter((item) => item.name !== copyVal.name)
} else if (this.editContent) {
const findFriend = this.friendList.findIndex(item => item.name === copyVal.name)
if (findFriend !== -1) {
const copyList = [...this.friendList]
copyList[findFriend] = copyVal
this.friendList = copyList
}
} else {
this.friendList = [...this.friendList, copyVal]
}
if (!copyVal.willFollow || isRemove) {
await this.unFollowName(copyVal.name)
} else if (copyVal.willFollow) {
await this.myFollowName(copyVal.name)
}
this.setMySelectedFeeds(val.mySelectedFeeds)
await new Promise((res) => {
setTimeout(() => {
res()
}, 50)
})
this.userSelected = {}
this.shadowRoot.getElementById('sendTo').value = ''
this.isLoading = false
this.isOpenAddFriendsModal = false
this.editContent = null
this.setMyFriends(this.friendList)
if (!isRemove && this.friendList.length === 1) {
this.refreshFeed()
}
}
setMyFriends(friendList) {
localStorage.setItem('friends-my-friend-list', JSON.stringify(friendList));
const tempSettingsData = JSON.parse(localStorage.getItem('temp-settings-data') || "{}")
const newTemp = {
...tempSettingsData,
userLists: {
data: [friendList],
timestamp: Date.now()
}
}
localStorage.setItem('temp-settings-data', JSON.stringify(newTemp))
this.dispatchEvent(
new CustomEvent('temp-settings-data-event', {
bubbles: true,
composed: true
})
)
}
setMySelectedFeeds(mySelectedFeeds) {
this.mySelectedFeeds = mySelectedFeeds
const tempSettingsData = JSON.parse(localStorage.getItem('temp-settings-data') || "{}")
const newTemp = {
...tempSettingsData,
friendsFeed: {
data: mySelectedFeeds,
timestamp: Date.now()
}
}
localStorage.setItem('temp-settings-data', JSON.stringify(newTemp))
localStorage.setItem('friends-my-selected-feeds', JSON.stringify(mySelectedFeeds))
}
openEditFriend(val) {
this.isOpenAddFriendsModal = true
this.userSelected = val
this.editContent = { ...val, mySelectedFeeds: this.mySelectedFeeds }
}
onClose() {
this.isLoading = false;
this.isOpenAddFriendsModal = false
this.editContent = null
this.userSelected = {}
}
// Standard functions
getApiKey() {
const coreNode = store.getState().app.nodeConfig.knownNodes[store.getState().app.nodeConfig.node]
return coreNode.apiKey
}
isEmptyArray(arr) {
if (!arr) { return true }
return arr.length === 0
}
round(number) {
return (Math.round(parseFloat(number) * 1e8) / 1e8).toFixed(8)
}
}
window.customElements.define('friends-view', FriendsView)

View File

@ -1,14 +1,15 @@
import {css, html, LitElement} from 'lit'; import { html, LitElement } from 'lit'
import { connect } from 'pwa-helpers'
import { store } from '../../store'
import { parentEpml } from '../show-plugin'
import { get, translate } from '../../../translate' import { get, translate } from '../../../translate'
import '@material/mwc-button'; import { profileModalUpdateStyles } from '../../styles/core-css'
import '@material/mwc-icon'; import '@material/mwc-button'
import '@vaadin/tooltip'; import '@material/mwc-checkbox'
import '@material/mwc-dialog'; import '@material/mwc-dialog'
import '@material/mwc-checkbox'; import '@material/mwc-icon'
import {connect} from 'pwa-helpers'; import '@polymer/paper-spinner/paper-spinner-lite.js'
import {store} from '../../store'; import '@vaadin/tooltip'
import '@polymer/paper-spinner/paper-spinner-lite.js';
import {parentEpml} from '../show-plugin';
class ProfileModalUpdate extends connect(store)(LitElement) { class ProfileModalUpdate extends connect(store)(LitElement) {
static get properties() { static get properties() {
@ -31,412 +32,49 @@ class ProfileModalUpdate extends connect(store)(LitElement) {
newCustomDataKey: { type: String }, newCustomDataKey: { type: String },
newCustomDataValue: { type: String }, newCustomDataValue: { type: String },
isSaving: { type: Boolean } isSaving: { type: Boolean }
}; }
}
static get styles() {
return [profileModalUpdateStyles]
} }
constructor() { constructor() {
super(); super();
this.isOpen = false; this.isOpen = false
this.isLoading = false; this.isLoading = false
this.nodeUrl = this.getNodeUrl(); this.nodeUrl = this.getNodeUrl()
this.myNode = this.getMyNode(); this.myNode = this.getMyNode()
this.tagline = ''; this.tagline = ''
this.bio = ''; this.bio = ''
this.walletList = ['btc', 'ltc', 'doge', 'dgb', 'rvn', 'arrr']; this.walletList = ['btc', 'ltc', 'doge', 'dgb', 'rvn', 'arrr']
let wallets = {}; let wallets = {}
this.walletList.forEach((item) => { this.walletList.forEach((item) => {
wallets[item] = ''; wallets[item] = ''
});
this.wallets = wallets;
this.walletsUi = new Map();
let coinProp = {
wallet: null,
};
this.walletList.forEach((c, i) => {
this.walletsUi.set(c, { ...coinProp });
});
this.walletsUi.get('btc').wallet =
window.parent.reduxStore.getState().app.selectedAddress.btcWallet;
this.walletsUi.get('ltc').wallet =
window.parent.reduxStore.getState().app.selectedAddress.ltcWallet;
this.walletsUi.get('doge').wallet =
window.parent.reduxStore.getState().app.selectedAddress.dogeWallet;
this.walletsUi.get('dgb').wallet =
window.parent.reduxStore.getState().app.selectedAddress.dgbWallet;
this.walletsUi.get('rvn').wallet =
window.parent.reduxStore.getState().app.selectedAddress.rvnWallet;
this.hasFetchedArrr = false;
this.isOpenCustomDataModal = false;
this.customData = {};
this.newCustomDataKey = "";
this.newCustomDataValue = "";
this.newCustomDataField = {};
this.newFieldName = '';
this.isSaving = false;
this.addPrivate = this.addPrivate.bind(this);
this.checkForPrivate = this.checkForPrivate.bind(this);
}
static get styles() {
return css`
* {
--mdc-theme-primary: rgb(3, 169, 244);
--mdc-theme-secondary: var(--mdc-theme-primary);
--mdc-theme-surface: var(--white);
--mdc-dialog-content-ink-color: var(--black);
--mdc-dialog-min-width: 400px;
--mdc-dialog-max-width: 1024px;
box-sizing: border-box;
}
.input {
width: 90%;
outline: 0;
border-width: 0 0 2px;
border-color: var(--mdc-theme-primary);
background-color: transparent;
padding: 10px;
font-family: Roboto, sans-serif;
font-size: 15px;
color: var(--chat-bubble-msg-color);
box-sizing: border-box;
}
.input::selection {
background-color: var(--mdc-theme-primary);
color: white;
}
.input::placeholder {
opacity: 0.6;
color: var(--black);
}
.modal-button {
font-family: Roboto, sans-serif;
font-size: 16px;
color: var(--mdc-theme-primary);
background-color: transparent;
padding: 8px 10px;
border-radius: 5px;
border: none;
transition: all 0.3s ease-in-out;
}
.modal-button-red {
font-family: Roboto, sans-serif;
font-size: 16px;
color: #f44336;
background-color: transparent;
padding: 8px 10px;
border-radius: 5px;
border: none;
transition: all 0.3s ease-in-out;
}
.modal-button-red:hover {
cursor: pointer;
background-color: #f4433663;
}
.modal-button:hover {
cursor: pointer;
background-color: #03a8f475;
}
.checkbox-row {
position: relative;
display: flex;
align-items: center;
align-content: center;
font-family: Montserrat, sans-serif;
font-weight: 600;
color: var(--black);
}
.modal-overlay {
display: block;
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
background-color: rgba(
0,
0,
0,
0.5
); /* Semi-transparent backdrop */
z-index: 1000;
}
.modal-content {
position: fixed;
top: 50vh;
left: 50vw;
transform: translate(-50%, -50%);
background-color: var(--mdc-theme-surface);
width: 80vw;
padding: 20px;
box-shadow: rgba(0, 0, 0, 0.1) 0px 4px 6px;
z-index: 1001;
border-radius: 5px;
display: flex;
flex-direction: column;
}
.modal-overlay.hidden {
display: none;
}
.avatar {
width: 36px;
height: 36px;
display: flex;
align-items: center;
}
.app-name {
display: flex;
gap: 20px;
align-items: center;
width: 100%;
cursor: pointer;
padding: 5px;
border-radius: 5px;
margin-bottom: 10px;
}
.inner-content {
display: flex;
flex-direction: column;
max-height: 75vh;
flex-grow: 1;
overflow: auto;
}
.inner-content::-webkit-scrollbar-track {
background-color: whitesmoke;
border-radius: 7px;
}
.inner-content::-webkit-scrollbar {
width: 12px;
border-radius: 7px;
background-color: whitesmoke;
}
.inner-content::-webkit-scrollbar-thumb {
background-color: rgb(180, 176, 176);
border-radius: 7px;
transition: all 0.3s ease-in-out;
}
.checkbox-row {
position: relative;
display: flex;
align-items: center;
align-content: center;
font-family: Montserrat, sans-serif;
font-weight: 600;
color: var(--black);
}
`;
}
async updated(changedProperties) {
if (
changedProperties &&
changedProperties.has('editContent') &&
this.editContent
) {
const {bio, tagline, wallets, customData} = this.editContent
this.bio = bio ?? '';
this.tagline = tagline ?? '';
let formWallets = {...this.wallets}
if(wallets && Object.keys(wallets).length){
Object.keys(formWallets).forEach((key)=> {
if(wallets[key]){
formWallets[key] = wallets[key]
}
}) })
this.wallets = wallets
this.walletsUi = new Map()
let coinProp = {
wallet: null
} }
this.wallets = formWallets this.walletList.forEach((c, i) => {
this.walletsUi.set(c, { ...coinProp })
this.customData = {...customData} })
this.requestUpdate(); this.walletsUi.get('btc').wallet = store.getState().app.selectedAddress.btcWallet
} this.walletsUi.get('ltc').wallet = store.getState().app.selectedAddress.ltcWallet
if ( this.walletsUi.get('doge').wallet = store.getState().app.selectedAddress.dogeWallet
changedProperties && this.walletsUi.get('dgb').wallet = store.getState().app.selectedAddress.dgbWallet
changedProperties.has('qortalRequestCustomData') && this.walletsUi.get('rvn').wallet = store.getState().app.selectedAddress.rvnWallet
this.qortalRequestCustomData this.hasFetchedArrr = false
) { this.isOpenCustomDataModal = false
this.isOpenCustomDataModal = true this.customData = {}
this.newCustomDataField = {...this.qortalRequestCustomData.payload.customData}
this.newCustomDataKey = this.qortalRequestCustomData.property
this.requestUpdate();
}
}
async firstUpdated() {
try {
await this.fetchWalletAddress('arrr');
} catch (error) {
console.log({ error });
} finally {
}
}
async fetchWalletAddress(coin) {
switch (coin) {
case 'arrr':
const arrrWalletName = `${coin}Wallet`;
let res = await parentEpml.request('apiCall', {
url: `/crosschain/${coin}/walletaddress?apiKey=${this.myNode.apiKey}`,
method: 'POST',
body: `${
window.parent.reduxStore.getState().app.selectedAddress[
arrrWalletName
].seed58
}`,
});
if (res != null && res.error != 1201 && res.length === 78) {
this.arrrWalletAddress = res;
this.hasFetchedArrr = true;
}
break;
default:
// Not used for other coins yet
break;
}
}
async getSelectedWalletAddress(wallet) {
switch (wallet) {
case 'arrr':
if(!this.arrrWalletAddress){
try {
await this.fetchWalletAddress('arrr');
} catch (error) {
console.log({error})
}
}
// Use address returned by core API
return this.arrrWalletAddress;
default:
// Use locally derived address
return this.walletsUi.get(wallet).wallet.address;
}
}
getNodeUrl() {
const myNode =
store.getState().app.nodeConfig.knownNodes[
window.parent.reduxStore.getState().app.nodeConfig.node
]
return myNode.protocol + '://' + myNode.domain + ':' + myNode.port
}
getMyNode() {
return store.getState().app.nodeConfig.knownNodes[
window.parent.reduxStore.getState().app.nodeConfig.node
]
}
clearFields() {
this.bio = '';
this.tagline = '';
}
async fillAddress(coin) {
const address = await this.getSelectedWalletAddress(coin);
if (address) {
this.wallets = {
...this.wallets,
[coin]: address,
};
}
}
async saveProfile() {
try {
const data = {
version: 1,
tagline: this.tagline,
bio: this.bio,
wallets: this.wallets,
customData: this.customData
};
this.isSaving = true
await this.onSubmit(data);
this.setIsOpen(false);
this.clearFields();
this.onClose('success');
} catch (error) {} finally {
this.isSaving = false
}
}
removeField(key){
const copyObj = {...this.newCustomDataField}
delete copyObj[key]
this.newCustomDataField = copyObj
}
addField(){
if (!this.newFieldName || !this.newCustomDataValue) {
let snack5string = get("profile.profile24");
parentEpml.request('showSnackBar', `${snack5string}`);
return;
}
const copyObj = {...this.newCustomDataField}
copyObj[this.newFieldName] = this.newCustomDataValue
this.newCustomDataField = copyObj
this.newFieldName = ""
this.newCustomDataValue = ""
}
addCustomData(){
const copyObj = {...this.customData}
copyObj[this.newCustomDataKey] = this.newCustomDataField
this.customData = copyObj
this.newCustomDataKey = "" this.newCustomDataKey = ""
this.newCustomDataField = {}; this.newCustomDataValue = ""
this.newCustomDataField = {}
this.newFieldName = '' this.newFieldName = ''
this.newCustomDataValue = '' this.isSaving = false
this.isOpenCustomDataModal = false; this.addPrivate = this.addPrivate.bind(this)
} this.checkForPrivate = this.checkForPrivate.bind(this)
updateCustomData(key, data){
this.isOpenCustomDataModal = true
this.newCustomDataField = data
this.newCustomDataKey = key
}
removeCustomData(key){
const copyObj = {...this.customData}
delete copyObj[key]
this.customData = copyObj
}
checkForPrivate(){
let isPrivate = false
if(this.newCustomDataKey.includes('-private')) isPrivate = true
return isPrivate
}
addPrivate(e){
if (e.target.checked) {
if(this.newCustomDataKey.includes('-private')){
} else {
this.newCustomDataKey = this.newCustomDataKey + '-private'
}
} else {
this.newCustomDataKey = this.newCustomDataKey.replace('-private', '');
}
} }
render() { render() {
@ -491,9 +129,7 @@ class ProfileModalUpdate extends connect(store)(LitElement) {
<div style="display: flex;flex-direction: column;"> <div style="display: flex;flex-direction: column;">
${Object.keys(this.wallets).map((key) => { ${Object.keys(this.wallets).map((key) => {
return html` return html`
<div <div style="display:flex;justify-content:center;flex-direction:column">
style="display:flex;justify-content:center;flex-direction:column"
>
<label <label
for=${key} for=${key}
id="taglineLabel" id="taglineLabel"
@ -501,9 +137,7 @@ class ProfileModalUpdate extends connect(store)(LitElement) {
> >
${key} ${key}
</label> </label>
<div <div style="display:flex;gap:15px;align-items:center">
style="display:flex;gap:15px;align-items:center"
>
<input <input
id=${key} id=${key}
placeholder=${key + ' ' + get('settings.address')} placeholder=${key + ' ' + get('settings.address')}
@ -516,65 +150,51 @@ class ProfileModalUpdate extends connect(store)(LitElement) {
}; };
}} }}
/> />
<mwc-icon <mwc-icon
id=${`${key}-upload`} id=${`${key}-upload`}
@click=${() => @click=${() =>
this.fillAddress(key)} this.fillAddress(key)}
style="color:var(--black);cursor:pointer" style="color:var(--black);cursor:pointer"
>upload_2</mwc-icon
> >
upload_2
</mwc-icon>
<vaadin-tooltip <vaadin-tooltip
for=${`${key}-upload`} for=${`${key}-upload`}
position="bottom" position="bottom"
hover-delay=${200} hover-delay=${200}
hide-delay=${1} hide-delay=${1}
text=${translate('profile.profile21')} text=${translate('profile.profile21')}
> ></vaadin-tooltip>
</vaadin-tooltip>
</div> </div>
</div> </div>
`; `
})} })}
</div> </div>
<div style="display: flex;flex-direction: column;"> <div style="display: flex;flex-direction: column;">
${Object.keys(this.customData).map((key) => { ${Object.keys(this.customData).map((key) => {
return html` return html`
<div <div style="display:flex;justify-content:center;flex-direction:column;gap:25px">
style="display:flex;justify-content:center;flex-direction:column;gap:25px" <div style="display:flex;gap:15px;align-items:center">
> <p style="color: var(--black);font-size:16px">${key}</p>
<div
style="display:flex;gap:15px;align-items:center"
>
<p
style="color: var(--black);font-size:16px"
>
${key}
</p>
<mwc-icon <mwc-icon
@click=${() => @click=${() => this.updateCustomData(key, this.customData[key])}
this.updateCustomData(key,this.customData[key])}
style="color:var(--black);cursor:pointer" style="color:var(--black);cursor:pointer"
>edit</mwc-icon
> >
edit
</mwc-icon>
<mwc-icon <mwc-icon
@click=${() => @click=${() => this.removeCustomData(key)}
this.removeCustomData(key)}
style="color:var(--black);cursor:pointer" style="color:var(--black);cursor:pointer"
>remove</mwc-icon
> >
remove
</mwc-icon>
</div> </div>
</div> </div>
`; `
})} })}
</div> </div>
</div> </div>
<div <div style="display:flex;justify-content:space-between;align-items:center;margin-top:20px">
style="display:flex;justify-content:space-between;align-items:center;margin-top:20px"
>
<button <button
class="modal-button-red" class="modal-button-red"
?disabled="${this.isLoading}" ?disabled="${this.isLoading}"
@ -586,9 +206,7 @@ class ProfileModalUpdate extends connect(store)(LitElement) {
> >
${translate('general.close')} ${translate('general.close')}
</button> </button>
<div style="display:flex;gap:10px;align-items:center"> <div style="display:flex;gap:10px;align-items:center">
<button <button
?disabled="${this.isLoading}" ?disabled="${this.isLoading}"
class="modal-button" class="modal-button"
@ -615,7 +233,6 @@ class ProfileModalUpdate extends connect(store)(LitElement) {
</div> </div>
</div> </div>
</div> </div>
<!-- add custom vars --> <!-- add custom vars -->
<div <div
class="modal-overlay ${this.isOpenCustomDataModal class="modal-overlay ${this.isOpenCustomDataModal
@ -627,17 +244,12 @@ class ProfileModalUpdate extends connect(store)(LitElement) {
<div class="inner-content"> <div class="inner-content">
<div style="display:flex; justify-content:flex-end"> <div style="display:flex; justify-content:flex-end">
<div class="checkbox-row" style="font-size:16px"> <div class="checkbox-row" style="font-size:16px">
<label for="isPrivate" style="color: var(--black);"> <label for="isPrivate" style="color: var(--black);">${get('profile.profile23')}</label>
${get('profile.profile23')} <mwc-checkbox id="isPrivate" @change=${(e) => this.addPrivate(e)} ?checked=${this.checkForPrivate()}></mwc-checkbox>
</label>
<mwc-checkbox id="isPrivate" @change=${(e) => this.addPrivate(e)} ?checked=${this.checkForPrivate()}>
</mwc-checkbox>
</div> </div>
</div> </div>
<div style="height:15px"></div> <div style="height:15px"></div>
<div <div style="display:flex;justify-content:center;flex-direction:column">
style="display:flex;justify-content:center;flex-direction:column"
>
<label <label
for="key-name" for="key-name"
id="taglineLabel" id="taglineLabel"
@ -645,9 +257,7 @@ class ProfileModalUpdate extends connect(store)(LitElement) {
> >
${translate('profile.profile9')} ${translate('profile.profile9')}
</label> </label>
<div <div style="display:flex;gap:15px;align-items:center">
style="display:flex;gap:15px;align-items:center"
>
<input <input
id="key-name" id="key-name"
placeholder=${translate( placeholder=${translate(
@ -668,19 +278,9 @@ class ProfileModalUpdate extends connect(store)(LitElement) {
<div style="display: flex;flex-direction: column;"> <div style="display: flex;flex-direction: column;">
${Object.keys(this.newCustomDataField).map((key) => { ${Object.keys(this.newCustomDataField).map((key) => {
return html` return html`
<div <div style="display:flex;justify-content:center;flex-direction:column">
style="display:flex;justify-content:center;flex-direction:column" <label for=${key} id="taglineLabel" style="color: var(--black);font-size:16px">${key}</label>
> <div style="display:flex;gap:15px;align-items:center">
<label
for=${key}
id="taglineLabel"
style="color: var(--black);font-size:16px"
>
${key}
</label>
<div
style="display:flex;gap:15px;align-items:center"
>
<input <input
id=${key} id=${key}
placeholder=${translate('profile.profile13')} placeholder=${translate('profile.profile13')}
@ -693,16 +293,15 @@ class ProfileModalUpdate extends connect(store)(LitElement) {
}; };
}} }}
/> />
<mwc-icon <mwc-icon
@click=${() => @click=${() => this.removeField(key)}
this.removeField(key)}
style="color:var(--black);cursor:pointer" style="color:var(--black);cursor:pointer"
>remove</mwc-icon
> >
remove
</mwc-icon>
</div> </div>
</div> </div>
`; `
})} })}
</div> </div>
<div style=${`display: flex; flex-direction: row; align-items: center;justify-content:space-between; ${Object.keys(this.newCustomDataField).length ? "margin-top: 10px" : ""}`}> <div style=${`display: flex; flex-direction: row; align-items: center;justify-content:space-between; ${Object.keys(this.newCustomDataField).length ? "margin-top: 10px" : ""}`}>
@ -736,9 +335,7 @@ class ProfileModalUpdate extends connect(store)(LitElement) {
</button> </button>
</div> </div>
</div> </div>
<div <div style="display:flex;justify-content:space-between;align-items:center;margin-top:20px">
style="display:flex;justify-content:space-between;align-items:center;margin-top:20px"
>
<button <button
class="modal-button-red" class="modal-button-red"
?disabled="${this.isLoading}" ?disabled="${this.isLoading}"
@ -750,7 +347,6 @@ class ProfileModalUpdate extends connect(store)(LitElement) {
> >
${translate('general.close')} ${translate('general.close')}
</button> </button>
<button <button
?disabled="${this.isSaving}" ?disabled="${this.isSaving}"
class="modal-button" class="modal-button"
@ -763,8 +359,204 @@ class ProfileModalUpdate extends connect(store)(LitElement) {
</div> </div>
</div> </div>
</div> </div>
`; `
}
async firstUpdated() {
try {
await this.fetchWalletAddress('arrr')
} catch (error) {
console.log({ error })
} finally {
} }
} }
customElements.define('profile-modal-update', ProfileModalUpdate); async updated(changedProperties) {
if (changedProperties && changedProperties.has('editContent') && this.editContent) {
const { bio, tagline, wallets, customData } = this.editContent
this.bio = bio ?? ''
this.tagline = tagline ?? ''
let formWallets = { ...this.wallets }
if (wallets && Object.keys(wallets).length) {
Object.keys(formWallets).forEach((key) => {
if (wallets[key]) {
formWallets[key] = wallets[key]
}
})
}
this.wallets = formWallets
this.customData = { ...customData }
this.requestUpdate();
}
if (changedProperties && changedProperties.has('qortalRequestCustomData') && this.qortalRequestCustomData) {
this.isOpenCustomDataModal = true
this.newCustomDataField = { ...this.qortalRequestCustomData.payload.customData }
this.newCustomDataKey = this.qortalRequestCustomData.property
this.requestUpdate()
}
}
async fetchWalletAddress(coin) {
switch (coin) {
case 'arrr':
const arrrWalletName = `${coin}Wallet`;
let res = await parentEpml.request('apiCall', {
url: `/crosschain/${coin}/walletaddress?apiKey=${this.myNode.apiKey}`,
method: 'POST',
body: `${store.getState().app.selectedAddress[arrrWalletName].seed58}`
})
if (res != null && res.error != 1201 && res.length === 78) {
this.arrrWalletAddress = res
this.hasFetchedArrr = true
}
break
default:
// Not used for other coins yet
break
}
}
async getSelectedWalletAddress(wallet) {
switch (wallet) {
case 'arrr':
if (!this.arrrWalletAddress) {
try {
await this.fetchWalletAddress('arrr')
} catch (error) {
console.log({ error })
}
}
// Use address returned by core API
return this.arrrWalletAddress
default:
// Use locally derived address
return this.walletsUi.get(wallet).wallet.address
}
}
getNodeUrl() {
const myNode = store.getState().app.nodeConfig.knownNodes[store.getState().app.nodeConfig.node]
return myNode.protocol + '://' + myNode.domain + ':' + myNode.port
}
getMyNode() {
return store.getState().app.nodeConfig.knownNodes[store.getState().app.nodeConfig.node]
}
clearFields() {
this.bio = ''
this.tagline = ''
}
async fillAddress(coin) {
const address = await this.getSelectedWalletAddress(coin)
if (address) {
this.wallets = {
...this.wallets,
[coin]: address
}
}
}
async saveProfile() {
try {
const data = {
version: 1,
tagline: this.tagline,
bio: this.bio,
wallets: this.wallets,
customData: this.customData
}
this.isSaving = true
await this.onSubmit(data)
this.setIsOpen(false)
this.clearFields()
this.onClose('success')
} catch (error) { } finally {
this.isSaving = false
}
}
removeField(key) {
const copyObj = { ...this.newCustomDataField }
delete copyObj[key]
this.newCustomDataField = copyObj
}
addField() {
if (!this.newFieldName || !this.newCustomDataValue) {
let snack5string = get("profile.profile24")
parentEpml.request('showSnackBar', `${snack5string}`)
return
}
const copyObj = { ...this.newCustomDataField }
copyObj[this.newFieldName] = this.newCustomDataValue
this.newCustomDataField = copyObj
this.newFieldName = ''
this.newCustomDataValue = ''
}
addCustomData() {
const copyObj = { ...this.customData }
copyObj[this.newCustomDataKey] = this.newCustomDataField
this.customData = copyObj
this.newCustomDataKey = ''
this.newCustomDataField = {}
this.newFieldName = ''
this.newCustomDataValue = ''
this.isOpenCustomDataModal = false
}
updateCustomData(key, data) {
this.isOpenCustomDataModal = true
this.newCustomDataField = data
this.newCustomDataKey = key
}
removeCustomData(key) {
const copyObj = { ...this.customData }
delete copyObj[key]
this.customData = copyObj
}
checkForPrivate() {
let isPrivate = false
if (this.newCustomDataKey.includes('-private')) isPrivate = true
return isPrivate
}
addPrivate(e) {
if (e.target.checked) {
if (this.newCustomDataKey.includes('-private')) {
} else {
this.newCustomDataKey = this.newCustomDataKey + '-private'
}
} else {
this.newCustomDataKey = this.newCustomDataKey.replace('-private', '')
}
}
// Standard functions
getApiKey() {
const coreNode = store.getState().app.nodeConfig.knownNodes[store.getState().app.nodeConfig.node]
return coreNode.apiKey
}
isEmptyArray(arr) {
if (!arr) { return true }
return arr.length === 0
}
round(number) {
return (Math.round(parseFloat(number) * 1e8) / 1e8).toFixed(8)
}
}
window.customElements.define('profile-modal-update', ProfileModalUpdate)

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,7 @@
import {css, html, LitElement} from 'lit' import { html, LitElement } from 'lit'
import { languageSelectorStyles } from '../styles/core-css'
// Multi language support
import { registerTranslateConfig, translate, use } from '../../translate' import { registerTranslateConfig, translate, use } from '../../translate'
registerTranslateConfig({ registerTranslateConfig({
@ -22,47 +25,7 @@ class LanguageSelector extends LitElement {
} }
static get styles() { static get styles() {
return [ return [languageSelectorStyles]
css`
select {
width: 175px;
height: 34px;
padding: 5px 0px 5px 5px;
font-size: 16px;
border: 1px solid var(--black);
border-radius: 3px;
color: var(--black);
background:
linear-gradient(45deg, transparent 50%, white 50%),
linear-gradient(135deg, white 50%, transparent 50%),
linear-gradient(to right, #03a9f4, #03a9f4);
background-position:
calc(100% - 17px) calc(0.5em + 4px),
calc(100% - 7px) calc(0.5em + 4px),
100% 0;
background-size:
10px 10px,
10px 10px,
2.2em 2.2em;
background-repeat: no-repeat;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
-webkit-appearance:none;
-moz-appearance:none;
}
*:focus {
outline: none;
}
select option {
color: var(--black);
background: var(--white);
line-height: 34px;
}
`
]
} }
constructor() { constructor() {
@ -103,7 +66,7 @@ class LanguageSelector extends LitElement {
firstUpdated() { firstUpdated() {
const myElement = this.shadowRoot.getElementById('languageSelect') const myElement = this.shadowRoot.getElementById('languageSelect')
myElement.addEventListener("change", () => { myElement.addEventListener('change', () => {
this.selectElement() this.selectElement()
}) })

View File

@ -1,29 +1,31 @@
import {css, html, LitElement} from 'lit' import { html, LitElement } from 'lit'
import { connect } from 'pwa-helpers' import { connect } from 'pwa-helpers'
import {store} from '../../store.js' import { store } from '../../store'
import {get, translate} from '../../../translate' import { createWallet } from '../../../../crypto/api/createWallet'
import { doLogin, doLogout, doSelectAddress } from '../../redux/app/app-actions'
import {createWallet} from '../../../../crypto/api/createWallet.js' import { doStoreWallet } from '../../redux/user/user-actions'
import {doLogin, doLogout, doSelectAddress} from '../../redux/app/app-actions.js' import { checkApiKey } from '../../apiKeyUtils'
import {doStoreWallet} from '../../redux/user/user-actions.js' import { createAccountSectionStyles } from '../../styles/core-css'
import {checkApiKey} from '../../apiKeyUtils.js'
import FileSaver from 'file-saver' import FileSaver from 'file-saver'
import ripple from '../../functional-components/loading-ripple.js' import ripple from '../../functional-components/loading-ripple'
import snackbar from '../../functional-components/snackbar.js' import snackbar from '../../functional-components/snackbar'
import '../../functional-components/random-sentence-generator.js' import '../../functional-components/random-sentence-generator'
import '@material/mwc-button' import '@material/mwc-button'
import '@material/mwc-checkbox' import '@material/mwc-checkbox'
import '@material/mwc-textfield'
import '@material/mwc-icon'
import '@material/mwc-dialog' import '@material/mwc-dialog'
import '@material/mwc-formfield' import '@material/mwc-formfield'
import '@material/mwc-icon'
import '@material/mwc-textfield'
import '@polymer/iron-pages' import '@polymer/iron-pages'
import '@polymer/paper-button/paper-button.js' import '@polymer/paper-button/paper-button.js'
import '@polymer/paper-input/paper-input-container.js' import '@polymer/paper-input/paper-input-container.js'
import '@polymer/paper-input/paper-input.js' import '@polymer/paper-input/paper-input.js'
import '@polymer/paper-tooltip/paper-tooltip.js' import '@polymer/paper-tooltip/paper-tooltip.js'
import '@vaadin/text-field/vaadin-text-field.js'
import '@vaadin/password-field/vaadin-password-field.js' import '@vaadin/password-field/vaadin-password-field.js'
import '@vaadin/text-field/vaadin-text-field.js'
// Multi language support
import { get, translate } from '../../../translate'
let lastPassword = '' let lastPassword = ''
@ -54,32 +56,7 @@ class CreateAccountSection extends connect(store)(LitElement) {
} }
static get styles() { static get styles() {
return [ return [createAccountSectionStyles]
css`
* {
--mdc-theme-primary: var(--login-button);
--mdc-theme-secondary: var(--mdc-theme-primary);
--paper-input-container-focus-color: var(--mdc-theme-primary);
--mdc-theme-surface: var(--white);
--mdc-dialog-content-ink-color: var(--black);
--mdc-checkbox-unchecked-color: var(--black);
--lumo-primary-text-color: var(--login-border);
--lumo-primary-color-50pct: var(--login-border-50pct);
--lumo-primary-color-10pct: var(--login-border-10pct);
--lumo-primary-color: hsl(199, 100%, 48%);
--lumo-base-color: var(--white);
--lumo-body-text-color: var(--black);
--lumo-secondary-text-color: var(--sectxt);
--lumo-contrast-60pct: var(--vdicon);
--_lumo-grid-border-color: var(--border);
--_lumo-grid-secondary-border-color: var(--border2);
}
.red {
--mdc-theme-primary: red;
}
`
]
} }
constructor() { constructor() {
@ -116,44 +93,54 @@ class CreateAccountSection extends connect(store)(LitElement) {
next: e => { next: e => {
// Create account and login :) // Create account and login :)
this.createAccountLoading = true this.createAccountLoading = true
const nameInput = this.shadowRoot.getElementById('nameInput').value const nameInput = this.shadowRoot.getElementById('nameInput').value
const password = this.shadowRoot.getElementById('password').value const password = this.shadowRoot.getElementById('password').value
const rePassword = this.shadowRoot.getElementById('rePassword').value const rePassword = this.shadowRoot.getElementById('rePassword').value
if (password === '') { if (password === '') {
let snackbar1string = get("login.pleaseenter") let snackbar1string = get("login.pleaseenter")
snackbar.add({ snackbar.add({
labelText: `${snackbar1string}`, labelText: `${snackbar1string}`,
dismiss: true dismiss: true
}) })
return return
} }
if (password != rePassword) { if (password != rePassword) {
let snackbar2string = get("login.notmatch") let snackbar2string = get("login.notmatch")
snackbar.add({ snackbar.add({
labelText: `${snackbar2string}`, labelText: `${snackbar2string}`,
dismiss: true dismiss: true
}) })
return return
} }
if (password.length < 8 && lastPassword !== password) { if (password.length < 8 && lastPassword !== password) {
let snackbar3string = get("login.lessthen8") let snackbar3string = get("login.lessthen8")
snackbar.add({ snackbar.add({
labelText: `${snackbar3string}`, labelText: `${snackbar3string}`,
dismiss: true dismiss: true
}) })
lastPassword = password lastPassword = password
return return
} }
if (this.saveAccount === true && nameInput === '') { if (this.saveAccount === true && nameInput === '') {
let snackbar4string = get("login.entername") let snackbar4string = get("login.entername")
snackbar.add({ snackbar.add({
labelText: `${snackbar4string}`, labelText: `${snackbar4string}`,
dismiss: true dismiss: true
}) })
return return
} }
@ -161,32 +148,31 @@ class CreateAccountSection extends connect(store)(LitElement) {
this._pass = password this._pass = password
let seedObj = {} let seedObj = {}
const seedPhrase = this.shadowRoot.getElementById('randSentence').parsedString const seedPhrase = this.shadowRoot.getElementById('randSentence').parsedString
seedObj = { seedPhrase: seedPhrase } seedObj = { seedPhrase: seedPhrase }
ripple.welcomeMessage = welcomeMessage ripple.welcomeMessage = welcomeMessage
ripple.open({
x: e.clientX, ripple.open({ x: e.clientX, y: e.clientY }).then(() => createWallet('phrase', seedObj, status => {
y: e.clientY
})
.then(() => createWallet('phrase', seedObj, status => {
ripple.loadingMessage = status ripple.loadingMessage = status
})) })).then(wallet => {
.then(wallet => {
this._wallet = wallet this._wallet = wallet
return ripple.fade() return ripple.fade()
}) }).then(() => {
.then(() => {
this.selectPage('backup') this.selectPage('backup')
this.updateNext() this.updateNext()
}) }).catch(e => {
.catch(e => {
snackbar.add({ snackbar.add({
labelText: e, labelText: e,
dismiss: true dismiss: true
}) })
console.error('== Error == \n', e) console.error('== Error == \n', e)
store.dispatch(doLogout()) store.dispatch(doLogout())
ripple.close() ripple.close()
}) })
}, },
@ -199,6 +185,7 @@ class CreateAccountSection extends connect(store)(LitElement) {
next: e => { next: e => {
if (!this.isDownloadedBackup) { if (!this.isDownloadedBackup) {
let snackbar5string = get("login.downloaded") let snackbar5string = get("login.downloaded")
snackbar.add({ snackbar.add({
labelText: `${snackbar5string}`, labelText: `${snackbar5string}`,
dismiss: true dismiss: true
@ -206,29 +193,28 @@ class CreateAccountSection extends connect(store)(LitElement) {
} else { } else {
if (this.saveAccount) { if (this.saveAccount) {
ripple.welcomeMessage = this.renderPrepareText() ripple.welcomeMessage = this.renderPrepareText()
ripple.open({
x: e.clientX, ripple.open({ x: e.clientX, y: e.clientY}).then(() => {
y: e.clientY
})
.then(() => {
store.dispatch(doStoreWallet(this._wallet, this._pass, this._name, () => { store.dispatch(doStoreWallet(this._wallet, this._pass, this._name, () => {
ripple.loadingMessage = this.renderLoadingText() ripple.loadingMessage = this.renderLoadingText()
})) })).then(() => {
.then(() => {
store.dispatch(doLogin(this._wallet)) store.dispatch(doLogin(this._wallet))
store.dispatch(doSelectAddress(this._wallet.addresses[0])) store.dispatch(doSelectAddress(this._wallet.addresses[0]))
checkApiKey(this.nodeConfig); checkApiKey(this.nodeConfig);
this.cleanup() this.cleanup()
return ripple.fade() return ripple.fade()
}).catch(err => {
console.error(err)
}) })
.catch(err => console.error(err))
}).catch(err => { }).catch(err => {
console.error(err) console.error(err)
}) })
} else { } else {
store.dispatch(doLogin(this._wallet)) store.dispatch(doLogin(this._wallet))
store.dispatch(doSelectAddress(this._wallet.addresses[0])) store.dispatch(doSelectAddress(this._wallet.addresses[0]))
checkApiKey() checkApiKey()
this.cleanup() this.cleanup()
} }
} }
@ -238,6 +224,7 @@ class CreateAccountSection extends connect(store)(LitElement) {
} }
} }
} }
this.pageIndexes = { this.pageIndexes = {
info: 0, info: 0,
password: 1, password: 1,
@ -307,20 +294,24 @@ class CreateAccountSection extends connect(store)(LitElement) {
cursor: pointer; cursor: pointer;
} }
mwc-checkbox::shadow .mdc-checkbox::after, mwc-checkbox::shadow .mdc-checkbox::before { mwc-checkbox::shadow .mdc-checkbox::after,
mwc-checkbox::shadow .mdc-checkbox::before {
background-color: var(--mdc-theme-primary) background-color: var(--mdc-theme-primary)
} }
@media only screen and (max-width: ${getComputedStyle(document.body).getPropertyValue('--layout-breakpoint-tablet')}) { @media only screen and (max-width: ${getComputedStyle(document.body).getPropertyValue('--layout-breakpoint-tablet')}) {
/* Mobile */ /* Mobile */
#createAccountSection { #createAccountSection {
max-width: 100%; max-width: 100%;
height: calc(var(--window-height) - 56px); height: calc(var(--window-height) - 56px);
} }
#infoContent { #infoContent {
height: auto; height: auto;
min-height: calc(var(--window-height) - 96px) min-height: calc(var(--window-height) - 96px)
} }
#nav { #nav {
flex-shrink: 0; flex-shrink: 0;
padding-top: 8px; padding-top: 8px;
@ -335,6 +326,7 @@ class CreateAccountSection extends connect(store)(LitElement) {
from { from {
opacity: 0; opacity: 0;
} }
to { to {
opacity: 1; opacity: 1;
} }
@ -360,7 +352,6 @@ class CreateAccountSection extends connect(store)(LitElement) {
padding: 0; padding: 0;
} }
</style> </style>
<div id="createAccountSection" class="flex column"> <div id="createAccountSection" class="flex column">
<iron-pages selected="${this.selectedPage}" attr-for-selected="page" id="createAccountPages"> <iron-pages selected="${this.selectedPage}" attr-for-selected="page" id="createAccountPages">
<div page="info"> <div page="info">
@ -371,11 +362,15 @@ class CreateAccountSection extends connect(store)(LitElement) {
${translate("login.createwelcome")} ${translate("login.createwelcome")}
</p> </p>
<p style="color: var(--black); margin-bottom:0;"> <p style="color: var(--black); margin-bottom:0;">
${translate("login.createa")} <paper-button id="myseedshow" @click=${() => this.shadowRoot.querySelector('#mySeedDialog').show()}>${translate("login.seedphrase")}</paper-button><paper-tooltip for="myseedshow" position="top" animation-delay="0">${translate("login.click")}</paper-tooltip> ${translate("login.willbe")} ${translate("login.createa")}
<paper-button id="myseedshow" @click=${() => this.shadowRoot.querySelector('#mySeedDialog').show()}>${translate("login.seedphrase")}</paper-button>
<paper-tooltip for="myseedshow" position="top" animation-delay="0">${translate("login.click")}</paper-tooltip>
${translate("login.willbe")}
</p> </p>
<p style="color: var(--black); margin-bottom: 0; text-align: center;"> <p style="color: var(--black); margin-bottom: 0; text-align: center;">
${translate("login.clicknext")} ${translate("login.clicknext")}
</p><br> </p>
<br>
</div> </div>
<mwc-dialog id="mySeedDialog"> <mwc-dialog id="mySeedDialog">
<div style="min-height:250px; min-width: 300px; box-sizing: border-box; position: relative;"> <div style="min-height:250px; min-width: 300px; box-sizing: border-box; position: relative;">
@ -389,8 +384,7 @@ class CreateAccountSection extends connect(store)(LitElement) {
<random-sentence-generator <random-sentence-generator
template="adverb verb noun adjective noun adverb verb noun adjective noun adjective verbed adjective noun" template="adverb verb noun adjective noun adverb verb noun adjective noun adjective verbed adjective noun"
id="randSentence" id="randSentence"
> ></random-sentence-generator>
</random-sentence-generator>
</div> </div>
<!-- <!--
--- --- --- --- --- --- --- --- --- --- --- --- --- - --- --- --- --- --- --- --- --- --- --- --- --- --- -
@ -405,7 +399,8 @@ class CreateAccountSection extends connect(store)(LitElement) {
sooo 243*3387*403*2353*3387*403*2353*403*2353 ~ 2^92 sooo 243*3387*403*2353*3387*403*2353*403*2353 ~ 2^92
--- --- --- --- --- --- --- --- --- --- --- --- --- - --- --- --- --- --- --- --- --- --- --- --- --- --- -
--> -->
</div><br> </div>
<br>
<div class="horizontal-center"> <div class="horizontal-center">
<mwc-button raised label="${translate("login.saveseed")}" icon="save" @click=${() => this.downloadSeedphrase()}></mwc-button> <mwc-button raised label="${translate("login.saveseed")}" icon="save" @click=${() => this.downloadSeedphrase()}></mwc-button>
</div> </div>
@ -413,7 +408,6 @@ class CreateAccountSection extends connect(store)(LitElement) {
<mwc-button slot="primaryAction" dialogAction="cancel" class="red">${translate("general.close")}</mwc-button> <mwc-button slot="primaryAction" dialogAction="cancel" class="red">${translate("general.close")}</mwc-button>
</mwc-dialog> </mwc-dialog>
</div> </div>
<div page="password"> <div page="password">
<div id="saveContent" class="section-content"> <div id="saveContent" class="section-content">
<h3 style="color: var(--black); text-align: center;">${translate("login.savein")}</h3> <h3 style="color: var(--black); text-align: center;">${translate("login.savein")}</h3>
@ -438,19 +432,21 @@ class CreateAccountSection extends connect(store)(LitElement) {
</div> </div>
</div> </div>
</div> </div>
<div page="backup"> <div page="backup">
<div id="downloadBackup" class="section-content"> <div id="downloadBackup" class="section-content">
<h3 style="color: var(--black); text-align: center;">${translate("login.savewallet")}</h3> <h3 style="color: var(--black); text-align: center;">${translate("login.savewallet")}</h3>
<p style="color: var(--black); text-align: justify;">${translate("login.created1")}${this.saveAccount ? this.renderCreateSaveText() : '.'}</p> <p style="color: var(--black); text-align: justify;">${translate("login.created1")}${this.saveAccount ? this.renderCreateSaveText() : '.'}</p>
<p style="color: var(--black); margin: 0;"> <p style="color: var(--black); margin: 0;">${translate("login.backup")}</p>
${translate("login.backup")} <br>
</p><br>
<div id="download-area"> <div id="download-area">
<div style="line-height: 40px;"> <div style="line-height: 40px;">
<span style="color: var(--black); padding-top: 6px; margin-right: 10px; text-align: center;">${translate("login.downloadbackup")}</span> <span style="color: var(--black); padding-top: 6px; margin-right: 10px; text-align: center;">${translate("login.downloadbackup")}</span>
<slot id="trigger" name="inputTrigger" @click=${() => this.downloadBackup(this._wallet)} style="dispay: inline; text-align: center;"> <slot id="trigger" name="inputTrigger" @click=${() =>
<mwc-button><mwc-icon>cloud_download</mwc-icon>&nbsp; ${translate("general.save")}</mwc-button> this.downloadBackup(this._wallet)} style="dispay: inline; text-align: center;">
<mwc-button>
<mwc-icon>cloud_download</mwc-icon>
&nbsp; ${translate("general.save")}
</mwc-button>
</slot> </slot>
</div> </div>
</div> </div>
@ -519,15 +515,19 @@ class CreateAccountSection extends connect(store)(LitElement) {
if (!this.shadowRoot.querySelector('#createAccountPages') || !newPage) { if (!this.shadowRoot.querySelector('#createAccountPages') || !newPage) {
return return
} }
const pages = this.shadowRoot.querySelector('#createAccountPages').children const pages = this.shadowRoot.querySelector('#createAccountPages').children
// Run the animation on the newly selected page // Run the animation on the newly selected page
const newIndex = this.pageIndexes[newPage] const newIndex = this.pageIndexes[newPage]
if (!pages[newIndex].className.includes('animated')) { if (!pages[newIndex].className.includes('animated')) {
pages[newIndex].className += ' animated' pages[newIndex].className += ' animated'
} }
if (typeof oldPage !== 'undefined') { if (typeof oldPage !== 'undefined') {
const oldIndex = this.pageIndexes[oldPage] const oldIndex = this.pageIndexes[oldPage]
// Stop the animation of hidden pages // Stop the animation of hidden pages
pages[oldIndex].classList.remove('animated') pages[oldIndex].classList.remove('animated')
} }
@ -535,6 +535,7 @@ class CreateAccountSection extends connect(store)(LitElement) {
selectPage(newPage) { selectPage(newPage) {
const oldPage = this.selectedPage const oldPage = this.selectedPage
this.selectedPage = newPage this.selectedPage = newPage
this._pageChange(newPage, oldPage) this._pageChange(newPage, oldPage)
} }
@ -552,6 +553,7 @@ class CreateAccountSection extends connect(store)(LitElement) {
this.backHidden = true this.backHidden = true
this.nextText = this.renderContinueText() this.nextText = this.renderContinueText()
} }
this.updatedProperty() this.updatedProperty()
} }
@ -583,24 +585,27 @@ class CreateAccountSection extends connect(store)(LitElement) {
this.nodeConfig = state.app.nodeConfig this.nodeConfig = state.app.nodeConfig
} }
createAccount() {
}
async downloadBackup(wallet) { async downloadBackup(wallet) {
let backupname = "" let backupname = ""
const state = store.getState() const state = store.getState()
const data = await wallet.generateSaveWalletData(this._pass, state.config.crypto.kdfThreads, () => { }) const data = await wallet.generateSaveWalletData(this._pass, state.config.crypto.kdfThreads, () => { })
const dataString = JSON.stringify(data) const dataString = JSON.stringify(data)
const blob = new Blob([dataString], { type: 'text/plain;charset=utf-8' }) const blob = new Blob([dataString], { type: 'text/plain;charset=utf-8' })
backupname = "qortal_backup_" + wallet.addresses[0].address + ".json" backupname = "qortal_backup_" + wallet.addresses[0].address + ".json"
await this.saveFileToDisk(blob, backupname) await this.saveFileToDisk(blob, backupname)
} }
async downloadSeedphrase() { async downloadSeedphrase() {
let seedname = "" let seedname = ""
const seed = this.shadowRoot.getElementById('randSentence').parsedString const seed = this.shadowRoot.getElementById('randSentence').parsedString
const blob = new Blob([seed], { type: 'text/plain;charset=utf-8' }) const blob = new Blob([seed], { type: 'text/plain;charset=utf-8' })
seedname = "qortal_seedphrase.txt" seedname = "qortal_seedphrase.txt"
await this.saveFileToDisk(blob, seedname) await this.saveFileToDisk(blob, seedname)
} }
@ -609,16 +614,21 @@ class CreateAccountSection extends connect(store)(LitElement) {
const fileHandle = await self.showSaveFilePicker({ const fileHandle = await self.showSaveFilePicker({
suggestedName: fileName, suggestedName: fileName,
types: [{ types: [{
description: "File", description: "File"
}] }]
}) })
const writeFile = async (fileHandle, contents) => { const writeFile = async (fileHandle, contents) => {
const writable = await fileHandle.createWritable() const writable = await fileHandle.createWritable()
await writable.write(contents) await writable.write(contents)
await writable.close() await writable.close()
} }
writeFile(fileHandle, blob).then(() => console.log("FILE SAVED")) writeFile(fileHandle, blob).then(() => console.log("FILE SAVED"))
let snack4string = get("general.save") let snack4string = get("general.save")
snackbar.add({ snackbar.add({
labelText: `${snack4string} ${fileName}`, labelText: `${snack4string} ${fileName}`,
dismiss: true dismiss: true
@ -627,6 +637,7 @@ class CreateAccountSection extends connect(store)(LitElement) {
if (error.name === 'AbortError') { if (error.name === 'AbortError') {
return return
} }
FileSaver.saveAs(blob, fileName) FileSaver.saveAs(blob, fileName)
} }
} }

View File

@ -1,15 +1,14 @@
import {css, html, LitElement} from 'lit' import { html, LitElement } from 'lit'
import { connect } from 'pwa-helpers' import { connect } from 'pwa-helpers'
import {store} from '../../store.js' import { store } from '../../store'
import {checkApiKey} from '../../apiKeyUtils.js' import { checkApiKey } from '../../apiKeyUtils'
import {translate} from '../../../translate' import { doLogin, doSelectAddress } from '../../redux/app/app-actions'
import {doLogin, doSelectAddress} from '../../redux/app/app-actions.js' import { doRemoveWallet, doStoreWallet } from '../../redux/user/user-actions'
import {doRemoveWallet, doStoreWallet} from '../../redux/user/user-actions.js' import { createWallet } from '../../../../crypto/api/createWallet'
import {createWallet} from '../../../../crypto/api/createWallet.js' import { createAccountSectionStyles } from '../../styles/core-css'
import snackbar from '../../functional-components/snackbar.js' import ripple from '../../functional-components/loading-ripple'
import '../../custom-elements/frag-file-input.js' import snackbar from '../../functional-components/snackbar'
import ripple from '../../functional-components/loading-ripple.js' import '../../functional-components/frag-file-input'
import '@material/mwc-button' import '@material/mwc-button'
import '@material/mwc-checkbox' import '@material/mwc-checkbox'
import '@material/mwc-dialog' import '@material/mwc-dialog'
@ -26,6 +25,9 @@ import '@polymer/paper-spinner/paper-spinner-lite.js'
import '@vaadin/text-field/vaadin-text-field.js' import '@vaadin/text-field/vaadin-text-field.js'
import '@vaadin/password-field/vaadin-password-field.js' import '@vaadin/password-field/vaadin-password-field.js'
// Multi language support
import { translate } from '../../../translate'
class LoginSection extends connect(store)(LitElement) { class LoginSection extends connect(store)(LitElement) {
static get properties() { static get properties() {
return { return {
@ -51,36 +53,7 @@ class LoginSection extends connect(store)(LitElement) {
} }
static get styles() { static get styles() {
return [ return [createAccountSectionStyles]
css`
* {
--mdc-theme-primary: var(--login-button);
--mdc-theme-secondary: var(--mdc-theme-primary);
--paper-input-container-focus-color: var(--mdc-theme-primary);
--mdc-theme-surface: var(--white);
--mdc-dialog-content-ink-color: var(--black);
--mdc-checkbox-unchecked-color: var(--black);
--lumo-primary-text-color: var(--login-border);
--lumo-primary-color-50pct: var(--login-border-50pct);
--lumo-primary-color-10pct: var(--login-border-10pct);
--lumo-primary-color: hsl(199, 100%, 48%);
--lumo-base-color: var(--white);
--lumo-body-text-color: var(--black);
--lumo-secondary-text-color: var(--sectxt);
--lumo-contrast-60pct: var(--vdicon);
--_lumo-grid-border-color: var(--border);
--_lumo-grid-secondary-border-color: var(--border2);
}
mwc-formfield {
color: var(--black);
}
.red {
--mdc-theme-primary: red;
}
`
]
} }
constructor() { constructor() {
@ -136,8 +109,7 @@ class LoginSection extends connect(store)(LitElement) {
overflow: visible; overflow: visible;
} }
#walletsPage { #walletsPage {}
}
#wallets { #wallets {
max-height: 50vh; max-height: 50vh;
@ -240,6 +212,7 @@ class LoginSection extends connect(store)(LitElement) {
} }
@media only screen and (max-width: ${getComputedStyle(document.body).getPropertyValue('--layout-breakpoint-tablet')}) { @media only screen and (max-width: ${getComputedStyle(document.body).getPropertyValue('--layout-breakpoint-tablet')}) {
/* Mobile */ /* Mobile */
#wallets { #wallets {
height: 100%; height: 100%;
@ -278,16 +251,11 @@ class LoginSection extends connect(store)(LitElement) {
${this.loginOptions.map(({ page, linkText, icon }) => html` ${this.loginOptions.map(({ page, linkText, icon }) => html`
<div class="login-option" @click=${() => { this.selectedPage = page }}> <div class="login-option" @click=${() => { this.selectedPage = page }}>
<paper-ripple></paper-ripple> <paper-ripple></paper-ripple>
<div> <div><mwc-icon class='loginIcon'>${icon}</mwc-icon></div>
<mwc-icon class='loginIcon'>${icon}</mwc-icon> <div><span style="color: var(--black)">${linkText}</span></div>
</div>
<div>
<span style="color: var(--black)">${linkText}</span>
</div>
</div> </div>
`)} `)}
</div> </div>
<div page="storedWallet" id="walletsPage"> <div page="storedWallet" id="walletsPage">
<div style="text-align: center; padding-left:0;"> <div style="text-align: center; padding-left:0;">
<h1 style="padding:0; color: var(--black);">${translate("login.youraccounts")}</h1> <h1 style="padding:0; color: var(--black);">${translate("login.youraccounts")}</h1>
@ -314,17 +282,10 @@ class LoginSection extends connect(store)(LitElement) {
</div> </div>
<br> <br>
<p>${translate("login.areyousure")}</p> <p>${translate("login.areyousure")}</p>
<mwc-button <mwc-button slot="primaryAction" @click="${(e) => this.removeWallet(this.myToDeleteWallet)}">
slot="primaryAction"
@click="${(e) => this.removeWallet(this.myToDeleteWallet)}"
>
${translate("general.yes")} ${translate("general.yes")}
</mwc-button> </mwc-button>
<mwc-button <mwc-button slot="secondaryAction" dialogAction="cancel" class="red">
slot="secondaryAction"
dialogAction="cancel"
class="red"
>
${translate("general.no")} ${translate("general.no")}
</mwc-button> </mwc-button>
</mwc-dialog> </mwc-dialog>
@ -332,7 +293,6 @@ class LoginSection extends connect(store)(LitElement) {
`)} `)}
</div> </div>
</div> </div>
<div page="phrase" id="phrasePage"> <div page="phrase" id="phrasePage">
<div style="padding:0;"> <div style="padding:0;">
<div style="display:flex;"> <div style="display:flex;">
@ -341,7 +301,6 @@ class LoginSection extends connect(store)(LitElement) {
</div> </div>
</div> </div>
</div> </div>
<div page="seed" id="seedPage"> <div page="seed" id="seedPage">
<div> <div>
<div style="display: flex;"> <div style="display: flex;">
@ -350,7 +309,6 @@ class LoginSection extends connect(store)(LitElement) {
</div> </div>
</div> </div>
</div> </div>
<div page="unlockStored" id="unlockStoredPage"> <div page="unlockStored" id="unlockStoredPage">
<div style="text-align:center;"> <div style="text-align:center;">
<mwc-icon id='accountIcon' style="padding-bottom: 24px; color: var(--black);">account_circle</mwc-icon> <mwc-icon id='accountIcon' style="padding-bottom: 24px; color: var(--black);">account_circle</mwc-icon>
@ -358,7 +316,6 @@ class LoginSection extends connect(store)(LitElement) {
<span style="font-size:14px; font-weight: 100; font-family: 'Roboto Mono', monospace; color: var(--black);">${this.selectedWallet.address0}</span> <span style="font-size:14px; font-weight: 100; font-family: 'Roboto Mono', monospace; color: var(--black);">${this.selectedWallet.address0}</span>
</div> </div>
</div> </div>
<div page="backedUpSeed"> <div page="backedUpSeed">
${!this.backedUpSeedLoading ? html` ${!this.backedUpSeedLoading ? html`
<h3 style="color: var(--black);">${translate("login.upload")}</h3> <h3 style="color: var(--black);">${translate("login.upload")}</h3>
@ -367,26 +324,22 @@ class LoginSection extends connect(store)(LitElement) {
<paper-spinner-lite active style="display: block; margin: 0 auto;"></paper-spinner-lite> <paper-spinner-lite active style="display: block; margin: 0 auto;"></paper-spinner-lite>
`} `}
</div> </div>
<div page="unlockBackedUpSeed"> <div page="unlockBackedUpSeed">
<h3 style="text-align: center; color: var(--black);">${translate("login.decrypt")}</h3> <h3 style="text-align: center; color: var(--black);">${translate("login.decrypt")}</h3>
</div> </div>
</iron-pages> </iron-pages>
<iron-collapse style="" ?opened=${this.showName(this.selectedPage)} id="passwordCollapse"> <iron-collapse style="" ?opened=${this.showName(this.selectedPage)} id="passwordCollapse">
<div style="display:flex;"> <div style="display:flex;">
<mwc-icon style="padding: 10px; padding-left: 0; padding-top: 42px; color: var(--black);">perm_identity</mwc-icon> <mwc-icon style="padding: 10px; padding-left: 0; padding-top: 42px; color: var(--black);">perm_identity</mwc-icon>
<vaadin-text-field style="width:100%;" label="${translate("login.name")}" id="nameInput"></vaadin-text-field> <vaadin-text-field style="width:100%;" label="${translate("login.name")}" id="nameInput"></vaadin-text-field>
</div> </div>
</iron-collapse> </iron-collapse>
<iron-collapse style="" ?opened=${this.showPassword(this.selectedPage)} id="passwordCollapse"> <iron-collapse style="" ?opened=${this.showPassword(this.selectedPage)} id="passwordCollapse">
<div style="display:flex;"> <div style="display:flex;">
<mwc-icon style="padding: 10px; padding-left: 0; padding-top: 42px; color: var(--black);">password</mwc-icon> <mwc-icon style="padding: 10px; padding-left: 0; padding-top: 42px; color: var(--black);">password</mwc-icon>
<vaadin-password-field style="width:100%;" label="${translate("login.password")}" id="password" @keyup=${e => this.keyupEnter(e, e => this.emitNext(e))} autofocus></vaadin-password-field> <vaadin-password-field style="width:100%;" label="${translate("login.password")}" id="password" @keyup=${e => this.keyupEnter(e, e => this.emitNext(e))} autofocus></vaadin-password-field>
</div> </div>
</iron-collapse> </iron-collapse>
<div style="text-align: right; color: var(--mdc-theme-error)"> <div style="text-align: right; color: var(--mdc-theme-error)">
${this.loginErrorMessage} ${this.loginErrorMessage}
</div> </div>
@ -400,13 +353,14 @@ class LoginSection extends connect(store)(LitElement) {
` : ''} ` : ''}
</div> </div>
</div> </div>
</div>
` `
} }
firstUpdated() { firstUpdated() {
this.loadingRipple = ripple this.loadingRipple = ripple
const pages = this.shadowRoot.querySelector('#loginPages') const pages = this.shadowRoot.querySelector('#loginPages')
pages.addEventListener('selected-item-changed', () => { pages.addEventListener('selected-item-changed', () => {
if (!pages.selectedItem) { if (!pages.selectedItem) {
// ... // ...
@ -475,10 +429,13 @@ class LoginSection extends connect(store)(LitElement) {
removeWallet(walletAddress) { removeWallet(walletAddress) {
delete store.getState().user.storedWallets[walletAddress] delete store.getState().user.storedWallets[walletAddress]
this.wallets = store.getState().user.storedWallets this.wallets = store.getState().user.storedWallets
store.dispatch( store.dispatch(
doRemoveWallet(walletAddress) doRemoveWallet(walletAddress)
) )
this.cleanup() this.cleanup()
} }
@ -503,14 +460,13 @@ class LoginSection extends connect(store)(LitElement) {
} }
emitNext(e) { emitNext(e) {
this.dispatchEvent(new CustomEvent('next', { this.dispatchEvent(new CustomEvent('next', { detail: {} }))
detail: {}
}))
} }
loadBackup(file) { loadBackup(file) {
let error = '' let error = ''
let pf let pf
this.selectedPage = 'unlockBackedUpSeed' this.selectedPage = 'unlockBackedUpSeed'
try { try {
@ -521,6 +477,7 @@ class LoginSection extends connect(store)(LitElement) {
try { try {
const requiredFields = ['address0', 'salt', 'iv', 'version', 'encryptedSeed', 'mac', 'kdfThreads'] const requiredFields = ['address0', 'salt', 'iv', 'version', 'encryptedSeed', 'mac', 'kdfThreads']
for (const field of requiredFields) { for (const field of requiredFields) {
if (!(field in pf)) throw new Error(field + ' not found in JSON') if (!(field in pf)) throw new Error(field + ' not found in JSON')
} }
@ -532,38 +489,23 @@ class LoginSection extends connect(store)(LitElement) {
snackbar.add({ snackbar.add({
labelText: error labelText: error
}) })
this.selectedPage = 'backedUpSeed' this.selectedPage = 'backedUpSeed'
return return
} }
this.backedUpWalletJSON = pf this.backedUpWalletJSON = pf
} }
showName(selectedPage) { showName(selectedPage) {
return ( return (this.saveInBrowser && ['unlockBackedUpSeed', 'seed', 'phrase'].includes(selectedPage)) || ([''].includes(selectedPage))
this.saveInBrowser && [
'unlockBackedUpSeed',
'seed',
'phrase'
].includes(selectedPage)
) ||
(
[
''
].includes(selectedPage)
)
} }
showPassword(selectedPage) { showPassword(selectedPage) {
let willBeShown = ( let willBeShown = (this.saveInBrowser && ['unlockBackedUpSeed', 'seed', 'phrase'].includes(selectedPage)) || (['unlockBackedUpSeed', 'unlockStored'].includes(selectedPage))
this.saveInBrowser && [
'unlockBackedUpSeed',
'seed',
'phrase'
].includes(selectedPage)
) || (['unlockBackedUpSeed', 'unlockStored'].includes(selectedPage))
if (willBeShown) if (willBeShown) this.shadowRoot.getElementById('password').focus()
this.shadowRoot.getElementById('password').focus()
return willBeShown return willBeShown
} }
@ -574,6 +516,7 @@ class LoginSection extends connect(store)(LitElement) {
const seed = this.shadowRoot.querySelector('#v1SeedInput').value const seed = this.shadowRoot.querySelector('#v1SeedInput').value
const name = this.shadowRoot.getElementById('nameInput').value const name = this.shadowRoot.getElementById('nameInput').value
const password = this.shadowRoot.getElementById('password').value const password = this.shadowRoot.getElementById('password').value
return { return {
seed, seed,
password, password,
@ -583,6 +526,7 @@ class LoginSection extends connect(store)(LitElement) {
storedWallet: () => { storedWallet: () => {
const wallet = this.selectedWallet const wallet = this.selectedWallet
const password = this.shadowRoot.getElementById('password').value const password = this.shadowRoot.getElementById('password').value
return { return {
wallet, wallet,
password password
@ -590,12 +534,15 @@ class LoginSection extends connect(store)(LitElement) {
}, },
phrase: () => { phrase: () => {
const seedPhrase = this.shadowRoot.querySelector('#existingSeedPhraseInput').value const seedPhrase = this.shadowRoot.querySelector('#existingSeedPhraseInput').value
if (seedPhrase == "") {
if (seedPhrase == '') {
throw new Error('Please enter a seedphrase') throw new Error('Please enter a seedphrase')
return return
} }
const name = this.shadowRoot.getElementById('nameInput').value const name = this.shadowRoot.getElementById('nameInput').value
const password = this.shadowRoot.getElementById('password').value const password = this.shadowRoot.getElementById('password').value
return { return {
seedPhrase, seedPhrase,
name, name,
@ -606,6 +553,7 @@ class LoginSection extends connect(store)(LitElement) {
const wallet = this.backedUpWalletJSON const wallet = this.backedUpWalletJSON
const name = this.shadowRoot.getElementById('nameInput').value const name = this.shadowRoot.getElementById('nameInput').value
const password = this.shadowRoot.getElementById('password').value const password = this.shadowRoot.getElementById('password').value
return { return {
password, password,
wallet, wallet,
@ -621,6 +569,7 @@ class LoginSection extends connect(store)(LitElement) {
login(e) { login(e) {
let type = this.selectedPage === 'unlockStored' ? 'storedWallet' : this.selectedPage let type = this.selectedPage === 'unlockStored' ? 'storedWallet' : this.selectedPage
type = type === 'unlockBackedUpSeed' ? 'backedUpSeed' : type type = type === 'unlockBackedUpSeed' ? 'backedUpSeed' : type
if (!this.loginOptionIsSelected(type)) { if (!this.loginOptionIsSelected(type)) {

View File

@ -1,20 +1,7 @@
import {css, html, LitElement} from 'lit' import { html, LitElement } from 'lit'
import { connect } from 'pwa-helpers' import { connect } from 'pwa-helpers'
import {store} from '../../store.js' import { store } from '../../store'
import {stateAwait} from '../../stateAwait.js' import { stateAwait } from '../../stateAwait'
import {get} from '../../../translate'
import '@material/mwc-button'
import '@material/mwc-icon'
import '@material/mwc-fab'
import '@polymer/iron-pages'
import '@polymer/paper-icon-button/paper-icon-button.js'
import './welcome-page.js'
import './create-account-section.js'
import './login-section.js'
import '../qort-theme-toggle.js'
import settings from '../../functional-components/settings-page.js'
import { import {
addAutoLoadImageChat, addAutoLoadImageChat,
addChatLastSeen, addChatLastSeen,
@ -32,9 +19,23 @@ import {
setNewTab, setNewTab,
setSideEffectAction, setSideEffectAction,
setTabNotifications setTabNotifications
} from '../../redux/app/app-actions.js' } from '../../redux/app/app-actions'
import settings from '../../functional-components/settings-page'
import './welcome-page'
import './create-account-section'
import './login-section'
import '../qort-theme-toggle'
import '@material/mwc-button'
import '@material/mwc-icon'
import '@material/mwc-fab'
import '@polymer/iron-pages'
import '@polymer/paper-icon-button/paper-icon-button.js'
// Multi language support
import { get } from '../../../translate'
window.reduxStore = store window.reduxStore = store
window.reduxAction = { window.reduxAction = {
addAutoLoadImageChat: addAutoLoadImageChat, addAutoLoadImageChat: addAutoLoadImageChat,
removeAutoLoadImageChat: removeAutoLoadImageChat, removeAutoLoadImageChat: removeAutoLoadImageChat,
@ -71,16 +72,6 @@ class LoginView extends connect(store)(LitElement) {
} }
} }
static get styles() {
return [
css``
]
}
getPreSelectedPage() {
return 'welcome'
}
constructor() { constructor() {
super() super()
this.selectedPage = this.getPreSelectedPage() this.selectedPage = this.getPreSelectedPage()
@ -95,50 +86,10 @@ class LoginView extends connect(store)(LitElement) {
this.theme = localStorage.getItem('qortalTheme') ? localStorage.getItem('qortalTheme') : 'light' this.theme = localStorage.getItem('qortalTheme') ? localStorage.getItem('qortalTheme') : 'light'
} }
firstUpdated() {
stateAwait(state => {
return 'primary' in state.config.styles.theme.colors
}).catch(e => console.error(e))
const loginContainerPages = this.shadowRoot.querySelector('#loginContainerPages')
const loginCard = this.shadowRoot.querySelector('#login-card')
const navigate = e => {
this.selectPage(e.detail.page)
}
const updatedProperty = e => {
// ...
const selectedPageElement = this.selectedPageElement
this.selectedPageElement = {}
setTimeout(() => { this.selectedPageElement = selectedPageElement }, 1) // Yuck
}
loginContainerPages.addEventListener('selected-item-changed', () => {
if (!loginContainerPages.selectedItem) {
if (this.selectedPageElement.removeEventListener) {
this.selectedPageElement.removeEventListener('navigate', navigate)
this.selectedPageElement.removeEventListener('updatedProperty', updatedProperty)
}
this.selectedPageElement = {}
loginCard.classList.remove('animated')
loginCard.className += ' animated'
} else {
setTimeout(() => {
this.selectedPageElement = loginContainerPages.selectedItem
this.selectedPageElement.addEventListener('navigate', navigate)
this.selectedPageElement.addEventListener('updatedProperty', updatedProperty)
setTimeout(() => loginCard.classList.remove('animated'), animationDuration * 1000)
}, 1)
}
})
}
render() { render() {
return html` return html`
<style> <style>
canvas { canvas {
display: block; display: block;
vertical-align: bottom; vertical-align: bottom;
@ -248,33 +199,39 @@ class LoginView extends connect(store)(LitElement) {
} }
@media only screen and (min-width: ${getComputedStyle(document.body).getPropertyValue('--layout-breakpoint-tablet')}) { @media only screen and (min-width: ${getComputedStyle(document.body).getPropertyValue('--layout-breakpoint-tablet')}) {
/* Desktop/tablet */ /* Desktop/tablet */
.login-card { .login-card {
max-width: 460px; max-width: 460px;
} }
#loginContainerPages [page] { #loginContainerPages [page] {
border-radius: 4px; border-radius: 4px;
} }
#loginContainerPages [page="welcome"] {
} #loginContainerPages [page="welcome"] {}
} }
@media only screen and (max-width: ${getComputedStyle(document.body).getPropertyValue('--layout-breakpoint-tablet')}) { @media only screen and (max-width: ${getComputedStyle(document.body).getPropertyValue('--layout-breakpoint-tablet')}) {
/* Mobile */ /* Mobile */
.qortal-logo { .qortal-logo {
display: none; display: none;
visibility: hidden; visibility: hidden;
} }
.login-card { .login-card {
width: 100%; width: 100%;
margin: 0; margin: 0;
top: 0; top: 0;
max-width: 100%; max-width: 100%;
} }
.backButton { .backButton {
text-align: left; text-align: left;
padding-left: 12px; padding-left: 12px;
} }
.login-card h5 { .login-card h5 {
margin-top: 0px; margin-top: 0px;
margin-left: 0px; margin-left: 0px;
@ -288,6 +245,7 @@ class LoginView extends connect(store)(LitElement) {
opacity: 0; opacity: 0;
transform: translateX(-20%); transform: translateX(-20%);
} }
to { to {
opacity: 1; opacity: 1;
transform: translateX(0); transform: translateX(0);
@ -299,13 +257,15 @@ class LoginView extends connect(store)(LitElement) {
overflow: hidden; overflow: hidden;
max-height: 0; max-height: 0;
} }
to { to {
overflow: hidden; overflow: hidden;
max-height: var(--window-height); max-height: var(--window-height);
} }
} }
iron-pages .animated, .animated { iron-pages .animated,
.animated {
animation-duration: ${animationDuration}s; animation-duration: ${animationDuration}s;
animation-name: grow-up; animation-name: grow-up;
} }
@ -325,7 +285,9 @@ class LoginView extends connect(store)(LitElement) {
</style> </style>
<div class="login-page" ?hidden=${this.loggedIn}> <div class="login-page" ?hidden=${this.loggedIn}>
<mwc-fab icon="settings" style="position:fixed; right:24px; bottom:24px;" @click=${() => settings.show()}></mwc-fab> <mwc-fab icon="settings" style="position:fixed; right:24px; bottom:24px;" @click=${() => settings.show()}></mwc-fab>
<span style="position:fixed; left:24px; bottom:24px;"><qort-theme-toggle></qort-theme-toggle></span> <span style="position:fixed; left:24px; bottom:24px;">
<qort-theme-toggle></qort-theme-toggle>
</span>
<div class="login-card-container"> <div class="login-card-container">
<div class="login-card-center-container"> <div class="login-card-center-container">
<div class="login-card" id="login-card"> <div class="login-card" id="login-card">
@ -338,11 +300,23 @@ class LoginView extends connect(store)(LitElement) {
<login-section @next=${e => this.selectedPageElement.next(e)} page="login"></login-section> <login-section @next=${e => this.selectedPageElement.next(e)} page="login"></login-section>
</iron-pages> </iron-pages>
<div id="login-pages-nav" ?hidden="${this.selectedPageElement.hideNav}"> <div id="login-pages-nav" ?hidden="${this.selectedPageElement.hideNav}">
<mwc-button @click=${e => this.selectedPageElement.back(e)} id="nav-back" ?hidden="${this.selectedPageElement.backHidden}" ?disabled="${this.selectedPageElement.backDisabled}"> <mwc-button
<mwc-icon>keyboard_arrow_left</mwc-icon>${this.selectedPageElement.backText} @click=${e => this.selectedPageElement.back(e)}
id="nav-back"
?hidden="${this.selectedPageElement.backHidden}"
?disabled="${this.selectedPageElement.backDisabled}"
>
<mwc-icon>keyboard_arrow_left</mwc-icon>
${this.selectedPageElement.backText}
</mwc-button> </mwc-button>
<mwc-button @click=${e => this.selectedPageElement.next(e)} id="nav-next" ?hidden="${this.selectedPageElement.nextHidden}" ?disabled="${this.selectedPageElement.nextDisabled}"> <mwc-button
${this.selectedPageElement.nextText}<mwc-icon>keyboard_arrow_right</mwc-icon> @click=${e => this.selectedPageElement.next(e)}
id="nav-next"
?hidden="${this.selectedPageElement.nextHidden}"
?disabled="${this.selectedPageElement.nextDisabled}"
>
${this.selectedPageElement.nextText}
<mwc-icon>keyboard_arrow_right</mwc-icon>
</mwc-button> </mwc-button>
</div> </div>
</div> </div>
@ -352,6 +326,54 @@ class LoginView extends connect(store)(LitElement) {
` `
} }
firstUpdated() {
stateAwait(state => {
return 'primary' in state.config.styles.theme.colors
}).catch(e => console.error(e))
const loginContainerPages = this.shadowRoot.querySelector('#loginContainerPages')
const loginCard = this.shadowRoot.querySelector('#login-card')
const navigate = e => {
this.selectPage(e.detail.page)
}
const updatedProperty = e => {
const selectedPageElement = this.selectedPageElement
this.selectedPageElement = {}
setTimeout(() => { this.selectedPageElement = selectedPageElement }, 1)
}
loginContainerPages.addEventListener('selected-item-changed', () => {
if (!loginContainerPages.selectedItem) {
if (this.selectedPageElement.removeEventListener) {
this.selectedPageElement.removeEventListener('navigate', navigate)
this.selectedPageElement.removeEventListener('updatedProperty', updatedProperty)
}
this.selectedPageElement = {}
loginCard.classList.remove('animated')
loginCard.className += ' animated'
} else {
setTimeout(() => {
this.selectedPageElement = loginContainerPages.selectedItem
this.selectedPageElement.addEventListener('navigate', navigate)
this.selectedPageElement.addEventListener('updatedProperty', updatedProperty)
setTimeout(() => loginCard.classList.remove('animated'), animationDuration * 1000)
}, 1)
}
})
}
getPreSelectedPage() {
return 'welcome'
}
renderSelectedNodeOnStart() { renderSelectedNodeOnStart() {
const selectedNodeIndexOnStart = localStorage.getItem('mySelectedNode') const selectedNodeIndexOnStart = localStorage.getItem('mySelectedNode')
const catchSavedNodes = JSON.parse(localStorage.getItem('myQortalNodes')) const catchSavedNodes = JSON.parse(localStorage.getItem('myQortalNodes'))
@ -370,6 +392,7 @@ class LoginView extends connect(store)(LitElement) {
stateChanged(state) { stateChanged(state) {
if (this.loggedIn && !state.app.loggedIn) this.cleanup() if (this.loggedIn && !state.app.loggedIn) this.cleanup()
this.loggedIn = state.app.loggedIn this.loggedIn = state.app.loggedIn
this.config = state.config this.config = state.config
this.nodeConfig = state.app.nodeConfig this.nodeConfig = state.app.nodeConfig

View File

@ -1,56 +1,28 @@
import {css, html, LitElement} from 'lit' import { html, LitElement } from 'lit'
import {translate} from '../../../translate' import { welcomePageStyles } from '../../styles/core-css'
import '@material/mwc-button' import '@material/mwc-button'
// Multi language support
import { translate } from '../../../translate'
class WelcomePage extends LitElement { class WelcomePage extends LitElement {
static get properties() { static get properties() {
return { return {
nextHidden: { type: Boolean, notify: true },
nextEnabled: { type: Boolean, notify: true },
nextText: { type: String, notify: true },
backHidden: { type: Boolean, notify: true },
backDisabled: { type: Boolean, notify: true },
backText: { type: String, notify: true },
hideNav: { type: Boolean, notify: true }, hideNav: { type: Boolean, notify: true },
welcomeMessage: { type: String },
theme: { type: String, reflect: true } theme: { type: String, reflect: true }
} }
} }
static get styles() { static get styles() {
return css` return [welcomePageStyles]
* {
--mdc-theme-primary: var(--login-button);
--mdc-theme-secondary: var(--mdc-theme-primary);
--mdc-button-outline-color: var(--general-color-blue);
}
.button-outline {
margin: 6px;
width: 90%;
max-width:90vw;
border-top: 0;
border-bottom: 0;
}
.welcome-page {
padding: 12px 0;
overflow: hidden;
}
`
} }
constructor() { constructor() {
super() super()
this.hideNav = true this.hideNav = true
this.nextText = ''
this.welcomeMessage = 'Welcome to Qortal'
this.theme = localStorage.getItem('qortalTheme') ? localStorage.getItem('qortalTheme') : 'light' this.theme = localStorage.getItem('qortalTheme') ? localStorage.getItem('qortalTheme') : 'light'
} }
firstUpdated() {}
render() { render() {
return html` return html`
<div class="welcome-page"> <div class="welcome-page">
@ -60,10 +32,6 @@ class WelcomePage extends LitElement {
` `
} }
back() {}
next() {}
navigate(page) { navigate(page) {
this.dispatchEvent(new CustomEvent('navigate', { this.dispatchEvent(new CustomEvent('navigate', {
detail: { page }, detail: { page },

View File

@ -1,11 +1,13 @@
import {css, html, LitElement} from 'lit' import { html, LitElement } from 'lit'
import { connect } from 'pwa-helpers' import { connect } from 'pwa-helpers'
import { store } from '../../store.js' import { store } from '../../store.js'
import { doLogout } from '../../redux/app/app-actions.js' import { doLogout } from '../../redux/app/app-actions.js'
import {translate} from '../../../translate' import { logoutViewStyles } from '../../styles/core-css'
import '@polymer/paper-dialog/paper-dialog.js'
import '@material/mwc-button' import '@material/mwc-button'
import '@polymer/paper-dialog/paper-dialog.js'
// Multi language support
import { translate } from '../../../translate'
class LogoutView extends connect(store)(LitElement) { class LogoutView extends connect(store)(LitElement) {
static get properties() { static get properties() {
@ -15,22 +17,7 @@ class LogoutView extends connect(store)(LitElement) {
} }
static get styles() { static get styles() {
return css` return [logoutViewStyles]
* {
--mdc-theme-primary: rgb(3, 169, 244);
--mdc-theme-secondary: var(--mdc-theme-primary);
--mdc-theme-surface: var(--white);
--mdc-dialog-content-ink-color: var(--black);
}
.decline {
--mdc-theme-primary: var(--mdc-theme-error)
}
.buttons {
text-align:right;
}
`
} }
constructor() { constructor() {
@ -49,7 +36,7 @@ class LogoutView extends connect(store)(LitElement) {
<h2 style="color: var(--black);">${translate("logout.confirmlogout")}</h2> <h2 style="color: var(--black);">${translate("logout.confirmlogout")}</h2>
</div> </div>
<div class="buttons"> <div class="buttons">
<mwc-button class='decline' @click=${e => this.decline(e)} dialog-dismiss>${translate("general.no")}</mwc-button> <mwc-button class='decline' @click=${() => this.decline()} dialog-dismiss>${translate("general.no")}</mwc-button>
<mwc-button class='confirm' @click=${e => this.confirm(e)} dialog-confirm autofocus>${translate("general.yes")}</mwc-button> <mwc-button class='confirm' @click=${e => this.confirm(e)} dialog-confirm autofocus>${translate("general.yes")}</mwc-button>
</div> </div>
</paper-dialog> </paper-dialog>
@ -62,10 +49,12 @@ class LogoutView extends connect(store)(LitElement) {
async confirm(e) { async confirm(e) {
store.dispatch(doLogout()) store.dispatch(doLogout())
e.stopPropagation()
} }
decline(e) { decline() {
this.shadowRoot.getElementById('userLogoutDialog').close() this.shadowRoot.getElementById('userLogoutDialog').close()
this.requestUpdate()
} }
} }

View File

@ -1,16 +1,14 @@
import { html, LitElement } from 'lit' import { html, LitElement } from 'lit'
import {installRouter} from 'pwa-helpers/router.js'
import { connect } from 'pwa-helpers' import { connect } from 'pwa-helpers'
import {store} from '../store.js' import { store } from '../store'
import {doNavigate} from '../redux/app/app-actions.js' import { installRouter } from 'pwa-helpers/router'
import { doNavigate } from '../redux/app/app-actions'
import { loadPlugins } from '../plugins/load-plugins'
import isElectron from 'is-electron' import isElectron from 'is-electron'
import '../plugins/streams.js' import './login-view/login-view'
import './app-view'
import {loadPlugins} from '../plugins/load-plugins.js' import '../plugins/streams'
import '../styles/app-styles'
import '../styles/app-styles.js'
import './login-view/login-view.js'
import './app-view.js'
installRouter((location) => store.dispatch(doNavigate(location))) installRouter((location) => store.dispatch(doNavigate(location)))
@ -22,44 +20,10 @@ class MainApp extends connect(store)(LitElement) {
} }
} }
static get styles() {
return []
}
render() { render() {
return html`${this.renderViews(this.loggedIn)}` return html`${this.renderViews(this.loggedIn)}`
} }
/**
* Dynamic renderViews method to introduce conditional rendering of views based on user's logged in state.
* @param {Boolean} isLoggedIn
*/
renderViews(isLoggedIn) {
if (isLoggedIn) {
return html`
<app-view></app-view>
`
} else {
return html`
<login-view></login-view>
`
}
}
stateChanged(state) {
this.loggedIn = state.app.loggedIn
if (this.loggedIn === true && this.initial === 0) {
this.initial = this.initial + 1
this._loadPlugins()
}
document.title = state.config.coin.name
}
_loadPlugins() {
loadPlugins()
}
connectedCallback() { connectedCallback() {
super.connectedCallback() super.connectedCallback()
this.initial = 0 this.initial = 0
@ -72,6 +36,31 @@ class MainApp extends connect(store)(LitElement) {
}) })
} }
} }
/**
* Dynamic renderViews method to introduce conditional rendering of views based on user's logged in state.
* @param {Boolean} isLoggedIn
*/
renderViews(isLoggedIn) {
if (isLoggedIn) {
return html`<app-view></app-view>`
} else {
return html`<login-view></login-view>`
}
}
_loadPlugins() {
loadPlugins()
}
stateChanged(state) {
this.loggedIn = state.app.loggedIn
if (this.loggedIn === true && this.initial === 0) {
this.initial = this.initial + 1
this._loadPlugins()
}
document.title = state.config.coin.name
}
} }
window.customElements.define('main-app', MainApp) window.customElements.define('main-app', MainApp)

View File

@ -1,4 +1,7 @@
import {css, html, LitElement} from 'lit' import { html, LitElement } from 'lit'
import { newSelectorStyles } from '../styles/core-css'
// Multi language support
import { registerTranslateConfig, translate, use } from '../../translate' import { registerTranslateConfig, translate, use } from '../../translate'
registerTranslateConfig({ registerTranslateConfig({
@ -22,40 +25,7 @@ class NewSelector extends LitElement {
} }
static get styles() { static get styles() {
return [ return [newSelectorStyles]
css`
select {
width: auto;
height: auto;
position: absolute;
top: 50px;
padding: 5px 5px 5px 5px;
font-size: 16px;
border: 1px solid var(--black);
border-radius: 3px;
color: var(--black);
background: var(--white);
overflow: auto;
}
*:focus {
outline: none;
}
select option {
color: var(--black);
background: var(--white);
line-height: 34px;
}
select option:hover {
color: var(--white);
background: var(--black);
line-height: 34px;
cursor: pointer;
}
`
]
} }
constructor() { constructor() {
@ -100,15 +70,16 @@ class NewSelector extends LitElement {
firstUpdated() { firstUpdated() {
const myElement = this.shadowRoot.getElementById('languageNew') const myElement = this.shadowRoot.getElementById('languageNew')
myElement.addEventListener("change", () => { myElement.addEventListener('change', () => {
this.selectElement() this.selectElement()
}) })
myElement.addEventListener("click", () => { myElement.addEventListener('click', () => {
const element1 = localStorage.getItem('qortalLanguage') const element1 = localStorage.getItem('qortalLanguage')
const element2 = this.shadowRoot.getElementById('languageNew').value const element2 = this.shadowRoot.getElementById('languageNew').value
if (element1 === element2) { if (element1 === element2) {
myElement.style.display = "none" myElement.style.display = 'none'
} }
}) })
@ -117,9 +88,11 @@ class NewSelector extends LitElement {
selectElement() { selectElement() {
const selectedLanguage = localStorage.getItem('qortalLanguage') const selectedLanguage = localStorage.getItem('qortalLanguage')
let element = this.shadowRoot.getElementById('languageNew') let element = this.shadowRoot.getElementById('languageNew')
element.value = selectedLanguage element.value = selectedLanguage
element.style.display = "none" element.style.display = 'none'
} }
changeLanguage(event) { changeLanguage(event) {
@ -129,10 +102,11 @@ class NewSelector extends LitElement {
toggleMenu() { toggleMenu() {
let mySwitchDisplay = this.shadowRoot.getElementById('languageNew') let mySwitchDisplay = this.shadowRoot.getElementById('languageNew')
if(mySwitchDisplay.style.display == "none") {
mySwitchDisplay.style.display = "block" if (mySwitchDisplay.style.display == 'none') {
mySwitchDisplay.style.display = 'block'
} else { } else {
mySwitchDisplay.style.display = "none" mySwitchDisplay.style.display = 'none'
} }
} }
} }

View File

@ -1,160 +1,68 @@
import {css, html, LitElement} from 'lit'; import { html, LitElement } from 'lit'
import {connect} from 'pwa-helpers'; import { repeat } from 'lit/directives/repeat.js'
import { connect } from 'pwa-helpers'
import { store } from '../../store'
import { setNewNotification } from '../../redux/app/app-actions'
import { notificationBellGeneralStyles, notificationItemTxStyles } from '../../styles/core-css'
import './popover.js'
import '../../../../plugins/plugins/core/components/TimeAgo'
import '@material/mwc-icon'
import '@polymer/paper-icon-button/paper-icon-button.js'
import '@polymer/iron-icons/iron-icons.js'
import '@vaadin/item'
import '@vaadin/list-box'
import '@vaadin/item'; // Multi language support
import '@vaadin/list-box';
import '@polymer/paper-icon-button/paper-icon-button.js';
import '@polymer/iron-icons/iron-icons.js';
import {store} from '../../store.js';
import {setNewNotification} from '../../redux/app/app-actions.js';
import '@material/mwc-icon';
import { get, translate } from '../../../translate' import { get, translate } from '../../../translate'
import {repeat} from 'lit/directives/repeat.js';
import '../../../../plugins/plugins/core/components/TimeAgo.js';
import './popover.js';
class NotificationBellGeneral extends connect(store)(LitElement) { class NotificationBellGeneral extends connect(store)(LitElement) {
static properties = { static get properties() {
return {
notifications: { type: Array }, notifications: { type: Array },
showNotifications: { type: Boolean }, showNotifications: { type: Boolean },
notificationCount: { type: Boolean }, notificationCount: { type: Boolean },
theme: { type: String, reflect: true },
currentNotification: { type: Object }, currentNotification: { type: Object },
}; theme: { type: String, reflect: true }
}
}
static get styles() {
return [notificationBellGeneralStyles]
}
constructor() { constructor() {
super(); super()
this.notifications = []; this.notifications = []
this.showNotifications = false; this.showNotifications = false
this.notificationCount = false; this.notificationCount = false
this.initialFetch = false; this.initialFetch = false
this.theme = localStorage.getItem('qortalTheme') this.currentNotification = null
? localStorage.getItem('qortalTheme') this.theme = localStorage.getItem('qortalTheme') ? localStorage.getItem('qortalTheme') : 'light'
: 'light';
this.currentNotification = null;
}
firstUpdated() {
try {
let value = JSON.parse(localStorage.getItem('isFirstTimeUser'));
if (!value && value !== false) {
value = true;
}
this.isFirstTimeUser = value;
} catch (error) {}
}
async stateChanged(state) {
if (state.app.newNotification) {
const newNotification = state.app.newNotification;
this.notifications = [newNotification, ...this.notifications];
store.dispatch(setNewNotification(null));
if (this.isFirstTimeUser) {
const target = this.shadowRoot.getElementById(
'popover-notification'
);
const popover =
this.shadowRoot.querySelector('popover-component');
if (popover) {
popover.openPopover(target);
}
localStorage.setItem('isFirstTimeUser', JSON.stringify(false));
this.isFirstTimeUser = false;
}
}
}
handleBlur() {
setTimeout(() => {
if (!this.shadowRoot.contains(document.activeElement)) {
this.showNotifications = false;
}
}, 0);
}
changeStatus(signature, statusTx) {
const copyNotifications = [...this.notifications];
const findNotification = this.notifications.findIndex(
(notification) => notification.reference.signature === signature
);
if (findNotification !== -1) {
copyNotifications[findNotification] = {
...copyNotifications[findNotification],
status: statusTx,
};
this.notifications = copyNotifications;
}
} }
render() { render() {
const hasOngoing = this.notifications.find( const hasOngoing = this.notifications.find(
(notification) => notification.status !== 'confirmed' (notification) => notification.status !== 'confirmed'
); )
return html` return html`
<div class="layout"> <div class="layout">
<popover-component <popover-component for="popover-notification" message=${get('notifications.explanation')}></popover-component>
for="popover-notification" <div id="popover-notification" @click=${() => this._toggleNotifications()}>
message=${get('notifications.explanation')} ${hasOngoing ? html`
></popover-component> <mwc-icon id="notification-general-icon" style="color: green;cursor:pointer;user-select:none">notifications</mwc-icon>
<div <vaadin-tooltip for="notification-general-icon" position="bottom" hover-delay=${400} hide-delay=${1} text=${get('notifications.notify4')}></vaadin-tooltip>
id="popover-notification" ` : html`
@click=${() => this._toggleNotifications()} <mwc-icon id="notification-general-icon" style="color: var(--black); cursor:pointer;user-select:none">notifications</mwc-icon>
> <vaadin-tooltip for="notification-general-icon" position="bottom" hover-delay=${400} hide-delay=${1}text=${get('notifications.notify4')}></vaadin-tooltip>
${hasOngoing
? html`
<mwc-icon id="notification-general-icon" style="color: green;cursor:pointer;user-select:none"
>notifications</mwc-icon
>
<vaadin-tooltip
for="notification-general-icon"
position="bottom"
hover-delay=${400}
hide-delay=${1}
text=${get('notifications.notify4')}>
</vaadin-tooltip>
`
: html`
<mwc-icon
id="notification-general-icon"
style="color: var(--black); cursor:pointer;user-select:none"
>notifications</mwc-icon
>
<vaadin-tooltip
for="notification-general-icon"
position="bottom"
hover-delay=${400}
hide-delay=${1}
text=${get('notifications.notify4')}>
</vaadin-tooltip>
`} `}
</div> </div>
${hasOngoing ${hasOngoing ? html`
? html` <span class="count" style="cursor:pointer" @click=${() => this._toggleNotifications()}>
<span <mwc-icon style="color: var(--black);font-size:18px">pending</mwc-icon>
class="count"
style="cursor:pointer"
@click=${() => this._toggleNotifications()}
>
<mwc-icon
style="color: var(--black);font-size:18px"
>pending</mwc-icon
>
</span> </span>
` ` : ''}
: ''} <div id="notification-panel" class="popover-panel" style="visibility:${this.showNotifications ? 'visibile' : 'hidden'}" tabindex="0" @blur=${this.handleBlur}>
<div
id="notification-panel"
class="popover-panel"
style="visibility:${this.showNotifications
? 'visibile'
: 'hidden'}"
tabindex="0"
@blur=${this.handleBlur}
>
<div class="notifications-list"> <div class="notifications-list">
${this.notifications.length === 0 ? html` ${this.notifications.length === 0 ? html`
<p style="font-size: 16px; width: 100%; text-align:center;margin-top:20px;">${translate('notifications.notify3')}</p> <p style="font-size: 16px; width: 100%; text-align:center;margin-top:20px;">${translate('notifications.notify3')}</p>
@ -170,14 +78,78 @@ class NotificationBellGeneral extends connect(store)(LitElement) {
timestamp=${notification.timestamp} timestamp=${notification.timestamp}
type=${notification.type} type=${notification.type}
signature=${notification.reference signature=${notification.reference
.signature} .signature
}
></notification-item-tx> ></notification-item-tx>
` `
)} )}
</div> </div>
</div> </div>
</div> </div>
`; `
}
firstUpdated() {
try {
let value = JSON.parse(localStorage.getItem('isFirstTimeUser'))
if (!value && value !== false) {
value = true
}
this.isFirstTimeUser = value
} catch (error) { }
}
async stateChanged(state) {
if (state.app.newNotification) {
const newNotification = state.app.newNotification
this.notifications = [newNotification, ...this.notifications]
store.dispatch(setNewNotification(null))
if (this.isFirstTimeUser) {
const target = this.shadowRoot.getElementById(
'popover-notification'
)
const popover = this.shadowRoot.querySelector('popover-component')
if (popover) {
popover.openPopover(target)
}
localStorage.setItem('isFirstTimeUser', JSON.stringify(false))
this.isFirstTimeUser = false
}
}
}
handleBlur() {
setTimeout(() => {
if (!this.shadowRoot.contains(document.activeElement)) {
this.showNotifications = false
}
}, 0)
}
changeStatus(signature, statusTx) {
const copyNotifications = [...this.notifications]
const findNotification = this.notifications.findIndex(
(notification) => notification.reference.signature === signature
)
if (findNotification !== -1) {
copyNotifications[findNotification] = {
...copyNotifications[findNotification],
status: statusTx
}
this.notifications = copyNotifications
}
} }
_toggleNotifications() { _toggleNotifications() {
@ -188,148 +160,29 @@ class NotificationBellGeneral extends connect(store)(LitElement) {
}); });
} }
} }
static styles = css`
.layout {
display: flex;
flex-direction: column;
align-items: center;
position: relative;
} }
.count { window.customElements.define('notification-bell-general', NotificationBellGeneral)
position: absolute;
top: -5px;
right: -5px;
font-size: 12px;
background-color: red;
color: white;
border-radius: 50%;
width: 16px;
height: 16px;
display: flex;
align-items: center;
justify-content: center;
}
.nocount {
display: none;
}
.popover-panel {
position: absolute;
width: 200px;
padding: 10px;
background-color: var(--white);
border: 1px solid var(--black);
border-radius: 4px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
top: 40px;
max-height: 350px;
overflow: auto;
scrollbar-width: thin;
scrollbar-color: #6a6c75 #a1a1a1;
}
.popover-panel::-webkit-scrollbar {
width: 11px;
}
.popover-panel::-webkit-scrollbar-track {
background: #a1a1a1;
}
.popover-panel::-webkit-scrollbar-thumb {
background-color: #6a6c75;
border-radius: 6px;
border: 3px solid #a1a1a1;
}
.notifications-list {
display: flex;
flex-direction: column;
}
.notification-item {
padding: 5px;
border-bottom: 1px solid;
display: flex;
justify-content: space-between;
cursor: pointer;
transition: 0.2s all;
}
.notification-item:hover {
background: var(--nav-color-hover);
}
p {
font-size: 14px;
color: var(--black);
margin: 0px;
padding: 0px;
}
`;
}
customElements.define('notification-bell-general', NotificationBellGeneral);
class NotificationItemTx extends connect(store)(LitElement) { class NotificationItemTx extends connect(store)(LitElement) {
static properties = { static get properties() {
return {
status: { type: String }, status: { type: String },
type: { type: String }, type: { type: String },
timestamp: { type: Number }, timestamp: { type: Number },
signature: { type: String }, signature: { type: String },
changeStatus: { attribute: false }, changeStatus: { attribute: false }
}; }
}
static get styles() {
return [notificationItemTxStyles]
}
constructor() { constructor() {
super(); super()
this.nodeUrl = this.getNodeUrl(); this.nodeUrl = this.getNodeUrl()
this.myNode = this.getMyNode(); this.myNode = this.getMyNode()
}
getNodeUrl() {
const myNode =
store.getState().app.nodeConfig.knownNodes[
window.parent.reduxStore.getState().app.nodeConfig.node
]
return myNode.protocol + '://' + myNode.domain + ':' + myNode.port
}
getMyNode() {
return store.getState().app.nodeConfig.knownNodes[
window.parent.reduxStore.getState().app.nodeConfig.node
]
}
async getStatus() {
let interval = null;
let stop = false;
const getAnswer = async () => {
const getTx = async (minterAddr) => {
const url = `${this.nodeUrl}/transactions/signature/${this.signature}`
const res = await fetch(url)
return await res.json()
}
if (!stop) {
stop = true;
try {
const txTransaction = await getTx();
if (!txTransaction.error && txTransaction.signature && txTransaction.blockHeight) {
clearInterval(interval);
this.changeStatus(this.signature, 'confirmed');
}
} catch (error) {}
stop = false;
}
};
interval = setInterval(getAnswer, 20000);
}
firstUpdated() {
this.getStatus();
} }
render() { render() {
@ -345,187 +198,66 @@ class NotificationItemTx extends connect(store)(LitElement) {
${translate('walletpage.wchange35')}: ${this.type} ${translate('walletpage.wchange35')}: ${this.type}
</p> </p>
<p style="margin-bottom:5px"> <p style="margin-bottom:5px">
${translate('tubespage.schange28')}: ${translate('tubespage.schange28')}: ${this.status === 'confirming' ? translate('notifications.notify1') : translate('notifications.notify2')}
${this.status === 'confirming'
? translate('notifications.notify1')
: translate('notifications.notify2')}
</p> </p>
${this.status !== 'confirmed' ${this.status !== 'confirmed' ? html`<div class="centered"><div class="loader">Loading...</div></div>` : ''}
? html` <div style="display:flex;justify-content:space-between;align-items:center">
<div class="centered"> <message-time timestamp=${this.timestamp} style="color:red;font-size:12px"></message-time>
<div class="loader">Loading...</div> ${this.status === 'confirmed' ? html`<mwc-icon style="color: green;">done</mwc-icon>` : ''}
</div>
</div>
</div> </div>
` `
: ''} }
<div
style="display:flex;justify-content:space-between;align-items:center" firstUpdated() {
> this.getStatus()
<message-time }
timestamp=${this.timestamp}
style="color:red;font-size:12px" getNodeUrl() {
></message-time> const myNode = store.getState().app.nodeConfig.knownNodes[store.getState().app.nodeConfig.node]
${this.status === 'confirmed' return myNode.protocol + '://' + myNode.domain + ':' + myNode.port
? html` }
<mwc-icon style="color: green;"
>done</mwc-icon getMyNode() {
> return store.getState().app.nodeConfig.knownNodes[store.getState().app.nodeConfig.node]
` }
: ''}
</div> async getStatus() {
</div> let interval = null
</div> let stop = false
`;
const getAnswer = async () => {
const getTx = async (minterAddr) => {
const url = `${this.nodeUrl}/transactions/signature/${this.signature}`
const res = await fetch(url)
return await res.json()
}
if (!stop) {
stop = true
try {
const txTransaction = await getTx()
if (!txTransaction.error && txTransaction.signature && txTransaction.blockHeight) {
clearInterval(interval)
this.changeStatus(this.signature, 'confirmed')
}
} catch (error) { }
stop = false
}
}
interval = setInterval(getAnswer, 20000)
} }
_toggleNotifications() { _toggleNotifications() {
if (this.notifications.length === 0) return; if (this.notifications.length === 0) return
this.showNotifications = !this.showNotifications; this.showNotifications = !this.showNotifications
}
} }
static styles = css` window.customElements.define('notification-item-tx', NotificationItemTx)
.centered {
display: flex;
justify-content: center;
align-items: center;
}
.layout {
width: 100px;
display: flex;
flex-direction: column;
align-items: center;
position: relative;
}
.count {
position: absolute;
top: -5px;
right: -5px;
font-size: 12px;
background-color: red;
color: white;
border-radius: 50%;
width: 16px;
height: 16px;
display: flex;
align-items: center;
justify-content: center;
}
.nocount {
display: none;
}
.popover-panel {
position: absolute;
width: 200px;
padding: 10px;
background-color: var(--white);
border: 1px solid var(--black);
border-radius: 4px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
top: 40px;
max-height: 350px;
overflow: auto;
scrollbar-width: thin;
scrollbar-color: #6a6c75 #a1a1a1;
}
.popover-panel::-webkit-scrollbar {
width: 11px;
}
.popover-panel::-webkit-scrollbar-track {
background: #a1a1a1;
}
.popover-panel::-webkit-scrollbar-thumb {
background-color: #6a6c75;
border-radius: 6px;
border: 3px solid #a1a1a1;
}
.notifications-list {
display: flex;
flex-direction: column;
}
.notification-item {
padding: 5px;
border-bottom: 1px solid;
display: flex;
flex-direction: column;
cursor: default;
}
.notification-item:hover {
background: var(--nav-color-hover);
}
p {
font-size: 14px;
color: var(--black);
margin: 0px;
padding: 0px;
}
.loader,
.loader:before,
.loader:after {
border-radius: 50%;
width: 10px;
height: 10px;
-webkit-animation-fill-mode: both;
animation-fill-mode: both;
-webkit-animation: load7 1.8s infinite ease-in-out;
animation: load7 1.8s infinite ease-in-out;
}
.loader {
color: var(--black);
font-size: 5px;
margin-bottom: 20px;
position: relative;
text-indent: -9999em;
-webkit-transform: translateZ(0);
-ms-transform: translateZ(0);
transform: translateZ(0);
-webkit-animation-delay: -0.16s;
animation-delay: -0.16s;
}
.loader:before,
.loader:after {
content: '';
position: absolute;
top: 0;
}
.loader:before {
left: -3.5em;
-webkit-animation-delay: -0.32s;
animation-delay: -0.32s;
}
.loader:after {
left: 3.5em;
}
@-webkit-keyframes load7 {
0%,
80%,
100% {
box-shadow: 0 2.5em 0 -1.3em;
}
40% {
box-shadow: 0 2.5em 0 0;
}
}
@keyframes load7 {
0%,
80%,
100% {
box-shadow: 0 2.5em 0 -1.3em;
}
40% {
box-shadow: 0 2.5em 0 0;
}
}
`;
}
customElements.define('notification-item-tx', NotificationItemTx);

View File

@ -1,25 +1,29 @@
import { css, html, LitElement } from 'lit' import { css, html, LitElement } from 'lit'
import { connect } from 'pwa-helpers' import { connect } from 'pwa-helpers'
import { store } from '../../store'
import { setNewTab } from '../../redux/app/app-actions'
import { routes } from '../../plugins/routes'
import { notificationBellStyles } from '../../styles/core-css'
import config from '../../notifications/config'
import '../../../../plugins/plugins/core/components/TimeAgo'
import '@material/mwc-icon'
import '@polymer/paper-icon-button/paper-icon-button'
import '@polymer/iron-icons/iron-icons.js'
import '@vaadin/item' import '@vaadin/item'
import '@vaadin/list-box' import '@vaadin/list-box'
import '@polymer/paper-icon-button/paper-icon-button.js'
import '@polymer/iron-icons/iron-icons.js'
import {store} from '../../store.js'
import {setNewTab} from '../../redux/app/app-actions.js'
import {routes} from '../../plugins/routes.js'
import '@material/mwc-icon';
import config from '../../notifications/config.js'
import '../../../../plugins/plugins/core/components/TimeAgo.js'
class NotificationBell extends connect(store)(LitElement) { class NotificationBell extends connect(store)(LitElement) {
static get properties() {
static properties = { return {
notifications: { type: Array }, notifications: { type: Array },
showNotifications: { type: Boolean }, showNotifications: { type: Boolean },
notificationCount: { type: Boolean }, notificationCount: { type: Boolean },
theme: { type: String, reflect: true }, theme: { type: String, reflect: true }
}
}
static get styles() {
return [notificationBellStyles]
} }
constructor() { constructor() {
@ -31,132 +35,13 @@ class NotificationBell extends connect(store)(LitElement) {
this.theme = localStorage.getItem('qortalTheme') ? localStorage.getItem('qortalTheme') : 'light' this.theme = localStorage.getItem('qortalTheme') ? localStorage.getItem('qortalTheme') : 'light'
} }
firstUpdated() {
this.getNotifications();
document.addEventListener('click', (event) => {
const path = event.composedPath()
if (!path.includes(this)) {
this.showNotifications = false
}
})
}
getApiKey() {
const apiNode = window.parent.reduxStore.getState().app.nodeConfig.knownNodes[window.parent.reduxStore.getState().app.nodeConfig.node]
return apiNode.apiKey
}
async getNotifications() {
const myNode = store.getState().app.nodeConfig.knownNodes[store.getState().app.nodeConfig.node]
const nodeUrl = myNode.protocol + '://' + myNode.domain + ':' + myNode.port
let interval = null
let stop = false
const getNewMail = async () => {
const getMail = async (recipientName, recipientAddress) => {
const query = `qortal_qmail_${recipientName.slice(
0,
20
)}_${recipientAddress.slice(-6)}_mail_`
const url = `${nodeUrl}/arbitrary/resources/search?service=MAIL_PRIVATE&query=${query}&limit=10&includemetadata=false&offset=0&reverse=true&excludeblocked=true`
const response = await fetch(url, {
method: 'GET',
headers: {
'Content-Type': 'application/json'
}
})
return await response.json()
}
if (!stop && !this.showNotifications) {
stop = true
try {
const address = window.parent.reduxStore.getState().app?.selectedAddress?.address;
const name = window.parent.reduxStore.getState().app?.accountInfo?.names[0]?.name
if (!name || !address) return
const mailArray = await getMail(name, address)
let notificationsToShow = []
if (mailArray.length > 0) {
const lastVisited = localStorage.getItem("Q-Mail-last-visited")
if (lastVisited) {
mailArray.forEach((mail) => {
if (mail.created > lastVisited) notificationsToShow.push(mail)
})
} else {
notificationsToShow = mailArray
}
}
if (!this.initialFetch && notificationsToShow.length > 0) {
const mail = notificationsToShow[0]
const urlPic = `${nodeUrl}/arbitrary/THUMBNAIL/${mail.name}/qortal_avatar?async=true&apiKey=${this.getApiKey()}`
await routes.showNotification({
data: {
title: "New Q-Mail",
type: "qapp",
sound: config.messageAlert,
url: "",
options: {
body: `You have an unread mail from ${mail.name}`,
icon: urlPic,
badge: urlPic
}
}
})
} else if (notificationsToShow.length > 0) {
if (notificationsToShow[0].created > (this.notifications[0]?.created || 0)) {
const mail = notificationsToShow[0]
const urlPic = `${nodeUrl}/arbitrary/THUMBNAIL/${mail.name}/qortal_avatar?async=true&apiKey=${this.getApiKey()}`
await routes.showNotification({
data: {
title: "New Q-Mail",
type: "qapp",
sound: config.messageAlert,
url: "",
options: {
body: `You have an unread mail from ${mail.name}`,
icon: urlPic,
badge: urlPic
}
}
})
}
}
this.notifications = notificationsToShow
this.notificationCount = this.notifications.length !== 0;
if (!this.initialFetch) this.initialFetch = true
} catch (error) {
console.error(error)
}
stop = false
}
}
try {
setTimeout(() => {
getNewMail()
}, 5000)
interval = setInterval(getNewMail, 60000)
} catch (error) {
console.error(error)
}
}
render() { render() {
return html` return html`
<div class="layout"> <div class="layout">
${this.notificationCount ? html` ${this.notificationCount ? html`
<mwc-icon @click=${() => this._toggleNotifications()} id="notification-mail-icon" style="color: green;cursor:pointer;user-select:none" <mwc-icon @click=${() => this._toggleNotifications()} id="notification-mail-icon" style="color: green;cursor:pointer;user-select:none">
>mail</mwc-icon mail
> </mwc-icon>
<vaadin-tooltip <vaadin-tooltip
for="notification-mail-icon" for="notification-mail-icon"
position="bottom" position="bottom"
@ -164,11 +49,10 @@ class NotificationBell extends connect(store)(LitElement) {
hide-delay=${1} hide-delay=${1}
text="Q-Mail"> text="Q-Mail">
</vaadin-tooltip> </vaadin-tooltip>
` : html` ` : html`
<mwc-icon @click=${() => this._openTabQmail()} id="notification-mail-icon" style="color: var(--black); cursor:pointer;user-select:none" <mwc-icon @click=${() => this._openTabQmail()} id="notification-mail-icon" style="color: var(--black); cursor:pointer;user-select:none">
>mail</mwc-icon mail
> </mwc-icon>
<vaadin-tooltip <vaadin-tooltip
for="notification-mail-icon" for="notification-mail-icon"
position="bottom" position="bottom"
@ -176,17 +60,16 @@ class NotificationBell extends connect(store)(LitElement) {
hide-delay=${1} hide-delay=${1}
text="Q-Mail"> text="Q-Mail">
</vaadin-tooltip> </vaadin-tooltip>
`} `}
${this.notificationCount ? html` ${this.notificationCount ? html`
<span class="count">${this.notifications.length}</span> <span class="count">${this.notifications.length}</span>
` : ''} ` : ''}
<div class="popover-panel" ?hidden=${!this.showNotifications}> <div class="popover-panel" ?hidden=${!this.showNotifications}>
<div class="notifications-list"> <div class="notifications-list">
${this.notifications.map(notification => html` ${this.notifications.map(notification => html`
<div class="notification-item" @click=${() => { <div
class="notification-item"
@click=${() => {
const query = `?service=APP&name=Q-Mail` const query = `?service=APP&name=Q-Mail`
store.dispatch(setNewTab({ store.dispatch(setNewTab({
url: `qdn/browser/index.html${query}`, url: `qdn/browser/index.html${query}`,
@ -204,7 +87,8 @@ class NotificationBell extends connect(store)(LitElement) {
})) }))
this.showNotifications = false this.showNotifications = false
this.notifications = [] this.notifications = []
}}> }}
>
<div> <div>
<p>Q-Mail</p> <p>Q-Mail</p>
<message-time timestamp=${notification.created} style="color:red;font-size:12px"></message-time> <message-time timestamp=${notification.created} style="color:red;font-size:12px"></message-time>
@ -220,12 +104,144 @@ class NotificationBell extends connect(store)(LitElement) {
` `
} }
firstUpdated() {
this.getNotifications()
document.addEventListener('click', (event) => {
const path = event.composedPath()
if (!path.includes(this)) {
this.showNotifications = false
}
})
}
getApiKey() {
const apiNode = store.getState().app.nodeConfig.knownNodes[store.getState().app.nodeConfig.node]
return apiNode.apiKey
}
async getNotifications() {
const myNode = store.getState().app.nodeConfig.knownNodes[store.getState().app.nodeConfig.node]
const nodeUrl = myNode.protocol + '://' + myNode.domain + ':' + myNode.port
let interval = null
let stop = false
const getNewMail = async () => {
const getMail = async (recipientName, recipientAddress) => {
const query = `qortal_qmail_${recipientName.slice(
0,
20
)}_${recipientAddress.slice(-6)}_mail_`
const url = `${nodeUrl}/arbitrary/resources/search?service=MAIL_PRIVATE&query=${query}&limit=10&includemetadata=false&offset=0&reverse=true&excludeblocked=true`
const response = await fetch(url, {
method: 'GET',
headers: {
'Content-Type': 'application/json'
}
})
return await response.json()
}
if (!stop && !this.showNotifications) {
stop = true
try {
const address = window.parent.reduxStore.getState().app?.selectedAddress?.address;
const name = window.parent.reduxStore.getState().app?.accountInfo?.names[0]?.name
if (!name || !address) return
const mailArray = await getMail(name, address)
let notificationsToShow = []
if (mailArray.length > 0) {
const lastVisited = localStorage.getItem("Q-Mail-last-visited")
if (lastVisited) {
mailArray.forEach((mail) => {
if (mail.created > lastVisited) notificationsToShow.push(mail)
})
} else {
notificationsToShow = mailArray
}
}
if (!this.initialFetch && notificationsToShow.length > 0) {
const mail = notificationsToShow[0]
const urlPic = `${nodeUrl}/arbitrary/THUMBNAIL/${mail.name}/qortal_avatar?async=true}`
await routes.showNotification({
data: {
title: 'New Q-Mail',
type: 'qapp',
sound: config.messageAlert,
url: '',
options: {
body: `You have an unread mail from ${mail.name}`,
icon: urlPic,
badge: urlPic
}
}
})
} else if (notificationsToShow.length > 0) {
if (notificationsToShow[0].created > (this.notifications[0]?.created || 0)) {
const mail = notificationsToShow[0]
const urlPic = `${nodeUrl}/arbitrary/THUMBNAIL/${mail.name}/qortal_avatar?async=true}`
await routes.showNotification({
data: {
title: 'New Q-Mail',
type: 'qapp',
sound: config.messageAlert,
url: '',
options: {
body: `You have an unread mail from ${mail.name}`,
icon: urlPic,
badge: urlPic
}
}
})
}
}
this.notifications = notificationsToShow
this.notificationCount = this.notifications.length !== 0
if (!this.initialFetch) this.initialFetch = true
} catch (error) {
console.error(error)
}
stop = false
}
}
try {
setTimeout(() => {
getNewMail()
}, 5000)
interval = setInterval(getNewMail, 60000)
} catch (error) {
console.error(error)
}
}
_toggleNotifications() { _toggleNotifications() {
if (this.notifications.length === 0) return if (this.notifications.length === 0) return
this.showNotifications = !this.showNotifications this.showNotifications = !this.showNotifications
} }
_openTabQmail() { _openTabQmail() {
const query = `?service=APP&name=Q-Mail` const query = `?service=APP&name=Q-Mail`
store.dispatch(setNewTab({ store.dispatch(setNewTab({
url: `qdn/browser/index.html${query}`, url: `qdn/browser/index.html${query}`,
id: 'q-mail-notification', id: 'q-mail-notification',
@ -241,89 +257,6 @@ class NotificationBell extends connect(store)(LitElement) {
} }
})) }))
} }
static styles = css`
.layout {
display: flex;
flex-direction: column;
align-items: center;
position: relative;
} }
.count { window.customElements.define('notification-bell', NotificationBell)
position: absolute;
top: -5px;
right: -5px;
font-size: 12px;
background-color: red;
color: white;
border-radius: 50%;
width: 16px;
height: 16px;
display: flex;
align-items: center;
justify-content: center;
user-select: none;
}
.nocount {
display: none;
}
.popover-panel {
position: absolute;
width: 200px;
padding: 10px;
background-color: var(--white);
border: 1px solid var(--black);
border-radius: 4px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
top: 40px;
max-height: 350px;
overflow: auto;
scrollbar-width: thin;
scrollbar-color: #6a6c75 #a1a1a1;
}
.popover-panel::-webkit-scrollbar {
width: 11px;
}
.popover-panel::-webkit-scrollbar-track {
background: #a1a1a1;
}
.popover-panel::-webkit-scrollbar-thumb {
background-color: #6a6c75;
border-radius: 6px;
border: 3px solid #a1a1a1;
}
.notifications-list {
display: flex;
flex-direction: column;
}
.notification-item {
padding: 5px;
border-bottom: 1px solid;
display: flex;
justify-content: space-between;
cursor: pointer;
transition: 0.2s all;
}
.notification-item:hover {
background: var(--nav-color-hover);
}
p {
font-size: 14px;
color: var(--black);
margin: 0px;
padding: 0px;
}
`
}
customElements.define('notification-bell', NotificationBell)

View File

@ -1,44 +1,30 @@
// popover-component.js import { css, html, LitElement } from 'lit'
import {css, html, LitElement} from 'lit'; import { createPopper } from '@popperjs/core'
import {createPopper} from '@popperjs/core'; import { popoverComponentStyles } from '../../styles/core-css'
import '@material/mwc-icon' import '@material/mwc-icon'
export class PopoverComponent extends LitElement { export class PopoverComponent extends LitElement {
static styles = css` static get properties() {
:host { return {
display: none;
position: absolute;
background-color: var(--white);
border: 1px solid #ddd;
padding: 8px;
z-index: 10;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
color: var(--black);
max-width: 250px;
}
.close-icon {
cursor: pointer;
float: right;
margin-left: 10px;
color: var(--black)
}
`;
static properties = {
for: { type: String, reflect: true }, for: { type: String, reflect: true },
message: { type: String } message: { type: String }
}; }
constructor() {
super();
this.message = '';
} }
firstUpdated() { static get styles() {
// We'll defer the popper attachment to the openPopover() method to ensure target availability return [popoverComponentStyles]
}
constructor() {
super()
this.message = ''
}
render() {
return html`
<span class="close-icon" @click="${this.closePopover}"><mwc-icon style="color: var(--black)">close</mwc-icon></span>
<div><mwc-icon style="color: var(--black)">info</mwc-icon> ${this.message} <slot></slot></div>
`
} }
attachToTarget(target) { attachToTarget(target) {
@ -46,31 +32,25 @@ export class PopoverComponent extends LitElement {
this.popperInstance = createPopper(target, this, { this.popperInstance = createPopper(target, this, {
placement: 'bottom', placement: 'bottom',
strategy: 'fixed' strategy: 'fixed'
}); })
} }
} }
openPopover(target) { openPopover(target) {
this.attachToTarget(target); this.attachToTarget(target)
this.style.display = 'block'; this.style.display = 'block'
} }
closePopover() { closePopover() {
this.style.display = 'none'; this.style.display = 'none'
if (this.popperInstance) { if (this.popperInstance) {
this.popperInstance.destroy(); this.popperInstance.destroy()
this.popperInstance = null; this.popperInstance = null
}
this.requestUpdate();
} }
render() { this.requestUpdate()
return html`
<span class="close-icon" @click="${this.closePopover}"><mwc-icon style="color: var(--black)">close</mwc-icon></span>
<div><mwc-icon style="color: var(--black)">info</mwc-icon> ${this.message} <slot></slot>
</div>
`;
} }
} }
customElements.define('popover-component', PopoverComponent); window.customElements.define('popover-component', PopoverComponent)

View File

@ -1,97 +1,23 @@
import {css, html, LitElement} from 'lit' import { html, LitElement } from 'lit'
import {svgMoon, svgSun} from '../../assets/js/svg.js' import { svgMoon, svgSun } from '../../assets/js/svg'
import { qortThemeToggleStyles } from '../styles/core-css'
class QortThemeToggle extends LitElement { class QortThemeToggle extends LitElement {
static get properties() { static get properties() {
return { return {
theme: { theme: { type: String, reflect: true }
type: String,
reflect: true
}
} }
} }
constructor() { constructor() {
super(); super()
this.theme = localStorage.getItem('qortalTheme') ? localStorage.getItem('qortalTheme') : 'light'; this.theme = localStorage.getItem('qortalTheme') ? localStorage.getItem('qortalTheme') : 'light'
} }
static styles = [ static get styles() {
css` return [qortThemeToggleStyles]
:host {
display: inline-block;
position: relative;
width: 54px;
height: 32px;
transform: translateY(-2px);
} }
svg {
width: 32px;
height: 32px;
}
input {
cursor: pointer;
position: absolute;
z-index: 1;
opacity: 0;
width: 100%;
height: 100%;
}
.slider {
position: absolute;
cursor: pointer;
width: 100%;
height: 16px;
top: 50%;
transform: translateY(-50%);
background-color: var(--switchbackground);
border: 2px solid var(--switchborder);
border-radius: 1rem;
transition: all .4s ease;
}
.icon {
width: 32px;
height: 32px;
display: inline-block;
position: absolute;
top: 50%;
background: var(--switchbackground);
border: 2px solid var(--switchborder);
border-radius: 50%;
transition: transform 300ms ease;
}
:host([theme="light"]) .icon {
transform: translate(0, -50%);
}
input:checked ~ .icon,
:host([theme="dark"]) .icon {
transform: translate(calc(100% - 12px), -50%);
}
.moon {
display: none;
}
.moon svg {
transform: scale(0.6);
}
:host([theme="dark"]) .sun {
display: none;
}
:host([theme="dark"]) .moon {
display: inline-block;
}
`
];
render() { render() {
return html` return html`
<input type="checkbox" @change=${() => this.toggleTheme()}/> <input type="checkbox" @change=${() => this.toggleTheme()}/>
@ -100,35 +26,37 @@ class QortThemeToggle extends LitElement {
<span class="sun">${svgSun}</span> <span class="sun">${svgSun}</span>
<span class="moon">${svgMoon}</span> <span class="moon">${svgMoon}</span>
</div> </div>
`; `
} }
firstUpdated() { firstUpdated() {
this.initTheme(); this.initTheme()
} }
toggleTheme() { toggleTheme() {
if (this.theme === 'light') { if (this.theme === 'light') {
this.theme = 'dark'; this.theme = 'dark'
} else { } else {
this.theme = 'light'; this.theme = 'light'
} }
this.dispatchEvent( this.dispatchEvent(
new CustomEvent('qort-theme-change', { new CustomEvent('qort-theme-change', {
bubbles: true, bubbles: true,
composed: true, composed: true,
detail: this.theme, detail: this.theme
}), })
); )
window.localStorage.setItem('qortalTheme', this.theme); window.localStorage.setItem('qortalTheme', this.theme)
this.initTheme();
this.initTheme()
} }
initTheme() { initTheme() {
document.querySelector('html').setAttribute('theme', this.theme); document.querySelector('html').setAttribute('theme', this.theme)
} }
} }
window.customElements.define('qort-theme-toggle', QortThemeToggle); window.customElements.define('qort-theme-toggle', QortThemeToggle)

View File

@ -1,12 +1,14 @@
import {css, html, LitElement} from 'lit' import { html, LitElement } from 'lit'
import {get, translate} from '../../translate' import { searchModalStyles } from '../styles/core-css'
import snackbar from '../functional-components/snackbar.js' import snackbar from '../functional-components/snackbar'
import '@polymer/paper-icon-button/paper-icon-button.js' import '@polymer/paper-icon-button/paper-icon-button.js'
import '@polymer/iron-icons/iron-icons.js' import '@polymer/iron-icons/iron-icons.js'
import '@polymer/paper-dialog/paper-dialog.js' import '@polymer/paper-dialog/paper-dialog.js'
import '@vaadin/text-field' import '@vaadin/text-field'
// Multi language support
import { get, translate } from '../../translate'
class SearchModal extends LitElement { class SearchModal extends LitElement {
static get properties() { static get properties() {
return { return {
@ -15,56 +17,16 @@ class SearchModal extends LitElement {
} }
} }
static get styles() {
return [searchModalStyles]
}
constructor() { constructor() {
super() super()
this.searchContentString = '' this.searchContentString = ''
this.theme = localStorage.getItem('qortalTheme') ? localStorage.getItem('qortalTheme') : 'light' this.theme = localStorage.getItem('qortalTheme') ? localStorage.getItem('qortalTheme') : 'light'
} }
static get styles() {
return css`
* {
--lumo-primary-text-color: rgb(0, 167, 245);
--lumo-primary-color-50pct: rgba(0, 167, 245, 0.5);
--lumo-primary-color-10pct: rgba(0, 167, 245, 0.1);
--lumo-primary-color: hsl(199, 100%, 48%);
--lumo-base-color: var(--white);
--lumo-body-text-color: var(--black);
--lumo-secondary-text-color: var(--sectxt);
--lumo-contrast-60pct: var(--vdicon);
--item-selected-color: var(--nav-selected-color);
--item-selected-color-text: var(--nav-selected-color-text);
--item-color-active: var(--nav-color-active);
--item-color-hover: var(--nav-color-hover);
--item-text-color: var(--nav-text-color);
--item-icon-color: var(--nav-icon-color);
--item-border-color: var(--nav-border-color);
--item-border-selected-color: var(--nav-border-selected-color);
}
paper-dialog.searchSettings {
min-width: 525px;
max-width: 525px;
min-height: auto;
max-height: 150px;
background-color: var(--white);
color: var(--black);
line-height: 1.6;
overflow: hidden;
border: 1px solid var(--black);
border-radius: 10px;
padding: 15px;
box-shadow: 0px 10px 15px rgba(0, 0, 0, 0.1);
}
.search {
display: inline;
width: 50%;
align-items: center;
}
`
}
render() { render() {
return html` return html`
<div style="display: inline;"> <div style="display: inline;">
@ -92,6 +54,7 @@ class SearchModal extends LitElement {
} }
firstUpdated() { firstUpdated() {
// ...
} }
openSearch() { openSearch() {
@ -110,19 +73,24 @@ class SearchModal extends LitElement {
openUserInfo() { openUserInfo() {
const checkvalue = this.shadowRoot.getElementById('searchContent').value const checkvalue = this.shadowRoot.getElementById('searchContent').value
if (checkvalue.length < 3) { if (checkvalue.length < 3) {
let snackbar1string = get("publishpage.pchange20") let snackbar1string = get("publishpage.pchange20")
let snackbar2string = get("welcomepage.wcchange4") let snackbar2string = get("welcomepage.wcchange4")
snackbar.add({ snackbar.add({
labelText: `${snackbar1string} ${snackbar2string}`, labelText: `${snackbar1string} ${snackbar2string}`,
dismiss: true dismiss: true
}) })
this.shadowRoot.getElementById('searchContent').value = this.searchContentString
this.shadowRoot.getElementById('searchContent').value = this.searchContentString
} else { } else {
let sendInfoAddress = this.shadowRoot.getElementById('searchContent').value let sendInfoAddress = this.shadowRoot.getElementById('searchContent').value
const infoDialog = document.getElementById('main-app').shadowRoot.querySelector('app-view').shadowRoot.querySelector('user-info-view') const infoDialog = document.getElementById('main-app').shadowRoot.querySelector('app-view').shadowRoot.querySelector('user-info-view')
infoDialog.openUserInfo(sendInfoAddress) infoDialog.openUserInfo(sendInfoAddress)
this.shadowRoot.getElementById('searchContent').value = this.searchContentString this.shadowRoot.getElementById('searchContent').value = this.searchContentString
this.shadowRoot.getElementById('searchSettingsDialog').close() this.shadowRoot.getElementById('searchSettingsDialog').close()
} }

View File

@ -1,73 +1,29 @@
import {css, html, LitElement} from 'lit' import { html, LitElement } from 'lit'
import { connect } from 'pwa-helpers' import { connect } from 'pwa-helpers'
import {store} from '../../store.js' import { store } from '../../store'
import { accountViewStyles } from '../../styles/core-css'
// Multi language support
import { get, translate } from '../../../translate' import { get, translate } from '../../../translate'
class AccountView extends connect(store)(LitElement) { class AccountView extends connect(store)(LitElement) {
static get properties() { static get properties() {
return { return {
accountInfo: { type: Object }, accountInfo: { type: Object },
theme: { type: String, reflect: true }, switchAvatar: { type: String },
switchAvatar: { type: String } theme: { type: String, reflect: true }
} }
} }
static get styles() { static get styles() {
return css` return [accountViewStyles]
.sub-main {
position: relative;
text-align: center;
}
.center-box {
position: relative;
top: 45%;
left: 50%;
transform: translate(-50%, 0%);
text-align: center;
}
.img-icon {
display: block;
margin-top: 10px;
}
.content-box {
border: 1px solid #a1a1a1;
padding: 10px 25px;
text-align: left;
display: inline-block;
}
.title {
font-weight: 600;
font-size: 15px;
display: block;
line-height: 32px;
opacity: 0.66;
}
.value {
font-size: 16px;
display: inline-block;
}
#accountName {
margin: 0;
font-size: 24px;
font-weight:500;
display: inline-block;
width:100%;
}
`
} }
constructor() { constructor() {
super() super()
this.accountInfo = store.getState().app.accountInfo this.accountInfo = store.getState().app.accountInfo
this.theme = localStorage.getItem('qortalTheme') ? localStorage.getItem('qortalTheme') : 'light'
this.switchAvatar = '' this.switchAvatar = ''
this.theme = localStorage.getItem('qortalTheme') ? localStorage.getItem('qortalTheme') : 'light'
} }
render() { render() {
@ -92,9 +48,10 @@ class AccountView extends connect(store)(LitElement) {
firstUpdated() { firstUpdated() {
this.getSwitchAvatar() this.getSwitchAvatar()
setInterval(() => { setInterval(() => {
this.getSwitchAvatar() this.getSwitchAvatar()
}, 2000) }, 10000)
} }
getAvatar() { getAvatar() {
@ -105,7 +62,8 @@ class AccountView extends connect(store)(LitElement) {
const avatarName = this.accountInfo.names[0].name const avatarName = this.accountInfo.names[0].name
const avatarNode = store.getState().app.nodeConfig.knownNodes[store.getState().app.nodeConfig.node] const avatarNode = store.getState().app.nodeConfig.knownNodes[store.getState().app.nodeConfig.node]
const avatarUrl = avatarNode.protocol + '://' + avatarNode.domain + ':' + avatarNode.port const avatarUrl = avatarNode.protocol + '://' + avatarNode.domain + ':' + avatarNode.port
const url = `${avatarUrl}/arbitrary/THUMBNAIL/${avatarName}/qortal_avatar?async=true&apiKey=${this.getApiKey()}` const url = `${avatarUrl}/arbitrary/THUMBNAIL/${avatarName}/qortal_avatar?async=true`
return html`<img src="${url}" style="width:150px; height:150px; border-radius: 25%;" onerror="this.src='/img/noavatar_light.png';">` return html`<img src="${url}" style="width:150px; height:150px; border-radius: 25%;" onerror="this.src='/img/noavatar_light.png';">`
} }
} else if (this.switchAvatar === 'dark') { } else if (this.switchAvatar === 'dark') {
@ -115,7 +73,8 @@ class AccountView extends connect(store)(LitElement) {
const avatarName = this.accountInfo.names[0].name const avatarName = this.accountInfo.names[0].name
const avatarNode = store.getState().app.nodeConfig.knownNodes[store.getState().app.nodeConfig.node] const avatarNode = store.getState().app.nodeConfig.knownNodes[store.getState().app.nodeConfig.node]
const avatarUrl = avatarNode.protocol + '://' + avatarNode.domain + ':' + avatarNode.port const avatarUrl = avatarNode.protocol + '://' + avatarNode.domain + ':' + avatarNode.port
const url = `${avatarUrl}/arbitrary/THUMBNAIL/${avatarName}/qortal_avatar?async=true&apiKey=${this.getApiKey()}` const url = `${avatarUrl}/arbitrary/THUMBNAIL/${avatarName}/qortal_avatar?async=true`
return html`<img src="${url}" style="width:150px; height:150px; border-radius: 25%;" onerror="this.src='/img/noavatar_dark.png';">` return html`<img src="${url}" style="width:150px; height:150px; border-radius: 25%;" onerror="this.src='/img/noavatar_dark.png';">`
} }
} }
@ -125,11 +84,6 @@ class AccountView extends connect(store)(LitElement) {
this.switchAvatar = localStorage.getItem('qortalTheme') this.switchAvatar = localStorage.getItem('qortalTheme')
} }
getApiKey() {
const apiNode = store.getState().app.nodeConfig.knownNodes[store.getState().app.nodeConfig.node]
return apiNode.apiKey
}
stateChanged(state) { stateChanged(state) {
this.accountInfo = state.app.accountInfo this.accountInfo = state.app.accountInfo
} }

View File

@ -1,16 +1,18 @@
import {css, html, LitElement} from 'lit' import { html, LitElement } from 'lit'
import { connect } from 'pwa-helpers' import { connect } from 'pwa-helpers'
import {store} from '../../store.js' import { store } from '../../store'
import {Epml} from '../../epml.js' import { Epml } from '../../epml'
import {addTradeBotRoutes} from '../../tradebot/addTradeBotRoutes.js' import { addTradeBotRoutes } from '../../tradebot/addTradeBotRoutes'
import {get, translate} from '../../../translate' import { exportKeysStyles } from '../../styles/core-css'
import snackbar from '../../functional-components/snackbar.js'
import FileSaver from 'file-saver' import FileSaver from 'file-saver'
import snackbar from '../../functional-components/snackbar'
import '@material/mwc-dialog'
import '@material/mwc-button' import '@material/mwc-button'
import '@material/mwc-dialog'
import '@material/mwc-icon' import '@material/mwc-icon'
// Multi language support
import { get, translate } from '../../../translate'
const parentEpml = new Epml({ type: 'WINDOW', source: window.parent }) const parentEpml = new Epml({ type: 'WINDOW', source: window.parent })
class ExportKeys extends connect(store)(LitElement) { class ExportKeys extends connect(store)(LitElement) {
@ -51,211 +53,7 @@ class ExportKeys extends connect(store)(LitElement) {
} }
static get styles() { static get styles() {
return css` return [exportKeysStyles]
* {
--mdc-theme-primary: rgb(3, 169, 244);
--mdc-theme-surface: var(--white);
--mdc-dialog-content-ink-color: var(--black);
--mdc-dialog-min-width: 500px;
--mdc-dialog-max-width: 750px;
--lumo-primary-text-color: rgb(0, 167, 245);
--lumo-primary-color-50pct: rgba(0, 167, 245, 0.5);
--lumo-primary-color-10pct: rgba(0, 167, 245, 0.1);
--lumo-primary-color: hsl(199, 100%, 48%);
--lumo-base-color: var(--white);
--lumo-body-text-color: var(--black);
--lumo-secondary-text-color: var(--sectxt);
--lumo-contrast-60pct: var(--vdicon);
}
.center-box {
position: relative;
top: 45%;
left: 50%;
transform: translate(-50%, 0%);
text-align: center;
}
.sub-main {
position: relative;
text-align: center;
height: auto;
width: 100%;
}
.content-box {
text-align: center;
display: inline-block;
min-width: 400px;
margin-bottom: 10px;
margin-left: 10px;
margin-top: 20px;
}
.export-button {
display: inline-flex;
flex-direction: column;
justify-content: center;
align-content: center;
border: none;
border-radius: 20px;
padding-left: 10px;
padding-right: 10px;
color: white;
background: #03a9f4;
width: 75%;
font-size: 16px;
cursor: pointer;
height: 40px;
margin-top: 1rem;
text-transform: uppercase;
text-decoration: none;
transition: all .2s;
position: relative;
}
.red {
--mdc-theme-primary: #F44336;
}
.green {
--mdc-theme-primary: #198754;
}
.button-row {
position: relative;
display: flex;
align-items: center;
align-content: center;
font-family: Montserrat, sans-serif;
font-weight: 600;
color: var(--black);
margin-top: 20px;
}
.repair-button {
height: 40px;
padding: 10px 10px;
font-size: 16px;
font-weight: 500;
background-color: #03a9f4;
color: white;
border: 1px solid transparent;
border-radius: 20px;
text-decoration: none;
text-transform: uppercase;
cursor: pointer;
}
.repair-button:hover {
opacity: 0.8;
cursor: pointer;
}
.lds-roller {
display: inline-block;
position: relative;
width: 80px;
height: 80px;
}
.lds-roller div {
animation: lds-roller 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite;
transform-origin: 40px 40px;
}
.lds-roller div:after {
content: " ";
display: block;
position: absolute;
width: 7px;
height: 7px;
border-radius: 50%;
background: var(--black);
margin: -4px 0 0 -4px;
}
.lds-roller div:nth-child(1) {
animation-delay: -0.036s;
}
.lds-roller div:nth-child(1):after {
top: 63px;
left: 63px;
}
.lds-roller div:nth-child(2) {
animation-delay: -0.072s;
}
.lds-roller div:nth-child(2):after {
top: 68px;
left: 56px;
}
.lds-roller div:nth-child(3) {
animation-delay: -0.108s;
}
.lds-roller div:nth-child(3):after {
top: 71px;
left: 48px;
}
.lds-roller div:nth-child(4) {
animation-delay: -0.144s;
}
.lds-roller div:nth-child(4):after {
top: 72px;
left: 40px;
}
.lds-roller div:nth-child(5) {
animation-delay: -0.18s;
}
.lds-roller div:nth-child(5):after {
top: 71px;
left: 32px;
}
.lds-roller div:nth-child(6) {
animation-delay: -0.216s;
}
.lds-roller div:nth-child(6):after {
top: 68px;
left: 24px;
}
.lds-roller div:nth-child(7) {
animation-delay: -0.252s;
}
.lds-roller div:nth-child(7):after {
top: 63px;
left: 17px;
}
.lds-roller div:nth-child(8) {
animation-delay: -0.288s;
}
.lds-roller div:nth-child(8):after {
top: 56px;
left: 12px;
}
@keyframes lds-roller {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
`
} }
constructor() { constructor() {
@ -405,7 +203,16 @@ class ExportKeys extends connect(store)(LitElement) {
</mwc-button> </mwc-button>
</mwc-dialog> </mwc-dialog>
<mwc-dialog id="pleaseWaitDialog" scrimClickAction="" escapeKeyAction=""> <mwc-dialog id="pleaseWaitDialog" scrimClickAction="" escapeKeyAction="">
<div class="lds-roller"><div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div></div> <div class="lds-roller">
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
</div>
<h2>${translate("nodepage.nchange41")}</h2> <h2>${translate("nodepage.nchange41")}</h2>
</mwc-dialog> </mwc-dialog>
<mwc-dialog id="okDialog" scrimClickAction="" escapeKeyAction=""> <mwc-dialog id="okDialog" scrimClickAction="" escapeKeyAction="">
@ -426,6 +233,7 @@ class ExportKeys extends connect(store)(LitElement) {
async firstUpdated() { async firstUpdated() {
addTradeBotRoutes(parentEpml) addTradeBotRoutes(parentEpml)
parentEpml.imReady() parentEpml.imReady()
await this.fetchArrrWalletAddress() await this.fetchArrrWalletAddress()
await this.checkArrrWalletPrivateKey() await this.checkArrrWalletPrivateKey()
} }
@ -510,6 +318,7 @@ class ExportKeys extends connect(store)(LitElement) {
async repairLtcWallet() { async repairLtcWallet() {
this.shadowRoot.querySelector('#repairLTCDialog').close() this.shadowRoot.querySelector('#repairLTCDialog').close()
this.shadowRoot.querySelector('#pleaseWaitDialog').show() this.shadowRoot.querySelector('#pleaseWaitDialog').show()
let resRepair = await parentEpml.request('apiCall', { let resRepair = await parentEpml.request('apiCall', {
url: `/crosschain/ltc/repair?apiKey=${this.getApiKey()}`, url: `/crosschain/ltc/repair?apiKey=${this.getApiKey()}`,
method: 'POST', method: 'POST',
@ -518,24 +327,32 @@ class ExportKeys extends connect(store)(LitElement) {
if (resRepair != null && resRepair.error != 128) { if (resRepair != null && resRepair.error != 128) {
this.shadowRoot.querySelector('#pleaseWaitDialog').close() this.shadowRoot.querySelector('#pleaseWaitDialog').close()
await this.openOkDialog() await this.openOkDialog()
} else { } else {
this.shadowRoot.querySelector('#pleaseWaitDialog').close() this.shadowRoot.querySelector('#pleaseWaitDialog').close()
await this.openErrorDialog() await this.openErrorDialog()
} }
} }
async openOkDialog() { async openOkDialog() {
const okDelay = ms => new Promise(res => setTimeout(res, ms)) const okDelay = ms => new Promise(res => setTimeout(res, ms))
this.shadowRoot.querySelector('#okDialog').show() this.shadowRoot.querySelector('#okDialog').show()
await okDelay(3000) await okDelay(3000)
this.shadowRoot.querySelector('#okDialog').close() this.shadowRoot.querySelector('#okDialog').close()
} }
async openErrorDialog() { async openErrorDialog() {
const errorDelay = ms => new Promise(res => setTimeout(res, ms)) const errorDelay = ms => new Promise(res => setTimeout(res, ms))
this.shadowRoot.querySelector('#errorDialog').show() this.shadowRoot.querySelector('#errorDialog').show()
await errorDelay(3000) await errorDelay(3000)
this.shadowRoot.querySelector('#errorDialog').close() this.shadowRoot.querySelector('#errorDialog').close()
} }
@ -552,12 +369,15 @@ class ExportKeys extends connect(store)(LitElement) {
} }
async exportKey(cMasterKey, cName, cAddress) { async exportKey(cMasterKey, cName, cAddress) {
let exportname = "" let exportname = ''
const myPrivateMasterKey = cMasterKey const myPrivateMasterKey = cMasterKey
const myCoinName = cName const myCoinName = cName
const myCoinAddress = cAddress const myCoinAddress = cAddress
const blob = new Blob([`${myPrivateMasterKey}`], { type: 'text/plain;charset=utf-8' }) const blob = new Blob([`${myPrivateMasterKey}`], { type: 'text/plain;charset=utf-8' })
exportname = "Private_Master_Key_" + myCoinName + "_" + myCoinAddress + ".txt"
exportname = 'Private_Master_Key_' + myCoinName + '_' + myCoinAddress + '.txt'
await this.saveFileToDisk(blob, exportname) await this.saveFileToDisk(blob, exportname)
} }
@ -566,16 +386,21 @@ class ExportKeys extends connect(store)(LitElement) {
const fileHandle = await self.showSaveFilePicker({ const fileHandle = await self.showSaveFilePicker({
suggestedName: fileName, suggestedName: fileName,
types: [{ types: [{
description: "File", description: "File"
}] }]
}) })
const writeFile = async (fileHandle, contents) => { const writeFile = async (fileHandle, contents) => {
const writable = await fileHandle.createWritable() const writable = await fileHandle.createWritable()
await writable.write(contents) await writable.write(contents)
await writable.close() await writable.close()
} }
writeFile(fileHandle, blob).then(() => console.log("FILE SAVED")) writeFile(fileHandle, blob).then(() => console.log("FILE SAVED"))
let snack4string = get("general.save") let snack4string = get("general.save")
snackbar.add({ snackbar.add({
labelText: `${snack4string} ${fileName}`, labelText: `${snack4string} ${fileName}`,
dismiss: true dismiss: true
@ -584,6 +409,7 @@ class ExportKeys extends connect(store)(LitElement) {
if (error.name === 'AbortError') { if (error.name === 'AbortError') {
return return
} }
FileSaver.saveAs(blob, fileName) FileSaver.saveAs(blob, fileName)
} }
} }

View File

@ -1,13 +1,15 @@
import {css, html, LitElement} from 'lit' import { html, LitElement } from 'lit'
import { connect } from 'pwa-helpers' import { connect } from 'pwa-helpers'
import {store} from '../../store.js' import { store } from '../../store'
import {allowShowSyncIndicator, removeShowSyncIndicator} from '../../redux/app/app-actions.js' import { allowShowSyncIndicator, removeShowSyncIndicator } from '../../redux/app/app-actions'
import {doSetQChatNotificationConfig} from '../../redux/user/user-actions.js' import { doSetQChatNotificationConfig } from '../../redux/user/user-actions'
import {translate} from '../../../translate' import { notificationsViewStyles } from '../../styles/core-css'
import isElectron from 'is-electron' import isElectron from 'is-electron'
import '@material/mwc-checkbox' import '@material/mwc-checkbox'
// Multi language support
import { translate } from '../../../translate'
class NotificationsView extends connect(store)(LitElement) { class NotificationsView extends connect(store)(LitElement) {
static get properties() { static get properties() {
return { return {
@ -18,130 +20,28 @@ class NotificationsView extends connect(store)(LitElement) {
} }
} }
static get styles() {
return [notificationsViewStyles]
}
constructor() { constructor() {
super() super()
this.notificationConfig = {} this.notificationConfig = {}
this.q_chatConfig = {} this.q_chatConfig = {}
this.theme = localStorage.getItem('qortalTheme') ? localStorage.getItem('qortalTheme') : 'light' this.theme = localStorage.getItem('qortalTheme') ? localStorage.getItem('qortalTheme') : 'light'
this.appNotificationList = [] // Fetch the list of apps from local storage this.appNotificationList = []
} }
firstUpdated() { firstUpdated() {
this.appNotificationList = this.getAppsFromStorage() this.appNotificationList = this.getAppsFromStorage()
} }
static get styles() {
return css`
.sub-main {
position: relative;
text-align: center;
}
.notification-box {
display: block;
position: relative;
top: 45%;
left: 50%;
transform: translate(-50%, 0%);
text-align: center;
}
@media(min-width: 1400px) {
.notification-box {
display: grid;
grid-template-columns: 1fr 1fr;
grid-gap: 30px;
}
}
.checkbox-row {
position: relative;
display: flex;
align-items: center;
align-content: center;
font-family: Montserrat, sans-serif;
font-weight: 600;
color: var(--black);
}
.content-box {
border: 1px solid #a1a1a1;
padding: 10px 25px;
text-align: left;
display: inline-block;
min-width: 350px;
min-height: 150px;
margin: 20px 0;
}
h4 {
margin-bottom: 0;
}
mwc-checkbox::shadow .mdc-checkbox::after, mwc-checkbox::shadow .mdc-checkbox::before {
background-color:var(--mdc-theme-primary)
}
label:hover {
cursor: pointer;
}
.title {
font-weight: 600;
font-size: 15px;
display: block;
line-height: 32px;
opacity: 0.66;
}
.value {
font-size: 16px;
display: inline-block;
}
.q-button {
display: inline-flex;
flex-direction: column;
justify-content: center;
align-content: center;
border: none;
border-radius: 20px;
padding-left: 25px;
padding-right: 25px;
color: white;
background: #03a9f4;
width: 50%;
font-size: 17px;
cursor: pointer;
height: 50px;
margin-top: 1rem;
text-transform: uppercase;
text-decoration: none;
transition: all .2s;
position: relative;
}
.remove-button {
font-family: Roboto, sans-serif;
font-size: 16px;
color: var(--mdc-theme-primary);
background-color: transparent;
padding: 8px 10px;
border-radius: 5px;
border: none;
transition: all 0.3s ease-in-out;
cursor: pointer;
}
`
}
render() { render() {
return html` return html`
<div class="sub-main"> <div class="sub-main">
<div class="notification-box"> <div class="notification-box">
<div class="content-box"> <div class="content-box">
<h4>Q-Chat ${translate("settings.notifications")}</h4> <h4>Q-Chat ${translate("settings.notifications")}</h4>
<div style="line-height: 3rem;"> <div style="line-height: 3rem;">
<mwc-checkbox id="qChatPlaySound" @click=${e => this.setQChatNotificationConfig({ type: 'PLAY_SOUND', value: e.target.checked })} ?checked=${this.q_chatConfig.playSound}></mwc-checkbox> <mwc-checkbox id="qChatPlaySound" @click=${e => this.setQChatNotificationConfig({ type: 'PLAY_SOUND', value: e.target.checked })} ?checked=${this.q_chatConfig.playSound}></mwc-checkbox>
<label <label
@ -151,7 +51,6 @@ class NotificationsView extends connect(store)(LitElement) {
${translate("settings.playsound")} ${translate("settings.playsound")}
</label> </label>
</div> </div>
<div style="line-height: 3rem;"> <div style="line-height: 3rem;">
<mwc-checkbox id="qChatShowNotification" @click=${e => this.setQChatNotificationConfig({ type: 'SHOW_NOTIFICATION', value: e.target.checked })} ?checked=${this.q_chatConfig.showNotification}></mwc-checkbox> <mwc-checkbox id="qChatShowNotification" @click=${e => this.setQChatNotificationConfig({ type: 'SHOW_NOTIFICATION', value: e.target.checked })} ?checked=${this.q_chatConfig.showNotification}></mwc-checkbox>
<label <label
@ -184,27 +83,29 @@ class NotificationsView extends connect(store)(LitElement) {
} }
getAppsFromStorage() { getAppsFromStorage() {
// Your method to fetch the list of apps from local storage
// Example:
const address = store.getState().app.selectedAddress.address const address = store.getState().app.selectedAddress.address
const id = `appNotificationList-${address}` const id = `appNotificationList-${address}`
const data = localStorage.getItem(id) const data = localStorage.getItem(id)
return data ? Object.keys(JSON.parse(data)) : [] return data ? Object.keys(JSON.parse(data)) : []
} }
removeApp(appName) { removeApp(appName) {
// Remove the app from local storage // Remove the app from local storage
this.removeAppFromStorage(appName); this.removeAppFromStorage(appName)
// Update the apps list in the component // Update the apps list in the component
this.appNotificationList = this.appNotificationList.filter(app => app !== appName); this.appNotificationList = this.appNotificationList.filter(app => app !== appName)
} }
removeAppFromStorage(appName) { removeAppFromStorage(appName) {
// Your method to remove the app from local storage // Your method to remove the app from local storage
const address = store.getState().app.selectedAddress.address const address = store.getState().app.selectedAddress.address
const id = `appNotificationList-${address}`; const id = `appNotificationList-${address}`
const data = JSON.parse(localStorage.getItem(id) || '{}'); const data = JSON.parse(localStorage.getItem(id) || '{}')
delete data[appName];
delete data[appName]
localStorage.setItem(id, JSON.stringify(data)); localStorage.setItem(id, JSON.stringify(data));
} }
@ -243,13 +144,14 @@ class NotificationsView extends connect(store)(LitElement) {
playSound: !valueObject.value, playSound: !valueObject.value,
showNotification: this.q_chatConfig.showNotification showNotification: this.q_chatConfig.showNotification
} }
store.dispatch(doSetQChatNotificationConfig(data)) store.dispatch(doSetQChatNotificationConfig(data))
} if (valueObject.type === 'SHOW_NOTIFICATION') { } if (valueObject.type === 'SHOW_NOTIFICATION') {
let data = { let data = {
playSound: this.q_chatConfig.playSound, playSound: this.q_chatConfig.playSound,
showNotification: !valueObject.value showNotification: !valueObject.value
} }
store.dispatch(doSetQChatNotificationConfig(data)) store.dispatch(doSetQChatNotificationConfig(data))
} }
} }

View File

@ -1,83 +1,27 @@
import {css, html, LitElement} from 'lit' import { html, LitElement } from 'lit'
import { connect } from 'pwa-helpers' import { connect } from 'pwa-helpers'
import {store} from '../../store.js' import { store } from '../../store'
import {translate} from '../../../translate' import { qrLoginViewStyles } from '../../styles/core-css'
import '../../../../plugins/plugins/core/components/QortalQrcodeGenerator'
import '@material/mwc-textfield'
import '@material/mwc-icon' import '@material/mwc-icon'
import '@material/mwc-textfield'
import '@vaadin/password-field/vaadin-password-field.js' import '@vaadin/password-field/vaadin-password-field.js'
import '../../../../plugins/plugins/core/components/QortalQrcodeGenerator.js'
// Multi language support
import { translate } from '../../../translate'
class QRLoginView extends connect(store)(LitElement) { class QRLoginView extends connect(store)(LitElement) {
static get properties() { static get properties() {
return { return {
theme: { type: String, reflect: true }, theme: { type: String, reflect: true },
savedWalletDataJson: { type: String }, savedWalletDataJson: { type: String },
translateDescriptionKey: { type: String }, // Description text translateDescriptionKey: { type: String },
translateButtonKey: { type: String }, // Button text translateButtonKey: { type: String }
} }
} }
static get styles() { static get styles() {
return css` return [qrLoginViewStyles]
* {
--lumo-primary-text-color: rgb(0, 167, 245);
--lumo-primary-color-50pct: rgba(0, 167, 245, 0.5);
--lumo-primary-color-10pct: rgba(0, 167, 245, 0.1);
--lumo-primary-color: hsl(199, 100%, 48%);
--lumo-base-color: var(--white);
--lumo-body-text-color: var(--black);
--lumo-secondary-text-color: var(--sectxt);
--lumo-contrast-60pct: var(--vdicon);
}
.center-box {
position: relative;
top: 45%;
left: 50%;
transform: translate(-50%, 0%);
text-align: center;
}
.q-button {
display: inline-flex;
flex-direction: column;
justify-content: center;
align-content: center;
border: none;
border-radius: 20px;
padding-left: 25px;
padding-right: 25px;
color: white;
background: #03a9f4;
width: 50%;
font-size: 17px;
cursor: pointer;
height: 50px;
margin-top: 1rem;
text-transform: uppercase;
text-decoration: none;
transition: all .2s;
position: relative;
}
.q-button.outlined {
background: unset;
border: 1px solid #03a9f4;
}
:host([theme="light"]) .q-button.outlined {
color: #03a9f4;
}
#qr-toggle-button {
margin-left: 12px;
}
#login-qr-code {
margin: auto;
}
`
} }
constructor() { constructor() {
@ -101,7 +45,6 @@ class QRLoginView extends connect(store)(LitElement) {
<div style="max-width: 600px; display: flex; justify-content: center; margin: auto;"> <div style="max-width: 600px; display: flex; justify-content: center; margin: auto;">
<div id="qr-toggle-button" @click=${() => this.showQRCode()} class="q-button outlined"> ${translate(this.translateButtonKey)} </div> <div id="qr-toggle-button" @click=${() => this.showQRCode()} class="q-button outlined"> ${translate(this.translateButtonKey)} </div>
</div> </div>
<div id="login-qr-code" style="display: none;"> <div id="login-qr-code" style="display: none;">
<qortal-qrcode-generator id="login-qr-code" data="${this.savedWalletDataJson}" mode="octet" format="html" auto></qortal-qrcode-generator> <qortal-qrcode-generator id="login-qr-code" data="${this.savedWalletDataJson}" mode="octet" format="html" auto></qortal-qrcode-generator>
</div> </div>
@ -114,25 +57,33 @@ class QRLoginView extends connect(store)(LitElement) {
const state = store.getState() const state = store.getState()
const address0 = state.app.wallet._addresses[0].address const address0 = state.app.wallet._addresses[0].address
const savedWalletData = state.user.storedWallets && state.user.storedWallets[address0] const savedWalletData = state.user.storedWallets && state.user.storedWallets[address0]
return !!savedWalletData return !!savedWalletData
} }
async setSavedWalletDataJson() { async setSavedWalletDataJson() {
const state = store.getState() const state = store.getState()
let data let data
if (this.isWalletStored()) { // if the wallet is stored, we use the existing encrypted backup
if (this.isWalletStored()) {
const address0 = state.app.wallet._addresses[0].address const address0 = state.app.wallet._addresses[0].address
data = state.user.storedWallets[address0] data = state.user.storedWallets[address0]
} else { // if the wallet is not stored, we generate new `saveWalletData` backup encrypted with the new password } else {
const password = this.shadowRoot.getElementById('newWalletPassword').value const password = this.shadowRoot.getElementById('newWalletPassword').value
data = await state.app.wallet.generateSaveWalletData(password, state.config.crypto.kdfThreads, () => { }) data = await state.app.wallet.generateSaveWalletData(password, state.config.crypto.kdfThreads, () => { })
} }
this.savedWalletDataJson = JSON.stringify(data) this.savedWalletDataJson = JSON.stringify(data)
} }
async showQRCode() { async showQRCode() {
await this.setSavedWalletDataJson() await this.setSavedWalletDataJson()
let el = this.shadowRoot.getElementById('login-qr-code') let el = this.shadowRoot.getElementById('login-qr-code')
el.style.display = 'flex' el.style.display = 'flex'
} }
} }

View File

@ -1,6 +1,6 @@
import {css, html, LitElement} from 'lit' import { html, LitElement } from 'lit'
import { connect } from 'pwa-helpers' import { connect } from 'pwa-helpers'
import {store} from '../../store.js' import { store } from '../../store'
import { import {
allowQAPPAutoAuth, allowQAPPAutoAuth,
allowQAPPAutoFriendsList, allowQAPPAutoFriendsList,
@ -9,16 +9,18 @@ import {
removeQAPPAutoFriendsList, removeQAPPAutoFriendsList,
removeQAPPAutoLists, removeQAPPAutoLists,
setIsOpenDevDialog setIsOpenDevDialog
} from '../../redux/app/app-actions.js' } from '../../redux/app/app-actions'
import {get, translate} from '../../../translate' import { securityViewStyles } from '../../styles/core-css'
import snackbar from '../../functional-components/snackbar.js'
import FileSaver from 'file-saver' import FileSaver from 'file-saver'
import snackbar from '../../functional-components/snackbar'
import '@material/mwc-checkbox' import '@material/mwc-checkbox'
import '@material/mwc-textfield'
import '@material/mwc-icon' import '@material/mwc-icon'
import '@material/mwc-textfield'
import '@vaadin/password-field/vaadin-password-field.js' import '@vaadin/password-field/vaadin-password-field.js'
// Multi language support
import { get, translate } from '../../../translate'
class SecurityView extends connect(store)(LitElement) { class SecurityView extends connect(store)(LitElement) {
static get properties() { static get properties() {
return { return {
@ -29,79 +31,7 @@ class SecurityView extends connect(store)(LitElement) {
} }
static get styles() { static get styles() {
return css` return [securityViewStyles]
* {
--lumo-primary-text-color: rgb(0, 167, 245);
--lumo-primary-color-50pct: rgba(0, 167, 245, 0.5);
--lumo-primary-color-10pct: rgba(0, 167, 245, 0.1);
--lumo-primary-color: hsl(199, 100%, 48%);
--lumo-base-color: var(--white);
--lumo-body-text-color: var(--black);
--lumo-secondary-text-color: var(--sectxt);
--lumo-contrast-60pct: var(--vdicon);
--mdc-checkbox-unchecked-color: var(--black);
--mdc-theme-on-surface: var(--black);
--mdc-checkbox-disabled-color: var(--black);
--mdc-checkbox-ink-color: var(--black);
}
.center-box {
position: relative;
top: 45%;
left: 50%;
transform: translate(-50%, 0%);
text-align: center;
}
.checkbox-row {
position: relative;
display: flex;
align-items: center;
align-content: center;
font-family: Montserrat, sans-serif;
font-weight: 600;
color: var(--black);
}
.q-button {
display: inline-flex;
flex-direction: column;
justify-content: center;
align-content: center;
border: none;
border-radius: 20px;
padding-left: 25px;
padding-right: 25px;
color: white;
background: #03a9f4;
width: 50%;
font-size: 17px;
cursor: pointer;
height: 50px;
margin-top: 1rem;
text-transform: uppercase;
text-decoration: none;
transition: all .2s;
position: relative;
}
.add-dev-button {
margin-top: 4px;
max-height: 28px;
padding: 5px 5px;
font-size: 14px;
background-color: #03a9f4;
color: white;
border: 1px solid transparent;
border-radius: 3px;
cursor: pointer;
}
.add-dev-button:hover {
opacity: 0.8;
cursor: pointer;
}
`
} }
constructor() { constructor() {
@ -125,8 +55,7 @@ class SecurityView extends connect(store)(LitElement) {
id="downloadBackupPassword" id="downloadBackupPassword"
helper-text="${translate("login.passwordhint")}" helper-text="${translate("login.passwordhint")}"
autofocus autofocus
> ></vaadin-password-field>
</vaadin-password-field>
</div> </div>
<div style="max-width: 500px; display: flex; justify-content: center; margin: auto;"> <div style="max-width: 500px; display: flex; justify-content: center; margin: auto;">
<mwc-icon style="padding: 10px; padding-left:0; padding-top: 42px;">password</mwc-icon> <mwc-icon style="padding: 10px; padding-left:0; padding-top: 42px;">password</mwc-icon>
@ -134,8 +63,7 @@ class SecurityView extends connect(store)(LitElement) {
style="width: 100%; color: var(--black);" style="width: 100%; color: var(--black);"
label="${translate("login.confirmpass")}" label="${translate("login.confirmpass")}"
id="rePassword" id="rePassword"
> ></vaadin-password-field>
</vaadin-password-field>
</div> </div>
<div style="text-align: center; color: var(--mdc-theme-error); text-transform: uppercase; font-size: 15px;"> <div style="text-align: center; color: var(--mdc-theme-error); text-transform: uppercase; font-size: 15px;">
${this.backupErrorMessage} ${this.backupErrorMessage}
@ -164,11 +92,7 @@ class SecurityView extends connect(store)(LitElement) {
<mwc-checkbox style="margin-right: -15px;" id="authButton" @click=${(e) => this.checkForFriends(e)} ?checked=${store.getState().app.qAPPFriendsList}></mwc-checkbox> <mwc-checkbox style="margin-right: -15px;" id="authButton" @click=${(e) => this.checkForFriends(e)} ?checked=${store.getState().app.qAPPFriendsList}></mwc-checkbox>
</div> </div>
<div class="checkbox-row"> <div class="checkbox-row">
<button <button class="add-dev-button" title="${translate('tabmenu.tm18')}" @click=${this.openDevDialog}>
class="add-dev-button"
title="${translate('tabmenu.tm18')}"
@click=${this.openDevDialog}
>
${translate('tabmenu.tm38')} ${translate('tabmenu.tm38')}
</button> </button>
</div> </div>
@ -176,9 +100,6 @@ class SecurityView extends connect(store)(LitElement) {
` `
} }
stateChanged(state) {
}
checkForAuth(e) { checkForAuth(e) {
if (e.target.checked) { if (e.target.checked) {
store.dispatch(removeQAPPAutoAuth(false)) store.dispatch(removeQAPPAutoAuth(false))
@ -225,13 +146,17 @@ class SecurityView extends connect(store)(LitElement) {
async downloadBackup() { async downloadBackup() {
let backupname = '' let backupname = ''
this.backupErrorMessage = '' this.backupErrorMessage = ''
const state = store.getState() const state = store.getState()
const password = this.shadowRoot.getElementById('downloadBackupPassword').value const password = this.shadowRoot.getElementById('downloadBackupPassword').value
const data = await state.app.wallet.generateSaveWalletData(password, state.config.crypto.kdfThreads, () => { }) const data = await state.app.wallet.generateSaveWalletData(password, state.config.crypto.kdfThreads, () => { })
const dataString = JSON.stringify(data) const dataString = JSON.stringify(data)
const blob = new Blob([dataString], { type: 'text/plain;charset=utf-8' }) const blob = new Blob([dataString], { type: 'text/plain;charset=utf-8' })
backupname = "qortal_backup_" + state.app.selectedAddress.address + ".json"
backupname = 'qortal_backup_' + state.app.selectedAddress.address + '.json'
await this.saveFileToDisk(blob, backupname) await this.saveFileToDisk(blob, backupname)
} }
@ -240,26 +165,33 @@ class SecurityView extends connect(store)(LitElement) {
const fileHandle = await self.showSaveFilePicker({ const fileHandle = await self.showSaveFilePicker({
suggestedName: fileName, suggestedName: fileName,
types: [{ types: [{
description: "File", description: "File"
}] }]
}) })
const writeFile = async (fileHandle, contents) => { const writeFile = async (fileHandle, contents) => {
const writable = await fileHandle.createWritable() const writable = await fileHandle.createWritable()
await writable.write(contents) await writable.write(contents)
await writable.close() await writable.close()
} }
writeFile(fileHandle, blob).then(() => console.log("FILE SAVED")) writeFile(fileHandle, blob).then(() => console.log("FILE SAVED"))
let snack4string = get("general.save") let snack4string = get("general.save")
snackbar.add({ snackbar.add({
labelText: `${snack4string} ${fileName}`, labelText: `${snack4string} ${fileName}`,
dismiss: true dismiss: true
}) })
this.shadowRoot.getElementById('downloadBackupPassword').value = '' this.shadowRoot.getElementById('downloadBackupPassword').value = ''
this.shadowRoot.getElementById('rePassword').value = '' this.shadowRoot.getElementById('rePassword').value = ''
} catch (error) { } catch (error) {
if (error.name === 'AbortError') { if (error.name === 'AbortError') {
return return
} }
FileSaver.saveAs(blob, fileName) FileSaver.saveAs(blob, fileName)
this.shadowRoot.getElementById('downloadBackupPassword').value = '' this.shadowRoot.getElementById('downloadBackupPassword').value = ''
this.shadowRoot.getElementById('rePassword').value = '' this.shadowRoot.getElementById('rePassword').value = ''

View File

@ -1,16 +1,17 @@
import {css, html, LitElement} from 'lit' import { html, LitElement } from 'lit'
import { connect } from 'pwa-helpers' import { connect } from 'pwa-helpers'
import {store} from '../../store.js' import { store } from '../../store'
import {translate} from '../../../translate' import { userSettingsStyles } from '../../styles/core-css'
import './account-view'
import '@polymer/paper-dialog/paper-dialog.js' import './export-keys'
import './notifications-view'
import './qr-login-view'
import './security-view'
import '@material/mwc-button' import '@material/mwc-button'
import '@polymer/paper-dialog/paper-dialog.js'
import './account-view.js' // Multi language support
import './security-view.js' import { translate } from '../../../translate'
import './notifications-view.js'
import './qr-login-view.js'
import './export-keys.js'
class UserSettings extends connect(store)(LitElement) { class UserSettings extends connect(store)(LitElement) {
static get properties() { static get properties() {
@ -23,189 +24,7 @@ class UserSettings extends connect(store)(LitElement) {
} }
static get styles() { static get styles() {
return css` return [userSettingsStyles]
:host {
margin: 0;
width: 100%;
max-width: 100vw;
height: 100%;
max-height: 100vh;
background-color: var(--white);
color: var(--black);
line-height: 1.6;
}
.decline {
--mdc-theme-primary: var(--mdc-theme-error)
}
paper-dialog.userSettings {
width: 100%;
max-width: 100vw;
height: 100%;
max-height: 100vh;
background-color: var(--white);
color: var(--black);
line-height: 1.6;
overflow-y: auto;
}
.actions {
display:flex;
justify-content: space-between;
padding: 0 4em;
margin: 15px 0 -2px 0;
}
.close-icon {
font-size: 36px;
}
.close-icon:hover {
cursor: pointer;
opacity: .6;
}
.buttons {
text-align:right;
}
.container {
max-width: 90vw;
margin-left: auto;
margin-right: auto;
margin-top: 20px;
padding: .6em;
}
ul {
list-style: none;
padding: 0;
margin-bottom: 0;
}
.leftBar {
background-color: var(--white);
color: var(--black);
border: 1px solid var(--border);
padding: 20px 0 0 0;
border-radius: 5px;
}
.leftBar img {
margin: 0 auto;
width: 75%;
height: 75%;
text-align: center;
}
.leftBar .slug {
text-align: center;
margin-top: 20px;
color: var(--black);
font-size: 16px;
font-weight: 600;
margin-bottom: 7px;
}
.leftBar ul li {
border-bottom: 1px solid var(--border);
}
.leftBar ul li:last-child {
border-bottom: none;
}
.leftBar ul li a {
color: var(--black);
font-size: 16px;
font-weight: 400;
text-decoration: none;
padding: .9em;
display: block;
}
.leftBar ul li a i {
margin-right: 8px;
font-size: 16px;
}
.leftBar ul li a:hover {
background-color: var(--menuhover);
color: #515151;
}
.leftBar ul li:active {
border-bottom: none;
}
.leftBar ul li a.active {
color: #515151;
background-color: var(--menuactive);
border-left: 2px solid #515151;
margin-left: -2px;
}
.mainPage {
background-color: var(--white);
color: var(--black);
border: 1px solid var(--border);
padding: 20px 0 10px 0;
border-radius: 5px;
font-size: 16px;
text-align: center;
min-height: 460px;
height: auto;
overflow: auto;
}
@media(max-width:700px) {
.mainPage {
margin-top: 30px;
}
}
@media(min-width:765px) {
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
.actions {
display:flex;
justify-content: space-between;
padding: 0 4em;
margin: 15px 0 -25px 0;
}
.container {
padding: 2em;
}
.wrapper {
display: grid;
grid-template-columns: 1fr 3fr;
grid-gap: 30px;
}
.wrapper > .mainPage {
padding: 2em;
}
.leftBar {
text-align: left;
max-height: 403px;
max-width: 400px;
font-size: 16px;
}
.mainPage {
font-size: 16px;
}
}
`
} }
constructor() { constructor() {

File diff suppressed because it is too large Load Diff

View File

@ -1,19 +1,20 @@
import {css, html, LitElement} from 'lit'; import { html, LitElement } from 'lit'
import {connect} from 'pwa-helpers'; import { asyncReplace } from 'lit/directives/async-replace.js'
import {store} from '../store.js'; import { connect } from 'pwa-helpers'
import {get, translate} from '../../translate' import { store } from '../store.js'
import {asyncReplace} from 'lit/directives/async-replace.js'; import { routes } from '../plugins/routes'
import { startMintingStyles } from '../styles/core-css'
import '../functional-components/my-button.js'; import '../functional-components/my-button'
import {routes} from '../plugins/routes.js';
import "@material/mwc-button" import "@material/mwc-button"
import '@material/mwc-dialog' import '@material/mwc-dialog'
// Multi language support
import { get, translate } from '../../translate'
async function* countDown(count, callback) { async function* countDown(count, callback) {
while (count > 0) { while (count > 0) {
yield count--; yield count--
await new Promise((r) => setTimeout(r, 1000)); await new Promise((r) => setTimeout(r, 1000))
if (count === 0) { if (count === 0) {
callback() callback()
} }
@ -30,187 +31,56 @@ class StartMinting extends connect(store)(LitElement) {
status: { type: Number }, status: { type: Number },
timer: { type: Number }, timer: { type: Number },
privateRewardShareKey: { type: String } privateRewardShareKey: { type: String }
}; }
} }
static get styles() { static get styles() {
return [ return [startMintingStyles]
css`
p, h1 {
color: var(--black)
}
.dialogCustom {
position: fixed;
z-index: 10000;
display: flex;
justify-content: center;
flex-direction: column;
align-items: center;
top: 0px;
bottom: 0px;
left: 0px;
width: 100vw;
}
.dialogCustomInner {
width: 300px;
min-height: 400px;
background-color: var(--white);
box-shadow: var(--mdc-dialog-box-shadow, 0px 11px 15px -7px rgba(0, 0, 0, 0.2), 0px 24px 38px 3px rgba(0, 0, 0, 0.14), 0px 9px 46px 8px rgba(0, 0, 0, 0.12));
padding: 20px 24px;
border-radius: 4px;
}
.dialogCustomInner ul {
padding-left: 0px
}
.dialogCustomInner li {
margin-bottom: 10px;
}
.start-minting-wrapper {
position: absolute;
transform: translate(50%, 20px);
z-index: 10;
}
.dialog-header h1 {
font-size: 18px;
}
.row {
display: flex;
width: 100%;
align-items: center;
}
.modalFooter {
width: 100%;
display: flex;
justify-content: flex-end;
}
.hide {
visibility: hidden
}
.inactiveText {
opacity: .60
}
.column {
display: flex;
flex-direction: column;
width: 100%;
}
.smallLoading,
.smallLoading:after {
border-radius: 50%;
width: 2px;
height: 2px;
}
.smallLoading {
border-width: 0.6em;
border-style: solid;
border-color: rgba(3, 169, 244, 0.2) rgba(3, 169, 244, 0.2)
rgba(3, 169, 244, 0.2) rgb(3, 169, 244);
font-size: 10px;
position: relative;
text-indent: -9999em;
transform: translateZ(0px);
animation: 1.1s linear 0s infinite normal none running loadingAnimation;
}
@-webkit-keyframes loadingAnimation {
0% {
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
-webkit-transform: rotate(360deg);
transform: rotate(360deg);
}
}
@keyframes loadingAnimation {
0% {
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
-webkit-transform: rotate(360deg);
transform: rotate(360deg);
}
}
.word-break {
word-break:break-all;
}
.dialog-container {
width: 300px;
min-height: 300px;
max-height: 75vh;
padding: 5px;
display: flex;
align-items: flex-start;
flex-direction: column;
}
.between {
justify-content: space-between;
}
.no-width {
width: auto
}
.between p {
margin: 0;
padding: 0;
color: var(--black);
}
.marginLoader {
margin-left: 10px;
}
.marginRight {
margin-right: 10px;
}
.warning{
display: flex;
flex-grow: 1
}
.message-error {
color: var(--error);
}
`,
];
} }
constructor() { constructor() {
super(); super()
this.addressInfo = {}; this.addressInfo = {}
this.mintingAccountData = []; this.mintingAccountData = []
this.errorMsg = ''; this.errorMsg = ''
this.openDialogRewardShare = false; this.openDialogRewardShare = false
this.status = 0; this.status = 0
this.privateRewardShareKey = ""; this.privateRewardShareKey = ''
this.address = this.getAddress(); this.address = this.getAddress()
this.nonce = this.getNonce(); this.nonce = this.getNonce()
this.base58PublicKey = this.getBase58PublicKey() this.base58PublicKey = this.getBase58PublicKey()
} }
getBase58PublicKey(){
const appState = window.parent.reduxStore.getState().app;
const selectedAddress = appState && appState.selectedAddress;
const base58PublicKey = selectedAddress && selectedAddress.base58PublicKey;
return base58PublicKey || ""
}
getAddress(){
const appState = window.parent.reduxStore.getState().app;
const selectedAddress = appState && appState.selectedAddress;
const address = selectedAddress && selectedAddress.address;
return address || ""
}
getNonce(){
const appState = window.parent.reduxStore.getState().app;
const selectedAddress = appState && appState.selectedAddress;
const nonce = selectedAddress && selectedAddress.nonce;
return nonce || ""
}
render() { render() {
return html` ${this.renderStartMintingButton()} `; return html`${this.renderStartMintingButton()}`
} }
firstUpdated() { firstUpdated() {
this.getMintingAcccounts(); this.getMintingAcccounts()
}
getBase58PublicKey() {
const appState = window.parent.reduxStore.getState().app
const selectedAddress = appState && appState.selectedAddress
const base58PublicKey = selectedAddress && selectedAddress.base58PublicKey
return base58PublicKey || ''
}
getAddress() {
const appState = window.parent.reduxStore.getState().app
const selectedAddress = appState && appState.selectedAddress
const address = selectedAddress && selectedAddress.address
return address || ''
}
getNonce() {
const appState = window.parent.reduxStore.getState().app
const selectedAddress = appState && appState.selectedAddress
const nonce = selectedAddress && selectedAddress.nonce
return nonce || ''
} }
renderErrorMsg1() { renderErrorMsg1() {
@ -230,124 +100,130 @@ const nonce = selectedAddress && selectedAddress.nonce;
} }
async getMintingAcccounts() { async getMintingAcccounts() {
const myNode = store.getState().app.nodeConfig.knownNodes[store.getState().app.nodeConfig.node]; const myNode = store.getState().app.nodeConfig.knownNodes[store.getState().app.nodeConfig.node]
const nodeUrl = myNode.protocol + '://' + myNode.domain + ':' + myNode.port; const nodeUrl = myNode.protocol + '://' + myNode.domain + ':' + myNode.port
const url = `${nodeUrl}/admin/mintingaccounts`; const url = `${nodeUrl}/admin/mintingaccounts`
try { try {
const res = await fetch(url); const res = await fetch(url)
this.mintingAccountData = await res.json();
this.mintingAccountData = await res.json()
} catch (error) { } catch (error) {
this.errorMsg = this.renderErrorMsg1(); this.errorMsg = this.renderErrorMsg1()
} }
} }
async changeStatus(value) { async changeStatus(value) {
const myNode = store.getState().app.nodeConfig.knownNodes[store.getState().app.nodeConfig.node]; const myNode = store.getState().app.nodeConfig.knownNodes[store.getState().app.nodeConfig.node]
const nodeUrl = myNode.protocol + '://' + myNode.domain + ':' + myNode.port; const nodeUrl = myNode.protocol + '://' + myNode.domain + ':' + myNode.port
this.status = value
const address = this.address const address = this.address
this.status = value
// Check to see if a sponsorship key on a newly-level 1 minter exists. If it does, remove it. // Check to see if a sponsorship key on a newly-level 1 minter exists. If it does, remove it.
const findMintingAccountFromOtherUser = this.mintingAccountData.find((ma) => ma.recipientAccount === address && ma.mintingAccount !== address); const findMintingAccountFromOtherUser = this.mintingAccountData.find((ma) => ma.recipientAccount === address && ma.mintingAccount !== address)
const removeMintingAccount = async (publicKey) => { const removeMintingAccount = async (publicKey) => {
const url = `${nodeUrl}/admin/mintingaccounts?apiKey=${myNode.apiKey}`; const url = `${nodeUrl}/admin/mintingaccounts?apiKey=${myNode.apiKey}`
return await fetch(url, { return await fetch(url, {
method: 'DELETE', method: 'DELETE',
body: publicKey, body: publicKey
}); })
}; }
const addMintingAccount = async (sponsorshipKeyValue) => { const addMintingAccount = async (sponsorshipKeyValue) => {
const url = `${nodeUrl}/admin/mintingaccounts?apiKey=${myNode.apiKey}`; const url = `${nodeUrl}/admin/mintingaccounts?apiKey=${myNode.apiKey}`
return await fetch(url, { return await fetch(url, {
method: 'POST', method: 'POST',
body: sponsorshipKeyValue, body: sponsorshipKeyValue
}); })
}; }
try { try {
if ( if (findMintingAccountFromOtherUser && findMintingAccountFromOtherUser.publicKey && findMintingAccountFromOtherUser.publicKey[0]) {
findMintingAccountFromOtherUser &&
findMintingAccountFromOtherUser.publicKey &&
findMintingAccountFromOtherUser.publicKey[0]
) {
await removeMintingAccount( await removeMintingAccount(
findMintingAccountFromOtherUser.publicKey[0] findMintingAccountFromOtherUser.publicKey[0]
); )
} }
} catch (error) { } catch (error) {
this.errorMsg = this.renderErrorMsg2(); this.errorMsg = this.renderErrorMsg2()
return;
return
} }
try { try {
await addMintingAccount(this.privateRewardShareKey); await addMintingAccount(this.privateRewardShareKey)
await routes.showSnackBar({ await routes.showSnackBar({
data: translate('becomeMinterPage.bchange19'), data: translate('becomeMinterPage.bchange19')
}); })
this.status = 5;
await this.getMintingAcccounts(); this.status = 5
await this.getMintingAcccounts()
} catch (error) { } catch (error) {
this.errorMsg = this.renderErrorMsg3(); this.errorMsg = this.renderErrorMsg3()
} }
} }
async confirmRelationship() { async confirmRelationship() {
const myNode = const myNode = store.getState().app.nodeConfig.knownNodes[store.getState().app.nodeConfig.node]
store.getState().app.nodeConfig.knownNodes[ const nodeUrl = myNode.protocol + '://' + myNode.domain + ':' + myNode.port
store.getState().app.nodeConfig.node
];
const nodeUrl = myNode.protocol + '://' + myNode.domain + ':' + myNode.port;
let interval = null let interval = null
let stop = false let stop = false
this.status = 2 this.status = 2
const getAnswer = async () => { const getAnswer = async () => {
const rewardShares = async (minterAddr) => { const rewardShares = async (minterAddr) => {
const url = `${nodeUrl}/addresses/rewardshares?minters=${minterAddr}&recipients=${minterAddr}`; const url = `${nodeUrl}/addresses/rewardshares?minters=${minterAddr}&recipients=${minterAddr}`
const res = await fetch(url); const res = await fetch(url)
return await res.json();
}; return await res.json()
}
if (!stop) { if (!stop) {
stop = true; stop = true
try { try {
const address = this.address const address = this.address
const myRewardShareArray = await rewardShares(address); const myRewardShareArray = await rewardShares(address)
if (myRewardShareArray.length > 0) { if (myRewardShareArray.length > 0) {
clearInterval(interval) clearInterval(interval)
this.status = 3
this.timer = countDown(180, () => this.changeStatus(4));
}
} catch (error) { this.status = 3
this.timer = countDown(180, () => this.changeStatus(4))
} }
} catch (error) { }
stop = false stop = false
} }
}; }
interval = setInterval(getAnswer, 5000);
interval = setInterval(getAnswer, 5000)
} }
renderStartMintingButton() { renderStartMintingButton() {
const myNode = store.getState().app.nodeConfig.knownNodes[store.getState().app.nodeConfig.node]; const myNode = store.getState().app.nodeConfig.knownNodes[store.getState().app.nodeConfig.node]
const nodeUrl = myNode.protocol + '://' + myNode.domain + ':' + myNode.port; const nodeUrl = myNode.protocol + '://' + myNode.domain + ':' + myNode.port
const mintingAccountData = this.mintingAccountData; const mintingAccountData = this.mintingAccountData
const addressInfo = window.parent.reduxStore.getState().app.accountInfo.addressInfo const addressInfo = window.parent.reduxStore.getState().app.accountInfo.addressInfo
const address = this.address const address = this.address
const nonce = this.nonce const nonce = this.nonce
const publicAddress = this.base58PublicKey const publicAddress = this.base58PublicKey
const findMintingAccount = mintingAccountData.find((ma) => ma.mintingAccount === address); const findMintingAccount = mintingAccountData.find((ma) => ma.mintingAccount === address)
const isMinterButKeyMintingKeyNotAssigned = addressInfo && addressInfo.error !== 124 && addressInfo.level >= 1 && !findMintingAccount; const isMinterButKeyMintingKeyNotAssigned = addressInfo && addressInfo.error !== 124 && addressInfo.level >= 1 && !findMintingAccount
const makeTransactionRequest = async (lastRef) => { const makeTransactionRequest = async (lastRef) => {
let mylastRef = lastRef; let mylastRef = lastRef
let rewarddialog1 = get('transactions.rewarddialog1'); let rewarddialog1 = get('transactions.rewarddialog1')
let rewarddialog2 = get('transactions.rewarddialog2'); let rewarddialog2 = get('transactions.rewarddialog2')
let rewarddialog3 = get('transactions.rewarddialog3'); let rewarddialog3 = get('transactions.rewarddialog3')
let rewarddialog4 = get('transactions.rewarddialog4'); let rewarddialog4 = get('transactions.rewarddialog4')
return await routes.transaction({ return await routes.transaction({
data: { data: {
@ -360,90 +236,93 @@ const nonce = selectedAddress && selectedAddress.nonce;
rewarddialog1: rewarddialog1, rewarddialog1: rewarddialog1,
rewarddialog2: rewarddialog2, rewarddialog2: rewarddialog2,
rewarddialog3: rewarddialog3, rewarddialog3: rewarddialog3,
rewarddialog4: rewarddialog4, rewarddialog4: rewarddialog4
}
}, },
}, disableModal: true
disableModal: true, })
}); }
};
const getTxnRequestResponse = (txnResponse) => { const getTxnRequestResponse = (txnResponse) => {
let err6string = get('rewardsharepage.rchange21'); let err6string = get('rewardsharepage.rchange21')
if (txnResponse && txnResponse.extraData && txnResponse.extraData.rewardSharePrivateKey && if (txnResponse && txnResponse.extraData && txnResponse.extraData.rewardSharePrivateKey &&
txnResponse.data && (txnResponse.data.message && (txnResponse.data.message.includes('multiple') || txnResponse.data.message.includes('SELF_SHARE_EXISTS')))) { txnResponse.data && (txnResponse.data.message && (txnResponse.data.message.includes('multiple') || txnResponse.data.message.includes('SELF_SHARE_EXISTS')))) {
return err6string; return err6string
} }
if (txnResponse.success === false && txnResponse.message) { if (txnResponse.success === false && txnResponse.message) {
throw txnResponse; throw txnResponse
} else if (txnResponse.success === true && txnResponse.data && !txnResponse.data.error) { } else if (txnResponse.success === true && txnResponse.data && !txnResponse.data.error) {
return err6string; return err6string
} else { } else {
throw txnResponse; throw txnResponse
}
} }
};
const createSponsorshipKey = async () => { const createSponsorshipKey = async () => {
this.status = 1; this.status = 1
let lastRef = await getLastRef();
let myTransaction = await makeTransactionRequest(lastRef); let lastRef = await getLastRef()
getTxnRequestResponse(myTransaction); let myTransaction = await makeTransactionRequest(lastRef)
getTxnRequestResponse(myTransaction)
if (myTransaction && myTransaction.extraData) { if (myTransaction && myTransaction.extraData) {
return myTransaction.extraData.rewardSharePrivateKey; return myTransaction.extraData.rewardSharePrivateKey
}
} }
};
const getLastRef = async () => { const getLastRef = async () => {
const url = `${nodeUrl}/addresses/lastreference/${address}`; const url = `${nodeUrl}/addresses/lastreference/${address}`
const res = await fetch(url); const res = await fetch(url)
return await res.text();
}; return await res.text()
}
const startMinting = async () => { const startMinting = async () => {
this.openDialogRewardShare = true this.openDialogRewardShare = true
this.errorMsg = ''; this.errorMsg = ''
const address = this.address const address = this.address
const findMintingAccountsFromUser = this.mintingAccountData.filter((ma) => ma.recipientAccount === address && ma.mintingAccount === address); const findMintingAccountsFromUser = this.mintingAccountData.filter((ma) => ma.recipientAccount === address && ma.mintingAccount === address)
if (findMintingAccountsFromUser.length > 2) { if (findMintingAccountsFromUser.length > 2) {
this.errorMsg = translate("startminting.smchange10") this.errorMsg = translate("startminting.smchange10")
return;
return
} }
try { try {
this.privateRewardShareKey = await createSponsorshipKey(); this.privateRewardShareKey = await createSponsorshipKey()
await this.confirmRelationship(publicAddress) await this.confirmRelationship(publicAddress)
} catch (error) { } catch (error) {
console.log({ error }) console.log({ error })
this.errorMsg = (error && error.data && error.data.message) ? error.data.message : this.renderErrorMsg4();
this.errorMsg = (error && error.data && error.data.message) ? error.data.message : this.renderErrorMsg4()
}
} }
};
return html` return html`
${isMinterButKeyMintingKeyNotAssigned ? html` ${isMinterButKeyMintingKeyNotAssigned ? html`
<div class="start-minting-wrapper"> <div class="start-minting-wrapper">
<my-button label="${translate('becomeMinterPage.bchange18')}" <my-button
label="${translate('becomeMinterPage.bchange18')}"
?isLoading=${false} ?isLoading=${false}
.onClick=${async () => { .onClick=${async () => {
await startMinting(); await startMinting();
if (this.errorMsg) { if (this.errorMsg) {
await routes.showSnackBar({ await routes.showSnackBar({
data: this.errorMsg, data: this.errorMsg
}); });
} }
}} }}
> ></my-button>
</my-button>
</div> </div>
<!-- Dialog for tracking the progress of starting minting --> <!-- Dialog for tracking the progress of starting minting -->
${this.openDialogRewardShare ? html` ${this.openDialogRewardShare ? html`
<div class="dialogCustom"> <div class="dialogCustom">
<div class="dialogCustomInner"> <div class="dialogCustomInner">
@ -457,73 +336,50 @@ const nonce = selectedAddress && selectedAddress.nonce;
<div class="dialog-container"> <div class="dialog-container">
<ul> <ul>
<li class="row between"> <li class="row between">
<p> <p>1. ${translate("startminting.smchange5")}</p>
1. ${translate("startminting.smchange5")}
</p>
<div class=${`smallLoading marginLoader ${this.status !== 1 && 'hide'}`}></div> <div class=${`smallLoading marginLoader ${this.status !== 1 && 'hide'}`}></div>
</li> </li>
<li class=${`row between ${this.status < 2 && 'inactiveText'}`}> <li class=${`row between ${this.status < 2 && 'inactiveText'}`}>
<p> <p>2. ${translate("startminting.smchange6")}</p>
2. ${translate("startminting.smchange6")}
</p>
<div class=${`smallLoading marginLoader ${this.status !== 2 && 'hide'}`}></div> <div class=${`smallLoading marginLoader ${this.status !== 2 && 'hide'}`}></div>
</li> </li>
<li class=${`row between ${this.status < 3 && 'inactiveText'}`}> <li class=${`row between ${this.status < 3 && 'inactiveText'}`}>
<p> <p>3. ${translate("startminting.smchange7")}</p>
3. ${translate("startminting.smchange7")}
</p>
<div class="row no-width"> <div class="row no-width">
<div class=${`smallLoading marginLoader marginRight ${this.status !== 3 && 'hide'}`} ></div> <p>${asyncReplace(this.timer)}</p> <div class=${`smallLoading marginLoader marginRight ${this.status !== 3 && 'hide'}`} ></div>
<p>${asyncReplace(this.timer)}</p>
</div> </div>
</li> </li>
<li class=${`row between ${this.status < 4 && 'inactiveText'}`}> <li class=${`row between ${this.status < 4 && 'inactiveText'}`}>
<p> <p>4. ${translate("startminting.smchange8")}</p>
4. ${translate("startminting.smchange8")}
</p>
<div class=${`smallLoading marginLoader ${this.status !== 4 && 'hide'}`}></div> <div class=${`smallLoading marginLoader ${this.status !== 4 && 'hide'}`}></div>
</li> </li>
<li class=${`row between ${this.status < 5 && 'inactiveText'}`}> <li class=${`row between ${this.status < 5 && 'inactiveText'}`}>
<p> <p>5. ${translate("startminting.smchange9")}</p>
5. ${translate("startminting.smchange9")}
</p>
</li> </li>
</ul> </ul>
<div class="warning column"> <div class="warning column">
<p> <p>Warning: do not close the Qortal UI until completion!</p>
Warning: do not close the Qortal UI until completion!
</p>
<p class="message-error">${this.errorMsg}</p> <p class="message-error">${this.errorMsg}</p>
</div> </div>
</div> </div>
<div class="modalFooter"> <div class="modalFooter">
${this.errorMsg || this.status === 5 ? html` ${this.errorMsg || this.status === 5 ? html`
<mwc-button <mwc-button slot="primaryAction" @click=${() => { this.openDialogRewardShare = false; this.errorMsg = '';}} class="red">
slot="primaryAction"
@click=${() => {
this.openDialogRewardShare = false
this.errorMsg = ''
}}
class="red"
>
${translate("general.close")} ${translate("general.close")}
</mwc-button> </mwc-button>
` : ''} ` : ''}
</div> </div>
</div> </div>
</div> </div>
` : ""}
` : ''} ` : ''}
`; ` : ''}
`
} }
stateChanged(state) { stateChanged(state) {
this.addressInfo = state.app.accountInfo.addressInfo; this.addressInfo = state.app.accountInfo.addressInfo
} }
} }
window.customElements.define('start-minting', StartMinting); window.customElements.define('start-minting', StartMinting)

View File

@ -1,9 +1,12 @@
import {css, html, LitElement} from 'lit' import { html, LitElement } from 'lit'
import {translate} from '../../translate' import { themeToggleStyles } from '../styles/core-css'
import '@polymer/paper-icon-button/paper-icon-button.js' import '@polymer/paper-icon-button/paper-icon-button.js'
import '@polymer/iron-icons/image-icons.js' import '@polymer/iron-icons/image-icons.js'
import '@polymer/iron-icons/iron-icons.js' import '@polymer/iron-icons/iron-icons.js'
// Multi language support
import { translate } from '../../translate'
class ThemeToggle extends LitElement { class ThemeToggle extends LitElement {
static get properties() { static get properties() {
return { return {
@ -11,58 +14,15 @@ class ThemeToggle extends LitElement {
} }
} }
static get styles() {
return [themeToggleStyles]
}
constructor() { constructor() {
super() super()
this.theme = localStorage.getItem('qortalTheme') ? localStorage.getItem('qortalTheme') : 'light' this.theme = localStorage.getItem('qortalTheme') ? localStorage.getItem('qortalTheme') : 'light'
} }
static styles = [
css`
* {
--mdc-theme-primary: rgb(3, 169, 244);
--mdc-theme-secondary: var(--mdc-theme-primary);
--mdc-theme-error: rgb(255, 89, 89);
--lumo-primary-text-color: rgb(0, 167, 245);
--lumo-primary-color-50pct: rgba(0, 167, 245, 0.5);
--lumo-primary-color-10pct: rgba(0, 167, 245, 0.1);
--lumo-primary-color: hsl(199, 100%, 48%);
--lumo-base-color: var(--white);
--lumo-body-text-color: var(--black);
--lumo-secondary-text-color: var(--sectxt);
--lumo-contrast-60pct: var(--vdicon);
--item-selected-color: var(--nav-selected-color);
--item-selected-color-text: var(--nav-selected-color-text);
--item-color-active: var(--nav-color-active);
--item-color-hover: var(--nav-color-hover);
--item-text-color: var(--nav-text-color);
--item-icon-color: var(--nav-icon-color);
--item-border-color: var(--nav-border-color);
--item-border-selected-color: var(--nav-border-selected-color);
}
paper-icon-button {
-ms-transform: rotate(120deg);
transform: rotate(120deg);
}
:host([theme="light"]) .light-mode {
display: inline-block;
}
:host([theme="light"]) .dark-mode {
display: none;
}
:host([theme="dark"]) .light-mode {
display: none;
}
:host([theme="dark"]) .dark-mode {
display: inline-block;
}
`
]
render() { render() {
return html` return html`
<div style="display: inline;"> <div style="display: inline;">
@ -87,16 +47,17 @@ class ThemeToggle extends LitElement {
this.dispatchEvent(new CustomEvent('qort-theme-change', { this.dispatchEvent(new CustomEvent('qort-theme-change', {
bubbles: true, bubbles: true,
composed: true, composed: true,
detail: this.theme, detail: this.theme
})) }))
window.localStorage.setItem('qortalTheme', this.theme) window.localStorage.setItem('qortalTheme', this.theme)
this.initTheme() this.initTheme()
} }
initTheme() { initTheme() {
document.querySelector('html').setAttribute('theme', this.theme); document.querySelector('html').setAttribute('theme', this.theme)
} }
} }
window.customElements.define('theme-toggle', ThemeToggle); window.customElements.define('theme-toggle', ThemeToggle)

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,9 @@
import {css, html, LitElement} from 'lit' import { html, LitElement } from 'lit'
import { connect } from 'pwa-helpers' import { connect } from 'pwa-helpers'
import {store} from '../store.js' import { store } from '../store'
import { walletProfileStyles } from '../styles/core-css'
// Multi language support
import { translate } from '../../translate' import { translate } from '../../translate'
class WalletProfile extends connect(store)(LitElement) { class WalletProfile extends connect(store)(LitElement) {
@ -14,56 +17,7 @@ class WalletProfile extends connect(store)(LitElement) {
} }
static get styles() { static get styles() {
return css` return [walletProfileStyles]
#profileInMenu {
padding: 12px;
border-top: var(--border);
background: var(--sidetopbar);
color: var(--black);
}
#accountName {
margin: 0;
font-size: 18px;
font-weight: 500;
width: 100%;
padding-bottom: 8px;
display: flex;
}
#blocksMinted {
margin:0;
margin-top: 0;
font-size: 12px;
color: #03a9f4;
}
#address {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
margin:0;
margin-top: 8px;
font-size: 11px;
}
.round-fullinfo {
position: relative;
width: 68px;
height: 68px;
border-radius: 50%;
}
.full-info-logo {
width: 68px;
height: 68px;
border-radius: 50%;
}
.inline-block-child {
flex: 1;
}
`
} }
constructor() { constructor() {
@ -85,8 +39,16 @@ class WalletProfile extends connect(store)(LitElement) {
<div id="child inline-block-child" class="full-info-logo">${this.getAvatar()}</div> <div id="child inline-block-child" class="full-info-logo">${this.getAvatar()}</div>
&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;
<div id="inline-block-child"> <div id="inline-block-child">
<div>${this.accountInfo.names.length !== 0 ? this.accountInfo.names[0].name : ''}</div> <div>
<div>${this.accountInfo.addressInfo ? html`<span style="margin-bottom: 8px; display: inline-block; font-size: 14px;">${translate("walletprofile.minterlevel")} - <span style="color: #03a9f4;">${this.accountInfo.addressInfo.level} ${this.accountInfo.addressInfo.flags === 1 ? html`<strong>(F)</strong>` : ''}</span>` : ''}</div> ${this.accountInfo.names.length !== 0 ? this.accountInfo.names[0].name : ''}
</div>
<div>
${this.accountInfo.addressInfo ? html`
<span style="margin-bottom: 8px; display: inline-block; font-size: 14px;">
${translate("walletprofile.minterlevel")} - <span style="color: #03a9f4;">${this.accountInfo.addressInfo.level} ${this.accountInfo.addressInfo.flags === 1 ? html`<strong>(F)</strong>` : ''}
</span>
` : ''}
</div>
<p id="blocksMinted">${translate("walletprofile.blocksminted")} - ${this.accountInfo.addressInfo.blocksMinted + this.accountInfo.addressInfo.blocksMintedAdjustment}</p> <p id="blocksMinted">${translate("walletprofile.blocksminted")} - ${this.accountInfo.addressInfo.blocksMinted + this.accountInfo.addressInfo.blocksMintedAdjustment}</p>
</div> </div>
</div> </div>
@ -96,7 +58,9 @@ class WalletProfile extends connect(store)(LitElement) {
` `
} }
firstUpdated() {} firstUpdated() {
// ...
}
getAvatar() { getAvatar() {
if (this.accountInfo.names.length === 0) { if (this.accountInfo.names.length === 0) {
@ -104,16 +68,12 @@ class WalletProfile extends connect(store)(LitElement) {
} else { } else {
const avatarNode = store.getState().app.nodeConfig.knownNodes[store.getState().app.nodeConfig.node] const avatarNode = store.getState().app.nodeConfig.knownNodes[store.getState().app.nodeConfig.node]
const avatarUrl = avatarNode.protocol + '://' + avatarNode.domain + ':' + avatarNode.port const avatarUrl = avatarNode.protocol + '://' + avatarNode.domain + ':' + avatarNode.port
const url = `${avatarUrl}/arbitrary/THUMBNAIL/${this.accountInfo.names[0].name}/qortal_avatar?async=true&apiKey=${this.getApiKey()}` const url = `${avatarUrl}/arbitrary/THUMBNAIL/${this.accountInfo.names[0].name}/qortal_avatar?async=true`
return html`<img class="round-fullinfo" src="${url}" onerror="this.src='/img/incognito.png';" />` return html`<img class="round-fullinfo" src="${url}" onerror="this.src='/img/incognito.png';" />`
} }
} }
getApiKey() {
const apiNode = store.getState().app.nodeConfig.knownNodes[store.getState().app.nodeConfig.node]
return apiNode.apiKey
}
stateChanged(state) { stateChanged(state) {
this.wallet = state.app.wallet this.wallet = state.app.wallet
this.nodeConfig = state.app.nodeConfig this.nodeConfig = state.app.nodeConfig

View File

@ -1,4 +1,3 @@
'use strict'
const utils = { const utils = {
int32ToBytes(word) { int32ToBytes(word) {
var byteArray = [] var byteArray = []
@ -16,15 +15,20 @@ const utils = {
message[i] = s.charCodeAt(i) & 0xff message[i] = s.charCodeAt(i) & 0xff
} }
} }
return message return message
}, },
// ...buffers then buffers.foreach and append to buffer1 // ...buffers then buffers.foreach and append to buffer1
appendBuffer(buffer1, buffer2) { appendBuffer(buffer1, buffer2) {
buffer1 = new Uint8Array(buffer1) buffer1 = new Uint8Array(buffer1)
buffer2 = new Uint8Array(buffer2) buffer2 = new Uint8Array(buffer2)
const tmp = new Uint8Array(buffer1.byteLength + buffer2.byteLength) const tmp = new Uint8Array(buffer1.byteLength + buffer2.byteLength)
tmp.set(buffer1, 0) tmp.set(buffer1, 0)
tmp.set(buffer2, buffer1.byteLength) tmp.set(buffer2, buffer1.byteLength)
return tmp return tmp
}, },
@ -43,11 +47,14 @@ const utils = {
equal(buf1, buf2) { equal(buf1, buf2) {
if (buf1.byteLength != buf2.byteLength) return false if (buf1.byteLength != buf2.byteLength) return false
var dv1 = new Uint8Array(buf1) var dv1 = new Uint8Array(buf1)
var dv2 = new Uint8Array(buf2) var dv2 = new Uint8Array(buf2)
for (var i = 0; i != buf1.byteLength; i++) { for (var i = 0; i != buf1.byteLength; i++) {
if (dv1[i] != dv2[i]) return false if (dv1[i] != dv2[i]) return false
} }
return true return true
} }
} }

View File

@ -1,115 +0,0 @@
import {css, html, LitElement} from 'lit'
import '@material/mwc-button'
import '@material/mwc-icon'
import {translate} from '../../translate'
class FragFileInput extends LitElement {
static get properties () {
return {
accept: { type: String },
readAs: { type: String }
}
}
static get styles () {
return css`
#drop-area {
border: 2px dashed #ccc;
font-family: "Roboto", sans-serif;
padding: 20px;
}
#trigger:hover {
cursor: pointer;
}
#drop-area.highlight {
border-color: var(--mdc-theme-primary, #000);
}
p {
margin-top: 0;
}
form {
margin-bottom: 10px;
}
#fileInput {
display: none;
}
`
}
constructor () {
super()
this.readAs = this.readAs || 'Text'
}
render () {
return html`
<div id="drop-area">
<slot name="info-text"></slot>
<div style="line-height: 40px; text-align: center;">
<slot id="trigger" name="inputTrigger" @click=${() => this.shadowRoot.getElementById('fileInput').click()} style="dispay:inline;">
<mwc-button><mwc-icon>cloud_upload</mwc-icon><span style="color: var(--black);">&nbsp; ${translate("fragfile.selectfile")}</span></mwc-button>
</slot><br>
<span style="text-align: center; padding-top: 4px; color: var(--black);">${translate("fragfile.dragfile")}</span>
</div>
</div>
<input type="file" id="fileInput" accept="${this.accept}" @change="${e => this.readFile(e.target.files[0])}">
`
}
readFile (file) {
const fr = new FileReader()
fr.onload = () => {
this.dispatchEvent(new CustomEvent('file-read-success', {
detail: { result: fr.result },
bubbles: true,
composed: true
}))
}
fr['readAs' + this.readAs](file)
}
firstUpdated () {
this._dropArea = this.shadowRoot.getElementById('drop-area')
const preventDefaults = e => {
e.preventDefault()
e.stopPropagation()
}
;['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
this._dropArea.addEventListener(eventName, preventDefaults, false)
})
const highlight = e => {
this._dropArea.classList.add('highlight')
}
const unhighlight = e => {
this._dropArea.classList.remove('highlight')
}
;['dragenter', 'dragover'].forEach(eventName => {
this._dropArea.addEventListener(eventName, highlight, false)
})
;['dragleave', 'drop'].forEach(eventName => {
this._dropArea.addEventListener(eventName, unhighlight, false)
})
this._dropArea.addEventListener('drop', e => {
const dt = e.dataTransfer
const file = dt.files[0]
this.readFile(file)
}, false)
}
}
window.customElements.define('frag-file-input', FragFileInput)

View File

@ -1,4 +1,15 @@
export const defaultQappsTabs = [ export const defaultQappsTabs = [
{
"url": "myapp",
"domain": "core",
"page": "qdn/browser/index.html?name=Q-Support&service=APP",
"title": "Q-Support",
"icon": "vaadin:external-browser",
"mwcicon": "apps",
"pluginNumber": "plugin-04tlGdLkkd",
"menus": [],
"parent": false
},
{ {
"url": "myapp", "url": "myapp",
"domain": "core", "domain": "core",

View File

@ -1,12 +1,13 @@
import { css, html, LitElement } from 'lit' import { css, html, LitElement } from 'lit'
import { connect } from 'pwa-helpers' import { connect } from 'pwa-helpers'
import {store} from '../store.js' import { store } from '../store'
import {get, translate} from '../../translate' import { listenForRequest } from '../transactionRequest'
import { confirmTransactionDialogStyles } from '../styles/core-css'
import {listenForRequest} from '../transactionRequest.js'
import '@polymer/paper-dialog/paper-dialog.js'
import '@material/mwc-button' import '@material/mwc-button'
import '@polymer/paper-dialog/paper-dialog.js'
// Multi language support
import { get, translate } from '../../translate'
class ConfirmTransactionDialog extends connect(store)(LitElement) { class ConfirmTransactionDialog extends connect(store)(LitElement) {
static get properties() { static get properties() {
@ -17,35 +18,7 @@ class ConfirmTransactionDialog extends connect(store)(LitElement) {
} }
static get styles() { static get styles() {
return css` return [confirmTransactionDialogStyles]
* {
--mdc-theme-primary: rgb(3, 169, 244);
--mdc-theme-secondary: var(--mdc-theme-primary);
--mdc-theme-surface: var(--white);
--mdc-dialog-content-ink-color: var(--black);
}
.decline {
--mdc-theme-primary: var(--mdc-theme-error)
}
#txInfo {
text-align: left;
max-width: 520px;
color: var(--black);
}
.buttons {
text-align:right;
}
table td, th{
padding:4px;
text-align:left;
font-size:14px;
color: var(--black);
}
`
} }
constructor() { constructor() {
@ -55,7 +28,7 @@ class ConfirmTransactionDialog extends connect(store)(LitElement) {
} }
this.txInfo = html`` this.txInfo = html``
listenForRequest(args => this.requestTransaction(args)) listenForRequest(args => this.requestTransaction(args))
this.theme = localStorage.getItem('qortalTheme') ? localStorage.getItem('qortalTheme') : 'light'; this.theme = localStorage.getItem('qortalTheme') ? localStorage.getItem('qortalTheme') : 'light'
} }
render() { render() {
@ -73,8 +46,8 @@ class ConfirmTransactionDialog extends connect(store)(LitElement) {
` `
} }
stateChanged(state) { firstUpdated() {
this.loggedIn = state.app.loggedIn // ...
} }
requestTransaction(transaction) { requestTransaction(transaction) {
@ -98,6 +71,10 @@ class ConfirmTransactionDialog extends connect(store)(LitElement) {
const rejecterror = get("transactions.declined") const rejecterror = get("transactions.declined")
this._reject(new Error(rejecterror)) this._reject(new Error(rejecterror))
} }
stateChanged(state) {
this.loggedIn = state.app.loggedIn
}
} }
window.customElements.define('confirm-transaction-dialog', ConfirmTransactionDialog) window.customElements.define('confirm-transaction-dialog', ConfirmTransactionDialog)

View File

@ -0,0 +1,93 @@
import { html, LitElement } from 'lit'
import { fragFileInputStyles } from '../styles/core-css'
import '@material/mwc-button'
import '@material/mwc-icon'
// Multi language support
import { translate } from '../../translate'
class FragFileInput extends LitElement {
static get properties() {
return {
accept: { type: String },
readAs: { type: String }
}
}
static get styles() {
return [fragFileInputStyles]
}
constructor() {
super()
this.readAs = this.readAs || 'Text'
}
render() {
return html`
<div id="drop-area">
<slot name="info-text"></slot>
<div style="line-height: 40px; text-align: center;">
<slot id="trigger" name="inputTrigger" @click=${() => this.shadowRoot.getElementById('fileInput').click()} style="dispay:inline;">
<mwc-button><mwc-icon>cloud_upload</mwc-icon><span style="color: var(--black);">&nbsp; ${translate("fragfile.selectfile")}</span></mwc-button>
</slot>
<br>
<span style="text-align: center; padding-top: 4px; color: var(--black);">${translate("fragfile.dragfile")}</span>
</div>
</div>
<input type="file" id="fileInput" accept="${this.accept}" @change="${e => this.readFile(e.target.files[0])}">
`
}
firstUpdated() {
this._dropArea = this.shadowRoot.getElementById('drop-area')
const preventDefaults = e => {
e.preventDefault()
e.stopPropagation()
}
;['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
this._dropArea.addEventListener(eventName, preventDefaults, false)
})
const highlight = e => {
this._dropArea.classList.add('highlight')
}
const unhighlight = e => {
this._dropArea.classList.remove('highlight')
}
;['dragenter', 'dragover'].forEach(eventName => {
this._dropArea.addEventListener(eventName, highlight, false)
})
;['dragleave', 'drop'].forEach(eventName => {
this._dropArea.addEventListener(eventName, unhighlight, false)
})
this._dropArea.addEventListener('drop', e => {
const dt = e.dataTransfer
const file = dt.files[0]
this.readFile(file)
}, false)
}
readFile(file) {
const fr = new FileReader()
fr.onload = () => {
this.dispatchEvent(new CustomEvent('file-read-success', {
detail: { result: fr.result },
bubbles: true,
composed: true
}))
}
fr['readAs' + this.readAs](file)
}
}
window.customElements.define('frag-file-input', FragFileInput)

View File

@ -1,4 +1,5 @@
import {css, html, LitElement} from 'lit' import { html, LitElement } from 'lit'
import { loadingRippleStyles } from '../styles/core-css'
const TRANSITION_EVENT_NAMES = ['transitionend', 'webkitTransitionEnd', 'oTransitionEnd', 'MSTransitionEnd'] const TRANSITION_EVENT_NAMES = ['transitionend', 'webkitTransitionEnd', 'oTransitionEnd', 'MSTransitionEnd']
@ -7,17 +8,13 @@ let rippleElement
class LoadingRipple extends LitElement { class LoadingRipple extends LitElement {
static get properties() { static get properties() {
return { return {
welcomeMessage: { welcomeMessage: { type: String, attribute: 'welcome-message', reflectToAttribute: true },
type: String, loadingMessage: { type: String, attribute: 'loading-message', reflectToAttribute: true}
attribute: 'welcome-message',
reflectToAttribute: true
},
loadingMessage: {
type: String,
attribute: 'loading-message',
reflectToAttribute: true
} }
} }
static get styles() {
return [loadingRippleStyles]
} }
constructor() { constructor() {
@ -26,83 +23,6 @@ class LoadingRipple extends LitElement {
this.loadingMessage = '' this.loadingMessage = ''
} }
static get styles () {
return css`
* {
--mdc-theme-primary: rgb(3, 169, 244);
--mdc-theme-secondary: var(--mdc-theme-primary);
--paper-spinner-color: var(--mdc-theme-secondary);
}
#rippleWrapper{
position:fixed;
top:0;
left:0;
bottom:0;
right:0;
height:0;
width:0;
z-index:999;
overflow: visible;
--ripple-activating-transition: transform 0.3s cubic-bezier(0.6, 0.0, 1, 1), opacity 0.3s cubic-bezier(0.6, 0.0, 1, 1);
--ripple-disable-transition: opacity 0.5s ease;
}
#ripple {
border-radius:50%;
border-width:0;
margin-left:-100vmax;
margin-top: -100vmax;
height:200vmax;
width:200vmax;
overflow:hidden;
background: var(--black);
transform: scale(0);
overflow:hidden;
}
#ripple.error {
transition: var(--ripple-activating-transition);
background: var(--mdc-theme-error)
}
#rippleShader {
background: var(--white);
opacity:0;
height:100%;
width:100%;
}
#ripple.activating{
transition: var(--ripple-activating-transition);
transform: scale(1)
}
.activating #rippleShader {
transition: var(--ripple-activating-transition);
opacity: 1;
}
#ripple.disabling{
transition: var(--ripple-disable-transition);
opacity: 0;
}
#rippleContentWrapper {
position: absolute;
top:100vmax;
left:100vmax;
height:var(--window-height);
width:100vw;
display: flex;
align-items: center;
justify-content: center;
}
#rippleContent {
opacity: 0;
text-align:center;
}
.activating-done #rippleContent {
opacity: 1;
transition: var(--ripple-activating-transition);
}
`
}
render() { render() {
return html` return html`
<div id="rippleWrapper"> <div id="rippleWrapper">
@ -126,7 +46,6 @@ class LoadingRipple extends LitElement {
this._rippleContentWrapper = this.shadowRoot.getElementById('rippleContentWrapper') this._rippleContentWrapper = this.shadowRoot.getElementById('rippleContentWrapper')
} }
// duh
open(origin) { open(origin) {
this._rippleWrapper.style.top = origin.y + 'px' this._rippleWrapper.style.top = origin.y + 'px'
this._rippleWrapper.style.left = origin.x + 'px' this._rippleWrapper.style.left = origin.x + 'px'
@ -135,53 +54,64 @@ class LoadingRipple extends LitElement {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
this._ripple.classList.add('activating') this._ripple.classList.add('activating')
let isOpened = false let isOpened = false
const doneOpeningEvent = () => { const doneOpeningEvent = () => {
if (isOpened) return if (isOpened) return
// Clear events // Clear events
TRANSITION_EVENT_NAMES.forEach(name => this._ripple.removeEventListener(name, doneOpeningEvent)) TRANSITION_EVENT_NAMES.forEach(name => this._ripple.removeEventListener(name, doneOpeningEvent))
this._ripple.classList.add('activating-done') this._ripple.classList.add('activating-done')
isOpened = true isOpened = true
resolve() resolve()
} }
TRANSITION_EVENT_NAMES.forEach(name => this._ripple.addEventListener(name, doneOpeningEvent)) TRANSITION_EVENT_NAMES.forEach(name => this._ripple.addEventListener(name, doneOpeningEvent))
}) })
} }
// Fades out
fade() { fade() {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
// CAN'T FADE OUT CAUSE THE STUPID THING GETS KILLED CAUSE OF STATE.APP.LOGGEEDIN
// let rippleClosed = false
this._ripple.classList.remove('activating') this._ripple.classList.remove('activating')
this._ripple.classList.remove('activating-done') this._ripple.classList.remove('activating-done')
this._ripple.classList.remove('disabling') this._ripple.classList.remove('disabling')
resolve() resolve()
}) })
} }
// un-ripples...
close() { close() {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
let rippleClosed = false let rippleClosed = false
this._ripple.classList.add('error') this._ripple.classList.add('error')
this._ripple.classList.remove('activating') this._ripple.classList.remove('activating')
this._ripple.classList.remove('activating-done') this._ripple.classList.remove('activating-done')
const rippleClosedEvent = () => { const rippleClosedEvent = () => {
if (rippleClosed) return if (rippleClosed) return
rippleClosed = true rippleClosed = true
TRANSITION_EVENT_NAMES.forEach(name => this._ripple.removeEventListener(name, rippleClosedEvent)) TRANSITION_EVENT_NAMES.forEach(name => this._ripple.removeEventListener(name, rippleClosedEvent))
// Reset the ripple // Reset the ripple
this._ripple.classList.remove('error') this._ripple.classList.remove('error')
this.rippleIsOpen = false this.rippleIsOpen = false
resolve() resolve()
} }
TRANSITION_EVENT_NAMES.forEach(name => this._ripple.addEventListener(name, rippleClosedEvent)) TRANSITION_EVENT_NAMES.forEach(name => this._ripple.addEventListener(name, rippleClosedEvent))
}) })
} }
stateChanged(state) { stateChanged(state) {
// this.loggedIn = state.app.loggedIn // ...
} }
} }
@ -195,7 +125,6 @@ setTimeout(() => {
const ripple = document.getElementById('ripple-node') const ripple = document.getElementById('ripple-node')
const mainApp = document.getElementById('main-app') const mainApp = document.getElementById('main-app')
const shadow = mainApp.shadowRoot const shadow = mainApp.shadowRoot
// console.log(shadow)
rippleElement = shadow.appendChild(ripple) rippleElement = shadow.appendChild(ripple)
}, 500) // Should just keep checking for the main-app and it's shadow and then append once it's there }, 500) // Should just keep checking for the main-app and it's shadow and then append once it's there
export default rippleElement export default rippleElement

View File

@ -1,47 +1,39 @@
import {css, html, LitElement} from 'lit'; import { html, LitElement } from 'lit'
import '@vaadin/button'; import { myButtonStyles } from '../styles/core-css'
import '@polymer/paper-spinner/paper-spinner-lite.js'; import '@vaadin/button'
import '@polymer/paper-spinner/paper-spinner-lite.js'
export class MyButton extends LitElement { export class MyButton extends LitElement {
static properties = { static get properties() {
return {
onClick: { type: Function }, onClick: { type: Function },
isLoading: { type: Boolean }, isLoading: { type: Boolean },
label: { type: String }, label: { type: String }
}; }
static styles = css`
vaadin-button {
height: 100%;
margin: 0;
cursor: pointer;
min-width: 80px;
background-color: #03a9f4;
color: white;
} }
vaadin-button:hover { static get styles() {
opacity: 0.8; return [myButtonStyles]
} }
`;
constructor() { constructor() {
super(); super()
this.onClick = () => {}; this.onClick = () => { }
this.isLoading = false; this.isLoading = false
this.label = ''; this.label = ''
} }
render() { render() {
return html` return html`
<vaadin-button <vaadin-button ?disabled="${this.isLoading}" @click="${this.onClick}">
?disabled="${this.isLoading}" ${this.isLoading === false ? html`${this.label}` : html`<paper-spinner-lite active></paper-spinner-lite>`}
@click="${this.onClick}"
>
${this.isLoading === false
? html`${this.label}`
: html`<paper-spinner-lite active></paper-spinner-lite>`}
</vaadin-button> </vaadin-button>
`; `
}
firstUpdated() {
// ...
} }
} }
customElements.define('my-button', MyButton);
window.customElements.define('my-button', MyButton)

View File

@ -1,16 +1,16 @@
import {css, html, LitElement} from 'lit' import { html, LitElement } from 'lit'
import { connect } from 'pwa-helpers' import { connect } from 'pwa-helpers'
import {store} from '../store.js' import { store } from '../store'
import {testApiKey} from '../apiKeyUtils.js' import { testApiKey } from '../apiKeyUtils'
import {get, translate} from '../../translate' import { mykeyPageStyles } from '../styles/core-css'
import snackbar from './snackbar'
import '@material/mwc-dialog'
import '@material/mwc-button' import '@material/mwc-button'
import '@material/mwc-select' import '@material/mwc-dialog'
import '@material/mwc-textfield'
import '@material/mwc-icon' import '@material/mwc-icon'
import '@material/mwc-textfield'
import snackbar from './snackbar.js' // Multi language support
import { get, translate } from '../../translate'
let mykeyDialog let mykeyDialog
@ -23,34 +23,13 @@ class MykeyPage extends connect(store)(LitElement) {
} }
static get styles() { static get styles() {
return css` return [mykeyPageStyles]
* {
--mdc-theme-primary: rgb(3, 169, 244);
--mdc-theme-secondary: var(--mdc-theme-primary);
--paper-input-container-focus-color: var(--mdc-theme-primary);
--mdc-theme-surface: var(--white);
--mdc-dialog-heading-ink-color: var(--black);
--mdc-dialog-content-ink-color: var(--black);
--lumo-primary-text-color: rgb(0, 167, 245);
--lumo-primary-color-50pct: rgba(0, 167, 245, 0.5);
--lumo-primary-color-10pct: rgba(0, 167, 245, 0.1);
--lumo-primary-color: hsl(199, 100%, 48%);
--lumo-base-color: var(--white);
--lumo-body-text-color: var(--black);
--_lumo-grid-border-color: var(--border);
--_lumo-grid-secondary-border-color: var(--border2);
}
.red {
--mdc-theme-primary: red;
}
`
} }
constructor() { constructor() {
super() super()
this.nodeConfig = {} this.nodeConfig = {}
this.theme = localStorage.getItem('qortalTheme') ? localStorage.getItem('qortalTheme') : 'light'; this.theme = localStorage.getItem('qortalTheme') ? localStorage.getItem('qortalTheme') : 'light'
} }
render() { render() {
@ -60,26 +39,18 @@ class MykeyPage extends connect(store)(LitElement) {
<mwc-textfield icon="fingerprint" id="mykeyInput" style="width:100%;" label="${translate("apipage.achange2")}"></mwc-textfield> <mwc-textfield icon="fingerprint" id="mykeyInput" style="width:100%;" label="${translate("apipage.achange2")}"></mwc-textfield>
<p style="margin-top: 45px;">${translate("apipage.achange3")}</p> <p style="margin-top: 45px;">${translate("apipage.achange3")}</p>
</div> </div>
<mwc-button <mwc-button slot="secondaryAction" dialogAction="close" class="red">
slot="secondaryAction"
dialogAction="close"
class="red"
>
${translate("apipage.achange4")} ${translate("apipage.achange4")}
</mwc-button> </mwc-button>
<mwc-button <mwc-button slot="primaryAction" @click="${this.addMykey}">
slot="primaryAction"
@click="${this.addMykey}"
>
${translate("apipage.achange5")} ${translate("apipage.achange5")}
</mwc-button> </mwc-button>
</mwc-dialog> </mwc-dialog>
` `
} }
stateChanged(state) { firstUpdated() {
this.config = state.config // ...
this.nodeConfig = state.app.nodeConfig
} }
show() { show() {
@ -88,35 +59,47 @@ class MykeyPage extends connect(store)(LitElement) {
async addMykey() { async addMykey() {
const mykeyInput = this.shadowRoot.getElementById('mykeyInput').value const mykeyInput = this.shadowRoot.getElementById('mykeyInput').value
let selectedNode = this.nodeConfig.knownNodes[this.nodeConfig.node];
let testResult = await testApiKey(mykeyInput); let selectedNode = this.nodeConfig.knownNodes[this.nodeConfig.node]
let testResult = await testApiKey(mykeyInput)
if (testResult === true) { if (testResult === true) {
selectedNode.apiKey = mykeyInput; selectedNode.apiKey = mykeyInput
this.nodeConfig.knownNodes[this.nodeConfig.node] = selectedNode;
localStorage.setItem('myQortalNodes', JSON.stringify(this.nodeConfig.knownNodes)); this.nodeConfig.knownNodes[this.nodeConfig.node] = selectedNode
localStorage.setItem('myQortalNodes', JSON.stringify(this.nodeConfig.knownNodes))
let snackbar1 = get("apipage.achange6") let snackbar1 = get("apipage.achange6")
snackbar.add({ snackbar.add({
labelText: `${snackbar1}`, labelText: `${snackbar1}`,
dismiss: true dismiss: true
}) })
this.shadowRoot.getElementById('mykeyInput').value = '' this.shadowRoot.getElementById('mykeyInput').value = ''
this.shadowRoot.querySelector('#mykeyDialog').close() this.shadowRoot.querySelector('#mykeyDialog').close()
} else { } else {
let snackbar2 = get("apipage.achange7") let snackbar2 = get("apipage.achange7")
snackbar.add({ snackbar.add({
labelText: `${snackbar2}`, labelText: `${snackbar2}`,
dismiss: true dismiss: true
}) })
this.shadowRoot.getElementById('mykeyInput').value = '' this.shadowRoot.getElementById('mykeyInput').value = ''
this.shadowRoot.querySelector('#mykeyDialog').close() this.shadowRoot.querySelector('#mykeyDialog').close()
} }
} }
stateChanged(state) {
this.config = state.config
this.nodeConfig = state.app.nodeConfig
}
} }
window.customElements.define('mykey-page', MykeyPage) window.customElements.define('mykey-page', MykeyPage)
const mykey = document.createElement('mykey-page') const mykey = document.createElement('mykey-page')
mykeyDialog = document.body.appendChild(mykey) mykeyDialog = document.body.appendChild(mykey)
export default mykeyDialog export default mykeyDialog

View File

@ -1,38 +1,18 @@
// Author: irontiga <irontiga@gmail.com> // Author: irontiga <irontiga@gmail.com>
'use strict'
import { html, LitElement } from 'lit' import { html, LitElement } from 'lit'
import * as WORDLISTS from './wordlists.js' import * as WORDLISTS from './wordlists'
class RandomSentenceGenerator extends LitElement { class RandomSentenceGenerator extends LitElement {
static get properties() { static get properties() {
return { return {
template: { template: { type: String, attribute: 'template' },
type: String, parsedString: { type: String },
attribute: 'template' fetchedWordlistCount: { type: Number, value: 0 },
}, capitalize: { type: Boolean },
parsedString: { partsOfSpeechMap: { type: Object },
type: String templateEntropy: { type: Number, reflect: true, attribute: 'template-entropy' },
}, maxWordLength: { type: Number, attribute: 'max-word-length' }
fetchedWordlistCount: {
type: Number,
value: 0
},
capitalize: {
type: Boolean
},
partsOfSpeechMap: {
type: Object
},
templateEntropy: {
type: Number,
reflect: true,
attribute: 'template-entropy'
},
maxWordLength: {
type: Number,
attribute: 'max-word-length'
}
} }
} }
@ -57,25 +37,42 @@ class RandomSentenceGenerator extends LitElement {
this._wordlists = WORDLISTS this._wordlists = WORDLISTS
} }
render() {
return html`
${this.parsedString}
`
}
firstUpdated() {
// ...
}
updated(changedProperties) { updated(changedProperties) {
let regen = false let regen = false
if (changedProperties.has('template')) { if (changedProperties.has('template')) {
regen = true regen = true
} }
if (changedProperties.has('maxWordLength')) { if (changedProperties.has('maxWordLength')) {
console.dir(this.maxWordLength) console.dir(this.maxWordLength)
if (this.maxWordLength) { if (this.maxWordLength) {
const wl = { ...this._wordlists } const wl = { ...this._wordlists }
for (const partOfSpeech in this._wordlists) { for (const partOfSpeech in this._wordlists) {
console.log(this._wordlists[partOfSpeech]) console.log(this._wordlists[partOfSpeech])
if (Array.isArray(this._wordlists[partOfSpeech])) { if (Array.isArray(this._wordlists[partOfSpeech])) {
wl[partOfSpeech] = this._wordlists[partOfSpeech].filter(word => word.length <= this.maxWordLength) wl[partOfSpeech] = this._wordlists[partOfSpeech].filter(word => word.length <= this.maxWordLength)
} }
} }
this._wordlists = wl this._wordlists = wl
} }
regen = true regen = true
} }
if (regen) this.generate() if (regen) this.generate()
} }
@ -83,13 +80,16 @@ class RandomSentenceGenerator extends LitElement {
if (entropy > 1074) { if (entropy > 1074) {
throw new Error('Javascript can not handle that much entropy!') throw new Error('Javascript can not handle that much entropy!')
} }
let randNum = 0 let randNum = 0
const crypto = window.crypto || window.msCrypto const crypto = window.crypto || window.msCrypto
if (crypto) { if (crypto) {
const entropy256 = Math.ceil(entropy / 8) const entropy256 = Math.ceil(entropy / 8)
let buffer = new Uint8Array(entropy256) let buffer = new Uint8Array(entropy256)
crypto.getRandomValues(buffer) crypto.getRandomValues(buffer)
randNum = buffer.reduce((num, value) => { randNum = buffer.reduce((num, value) => {
@ -97,8 +97,10 @@ class RandomSentenceGenerator extends LitElement {
}, 1) / Math.pow(256, entropy256) }, 1) / Math.pow(256, entropy256)
} else { } else {
console.warn('Secure RNG not found. Using Math.random') console.warn('Secure RNG not found. Using Math.random')
randNum = Math.random() randNum = Math.random()
} }
return randNum return randNum
} }
@ -127,7 +129,9 @@ class RandomSentenceGenerator extends LitElement {
parse(template) { parse(template) {
const split = template.split(/[\s]/g) const split = template.split(/[\s]/g)
let entropy = 1 let entropy = 1
const final = split.map(word => { const final = split.map(word => {
const lower = word.toLowerCase() const lower = word.toLowerCase()
@ -139,22 +143,20 @@ class RandomSentenceGenerator extends LitElement {
const replacement = this.getWord(partOfSpeech) const replacement = this.getWord(partOfSpeech)
word = replacement.word + word.slice(partOfSpeech.length) // Append the rest of the "word" (punctuation) word = replacement.word + word.slice(partOfSpeech.length) // Append the rest of the "word" (punctuation)
entropy = entropy * replacement.entropy entropy = entropy * replacement.entropy
return true return true
} }
}) })
return word return word
}) })
this.templateEntropy = Math.floor(Math.log(entropy) / Math.log(8)) this.templateEntropy = Math.floor(Math.log(entropy) / Math.log(8))
return final.join(' ') return final.join(' ')
} }
render() {
return html`
${this.parsedString}
`
}
} }
customElements.define('random-sentence-generator', RandomSentenceGenerator) window.customElements.define('random-sentence-generator', RandomSentenceGenerator)
export default RandomSentenceGenerator export default RandomSentenceGenerator

View File

@ -1,19 +1,21 @@
import {css, html, LitElement} from 'lit' import { html, LitElement } from 'lit'
import { connect } from 'pwa-helpers' import { connect } from 'pwa-helpers'
import {store} from '../store.js' import { store } from '../store'
import {doAddNode, doEditNode, doLoadNodeConfig, doRemoveNode, doSetNode} from '../redux/app/app-actions.js' import { doAddNode, doEditNode, doLoadNodeConfig, doRemoveNode, doSetNode } from '../redux/app/app-actions'
import {get, registerTranslateConfig, translate, use} from '../../translate' import { settingsPageStyles } from '../styles/core-css'
import snackbar from './snackbar.js'
import '../components/language-selector.js'
import '../custom-elements/frag-file-input.js'
import FileSaver from 'file-saver' import FileSaver from 'file-saver'
import snackbar from './snackbar'
import '@material/mwc-dialog' import './frag-file-input'
import '../components/language-selector'
import '@material/mwc-button' import '@material/mwc-button'
import '@material/mwc-select' import '@material/mwc-dialog'
import '@material/mwc-textfield'
import '@material/mwc-icon' import '@material/mwc-icon'
import '@material/mwc-list/mwc-list-item.js' import '@material/mwc-list/mwc-list-item.js'
import '@material/mwc-select'
import '@material/mwc-textfield'
// Multi language support
import { get, registerTranslateConfig, translate, use } from '../../translate'
registerTranslateConfig({ registerTranslateConfig({
loader: (lang) => fetch(`/language/${lang}.json`).then((res) => res.json()) loader: (lang) => fetch(`/language/${lang}.json`).then((res) => res.json())
@ -35,132 +37,23 @@ class SettingsPage extends connect(store)(LitElement) {
return { return {
lastSelected: { type: Number }, lastSelected: { type: Number },
nodeConfig: { type: Object }, nodeConfig: { type: Object },
theme: { type: String, reflect: true },
isBeingEdited: { type: Boolean }, isBeingEdited: { type: Boolean },
dropdownOpen: { type: Boolean } dropdownOpen: { type: Boolean },
theme: { type: String, reflect: true }
} }
} }
static get styles() { static get styles() {
return css` return [settingsPageStyles]
* {
--mdc-theme-primary: var(--login-button);
--mdc-theme-secondary: var(--mdc-theme-primary);
--mdc-dialog-content-ink-color: var(--black);
--mdc-theme-surface: var(--white);
--mdc-theme-text-primary-on-background: var(--black);
--mdc-dialog-min-width: 300px;
--mdc-dialog-max-width: 650px;
--mdc-dialog-max-height: 700px;
--mdc-list-item-text-width: 100%;
}
#main {
width: 210px;
display: flex;
align-items: center;
}
.globe {
color: var(--black);
--mdc-icon-size: 36px;
}
span.name {
display: inline-block;
width: 150px;
font-weight: 600;
color: var(--general-color-blue);
border: 1px solid transparent;
}
.red {
--mdc-theme-primary: red;
}
.buttonred {
color: #f44336;
}
.buttongreen {
color: #03c851;
}
.buttonBlue {
color: var(--general-color-blue);
}
.floatleft {
float: left;
}
.floatright {
float: right;
}
.list-parent {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
}
#customSelect {
position: relative;
border: 1px solid #ccc;
cursor: pointer;
background: var(--plugback);
}
#customSelect .selected {
padding: 10px;
display: flex;
align-items: center;
justify-content: space-between;
}
#customSelect ul {
position: absolute;
top: 100%;
left: 0;
list-style: none;
margin: 0;
padding: 0;
border: 1px solid #ccc;
display: none;
background: var(--plugback);
width: 100%;
box-sizing: border-box;
z-index: 10;
}
#customSelect ul.open {
display: block;
}
#customSelect ul li {
padding: 10px;
transition: 0.2s all;
}
#customSelect ul li:hover {
background-color: var(--graylight);
}
.selected-left-side {
display: flex;
align-items: center;
}
`
} }
constructor() { constructor() {
super() super()
this.theme = localStorage.getItem('qortalTheme') ? localStorage.getItem('qortalTheme') : 'light'
this.nodeConfig = {} this.nodeConfig = {}
this.isBeingEdited = false this.isBeingEdited = false
this.isBeingEditedIndex = null this.isBeingEditedIndex = null
this.dropdownOpen = false this.dropdownOpen = false
this.theme = localStorage.getItem('qortalTheme') ? localStorage.getItem('qortalTheme') : 'light'
} }
render() { render() {
@ -176,20 +69,21 @@ class SettingsPage extends connect(store)(LitElement) {
<div class="selected"> <div class="selected">
<div class="selected-left-side"> <div class="selected-left-side">
<mwc-icon style="margin-right: 10px">link</mwc-icon> <mwc-icon style="margin-right: 10px">link</mwc-icon>
${this.selectedItem ? html ${this.selectedItem ?
` html `
<div> <div>
<span class="name">${this.selectedItem.name}</span> <span class="name">${this.selectedItem.name}</span>
<span>${this.selectedItem.protocol + '://' + this.selectedItem.domain + ':' + this.selectedItem.port}</span> <span>${this.selectedItem.protocol + '://' + this.selectedItem.domain + ':' + this.selectedItem.port}</span>
</div> </div>
` : html`${translate('settings.selectnode')}` ` : html`
${translate('settings.selectnode')}
`
} }
</div> </div>
<mwc-icon>expand_more</mwc-icon> <mwc-icon>expand_more</mwc-icon>
</div> </div>
<ul class="${this.dropdownOpen ? 'open' : ''}"> <ul class="${this.dropdownOpen ? 'open' : ''}">
${this.nodeConfig.knownNodes.map( ${this.nodeConfig.knownNodes.map((n, index) => html`
(n, index) => html`
<li @click="${(e) => this.handleSelection(e, n, index)}"> <li @click="${(e) => this.handleSelection(e, n, index)}">
<div class="list-parent"> <div class="list-parent">
<div> <div>
@ -200,7 +94,7 @@ class SettingsPage extends connect(store)(LitElement) {
<mwc-button <mwc-button
outlined outlined
@click="${(e) => { @click="${(e) => {
e.stopPropagation(); e.stopPropagation()
const currentValues = this.nodeConfig.knownNodes[index] const currentValues = this.nodeConfig.knownNodes[index]
const dialog = this.shadowRoot.querySelector('#addNodeDialog') const dialog = this.shadowRoot.querySelector('#addNodeDialog')
@ -225,19 +119,15 @@ class SettingsPage extends connect(store)(LitElement) {
</div> </div>
</div> </div>
</li> </li>
` `)}
)}
</ul> </ul>
</div> </div>
<p style="margin-top: 30px; text-align: center;"> <p style="margin-top: 30px; text-align: center;">
${translate('settings.nodehint')} ${translate('settings.nodehint')}
</p> </p>
<center> <center>
<mwc-button <mwc-button outlined @click="${() => this.shadowRoot.querySelector('#addNodeDialog').show()}">
outlined <mwc-icon class="buttongreen">add</mwc-icon>
@click="${() => this.shadowRoot.querySelector('#addNodeDialog').show()}"
><mwc-icon class="buttongreen">add</mwc-icon
>
${translate('settings.addcustomnode')} ${translate('settings.addcustomnode')}
</mwc-button> </mwc-button>
</center> </center>
@ -261,8 +151,7 @@ class SettingsPage extends connect(store)(LitElement) {
<br /> <br />
<center> <center>
<div id="main"> <div id="main">
<mwc-icon class="globe">language</mwc-icon <mwc-icon class="globe">language</mwc-icon>&nbsp;<language-selector></language-selector>
>&nbsp;<language-selector></language-selector>
</div> </div>
</center> </center>
</div> </div>
@ -270,7 +159,6 @@ class SettingsPage extends connect(store)(LitElement) {
${translate('general.close')} ${translate('general.close')}
</mwc-button> </mwc-button>
</mwc-dialog> </mwc-dialog>
<mwc-dialog id="addNodeDialog"> <mwc-dialog id="addNodeDialog">
<div style="text-align: center;"> <div style="text-align: center;">
<h2>${translate('settings.addcustomnode')}</h2> <h2>${translate('settings.addcustomnode')}</h2>
@ -293,7 +181,6 @@ class SettingsPage extends connect(store)(LitElement) {
${translate('settings.addandsave')} ${translate('settings.addandsave')}
</mwc-button> </mwc-button>
</mwc-dialog> </mwc-dialog>
<mwc-dialog id="importQortalNodesListDialog"> <mwc-dialog id="importQortalNodesListDialog">
<div style="text-align:center"> <div style="text-align:center">
<h2>${translate('settings.import')}</h2> <h2>${translate('settings.import')}</h2>
@ -326,6 +213,7 @@ class SettingsPage extends connect(store)(LitElement) {
firstUpdated() { firstUpdated() {
const checkNode = localStorage.getItem('mySelectedNode') const checkNode = localStorage.getItem('mySelectedNode')
if (checkNode === null || checkNode.length === 0) { if (checkNode === null || checkNode.length === 0) {
localStorage.setItem('mySelectedNode', 0) localStorage.setItem('mySelectedNode', 0)
} else { } else {
@ -364,6 +252,29 @@ class SettingsPage extends connect(store)(LitElement) {
this.dropdownOpen = false this.dropdownOpen = false
this.requestUpdate() this.requestUpdate()
this.nodeSelected(index) this.nodeSelected(index)
localStorage.setItem('mySelectedNode', index)
const selectedNodeIndexOnNewStart = localStorage.getItem('mySelectedNode')
const catchSavedNodes = JSON.parse(localStorage.getItem('myQortalNodes'))
const selectedNodeOnNewStart = catchSavedNodes[selectedNodeIndexOnNewStart]
const selectedNameOnNewStart = `${selectedNodeOnNewStart.name}`
const selectedNodeUrlOnNewStart = `${selectedNodeOnNewStart.protocol + '://' + selectedNodeOnNewStart.domain + ':' + selectedNodeOnNewStart.port}`
let snack2string = get('settings.snack2')
snackbar.add({
labelText: `${snack2string} : ${selectedNameOnNewStart} ${selectedNodeUrlOnNewStart}`,
dismiss: true
})
}
handleAddNodeSelection(node, index) {
this.selectedItem = node
this.dropdownOpen = false
this.requestUpdate()
this.nodeSelected(index)
localStorage.setItem('mySelectedNode', index) localStorage.setItem('mySelectedNode', index)
const selectedNodeIndexOnNewStart = localStorage.getItem('mySelectedNode') const selectedNodeIndexOnNewStart = localStorage.getItem('mySelectedNode')
@ -408,10 +319,13 @@ class SettingsPage extends connect(store)(LitElement) {
} }
var renewNodes = [] var renewNodes = []
renewNodes.push(obj1, obj2) renewNodes.push(obj1, obj2)
localStorage.setItem('myQortalNodes', JSON.stringify(renewNodes)) localStorage.setItem('myQortalNodes', JSON.stringify(renewNodes))
let snack1string = get('settings.snack1') let snack1string = get('settings.snack1')
snackbar.add({ snackbar.add({
labelText: `${snack1string}`, labelText: `${snack1string}`,
dismiss: true dismiss: true
@ -427,8 +341,8 @@ class SettingsPage extends connect(store)(LitElement) {
const selectedNode = this.nodeConfig.knownNodes[selectedNodeIndex] const selectedNode = this.nodeConfig.knownNodes[selectedNodeIndex]
const selectedName = `${selectedNode.name}` const selectedName = `${selectedNode.name}`
const selectedNodeUrl = `${selectedNode.protocol + '://' + selectedNode.domain + ':' + selectedNode.port}` const selectedNodeUrl = `${selectedNode.protocol + '://' + selectedNode.domain + ':' + selectedNode.port}`
const index = parseInt(selectedNodeIndex) const index = parseInt(selectedNodeIndex)
if (isNaN(index)) return if (isNaN(index)) return
store.dispatch(doSetNode(selectedNodeIndex)) store.dispatch(doSetNode(selectedNodeIndex))
@ -462,13 +376,23 @@ class SettingsPage extends connect(store)(LitElement) {
store.dispatch(doAddNode(nodeObject)) store.dispatch(doAddNode(nodeObject))
const haveNodes = JSON.parse(localStorage.getItem('myQortalNodes')) const haveNodes = JSON.parse(localStorage.getItem('myQortalNodes'))
const indexLength = this.nodeConfig.knownNodes.length
let choosedIndex
let choosedNode
choosedIndex = indexLength -1
choosedNode = this.nodeConfig.knownNodes[this.nodeConfig.knownNodes.length - 1]
if (haveNodes === null || haveNodes.length === 0) { if (haveNodes === null || haveNodes.length === 0) {
var savedNodes = [] var savedNodes = []
savedNodes.push(nodeObject) savedNodes.push(nodeObject)
localStorage.setItem('myQortalNodes', JSON.stringify(savedNodes)) localStorage.setItem('myQortalNodes', JSON.stringify(savedNodes))
let snack3string = get('settings.snack3') let snack3string = get('settings.snack3')
snackbar.add({ snackbar.add({
labelText: `${snack3string}`, labelText: `${snack3string}`,
dismiss: true dismiss: true
@ -478,14 +402,16 @@ class SettingsPage extends connect(store)(LitElement) {
this.shadowRoot.getElementById('protocolList').value = '' this.shadowRoot.getElementById('protocolList').value = ''
this.shadowRoot.getElementById('domainInput').value = '' this.shadowRoot.getElementById('domainInput').value = ''
this.shadowRoot.getElementById('portInput').value = '' this.shadowRoot.getElementById('portInput').value = ''
this.shadowRoot.querySelector('#addNodeDialog').close() this.shadowRoot.querySelector('#addNodeDialog').close()
} else { } else {
var stored = JSON.parse(localStorage.getItem('myQortalNodes')) var stored = JSON.parse(localStorage.getItem('myQortalNodes'))
stored.push(nodeObject) stored.push(nodeObject)
localStorage.setItem('myQortalNodes', JSON.stringify(stored)) localStorage.setItem('myQortalNodes', JSON.stringify(stored))
let snack3string = get('settings.snack3'); let snack3string = get('settings.snack3')
snackbar.add({ snackbar.add({
labelText: `${snack3string}`, labelText: `${snack3string}`,
dismiss: true dismiss: true
@ -495,9 +421,10 @@ class SettingsPage extends connect(store)(LitElement) {
this.shadowRoot.getElementById('protocolList').value = '' this.shadowRoot.getElementById('protocolList').value = ''
this.shadowRoot.getElementById('domainInput').value = '' this.shadowRoot.getElementById('domainInput').value = ''
this.shadowRoot.getElementById('portInput').value = '' this.shadowRoot.getElementById('portInput').value = ''
this.shadowRoot.querySelector('#addNodeDialog').close() this.shadowRoot.querySelector('#addNodeDialog').close()
} }
this.handleAddNodeSelection(choosedNode, choosedIndex)
} }
} }
@ -505,11 +432,15 @@ class SettingsPage extends connect(store)(LitElement) {
event.stopPropagation() event.stopPropagation()
let stored = JSON.parse(localStorage.getItem('myQortalNodes')) let stored = JSON.parse(localStorage.getItem('myQortalNodes'))
stored.splice(index, 1) stored.splice(index, 1)
localStorage.setItem('myQortalNodes', JSON.stringify(stored)) localStorage.setItem('myQortalNodes', JSON.stringify(stored))
store.dispatch(doRemoveNode(index)) store.dispatch(doRemoveNode(index))
let snack6string = get('settings.snack6') let snack6string = get('settings.snack6')
snackbar.add({ snackbar.add({
labelText: `${snack6string}`, labelText: `${snack6string}`,
dismiss: true dismiss: true
@ -530,16 +461,21 @@ class SettingsPage extends connect(store)(LitElement) {
protocol: protocolList, protocol: protocolList,
domain: domainInput, domain: domainInput,
port: portInput, port: portInput,
enableManagement: true, enableManagement: true
} }
let stored = JSON.parse(localStorage.getItem('myQortalNodes')) let stored = JSON.parse(localStorage.getItem('myQortalNodes'))
const copyStored = [...stored] const copyStored = [...stored]
copyStored[index] = nodeObject copyStored[index] = nodeObject
localStorage.setItem('myQortalNodes', JSON.stringify(copyStored)) localStorage.setItem('myQortalNodes', JSON.stringify(copyStored))
store.dispatch(doEditNode(index, nodeObject)) store.dispatch(doEditNode(index, nodeObject))
let snack7string = get('settings.snack7') let snack7string = get('settings.snack7')
snackbar.add({ snackbar.add({
labelText: `${snack7string}`, labelText: `${snack7string}`,
dismiss: true dismiss: true
@ -551,7 +487,6 @@ class SettingsPage extends connect(store)(LitElement) {
this.shadowRoot.getElementById('portInput').value = '' this.shadowRoot.getElementById('portInput').value = ''
this.isBeingEdited = false this.isBeingEdited = false
this.isBeingEditedIndex = null this.isBeingEditedIndex = null
this.shadowRoot.querySelector('#addNodeDialog').close() this.shadowRoot.querySelector('#addNodeDialog').close()
} }
} }
@ -566,26 +501,25 @@ class SettingsPage extends connect(store)(LitElement) {
renderExportNodesListButton() { renderExportNodesListButton() {
return html` return html`
<mwc-button <mwc-button dense unelevated label="${translate('settings.export')}" @click="${() => this.exportQortalNodesList()}"></mwc-button>
dense
unelevated
label="${translate('settings.export')}"
@click="${() => this.exportQortalNodesList()}"
>
</mwc-button>
` `
} }
exportQortalNodesList() { exportQortalNodesList() {
let nodelist = '' let nodelist = ''
const qortalNodesList = JSON.stringify( const qortalNodesList = JSON.stringify(
localStorage.getItem('myQortalNodes') localStorage.getItem('myQortalNodes')
); )
const qortalNodesListSave = JSON.parse(qortalNodesList || '[]') const qortalNodesListSave = JSON.parse(qortalNodesList || '[]')
const blob = new Blob([qortalNodesListSave], { const blob = new Blob([qortalNodesListSave], {
type: 'text/plain;charset=utf-8', type: 'text/plain;charset=utf-8'
}) })
nodelist = 'qortal.nodes' nodelist = 'qortal.nodes'
this.saveFileToDisk(blob, nodelist) this.saveFileToDisk(blob, nodelist)
} }
@ -595,16 +529,20 @@ class SettingsPage extends connect(store)(LitElement) {
suggestedName: fileName, suggestedName: fileName,
types: [{ types: [{
description: 'File' description: 'File'
}], }]
}) })
const writeFile = async (fileHandle, contents) => { const writeFile = async (fileHandle, contents) => {
const writable = await fileHandle.createWritable() const writable = await fileHandle.createWritable()
await writable.write(contents) await writable.write(contents)
await writable.close() await writable.close()
} }
writeFile(fileHandle, blob).then(() => console.log('FILE SAVED')) writeFile(fileHandle, blob).then(() => console.log('FILE SAVED'))
let snack4string = get('settings.snack4') let snack4string = get('settings.snack4')
snackbar.add({ snackbar.add({
labelText: `${snack4string} qortal.nodes`, labelText: `${snack4string} qortal.nodes`,
dismiss: true dismiss: true
@ -613,29 +551,28 @@ class SettingsPage extends connect(store)(LitElement) {
if (error.name === 'AbortError') { if (error.name === 'AbortError') {
return return
} }
FileSaver.saveAs(blob, fileName) FileSaver.saveAs(blob, fileName)
} }
} }
renderImportNodesListButton() { renderImportNodesListButton() {
return html` return html`
<mwc-button <mwc-button dense unelevated label="${translate('settings.import')}" @click="${() => this.openImportNodesDialog()}"></mwc-button>
dense
unelevated
label="${translate('settings.import')}"
@click="${() => this.openImportNodesDialog()}"
>
</mwc-button>
` `
} }
async importQortalNodesList(file) { async importQortalNodesList(file) {
localStorage.removeItem('myQortalNodes') localStorage.removeItem('myQortalNodes')
const newItems = JSON.parse(file || '[]') const newItems = JSON.parse(file || '[]')
localStorage.setItem('myQortalNodes', JSON.stringify(newItems)) localStorage.setItem('myQortalNodes', JSON.stringify(newItems))
this.shadowRoot.querySelector('#importQortalNodesListDialog').close() this.shadowRoot.querySelector('#importQortalNodesListDialog').close()
let snack5string = get('settings.snack5') let snack5string = get('settings.snack5')
snackbar.add({ snackbar.add({
labelText: `${snack5string}`, labelText: `${snack5string}`,
dismiss: true, dismiss: true,
@ -657,5 +594,4 @@ window.customElements.define('settings-page', SettingsPage)
const settings = document.createElement('settings-page') const settings = document.createElement('settings-page')
settingsDialog = document.body.appendChild(settings) settingsDialog = document.body.appendChild(settings)
export default settingsDialog export default settingsDialog

View File

@ -1,157 +0,0 @@
import {css} from 'lit'
export const sideMenuItemStyle = css`
:host {
--font-family: "Roboto", sans-serif;
--item-font-size: 0.9375rem;
--sub-item-font-size: 0.75rem;
--item-padding: 0.875rem;
--item-content-padding: 0.875rem;
--icon-height: 1.125rem;
--icon-width: 1.125rem;
--item-border-radius: 5px;
--item-selected-color: #dddddd;
--item-selected-color-text: #333333;
--item-color-active: #d1d1d1;
--item-color-hover: #eeeeee;
--item-text-color: #080808;
--item-icon-color: #080808;
--item-border-color: #eeeeee;
--item-border-selected-color: #333333;
--overlay-box-shadow: 0 2px 4px -1px hsla(214, 53%, 23%, 0.16), 0 3px 12px -1px hsla(214, 50%, 22%, 0.26);
--overlay-background-color: #ffffff;
--spacing: 4px;
font-family: var(--font-family);
display: flex;
overflow: hidden;
flex-direction: column;
border-radius: var(--item-border-radius);
}
#itemLink {
align-items: center;
font-size: var(--item-font-size);
font-weight: 400;
height: var(--icon-height);
transition: background-color 200ms;
padding: var(--item-padding);
cursor: pointer;
display: inline-flex;
flex-grow: 1;
align-items: center;
overflow: hidden;
text-decoration: none;
border-bottom: 1px solid var(--item-border-color);
text-transform: uppercase;
}
.hideItem {
display: none !important;
}
#itemLink:hover {
background-color: var(--item-color-hover);
}
#itemLink:active {
background-color: var(--item-color-active);
}
#content {
padding-left: var(--item-content-padding);
flex: 1;
}
:host([compact]) #content {
padding-left: 0;
display: none;
}
:host([selected]) #itemLink {
background-color: var(--item-selected-color);
color: var(--item-selected-color-text);
border-left: 3px solid var(--item-border-selected-color);
}
:host([selected]) slot[name="icon"]::slotted(*) {
color: var(--item-selected-color-text);
}
:host(:not([selected])) #itemLink{
color: var(--item-text-color);
}
:host([expanded]){
background-color: var(--item-selected-color);
}
:host([hasSelectedChild]){
background-color: var(--item-selected-color);
}
:host span {
cursor: inherit;
overflow: hidden;
text-overflow: ellipsis;
user-select: none;
-webkit-user-select: none;
white-space: nowrap;
}
slot[name="icon"]::slotted(*) {
flex-shrink: 0;
color: var(--item-icon-color);
height: var(--icon-height);
width: var(--icon-width);
pointer-events: none;
}
#collapse-button {
float: right;
}
:host([compact]) #itemLink[level]:not([level="0"]) {
padding: calc( var(--item-padding) / 2);
}
:host(:not([compact])) #itemLink[level]:not([level="0"]) {
padding-left: calc(var(--icon-width) + var(--item-content-padding));
}
#itemLink[level]:not([level="0"]) #content {
display: block;
visibility: visible;
width: auto;
font-weight: 400;
font-size: var(--sub-item-font-size)
}
#overlay {
display: block;
left: 101%;
min-width: 200px;
padding: 4px 2px;
background-color: var(--overlay-background-color);
background-image: var(--overlay-background-image, none);
box-shadow: var(--overlay-box-shadow);
border: 1px solid var(--overlay-background-color);
border-left: 0;
border-radius: 0 3px 3px 0;
position: absolute;
z-index: 1;
animation: pop 200ms forwards;
}
@keyframes pop{
0% {
transform: translateX(-5px);
opacity: 0.5;
}
100% {
transform: translateX(0);
opacity: 1;
}
}
`;

View File

@ -1,6 +1,6 @@
import {css, html, LitElement} from 'lit' import { html, LitElement } from 'lit'
import { ifDefined } from 'lit/directives/if-defined.js' import { ifDefined } from 'lit/directives/if-defined.js'
import {sideMenuItemStyle} from './side-menu-item-style.js' import { sideMenuItemStyles } from '../styles/core-css'
import '@vaadin/icon' import '@vaadin/icon'
import '@vaadin/icons' import '@vaadin/icons'
import '@polymer/paper-tooltip' import '@polymer/paper-tooltip'
@ -19,9 +19,7 @@ export class SideMenuItem extends LitElement {
} }
static get styles() { static get styles() {
return css` return [sideMenuItemStyles]
${sideMenuItemStyle}
`
} }
constructor() { constructor() {
@ -42,6 +40,7 @@ export class SideMenuItem extends LitElement {
if (!this.hasChildren()) { if (!this.hasChildren()) {
return return
} }
this.collapseExpandIcon = document.createElement("vaadin-icon") this.collapseExpandIcon = document.createElement("vaadin-icon")
this.collapseExpandIcon.id = "collapse-button" this.collapseExpandIcon.id = "collapse-button"
this.shadowRoot.getElementById("content").appendChild(this.collapseExpandIcon) this.shadowRoot.getElementById("content").appendChild(this.collapseExpandIcon)
@ -67,26 +66,29 @@ export class SideMenuItem extends LitElement {
_tooltipTemplate() { _tooltipTemplate() {
return html` return html`
${this._getLevel === 0 && this.compact ? html` ${this._getLevel === 0 && this.compact ?
html`
<paper-tooltip for="itemLink" position="right" animation-delay="0"> <paper-tooltip for="itemLink" position="right" animation-delay="0">
${this.label} ${this.label}
</paper-tooltip> </paper-tooltip>
` ` : undefined
: undefined} }
` `
} }
_childrenTemplate() { _childrenTemplate() {
return html` return html`
${this.expanded ? html` ${this.expanded ?
${this.compact ? html` html`
${this.compact ?
html`
<div id="overlay"><slot></slot></div> <div id="overlay"><slot></slot></div>
` ` : html`
: html`
<slot></slot> <slot></slot>
`}
` `
: undefined} }
` : undefined
}
` `
} }
@ -110,14 +112,14 @@ export class SideMenuItem extends LitElement {
this._markParentWithSelectedChild() this._markParentWithSelectedChild()
} }
} }
}); })
} }
_onCompactChanged() { _onCompactChanged() {
this.expanded = false; this.expanded = false
if (this.collapseExpandIcon == null) { if (this.collapseExpandIcon == null) {
return; return
} }
if (!this.compact) { if (!this.compact) {
@ -129,13 +131,13 @@ export class SideMenuItem extends LitElement {
_onExpandedChanged() { _onExpandedChanged() {
if (this.collapseExpandIcon == null) { if (this.collapseExpandIcon == null) {
return; return
} }
if (this.expanded) { if (this.expanded) {
this._onHandleExpanded(); this._onHandleExpanded()
} else { } else {
this._onHandleCollapsed(); this._onHandleCollapsed()
} }
} }
@ -166,12 +168,14 @@ export class SideMenuItem extends LitElement {
this.selected = true this.selected = true
} else { } else {
this.expanded = !this.expanded this.expanded = !this.expanded
e.preventDefault() e.preventDefault()
} }
} }
_outsideClickListener(event) { _outsideClickListener(event) {
const eventPath = event.composedPath() const eventPath = event.composedPath()
if (eventPath.indexOf(this) < 0) { if (eventPath.indexOf(this) < 0) {
this.expanded = false this.expanded = false
} }
@ -179,11 +183,13 @@ export class SideMenuItem extends LitElement {
_changeSelectedState(selected, sourceEvent) { _changeSelectedState(selected, sourceEvent) {
this.selected = selected this.selected = selected
let evt = new CustomEvent("side-menu-item-select", { let evt = new CustomEvent("side-menu-item-select", {
bubbles: true, bubbles: true,
cancelable: true, cancelable: true,
detail: { sourceEvent: sourceEvent } detail: { sourceEvent: sourceEvent }
}); })
this.dispatchEvent(evt) this.dispatchEvent(evt)
} }
@ -195,19 +201,21 @@ export class SideMenuItem extends LitElement {
let element = this.parentElement; let element = this.parentElement;
while (element instanceof SideMenuItem) { while (element instanceof SideMenuItem) {
element.setAttribute('hasSelectedChild', true) element.setAttribute('hasSelectedChild', true)
element = element.parentElement; element = element.parentElement
} }
} }
get _getLevel() { get _getLevel() {
let level = 0 let level = 0
let element = this.parentElement let element = this.parentElement
while (element instanceof SideMenuItem) { while (element instanceof SideMenuItem) {
level++; level++
element = element.parentElement element = element.parentElement
} }
return level return level
} }
} }
window.customElements.define("side-menu-item", SideMenuItem); window.customElements.define("side-menu-item", SideMenuItem)

View File

@ -1,4 +1,5 @@
import {css, html, LitElement} from 'lit' import { html, LitElement } from 'lit'
import { sideMenuStyles } from '../styles/core-css'
class SideMenu extends LitElement { class SideMenu extends LitElement {
static get properties() { static get properties() {
@ -10,21 +11,7 @@ class SideMenu extends LitElement {
} }
static get styles() { static get styles() {
return css` return [sideMenuStyles]
nav {
padding: 0;
}
:host {
list-style: none;
width: 100%;
position: relative;
}
:host([compact]) {
width: auto;
}
`
} }
constructor() { constructor() {
@ -56,23 +43,26 @@ class SideMenu extends LitElement {
if (this.compact) { if (this.compact) {
element.expanded = false element.expanded = false
} }
element.selected = false element.selected = false
element.hasChildren() ? element.removeAttribute('hasSelectedChild') : undefined element.hasChildren() ? element.removeAttribute('hasSelectedChild') : undefined
}); })
} }
updated(changedProperties) { updated(changedProperties) {
changedProperties.forEach((oldValue, propName) => { changedProperties.forEach((oldValue, propName) => {
if (propName === "compact") { if (propName === "compact") {
this.items.forEach(item => (item.compact = this.compact)) this.items.forEach(item => (item.compact = this.compact))
let evt = new CustomEvent("side-menu-compact-change", { let evt = new CustomEvent("side-menu-compact-change", {
bubbles: true, bubbles: true,
cancelable: true cancelable: true
}) })
this.dispatchEvent(evt) this.dispatchEvent(evt)
} }
}) })
} }
} }
window.customElements.define("side-menu", SideMenu); window.customElements.define("side-menu", SideMenu)

View File

@ -1,4 +1,4 @@
import {css, html, LitElement} from 'lit' import { html, LitElement } from 'lit'
import '@material/mwc-snackbar' import '@material/mwc-snackbar'
let queueElement let queueElement
@ -6,34 +6,20 @@ let queueElement
class SnackQueue extends LitElement { class SnackQueue extends LitElement {
static get properties() { static get properties() {
return { return {
busy: { busy: { type: Boolean, attribute: 'busy', reflectToAttribute: true },
type: Boolean, currentSnack: { type: Object, attribute: 'current-snack', reflectToAttribute: true },
attribute: 'busy', _queue: { type: Array },
reflectToAttribute: true
},
currentSnack: {
type: Object,
attribute: 'current-snack',
reflectToAttribute: true
},
_queue: {
type: Array
},
_labelText: { type: String }, _labelText: { type: String },
_stacked: { type: Boolean }, _stacked: { type: Boolean },
_leading: { type: Boolean }, _leading: { type: Boolean },
_closeOnEscape: { type: Boolean }, _closeOnEscape: { type: Boolean },
_timeoutMs: { type: Number }, _timeoutMs: { type: Number },
action: {},
_dismiss: {}, _dismiss: {},
_dismissIcon: { type: String } _dismissIcon: { type: String },
action: {}
} }
} }
static get styles() {
return css``
}
constructor() { constructor() {
super() super()
this._queue = [] this._queue = []
@ -45,9 +31,11 @@ class SnackQueue extends LitElement {
return html` return html`
<mwc-snackbar id="snack" labelText="${this._labelText}" ?stacked=${this._stacked} ?leading=${this._leading} ?closeOnEscape=${this._closeOnEscape} timeoutMs=${this._timeoutMs}> <mwc-snackbar id="snack" labelText="${this._labelText}" ?stacked=${this._stacked} ?leading=${this._leading} ?closeOnEscape=${this._closeOnEscape} timeoutMs=${this._timeoutMs}>
${this._action} ${this._action}
${this._dismiss ? html` ${this._dismiss ?
html`
<mwc-icon-button icon="${this._dismissIcon}" slot="dismiss"></mwc-icon-button> <mwc-icon-button icon="${this._dismissIcon}" slot="dismiss"></mwc-icon-button>
` : ''} ` : ''
}
</mwc-snackbar> </mwc-snackbar>
` `
} }

View File

@ -1,5 +1,3 @@
// Sourced from https://gist.github.com/letsgetrandy/1e05a68ea74ba6736eb5
export const EXCEPTIONS = { export const EXCEPTIONS = {
'are': 'were', 'are': 'were',
'eat': 'ate', 'eat': 'ate',
@ -16,21 +14,27 @@ export const getPastTense = (verb, exceptions = EXCEPTIONS) => {
if (exceptions[verb]) { if (exceptions[verb]) {
return exceptions[verb] return exceptions[verb]
} }
if ((/e$/i).test(verb)) { if ((/e$/i).test(verb)) {
return verb + 'd' return verb + 'd'
} }
if ((/[aeiou]c$/i).test(verb)) { if ((/[aeiou]c$/i).test(verb)) {
return verb + 'ked' return verb + 'ked'
} }
// for american english only // for american english only
if ((/el$/i).test(verb)) { if ((/el$/i).test(verb)) {
return verb + 'ed' return verb + 'ed'
} }
if ((/[aeio][aeiou][dlmnprst]$/).test(verb)) { if ((/[aeio][aeiou][dlmnprst]$/).test(verb)) {
return verb + 'ed' return verb + 'ed'
} }
if ((/[aeiou][bdglmnprst]$/i).test(verb)) { if ((/[aeiou][bdglmnprst]$/i).test(verb)) {
return verb.replace(/(.+[aeiou])([bdglmnprst])/, '$1$2$2ed') return verb.replace(/(.+[aeiou])([bdglmnprst])/, '$1$2$2ed')
} }
return verb + 'ed' return verb + 'ed'
} }

File diff suppressed because one or more lines are too long

View File

@ -1,11 +1,9 @@
import {store} from './store.js' import { store } from './store'
import {doLoadConfigFromAPI} from './redux/config/config-actions.js' import { doLoadConfigFromAPI } from './redux/config/config-actions'
import {doInitWorkers, doLoadNodeConfig} from './redux/app/app-actions.js' import { doInitWorkers, doLoadNodeConfig } from './redux/app/app-actions'
import {doLoadNotificationConfig} from './redux/user/user-actions.js' import { doLoadNotificationConfig } from './redux/user/user-actions'
import './persistState.js'
import { initApi } from 'qortal-ui-crypto' import { initApi } from 'qortal-ui-crypto'
import './persistState'
initApi(store) initApi(store)
@ -20,15 +18,15 @@ const workerInitChecker = () => {
store.dispatch(doLoadNodeConfig()) store.dispatch(doLoadNodeConfig())
if (state.app.workers.ready) { if (state.app.workers.ready) {
workerInitSubscription() workerInitSubscription()
} else { } else {
if (!state.app.workers.loading) store.dispatch(doInitWorkers(state.config.crypto.kdfThreads, state.config.user.constants.workerURL)) if (!state.app.workers.loading) store.dispatch(doInitWorkers(state.config.crypto.kdfThreads, state.config.user.constants.workerURL))
} }
} }
} }
workerInitChecker() workerInitChecker()
const workerInitSubscription = store.subscribe(workerInitChecker) const workerInitSubscription = store.subscribe(workerInitChecker)
if (!store.getState().config.loaded) { if (!store.getState().config.loaded) {

View File

@ -1,8 +1,10 @@
import CryptoJS from 'crypto-js' import CryptoJS from 'crypto-js'
export const encryptData = (data, salt) => CryptoJS.AES.encrypt(JSON.stringify(data), salt).toString() export const encryptData = (data, salt) => CryptoJS.AES.encrypt(JSON.stringify(data), salt).toString()
export const decryptData = (ciphertext, salt) => { export const decryptData = (ciphertext, salt) => {
const bytes = CryptoJS.AES.decrypt(ciphertext, salt) const bytes = CryptoJS.AES.decrypt(ciphertext, salt)
try { try {
return JSON.parse(bytes.toString(CryptoJS.enc.Utf8)) return JSON.parse(bytes.toString(CryptoJS.enc.Utf8))
} catch (err) { } catch (err) {

View File

@ -1,2 +1,2 @@
import './initStore.js' import './initStore'
import './components/main-app.js' import './components/main-app'

View File

@ -1,23 +1,24 @@
import config from './config'
import { dispatcher } from './dispatcher' import { dispatcher } from './dispatcher'
import snackbar from '../functional-components/snackbar.js'
import { NEW_MESSAGE, NEW_MESSAGE_NOTIFICATION_QAPP, NEW_MESSAGE_NOTIFICATION_QAPP_LOCAL } from './types' import { NEW_MESSAGE, NEW_MESSAGE_NOTIFICATION_QAPP, NEW_MESSAGE_NOTIFICATION_QAPP_LOCAL } from './types'
import config from './config'
import snackbar from '../functional-components/snackbar'
let initial = 0 let initial = 0
let _state let _state
const notificationCheck = function () { const notificationCheck = function () {
if (window.Notification && Notification.permission === 'granted') { if (window.Notification && Notification.permission === 'granted') {
// ...
return true return true
} else if (window.Notification && Notification.permission !== 'denied') { } else if (window.Notification && Notification.permission !== 'denied') {
Notification.requestPermission().then(permission => { Notification.requestPermission().then(permission => {
if (permission === 'granted') { if (permission === 'granted') {
dispatcher(_state) dispatcher(_state)
_state = '' _state = ''
return true return true
} else { } else {
initial = initial + 1 initial = initial + 1
snackbar.add({ snackbar.add({
labelText: 'Notification is disabled, Enable it to recieve notifications.', labelText: 'Notification is disabled, Enable it to recieve notifications.',
dismiss: true dismiss: true
@ -41,21 +42,21 @@ const notificationCheck = function () {
* @property notificationState = { type: NEW_MESSAGE, data } * @property notificationState = { type: NEW_MESSAGE, data }
* @property data = { title: 'Qortal Chat', sound: config.messageAlert, options: { body: 'New Message', icon: config.default.icon, badge: config.default.icon }, req } * @property data = { title: 'Qortal Chat', sound: config.messageAlert, options: { body: 'New Message', icon: config.default.icon, badge: config.default.icon }, req }
*/ */
export const doNewMessage = function (req) { export const doNewMessage = function (req) {
const newMessage = () => { const newMessage = () => {
let data let data
if (req.type && req.type === 'qapp') { if (req.type && req.type === 'qapp') {
data = req data = req
} else if (req.groupId) { } else if (req.groupId) {
const title = `${req.groupName}` const title = `${req.groupName}`
const body = `New Message from ${req.senderName === undefined ? req.sender : req.senderName}` const body = `New Message from ${req.senderName === undefined ? req.sender : req.senderName}`
data = { title, sound: config.messageAlert, options: { body, icon: config.default.icon, badge: config.default.icon }, req } data = { title, sound: config.messageAlert, options: { body, icon: config.default.icon, badge: config.default.icon }, req }
} else { } else {
const title = `${req.senderName === undefined ? req.sender : req.senderName}` const title = `${req.senderName === undefined ? req.sender : req.senderName}`
const body = 'New Message' const body = 'New Message'
data = { title, sound: config.messageAlert, options: { body, icon: config.default.icon, badge: config.default.icon }, req } data = { title, sound: config.messageAlert, options: { body, icon: config.default.icon, badge: config.default.icon }, req }
} }
@ -69,12 +70,13 @@ export const doNewMessage = function (req) {
} else { } else {
dispatcher(notificationState) dispatcher(notificationState)
} }
} else { } else {
_state = notificationState _state = notificationState
} }
} }
const page = window.top.location.href const page = window.top.location.href
if (req.type && req.type === 'qapp-local-notification') { if (req.type && req.type === 'qapp-local-notification') {
try { try {
dispatcher({ type: NEW_MESSAGE_NOTIFICATION_QAPP_LOCAL, data: req }) dispatcher({ type: NEW_MESSAGE_NOTIFICATION_QAPP_LOCAL, data: req })

View File

@ -2,14 +2,16 @@ import {NEW_MESSAGE, NEW_MESSAGE_NOTIFICATION_QAPP, NEW_MESSAGE_NOTIFICATION_QAP
import {newMessage, newMessageNotificationQapp, newMessageNotificationQappLocal} from './notification-actions' import {newMessage, newMessageNotificationQapp, newMessageNotificationQappLocal} from './notification-actions'
export const dispatcher = function (notificationState) { export const dispatcher = function (notificationState) {
switch (notificationState.type) { switch (notificationState.type) {
case NEW_MESSAGE: case NEW_MESSAGE:
return newMessage(notificationState.data) return newMessage(notificationState.data)
case NEW_MESSAGE_NOTIFICATION_QAPP: case NEW_MESSAGE_NOTIFICATION_QAPP:
return newMessageNotificationQapp(notificationState.data) return newMessageNotificationQapp(notificationState.data)
case NEW_MESSAGE_NOTIFICATION_QAPP_LOCAL: case NEW_MESSAGE_NOTIFICATION_QAPP_LOCAL:
return newMessageNotificationQappLocal(notificationState.data) return newMessageNotificationQappLocal(notificationState.data)
default: default:
} }
} }

View File

@ -1,7 +1,7 @@
import {store} from '../../store.js' import { store } from '../../store'
import {doPageUrl, setNewTab} from '../../redux/app/app-actions.js' import { doPageUrl, setNewTab } from '../../redux/app/app-actions'
import isElectron from 'is-electron' import isElectron from 'is-electron'
import ShortUniqueId from 'short-unique-id'; import ShortUniqueId from 'short-unique-id'
const uid = new ShortUniqueId() const uid = new ShortUniqueId()
@ -10,10 +10,8 @@ export const newMessage = (data) => {
// Should I show notification ? // Should I show notification ?
if (store.getState().user.notifications.q_chat.showNotification) { if (store.getState().user.notifications.q_chat.showNotification) {
// Yes, I can but should I play sound ? // Yes, I can but should I play sound ?
if (store.getState().user.notifications.q_chat.playSound) { if (store.getState().user.notifications.q_chat.playSound) {
const notify = new Notification(data.title, data.options) const notify = new Notification(data.title, data.options)
notify.onshow = (e) => { notify.onshow = (e) => {
@ -25,7 +23,6 @@ export const newMessage = (data) => {
store.dispatch(doPageUrl(pageUrl)) store.dispatch(doPageUrl(pageUrl))
} }
} else { } else {
const notify = new Notification(data.title, data.options) const notify = new Notification(data.title, data.options)
notify.onclick = (e) => { notify.onclick = (e) => {
@ -37,18 +34,15 @@ export const newMessage = (data) => {
} else if (store.getState().user.notifications.q_chat.playSound) { } else if (store.getState().user.notifications.q_chat.playSound) {
alert.play() alert.play()
} }
} }
export const newMessageNotificationQapp = (data) => {
export const newMessageNotificationQapp = (data) => {
const alert = playSound(data.sound) const alert = playSound(data.sound)
// Should I show notification ? // Should I show notification ?
if (store.getState().user.notifications.q_chat.showNotification) { if (store.getState().user.notifications.q_chat.showNotification) {
// Yes, I can but should I play sound ? // Yes, I can but should I play sound ?
if (store.getState().user.notifications.q_chat.playSound) { if (store.getState().user.notifications.q_chat.playSound) {
const notify = new Notification(data.title, data.options) const notify = new Notification(data.title, data.options)
notify.onshow = (e) => { notify.onshow = (e) => {
@ -71,15 +65,14 @@ export const newMessageNotificationQapp = (data) => {
"parent": false "parent": false
} }
})) }))
if (!isElectron()) { if (!isElectron()) {
window.focus(); window.focus();
} else { } else {
window.electronAPI.focusApp() window.electronAPI.focusApp()
} }
} }
} else { } else {
const notify = new Notification(data.title, data.options) const notify = new Notification(data.title, data.options)
notify.onclick = (e) => { notify.onclick = (e) => {
@ -98,6 +91,7 @@ export const newMessageNotificationQapp = (data) => {
"parent": false "parent": false
} }
})) }))
if (!isElectron()) { if (!isElectron()) {
window.focus(); window.focus();
} else { } else {
@ -109,25 +103,32 @@ export const newMessageNotificationQapp = (data) => {
} else if (store.getState().user.notifications.q_chat.playSound) { } else if (store.getState().user.notifications.q_chat.playSound) {
alert.play() alert.play()
} }
} }
const extractComponents = async (url) => { const extractComponents = async (url) => {
if (!url.startsWith("qortal://")) { if (!url.startsWith("qortal://")) {
return null; return null
} }
url = url.replace(/^(qortal\:\/\/)/, ""); url = url.replace(/^(qortal\:\/\/)/, "")
if (url.includes("/")) { if (url.includes("/")) {
let parts = url.split("/"); let parts = url.split("/")
const service = parts[0].toUpperCase();
parts.shift(); const service = parts[0].toUpperCase()
const name = parts[0];
parts.shift(); parts.shift()
let identifier;
const name = parts[0]
parts.shift()
let identifier
if (parts.length > 0) { if (parts.length > 0) {
identifier = parts[0]; // Do not shift yet // Do not shift yet
identifier = parts[0]
// Check if a resource exists with this service, name and identifier combination // Check if a resource exists with this service, name and identifier combination
let responseObj = await parentEpml.request('apiCall', { let responseObj = await parentEpml.request('apiCall', {
url: `/arbitrary/resource/status/${service}/${name}/${identifier}?apiKey=${this.getApiKey()}` url: `/arbitrary/resource/status/${service}/${name}/${identifier}?apiKey=${this.getApiKey()}`
@ -135,24 +136,24 @@ export const newMessageNotificationQapp = (data) => {
if (responseObj.totalChunkCount > 0) { if (responseObj.totalChunkCount > 0) {
// Identifier exists, so don't include it in the path // Identifier exists, so don't include it in the path
parts.shift(); parts.shift()
} } else {
else { identifier = null
identifier = null;
} }
} }
const path = parts.join("/"); const path = parts.join("/")
const components = {}
const components = {}; components["service"] = service
components["service"] = service; components["name"] = name
components["name"] = name; components["identifier"] = identifier
components["identifier"] = identifier; components["path"] = path
components["path"] = path;
return components; return components
} }
return null; return null
} }
export const newMessageNotificationQappLocal = (data) => { export const newMessageNotificationQappLocal = (data) => {
@ -160,10 +161,8 @@ export const newMessageNotificationQappLocal = (data) => {
// Should I show notification ? // Should I show notification ?
if (store.getState().user.notifications.q_chat.showNotification) { if (store.getState().user.notifications.q_chat.showNotification) {
// Yes, I can but should I play sound ? // Yes, I can but should I play sound ?
if (store.getState().user.notifications.q_chat.playSound) { if (store.getState().user.notifications.q_chat.playSound) {
const notify = new Notification(data.title, data.options) const notify = new Notification(data.title, data.options)
notify.onshow = (e) => { notify.onshow = (e) => {
@ -171,23 +170,32 @@ export const newMessageNotificationQappLocal = (data) => {
} }
notify.onclick = async (e) => { notify.onclick = async (e) => {
let newQuery = data?.url; let newQuery = data?.url
if (newQuery.endsWith('/')) { if (newQuery.endsWith('/')) {
newQuery = newQuery.slice(0, -1); newQuery = newQuery.slice(0, -1)
} }
const res = await extractComponents(newQuery) const res = await extractComponents(newQuery)
if (!res) return if (!res) return
const { service, name, identifier, path } = res const { service, name, identifier, path } = res
let query = `?service=${service}` let query = `?service=${service}`
if (name) { if (name) {
query = query + `&name=${name}` query = query + `&name=${name}`
} }
if (identifier) { if (identifier) {
query = query + `&identifier=${identifier}` query = query + `&identifier=${identifier}`
} }
if (path) { if (path) {
query = query + `&path=${path}` query = query + `&path=${path}`
} }
const tab = { const tab = {
url: `qdn/browser/index.html${query}`, url: `qdn/browser/index.html${query}`,
id: uid.rnd(), id: uid.rnd(),
@ -202,36 +210,45 @@ export const newMessageNotificationQappLocal = (data) => {
"parent": false "parent": false
} }
} }
store.dispatch(setNewTab(tab)) store.dispatch(setNewTab(tab))
if (!isElectron()) { if (!isElectron()) {
window.focus(); window.focus()
} else { } else {
window.electronAPI.focusApp() window.electronAPI.focusApp()
} }
} }
} else { } else {
const notify = new Notification(data.title, data.options) const notify = new Notification(data.title, data.options)
notify.onclick = async (e) => { notify.onclick = async (e) => {
let newQuery = data?.url; let newQuery = data?.url
if (newQuery.endsWith('/')) { if (newQuery.endsWith('/')) {
newQuery = newQuery.slice(0, -1); newQuery = newQuery.slice(0, -1)
} }
const res = await extractComponents(newQuery) const res = await extractComponents(newQuery)
if (!res) return if (!res) return
const { service, name, identifier, path } = res const { service, name, identifier, path } = res
let query = `?service=${service}` let query = `?service=${service}`
if (name) { if (name) {
query = query + `&name=${name}` query = query + `&name=${name}`
} }
if (identifier) { if (identifier) {
query = query + `&identifier=${identifier}` query = query + `&identifier=${identifier}`
} }
if (path) { if (path) {
query = query + `&path=${path}` query = query + `&path=${path}`
} }
const tab = { const tab = {
url: `qdn/browser/index.html${query}`, url: `qdn/browser/index.html${query}`,
id: uid.rnd(), id: uid.rnd(),
@ -246,20 +263,20 @@ export const newMessageNotificationQappLocal = (data) => {
"parent": false "parent": false
} }
} }
store.dispatch(setNewTab(tab)) store.dispatch(setNewTab(tab))
if (!isElectron()) { if (!isElectron()) {
window.focus(); window.focus()
} else { } else {
window.electronAPI.focusApp() window.electronAPI.focusApp()
} }
} }
} }
// If sounds are enabled, but notifications are not // If sounds are enabled, but notifications are not
} else if (store.getState().user.notifications.q_chat.playSound) { } else if (store.getState().user.notifications.q_chat.playSound) {
alert.play() alert.play()
} }
} }
const playSound = (soundUrl) => { const playSound = (soundUrl) => {

View File

@ -1,20 +1,17 @@
import {store} from './store.js' import { store } from './store'
import {saveStateToLocalStorage} from './localStorageHelpers.js' import { saveStateToLocalStorage } from './localStorageHelpers'
const keys = [
'config',
'user'
]
const keys = ['config', 'user']
const oldReducers = {} const oldReducers = {}
const oldState = store.getState() const oldState = store.getState()
for (const key of keys) { for (const key of keys) {
oldReducers[key] = oldState[key] oldReducers[key] = oldState[key]
} }
store.subscribe(() => { store.subscribe(() => {
const newState = store.getState() const newState = store.getState()
keys.forEach(key => { keys.forEach(key => {
if (newState[key] !== oldState[key]) { if (newState[key] !== oldState[key]) {
saveStateToLocalStorage(key, store.getState()[key]) saveStateToLocalStorage(key, store.getState()[key])

View File

@ -1,4 +1,4 @@
import {routes} from './routes.js' import { routes } from './routes'
export const addPluginRoutes = epmlInstance => { export const addPluginRoutes = epmlInstance => {
Object.entries(routes).forEach(([route, handler]) => { Object.entries(routes).forEach(([route, handler]) => {

View File

@ -1,17 +1,16 @@
import {store} from '../store.js' import { store } from '../store'
import {Epml} from '../epml.js' import { Epml } from '../epml'
import { addPluginRoutes } from './addPluginRoutes' import { addPluginRoutes } from './addPluginRoutes'
import {doAddPlugin} from '../redux/app/app-actions.js' import { doAddPlugin } from '../redux/app/app-actions'
let retryLoadPluginsInterval = 0 let retryLoadPluginsInterval = 0
export const loadPlugins = () => fetch('/getPlugins') export const loadPlugins = () => fetch('/getPlugins')
.then(response => response.json()) .then(response => response.json()).then(response => {
.then(response => {
const plugins = response.plugins const plugins = response.plugins
const config = store.getState().config const config = store.getState().config
pluginLoader(plugins, config) pluginLoader(plugins, config)
}) }).catch(err => {
.catch(err => {
retryLoadPluginsInterval += 1000 retryLoadPluginsInterval += 1000
console.error(err) console.error(err)
console.error(`Could not load plugins. Retrying in ${retryLoadPluginsInterval / 1000} second(s)`) console.error(`Could not load plugins. Retrying in ${retryLoadPluginsInterval / 1000} second(s)`)
@ -20,11 +19,12 @@ export const loadPlugins = () => fetch('/getPlugins')
export const pluginLoader = (plugins, config) => { export const pluginLoader = (plugins, config) => {
const pluginContentWindows = [] const pluginContentWindows = []
plugins.forEach(plugin => { plugins.forEach(plugin => {
const frame = document.createElement('iframe') const frame = document.createElement('iframe')
frame.className += 'pluginJSFrame' frame.className += 'pluginJSFrame'
frame.sandbox = 'allow-scripts allow-same-origin' frame.sandbox = 'allow-scripts allow-same-origin'
frame.src = window.location.origin + '/qortal-components/plugin-mainjs-loader.html#' + plugin + '/main.js' frame.src = window.location.origin + '/qortal-components/plugin-mainjs-loader.html#' + plugin + '/main.js'
const insertedFrame = window.document.body.appendChild(frame) const insertedFrame = window.document.body.appendChild(frame)
@ -37,6 +37,7 @@ export const pluginLoader = (plugins, config) => {
}) })
addPluginRoutes(epmlInstance) addPluginRoutes(epmlInstance)
epmlInstance.imReady() epmlInstance.imReady()
Epml.registerProxyInstance(`${plugin}-plugin`, epmlInstance) Epml.registerProxyInstance(`${plugin}-plugin`, epmlInstance)

View File

@ -1,9 +1,6 @@
<html> <html>
<head></head> <head></head>
<body> <body>
<script src="/qortal-components/plugin-mainjs-loader.js" type="application/javascript"></script> <script src="/qortal-components/plugin-mainjs-loader.js" type="application/javascript"></script>
</body> </body>
</html> </html>

View File

@ -1,12 +1,15 @@
'use strict' import { Epml, EpmlStream } from '../epml'
import {Epml, EpmlStream} from '../epml.js'
window.Epml = Epml window.Epml = Epml
window.EpmlStream = EpmlStream window.EpmlStream = EpmlStream
const pluginScript = document.createElement('script') const pluginScript = document.createElement('script')
pluginScript.async = false pluginScript.async = false
pluginScript.type = 'module' pluginScript.type = 'module'
const hash = window.location.hash const hash = window.location.hash
pluginScript.src = '/plugin/' + hash.slice(1) pluginScript.src = '/plugin/' + hash.slice(1)
document.body.appendChild(pluginScript) document.body.appendChild(pluginScript)

View File

@ -1,4 +1,4 @@
import {store} from '../store.js' import { store } from '../store'
import { import {
doAddPluginUrl, doAddPluginUrl,
doPageUrl, doPageUrl,
@ -7,13 +7,14 @@ import {
doUpdateAccountInfo, doUpdateAccountInfo,
doUpdateBlockInfo, doUpdateBlockInfo,
doUpdateNodeInfo, doUpdateNodeInfo,
doUpdateNodeStatus, doUpdateNodeStatus
} from '../redux/app/app-actions.js' } from '../redux/app/app-actions'
import { loadStateFromLocalStorage, saveStateToLocalStorage, } from '../localStorageHelpers'
import { requestTransactionDialog } from '../functional-components/confirm-transaction-dialog'
import { doNewMessage } from '../notifications/controller'
import * as api from 'qortal-ui-crypto' import * as api from 'qortal-ui-crypto'
import {requestTransactionDialog} from '../functional-components/confirm-transaction-dialog.js' import snackbar from '../functional-components/snackbar'
import {doNewMessage} from '../notifications/controller.js'
import snackbar from '../functional-components/snackbar.js'
import {loadStateFromLocalStorage, saveStateToLocalStorage,} from '../localStorageHelpers.js'
const createTransaction = api.createTransaction const createTransaction = api.createTransaction
const processTransaction = api.processTransaction const processTransaction = api.processTransaction
@ -122,16 +123,17 @@ export const routes = {
if (req.data.apiVersion && req.data.apiVersion === 2) { if (req.data.apiVersion && req.data.apiVersion === 2) {
res = await processTransactionVersion2(tx.signedBytes) res = await processTransactionVersion2(tx.signedBytes)
} }
if (!req.data.apiVersion) { if (!req.data.apiVersion) {
res = await processTransaction(tx.signedBytes) res = await processTransaction(tx.signedBytes)
} }
let extraData = {} let extraData = {}
if (req.data.type === 38 && tx && tx._rewardShareKeyPair && tx._rewardShareKeyPair.secretKey) { if (req.data.type === 38 && tx && tx._rewardShareKeyPair && tx._rewardShareKeyPair.secretKey) {
extraData.rewardSharePrivateKey = Base58.encode(tx._rewardShareKeyPair.secretKey) extraData.rewardSharePrivateKey = Base58.encode(tx._rewardShareKeyPair.secretKey)
} }
response = { response = {
success: true, success: true,
data: res, data: res,
@ -140,70 +142,80 @@ export const routes = {
} catch (e) { } catch (e) {
console.error(e) console.error(e)
console.error(e.message) console.error(e.message)
response = { response = {
success: false, success: false,
message: e.message, message: e.message
} }
} }
return response return response
}, },
standaloneTransaction: async (req) => { standaloneTransaction: async (req) => {
const rebuildUint8Array = (obj) => { const rebuildUint8Array = (obj) => {
let _array = new Uint8Array(Object.keys(obj).length) let _array = new Uint8Array(Object.keys(obj).length)
for (let i = 0; i < _array.byteLength; ++i) { for (let i = 0; i < _array.byteLength; ++i) {
_array.set([obj[i]], i) _array.set([obj[i]], i)
} }
return _array return _array
} }
let response let response
try { try {
// req.data.keyPair unfortunately "prepared" into horrible object so we need to convert back // req.data.keyPair unfortunately "prepared" into horrible object so we need to convert back
let _keyPair = {} let _keyPair = {}
for (let _keyName in req.data.keyPair) { for (let _keyName in req.data.keyPair) {
_keyPair[_keyName] = rebuildUint8Array( _keyPair[_keyName] = rebuildUint8Array(
req.data.keyPair[_keyName] req.data.keyPair[_keyName]
) )
} }
const tx = createTransaction( const tx = createTransaction(
req.data.type, req.data.type,
_keyPair, _keyPair,
req.data.params req.data.params
) )
let res let res
if (req.data.apiVersion && req.data.apiVersion === 2) { if (req.data.apiVersion && req.data.apiVersion === 2) {
res = await processTransactionVersion2(tx.signedBytes) res = await processTransactionVersion2(tx.signedBytes)
} }
if (!req.data.apiVersion) { if (!req.data.apiVersion) {
res = await processTransaction(tx.signedBytes) res = await processTransaction(tx.signedBytes)
} }
response = { response = {
success: true, success: true,
data: res, data: res
} }
} catch (e) { } catch (e) {
console.error(e) console.error(e)
console.error(e.message) console.error(e.message)
response = { response = {
success: false, success: false,
message: e.message, message: e.message
} }
} }
return response return response
}, },
username: async (req) => { username: async (req) => {
const state = store.getState() const state = store.getState()
return state.user.storedWallets[state.app.wallet.addresses[0].address] return state.user.storedWallets[state.app.wallet.addresses[0].address].name
.name
}, },
chat: async (req) => { chat: async (req) => {
let response let response
try { try {
const tx = createTransaction( const tx = createTransaction(
req.data.type, req.data.type,
@ -215,13 +227,16 @@ export const routes = {
} catch (e) { } catch (e) {
console.error(e) console.error(e)
console.error(e.message) console.error(e.message)
response = false response = false
} }
return response return response
}, },
sign_chat: async (req) => { sign_chat: async (req) => {
let response let response
try { try {
const signedChatBytes = await signChatTransaction( const signedChatBytes = await signChatTransaction(
req.data.chatBytesArray, req.data.chatBytesArray,
@ -234,6 +249,7 @@ export const routes = {
if (req.data.apiVersion && req.data.apiVersion === 2) { if (req.data.apiVersion && req.data.apiVersion === 2) {
res = await processTransactionVersion2(signedChatBytes) res = await processTransactionVersion2(signedChatBytes)
} }
if (!req.data.apiVersion) { if (!req.data.apiVersion) {
res = await processTransaction(signedChatBytes) res = await processTransaction(signedChatBytes)
} }
@ -242,13 +258,16 @@ export const routes = {
} catch (e) { } catch (e) {
console.error(e) console.error(e)
console.error(e.message) console.error(e.message)
response = false response = false
} }
return response return response
}, },
sign_arbitrary: async (req) => { sign_arbitrary: async (req) => {
let response let response
try { try {
const signedArbitraryBytes = await signArbitraryTransaction( const signedArbitraryBytes = await signArbitraryTransaction(
req.data.arbitraryBytesBase58, req.data.arbitraryBytesBase58,
@ -256,11 +275,13 @@ export const routes = {
req.data.arbitraryNonce, req.data.arbitraryNonce,
store.getState().app.wallet._addresses[req.data.nonce].keyPair store.getState().app.wallet._addresses[req.data.nonce].keyPair
) )
let res let res
if (req.data.apiVersion && req.data.apiVersion === 2) { if (req.data.apiVersion && req.data.apiVersion === 2) {
res = await processTransactionVersion2(signedArbitraryBytes) res = await processTransactionVersion2(signedArbitraryBytes)
} }
if (!req.data.apiVersion) { if (!req.data.apiVersion) {
res = await processTransaction(signedArbitraryBytes) res = await processTransaction(signedArbitraryBytes)
} }
@ -269,24 +290,29 @@ export const routes = {
} catch (e) { } catch (e) {
console.error(e) console.error(e)
console.error(e.message) console.error(e.message)
response = false response = false
} }
return response return response
}, },
sign_arbitrary_with_fee: async (req) => { sign_arbitrary_with_fee: async (req) => {
let response let response
try { try {
const signedArbitraryBytes = await signArbitraryWithFeeTransaction( const signedArbitraryBytes = await signArbitraryWithFeeTransaction(
req.data.arbitraryBytesBase58, req.data.arbitraryBytesBase58,
req.data.arbitraryBytesForSigningBase58, req.data.arbitraryBytesForSigningBase58,
store.getState().app.wallet._addresses[req.data.nonce].keyPair store.getState().app.wallet._addresses[req.data.nonce].keyPair
) )
let res let res
if (req.data.apiVersion && req.data.apiVersion === 2) { if (req.data.apiVersion && req.data.apiVersion === 2) {
res = await processTransactionVersion2(signedArbitraryBytes) res = await processTransactionVersion2(signedArbitraryBytes)
} }
if (!req.data.apiVersion) { if (!req.data.apiVersion) {
res = await processTransaction(signedArbitraryBytes) res = await processTransaction(signedArbitraryBytes)
} }
@ -295,8 +321,10 @@ export const routes = {
} catch (e) { } catch (e) {
console.error(e) console.error(e)
console.error(e.message) console.error(e.message)
response = false response = false
} }
return response return response
}, },
@ -307,12 +335,13 @@ export const routes = {
showSnackBar: async (req) => { showSnackBar: async (req) => {
snackbar.add({ snackbar.add({
labelText: req.data, labelText: req.data,
dismiss: true, dismiss: true
}) })
}, },
tradeBotCreateRequest: async (req) => { tradeBotCreateRequest: async (req) => {
let response let response
try { try {
const unsignedTxn = await tradeBotCreateRequest(req.data) const unsignedTxn = await tradeBotCreateRequest(req.data)
@ -320,37 +349,46 @@ export const routes = {
unsignedTxn, unsignedTxn,
store.getState().app.selectedAddress.keyPair store.getState().app.selectedAddress.keyPair
) )
let res let res
if (req.data.apiVersion && req.data.apiVersion === 2) { if (req.data.apiVersion && req.data.apiVersion === 2) {
res = await processTransactionVersion2(signedTxnBytes) res = await processTransactionVersion2(signedTxnBytes)
} }
if (!req.data.apiVersion) { if (!req.data.apiVersion) {
res = await processTransaction(signedTxnBytes) res = await processTransaction(signedTxnBytes)
} }
response = res response = res
} catch (e) { } catch (e) {
console.error(e) console.error(e)
console.error(e.message) console.error(e.message)
response = e.message response = e.message
} }
return response return response
}, },
tradeBotRespondRequest: async (req) => { tradeBotRespondRequest: async (req) => {
let response let response
try { try {
response = await tradeBotRespondRequest(req.data) response = await tradeBotRespondRequest(req.data)
} catch (e) { } catch (e) {
console.error(e) console.error(e)
console.error(e.message) console.error(e.message)
response = e.message response = e.message
} }
return response return response
}, },
deleteTradeOffer: async (req) => { deleteTradeOffer: async (req) => {
let response let response
try { try {
const unsignedTxn = await deleteTradeOffer(req.data) const unsignedTxn = await deleteTradeOffer(req.data)
@ -364,6 +402,7 @@ export const routes = {
if (req.data.apiVersion && req.data.apiVersion === 2) { if (req.data.apiVersion && req.data.apiVersion === 2) {
res = await processTransactionVersion2(signedTxnBytes) res = await processTransactionVersion2(signedTxnBytes)
} }
if (!req.data.apiVersion) { if (!req.data.apiVersion) {
res = await processTransaction(signedTxnBytes) res = await processTransaction(signedTxnBytes)
} }
@ -372,94 +411,115 @@ export const routes = {
} catch (e) { } catch (e) {
console.error(e) console.error(e)
console.error(e.message) console.error(e.message)
response = e.message response = e.message
} }
return response return response
}, },
cancelAllOffers: async (req) => { cancelAllOffers: async (req) => {
let response let response
try { try {
response = await cancelAllOffers( response = await cancelAllOffers(store.getState().app.selectedAddress)
store.getState().app.selectedAddress
)
} catch (e) { } catch (e) {
console.error(e) console.error(e)
console.error(e.message) console.error(e.message)
response = e.message response = e.message
} }
return response return response
}, },
sendBtc: async (req) => { sendBtc: async (req) => {
let response let response
try { try {
response = await sendBtc(req.data) response = await sendBtc(req.data)
} catch (e) { } catch (e) {
console.error(e) console.error(e)
console.error(e.message) console.error(e.message)
response = e.message response = e.message
} }
return response return response
}, },
sendLtc: async (req) => { sendLtc: async (req) => {
let response let response
try { try {
response = await sendLtc(req.data) response = await sendLtc(req.data)
} catch (e) { } catch (e) {
console.error(e) console.error(e)
console.error(e.message) console.error(e.message)
response = e.message response = e.message
} }
return response return response
}, },
sendDoge: async (req) => { sendDoge: async (req) => {
let response let response
try { try {
response = await sendDoge(req.data) response = await sendDoge(req.data)
} catch (e) { } catch (e) {
console.error(e) console.error(e)
console.error(e.message) console.error(e.message)
response = e.message response = e.message
} }
return response return response
}, },
sendDgb: async (req) => { sendDgb: async (req) => {
let response let response
try { try {
response = await sendDgb(req.data) response = await sendDgb(req.data)
} catch (e) { } catch (e) {
console.error(e) console.error(e)
console.error(e.message) console.error(e.message)
response = e.message response = e.message
} }
return response return response
}, },
sendRvn: async (req) => { sendRvn: async (req) => {
let response let response
try { try {
response = await sendRvn(req.data) response = await sendRvn(req.data)
} catch (e) { } catch (e) {
console.error(e) console.error(e)
console.error(e.message) console.error(e.message)
response = e.message response = e.message
} }
return response return response
}, },
sendArrr: async (req) => { sendArrr: async (req) => {
let response let response
try { try {
response = await sendArrr(req.data) response = await sendArrr(req.data)
} catch (e) { } catch (e) {
console.error(e) console.error(e)
console.error(e.message) console.error(e.message)
response = e.message response = e.message
} }
return response return response
}, }
} }

View File

@ -1,4 +1,4 @@
import {store} from '../store.js' import { store } from '../store'
import { EpmlStream } from 'epml' import { EpmlStream } from 'epml'
const LOGIN_STREAM_NAME = 'logged_in' const LOGIN_STREAM_NAME = 'logged_in'
@ -23,9 +23,6 @@ export const sideEffectActionStream = new EpmlStream(SIDE_EFFECT_ACTION, () => s
export const profileDataActionStream = new EpmlStream(SIDE_EFFECT_ACTION, () => store.getState().app.profileData) export const profileDataActionStream = new EpmlStream(SIDE_EFFECT_ACTION, () => store.getState().app.profileData)
export const coinBalancesActionStream = new EpmlStream(COIN_BALANCES_ACTION, () => store.getState().app.coinBalances) export const coinBalancesActionStream = new EpmlStream(COIN_BALANCES_ACTION, () => store.getState().app.coinBalances)
let oldState = { let oldState = {
app: {} app: {}
} }
@ -57,6 +54,7 @@ store.subscribe(() => {
textColor: state.app.selectedAddress.textColor textColor: state.app.selectedAddress.textColor
}) })
} }
if (oldState.app.chatHeads !== state.app.chatHeads) { if (oldState.app.chatHeads !== state.app.chatHeads) {
chatHeadsStateStream.emit(state.app.chatHeads) chatHeadsStateStream.emit(state.app.chatHeads)
} }
@ -74,6 +72,5 @@ store.subscribe(() => {
coinBalancesActionStream.emit(state.app.coinBalances) coinBalancesActionStream.emit(state.app.coinBalances)
} }
oldState = state oldState = state
}) })

View File

@ -25,7 +25,7 @@ import {
REMOVE_QAPP_FRIENDS_LIST, REMOVE_QAPP_FRIENDS_LIST,
ALLOW_SHOW_SYNC_INDICATOR, ALLOW_SHOW_SYNC_INDICATOR,
REMOVE_SHOW_SYNC_INDICATOR REMOVE_SHOW_SYNC_INDICATOR
} from '../app-action-types.js' } from '../app-action-types'
export const doUpdateBlockInfo = (blockObj) => { export const doUpdateBlockInfo = (blockObj) => {
return (dispatch, getState) => { return (dispatch, getState) => {
@ -119,6 +119,7 @@ export const removeQAPPAutoAuth = (payload) => {
payload payload
} }
} }
export const allowQAPPAutoLists = (payload) => { export const allowQAPPAutoLists = (payload) => {
return { return {
type: ALLOW_QAPP_AUTO_LISTS, type: ALLOW_QAPP_AUTO_LISTS,
@ -153,6 +154,7 @@ export const setChatLastSeen = (payload) => {
payload payload
} }
} }
export const addChatLastSeen = (payload) => { export const addChatLastSeen = (payload) => {
return { return {
type: ADD_CHAT_LAST_SEEN, type: ADD_CHAT_LAST_SEEN,

View File

@ -1,20 +1,21 @@
import {Epml} from '../../../epml.js' import { Epml } from '../../../epml'
import { EpmlWorkerPlugin } from 'epml' import { EpmlWorkerPlugin } from 'epml'
import { INIT_WORKERS } from '../app-action-types'
import {INIT_WORKERS} from '../app-action-types.js'
Epml.registerPlugin(EpmlWorkerPlugin) Epml.registerPlugin(EpmlWorkerPlugin)
export const doInitWorkers = (numberOfWorkers, workerURL) => { export const doInitWorkers = (numberOfWorkers, workerURL) => {
const workers = [] const workers = []
return (dispatch, getState) => { return (dispatch, getState) => {
dispatch(initWorkers()) dispatch(initWorkers())
try { try {
for (let i = 0; i < numberOfWorkers; i++) { for (let i = 0; i < numberOfWorkers; i++) {
workers.push(new Epml({ type: 'WORKER', source: new Worker(workerURL) })) workers.push(new Epml({ type: 'WORKER', source: new Worker(workerURL) }))
} }
Promise.all(workers.map(workerEpml => workerEpml.ready()))
.then(() => { Promise.all(workers.map(workerEpml => workerEpml.ready())).then(() => {
dispatch(initWorkers('success', workers)) dispatch(initWorkers('success', workers))
}) })
} catch (e) { } catch (e) {
@ -22,6 +23,7 @@ export const doInitWorkers = (numberOfWorkers, workerURL) => {
} }
} }
} }
const initWorkers = (status, payload) => { const initWorkers = (status, payload) => {
return { return {
type: INIT_WORKERS, type: INIT_WORKERS,

View File

@ -1,4 +1,4 @@
import {LOG_IN, LOG_OUT, SELECT_ADDRESS} from '../app-action-types.js' import { LOG_IN, LOG_OUT, SELECT_ADDRESS } from '../app-action-types'
export const doSelectAddress = address => { export const doSelectAddress = address => {
return (dispatch, getState) => { return (dispatch, getState) => {

View File

@ -1,21 +1,16 @@
// Node Config Actions here... // Node Config Actions here...
import {ADD_NODE, EDIT_NODE, LOAD_NODE_CONFIG, REMOVE_NODE, SET_NODE} from '../app-action-types.js' import { ADD_NODE, EDIT_NODE, LOAD_NODE_CONFIG, REMOVE_NODE, SET_NODE } from '../app-action-types'
import {UI_VERSION} from '../version.js' import { UI_VERSION } from '../version'
const nodeConfigUrl = '/getConfig' const nodeConfigUrl = '/getConfig'
const checkNodes = JSON.parse(localStorage.getItem('myQortalNodes')) const checkNodes = JSON.parse(localStorage.getItem('myQortalNodes'))
const checkMyNode = localStorage.getItem('mySelectedNode') const checkMyNode = localStorage.getItem('mySelectedNode')
export const doLoadNodeConfig = () => { export const doLoadNodeConfig = () => {
return (dispatch, getState) => { return (dispatch, getState) => {
fetch(nodeConfigUrl) fetch(nodeConfigUrl).then(res => {
.then(res => {
return res.json() return res.json()
}) }).then(data => {
.then(data => {
const nodeConfig = { const nodeConfig = {
node: 0, node: 0,
knownNodes: [{}], knownNodes: [{}],
@ -30,18 +25,18 @@ export const doLoadNodeConfig = () => {
} }
if (checkNodes === null || checkNodes.length === 0) { if (checkNodes === null || checkNodes.length === 0) {
var saveNode = []; var saveNode = []
saveNode.push(obj1,obj2); saveNode.push(obj1, obj2)
localStorage.setItem('myQortalNodes', JSON.stringify(saveNode)); localStorage.setItem('myQortalNodes', JSON.stringify(saveNode))
nodeConfig.knownNodes = JSON.parse(localStorage.getItem('myQortalNodes')); nodeConfig.knownNodes = JSON.parse(localStorage.getItem('myQortalNodes'))
} else { } else {
nodeConfig.knownNodes = JSON.parse(localStorage.getItem('myQortalNodes')); nodeConfig.knownNodes = JSON.parse(localStorage.getItem('myQortalNodes'))
} }
nodeConfig.version = UI_VERSION; nodeConfig.version = UI_VERSION
return dispatch(loadNodeConfig(nodeConfig)) return dispatch(loadNodeConfig(nodeConfig))
}) }).catch(err => {
.catch(err => {
console.error(err) console.error(err)
}) })
} }
@ -72,11 +67,13 @@ export const doAddNode = (nodeObject) => {
return dispatch(addNode(nodeObject)) return dispatch(addNode(nodeObject))
} }
} }
export const doRemoveNode = (index) => { export const doRemoveNode = (index) => {
return (dispatch, getState) => { return (dispatch, getState) => {
return dispatch(removeNode(index)) return dispatch(removeNode(index))
} }
} }
export const doEditNode = (index, nodeObject) => { export const doEditNode = (index, nodeObject) => {
return (dispatch, getState) => { return (dispatch, getState) => {
return dispatch(editNode({ index, nodeObject })) return dispatch(editNode({ index, nodeObject }))
@ -96,12 +93,14 @@ const editNode = (payload) => {
payload payload
} }
} }
const removeNode = (payload) => { const removeNode = (payload) => {
return { return {
type: REMOVE_NODE, type: REMOVE_NODE,
payload payload
} }
} }
const obj1 = { const obj1 = {
name: 'Local Node', name: 'Local Node',
protocol: 'http', protocol: 'http',
@ -117,4 +116,3 @@ const obj2 = {
port: 62391, port: 62391,
enableManagement: true enableManagement: true
} }

Some files were not shown because too many files have changed in this diff Show More