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 ~/.bashrc ``` (For Fedora / CentOS) <br/>
``` nvm ls-remote ``` (Fetch list of available versions) <br/>
``` nvm install v18.17.1 ``` (LTS: Hydrogen supported by Electron V27) <br/>
``` npm --location=global install npm@10.5.0 ``` <br/>
``` nvm install v20.11.1 ``` (LTS: Iron supported by Electron V30) <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.

View File

@ -1,28 +1,23 @@
const path = require('path')
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 build = uiCore('build')
const config = require('./config/config.js')
const pluginsController = require('./plugins/default-plugins.js')
const buildDefalutPlugins = pluginsController('build')
srcConfig = {
...config.build,
options: {
...config.build.options,
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)
build(buildConfig.options, buildConfig.outputs, buildConfig.outputOptions, buildConfig.inputOptions, inlineConfigs)
.then(() => {
console.log("Building and Bundling Plugins");
build(buildConfig.options, buildConfig.outputs, buildConfig.outputOptions, buildConfig.inputOptions, inlineConfigs).then(() => {
console.log("Building and Bundling Plugins")
buildDefalutPlugins()
})

View File

@ -1,14 +1,19 @@
let config = require('./default.config.js')
let userConfig = {}
try {
userConfig = require('./customConfig.js')
} catch (e) {
console.warn(e)
console.warn('Error loading user config')
}
const checkKeys = (storeObj, 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') {
storeObj[key] = checkKeys(storeObj[key], newObj[key])
@ -16,6 +21,7 @@ const checkKeys = (storeObj, newObj) => {
storeObj[key] = newObj[key]
}
}
return storeObj
}

View File

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

View File

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

View File

@ -1,10 +1,11 @@
const user = require('./default.config.js').user
module.exports = {
node: 0, // set to mainnet
server: {
primary: {
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

View File

@ -3,11 +3,11 @@ const path = require('path')
const user = {
node: 0,
nodeSettings: {
pingInterval: 30 * 1000,
pingInterval: 30 * 1000
},
server: {
writeHosts: {
enabled: true,
enabled: true
},
relativeTo: path.join(__dirname, '../'),
primary: {
@ -16,27 +16,28 @@ const user = {
port: 12388,
directory: './src/',
page404: './src/404.html',
host: '0.0.0.0',
},
host: '0.0.0.0'
}
},
tls: {
enabled: false,
options: {
key: '',
cert: '',
},
cert: ''
}
},
constants: {
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)
notifications: {
q_chat: {
playSound: true,
showNotification: true,
},
},
showNotification: true
}
}
}
module.exports = user

View File

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

View File

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

View File

@ -945,7 +945,16 @@
"gchange56": "Group Name To Search",
"gchange57": "Private Group Name Not Found",
"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": {
"pchange1": "Puzzles",

View File

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

View File

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

View File

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

View File

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

View File

@ -5,18 +5,16 @@ const createPrimaryRoutes = require('./routes/createPrimaryRoutes.js')
const createServer = (config, plugins) => {
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)
primaryServer.startServer()
.then(server => {
primaryServer.startServer().then(server => {
console.log(`Qortal UI Server started at ${server.info.uri} and listening on ${server.info.address}`)
})
.catch(e => {
}).catch(e => {
console.error(e)
})
}
return this
}
const serverExports = {
createServer
}

View File

@ -1,54 +1,55 @@
import * as api from 'qortal-ui-crypto'
import mykey from './functional-components/mykey-page.js'
'use strict'
import mykey from './functional-components/mykey-page'
export const checkApiKey = async (nodeConfig) => {
let selectedNode = nodeConfig.knownNodes[nodeConfig.node];
let apiKey = selectedNode.apiKey;
let selectedNode = nodeConfig.knownNodes[nodeConfig.node]
let apiKey = selectedNode.apiKey
// Attempt to generate an API key
const generateUrl = "/admin/apikey/generate";
const generateUrl = '/admin/apikey/generate'
let generateRes = await api.request(generateUrl, {
method: "POST"
});
method: 'POST'
})
if (generateRes != null && generateRes.error == null && generateRes.length >= 8) {
console.log("Generated API key");
apiKey = generateRes;
console.log('Generated API key')
apiKey = generateRes
// Store the generated API key
selectedNode.apiKey = apiKey;
nodeConfig.knownNodes[nodeConfig.node] = selectedNode;
localStorage.setItem('myQortalNodes', JSON.stringify(nodeConfig.knownNodes));
}
else {
console.log("Unable to generate API key");
selectedNode.apiKey = apiKey
nodeConfig.knownNodes[nodeConfig.node] = selectedNode
localStorage.setItem('myQortalNodes', JSON.stringify(nodeConfig.knownNodes))
} else {
console.log("Unable to generate API key")
}
// Now test the API key
let testResult = await testApiKey(apiKey);
let testResult = await testApiKey(apiKey)
if (testResult === true) {
console.log("API key test passed");
}
else {
console.log("API key test failed");
mykey.show();
console.log('API key test passed')
} else {
console.log('API key test failed')
mykey.show()
this.dispatchEvent(
new CustomEvent('disable-tour', {
bubbles: true,
composed: true
}),
);
})
)
}
}
export const testApiKey = async (apiKey) => {
const testUrl = "/admin/apikey/test?apiKey=" + apiKey;
let testRes = await api.request(testUrl, {
method: "GET"
});
return testRes === true;
const testUrl = '/admin/apikey/test?apiKey=' + apiKey
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 {store} from '../store.js'
import { store } from '../store'
import { appInfoStyles } from '../styles/core-css'
// Multi language support
import { translate } from '../../translate'
class AppInfo extends connect(store)(LitElement) {
@ -14,49 +17,7 @@ class AppInfo extends connect(store)(LitElement) {
}
static get styles() {
return css`
* {
--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;
}
`
return [appInfoStyles]
}
constructor() {
@ -74,7 +35,6 @@ class AppInfo extends connect(store)(LitElement) {
${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.peers")}: ${this.nodeInfo.numberOfConnections ? this.nodeInfo.numberOfConnections : ''}
<a id="pageLink"></a>
</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 {store} from '../store.js'
import { store } from '../store'
class MyElement extends connect(store)(LitElement) {
static get properties () {
return {
}
}
static get styles () {
return css``
}
render () {
return html`
<style>
</style>
`
}
stateChanged (state) {
return html`<style></style>`
}
}

View File

@ -1,11 +1,13 @@
import {css, html, LitElement} from 'lit'
import {store} from '../../store'
import { html, LitElement } from 'lit'
import { connect } from 'pwa-helpers'
import {translate} from '../../../translate'
import { store } from '../../store'
import { parentEpml } from '../show-plugin'
import { syncIndicator2Styles } from '../../styles/core-css'
import '@material/mwc-icon'
// Multi language support
import {translate} from '../../../translate'
class SyncIndicator extends connect(store)(LitElement) {
static get properties() {
return {
@ -18,6 +20,10 @@ class SyncIndicator extends connect(store)(LitElement) {
}
}
static get styles() {
return [syncIndicator2Styles]
}
constructor() {
super()
this.blocksBehind = 0
@ -32,64 +38,6 @@ class SyncIndicator extends connect(store)(LitElement) {
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() {
return html`
${!this.hasCoreRunning ? html`
@ -225,7 +173,7 @@ class SyncIndicator extends connect(store)(LitElement) {
this.dispatchEvent(
new CustomEvent('open-welcome-modal-sync', {
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 {driver} from 'driver.js';
import 'driver.js/dist/driver.css';
import '@material/mwc-icon';
import '@polymer/paper-spinner/paper-spinner-lite.js';
import '@vaadin/tooltip';
import '@material/mwc-button';
import {get, translate} from '../../../translate';
import '@polymer/paper-dialog/paper-dialog.js';
import {setNewTab} from '../../redux/app/app-actions.js';
import {store} from '../../store.js';
import {connect} from 'pwa-helpers';
import './tour.css';
import { html, LitElement } from 'lit'
import { connect } from 'pwa-helpers'
import { store } from '../../store'
import { setNewTab } from '../../redux/app/app-actions'
import { tourComponentStyles } from '../../styles/core-css'
import { driver } from 'driver.js'
import 'driver.js/dist/driver.css'
import './tour.css'
import '@material/mwc-button'
import '@material/mwc-icon'
import '@polymer/paper-dialog/paper-dialog.js'
import '@polymer/paper-spinner/paper-spinner-lite.js'
import '@vaadin/tooltip'
// Multi language support
import { get, translate } from '../../../translate'
class TourComponent extends connect(store)(LitElement) {
static get properties() {
@ -18,219 +21,87 @@ class TourComponent extends connect(store)(LitElement) {
getElements: { attribute: false },
dialogOpenedCongrats: { type: Boolean },
hasViewedTour: { type: Boolean },
disableTour: {type: Boolean}
};
disableTour: { type: Boolean },
nodeUrl: { type: String },
address: { type: String }
}
}
static get styles() {
return [tourComponentStyles]
}
constructor() {
super();
this.dialogOpenedCongrats = false;
this._controlOpenWelcomeModal =
this._controlOpenWelcomeModal.bind(this);
this.hasName = false;
this.nodeUrl = this.getNodeUrl();
this.myNode = this.getMyNode();
super()
this.dialogOpenedCongrats = false
this._controlOpenWelcomeModal = this._controlOpenWelcomeModal.bind(this)
this.hasName = false
this.nodeUrl = ''
this.address = ''
this._disableTour = this._disableTour.bind(this)
this.disableTour = false
}
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);
box-sizing: border-box;
color: var(--black);
background: var(--white);
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>
` : ''}
`
}
: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() {
this.getNodeUrl()
this.address = store.getState().app.selectedAddress.address
const hasViewedTour = JSON.parse(
localStorage.getItem(`hasViewedTour-${this.address}`) || 'false'
);
const name = await this.getName(this.address);
const hasViewedTour = JSON.parse(localStorage.getItem(`hasViewedTour-${this.address}`) || 'false')
const name = await this.getName(this.address)
if (name) {
this.hasName = true;
this.hasName = true
}
this.hasViewedTour = hasViewedTour;
this.hasViewedTour = hasViewedTour
if (!hasViewedTour) {
try {
if (name) {
this.hasViewedTour = true;
this.hasName = true;
this.hasViewedTour = true
this.hasName = true
localStorage.setItem(`hasViewedTour-${this.address}`, JSON.stringify(true))
}
} catch (error) {
console.log({ error });
console.log({ error })
}
}
await new Promise((res) => {
setTimeout(() => {
res();
}, 1000);
});
res()
}, 1000)
})
if (!this.hasViewedTour && this.disableTour !== true) {
const elements = this.getElements();
let steps = [
{
const elements = this.getElements()
let steps = [{
popover: {
title: get("tour.tour6"),
description: `
@ -238,22 +109,24 @@ class TourComponent extends connect(store)(LitElement) {
<img style="height:40px;width:auto;margin:15px 0px;" src="/img/qort.png" />
</div>
<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 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 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>
`,
// ... other options
},
},
];
const step2 = elements['core-sync-status-id'];
const step3 = elements['tab'];
const step4 = elements['checklist'];
`
}
}]
const step2 = elements['core-sync-status-id']
const step3 = elements['tab']
const step4 = elements['checklist']
if (step2) {
steps.push({
@ -277,10 +150,11 @@ class TourComponent extends connect(store)(LitElement) {
<p style="margin:0px;padding:0px">${get("tour.tour4")}</p>
</div>
`,
},
});
`
}
})
}
if (step3) {
steps.push({
element: step3,
@ -288,31 +162,26 @@ class TourComponent extends connect(store)(LitElement) {
title: 'Tab View',
description: `
<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>
<p style="margin:0px;padding:0px">${get("tour.tour10")}</p>
</div>
<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>
<p style="margin:0px;padding:0px">You can also bookmark other Q-Apps and Plugins by clicking on the ${get(
'tabmenu.tm19'
)} button</p>
<p style="margin:0px;padding:0px">
You can also bookmark other Q-Apps and Plugins by clicking on the ${get('tabmenu.tm19')} button
</p>
</div>
`,
},
});
`
}
})
}
if (step4) {
steps.push(
{
element: step4,
popover: {
title: get("tour.tour11"),
description: get("tour.tour12"),
},
steps.push({ element: step4, popover: { title: get("tour.tour11"), description: get("tour.tour12")}})
this.hasViewedTour
}
);this.hasViewedTour
}
let currentStepIndex = 0;
let currentStepIndex = 0
const driverObj = driver({
popoverClass: 'driverjs-theme',
showProgress: true,
@ -321,25 +190,93 @@ class TourComponent extends connect(store)(LitElement) {
allowClose: false,
onDestroyed: () => {
localStorage.setItem(`hasViewedTour-${this.address}`, JSON.stringify(true))
this.hasViewedTour = true;
this.openWelcomeModal();
this.hasViewedTour = true
this.openWelcomeModal()
}
});
})
driverObj.drive();
driverObj.drive()
} else {
this.dispatchEvent(
new CustomEvent('send-tour-finished', {
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() {
this.onClose();
const query = `?service=APP&name=Q-Tube`;
this.onClose()
const query = `?service=APP&name=Q-Tube`
store.dispatch(
setNewTab({
url: `qdn/browser/index.html${query}`,
@ -350,59 +287,16 @@ class TourComponent extends connect(store)(LitElement) {
page: `qdn/browser/index.html${query}`,
title: 'Q-Tube',
menus: [],
parent: false,
},
parent: false
}
})
);
)
}
onClose() {
localStorage.setItem(`welcome-sync-${this.address}`, JSON.stringify(true))
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>
`
: ''}
`;
this.dialogOpenedCongrats = false
}
}
customElements.define('tour-component', TourComponent);
window.customElements.define('tour-component', TourComponent)

View File

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

View File

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

View File

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

View File

@ -1,42 +1,182 @@
import {html, LitElement} from 'lit';
import '@material/mwc-icon';
import {store} from '../../store';
import {connect} from 'pwa-helpers';
import '@vaadin/tooltip';
import {parentEpml} from '../show-plugin';
import {setCoinBalances} from '../../redux/app/app-actions';
import { html, LitElement } from 'lit'
import { connect } from 'pwa-helpers'
import { store } from '../../store'
import { parentEpml } from '../show-plugin'
import { setCoinBalances } from '../../redux/app/app-actions'
class CoinBalancesController extends connect(store)(LitElement) {
static get properties() {
return {
coinList: { type: Object },
};
coinList: { type: Object }
}
}
constructor() {
super();
this.coinList = {}
this.nodeUrl = this.getNodeUrl();
this.myNode = this.getMyNode();
this.nodeUrl = this.getNodeUrl()
this.myNode = this.getMyNode()
this.fetchBalance = this.fetchBalance.bind(this)
this._updateCoinList = this._updateCoinList.bind(this)
this.stop = false
}
getNodeUrl() {
const myNode =
store.getState().app.nodeConfig.knownNodes[
store.getState().app.nodeConfig.node
]
render() {
return html``
}
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
]
return store.getState().app.nodeConfig.knownNodes[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() {
let _url = `/crosschain/arrr/walletbalance?apiKey=${this.myNode.apiKey}`
@ -56,150 +196,7 @@ class CoinBalancesController extends connect(store)(LitElement) {
type: 'arrr',
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(() => {
console.log('error')
@ -209,16 +206,17 @@ class CoinBalancesController extends connect(store)(LitElement) {
_updateCoinList(event) {
const copyCoinList = { ...this.coinList }
const coin = event.detail
if (!copyCoinList[coin]) {
try {
if(coin === 'ltc'){
this.updateLtcWalletBalance()
} else if(coin === 'qort'){
if (coin === 'qort') {
this.updateQortWalletBalance()
} else if(coin === 'doge'){
this.updateDogeWalletBalance()
} else if (coin === 'btc') {
this.updateBtcWalletBalance()
} else if (coin === 'ltc') {
this.updateLtcWalletBalance()
} else if (coin === 'doge') {
this.updateDogeWalletBalance()
} else if (coin === 'dgb') {
this.updateDgbWalletBalance()
} else if (coin === 'rvn') {
@ -226,28 +224,25 @@ class CoinBalancesController extends connect(store)(LitElement) {
} else if (coin === 'arrr') {
this.updateArrrWalletBalance()
}
} catch (error) {
} catch (error) { }
}
copyCoinList[coin] = Date.now() + 120000
}
}
copyCoinList[coin] = Date.now() + 120000;
this.coinList = copyCoinList
this.requestUpdate()
}
async fetchCoins(arrayOfCoins) {
const getCoinBalances = (arrayOfCoins || []).map(
async (coin) => {
if(coin === 'ltc'){
await this.updateLtcWalletBalance()
} else if(coin === 'qort'){
const getCoinBalances = (arrayOfCoins || []).map(async (coin) => {
if (coin === 'qort') {
await this.updateQortWalletBalance()
} else if(coin === 'doge'){
await this.updateDogeWalletBalance()
} else if (coin === 'btc') {
await this.updateBtcWalletBalance()
} else if (coin === 'ltc') {
await this.updateLtcWalletBalance()
} else if (coin === 'doge') {
await this.updateDogeWalletBalance()
} else if (coin === 'dgb') {
await this.updateDgbWalletBalance()
} else if (coin === 'rvn') {
@ -257,16 +252,17 @@ class CoinBalancesController extends connect(store)(LitElement) {
}
})
await Promise.all(getCoinBalances);
await Promise.all(getCoinBalances)
}
async fetchBalance() {
try {
let arrayOfCoins = []
const copyObject = { ...this.coinList }
const currentDate = Date.now()
const array = Object.keys(this.coinList)
for (const key of array) {
const item = this.coinList[key]
@ -276,11 +272,15 @@ class CoinBalancesController extends connect(store)(LitElement) {
arrayOfCoins.push(key)
}
}
if (!this.stop) {
this.stop = true
await this.fetchCoins(arrayOfCoins)
this.stop = false
}
this.coinList = copyObject
} catch (error) {
this.stop = false
@ -288,33 +288,16 @@ class CoinBalancesController extends connect(store)(LitElement) {
}
connectedCallback() {
super.connectedCallback();
this.intervalID = setInterval(this.fetchBalance, 45000);
window.addEventListener(
'ping-coin-controller-with-coin',
this._updateCoinList
);
super.connectedCallback()
this.intervalID = setInterval(this.fetchBalance, 45000)
window.addEventListener('ping-coin-controller-with-coin', this._updateCoinList)
}
disconnectedCallback() {
super.disconnectedCallback();
window.removeEventListener(
'ping-coin-controller-with-coin',
this._updateCoinList
);
if(this.intervalID){
clearInterval(this.intervalID);
}
}
render() {
return html``;
if (this.intervalID) { clearInterval(this.intervalID) }
window.removeEventListener('ping-coin-controller-with-coin', this._updateCoinList)
super.disconnectedCallback()
}
}
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 '@material/mwc-icon'
import '@vaadin/tooltip';
import { chatSideNavHeadsStyles } from '../../styles/core-css'
import './friend-item-actions'
import '@material/mwc-icon'
import '@vaadin/tooltip'
class ChatSideNavHeads extends LitElement {
class ChatSideNavHeads extends connect(store)(LitElement) {
static get properties() {
return {
selectedAddress: { type: Object },
@ -21,55 +23,7 @@ class ChatSideNavHeads extends LitElement {
}
static get styles() {
return css`
: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;
}
`
return [chatSideNavHeadsStyles]
}
constructor() {
@ -78,7 +32,6 @@ class ChatSideNavHeads extends LitElement {
this.config = {
user: {
node: {
}
}
}
@ -89,66 +42,52 @@ class ChatSideNavHeads extends LitElement {
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() {
let avatarImg = ""
let avatarImg = ''
if (this.chatInfo.name) {
const myNode = window.parent.reduxStore.getState().app.nodeConfig.knownNodes[window.parent.reduxStore.getState().app.nodeConfig.node];
const nodeUrl = myNode.protocol + '://' + myNode.domain + ':' + myNode.port;
const avatarUrl = `${nodeUrl}/arbitrary/THUMBNAIL/${this.chatInfo.name}/qortal_avatar?async=true&apiKey=${myNode.apiKey}`;
const myNode = store.getState().app.nodeConfig.knownNodes[store.getState().app.nodeConfig.node]
const nodeUrl = myNode.protocol + '://' + myNode.domain + ':' + myNode.port
const avatarUrl = `${nodeUrl}/arbitrary/THUMBNAIL/${this.chatInfo.name}/qortal_avatar?async=true`
avatarImg = this.createImage(avatarUrl)
}
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 popover =
this.shadowRoot.querySelector('friend-item-actions');
const popover = this.shadowRoot.querySelector('friend-item-actions');
if (popover) {
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">
${this.isImageLoaded ? html`${avatarImg}` : html``}
${!this.isImageLoaded && !this.chatInfo.name && !this.chatInfo.groupName
? html`<mwc-icon class="img-icon">account_circle</mwc-icon>`
: html``}
${!this.isImageLoaded && this.chatInfo.name
? html`<div
${!this.isImageLoaded && !this.chatInfo.name && !this.chatInfo.groupName ?
html`
<mwc-icon class="img-icon">account_circle</mwc-icon>
`
: html``
}
${!this.isImageLoaded && this.chatInfo.name ?
html`
<div
style="width:30px; height:30px; float: left; border-radius:50%; background: ${this.activeChatHeadUrl === this.chatInfo.url
? "var(--chatHeadBgActive)"
: "var(--chatHeadBg)"}; color: ${this.activeChatHeadUrl ===
this.chatInfo.url
: "var(--chatHeadBg)"}; color: ${this.activeChatHeadUrl === this.chatInfo.url
? "var(--chatHeadTextActive)"
: "var(--chatHeadText)"}; font-weight:bold; display: flex; justify-content: center; align-items: center; text-transform: capitalize"
>
${this.chatInfo.name.charAt(0)}
</div>`
: ""}
${!this.isImageLoaded && this.chatInfo.groupName
? html`<div
</div>
` : ''
}
${!this.isImageLoaded && this.chatInfo.groupName ?
html`
<div
style="width:30px; height:30px; float: left; border-radius:50%; background: ${this.activeChatHeadUrl === this.chatInfo.url
? "var(--chatHeadBgActive)"
: "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"
>
${this.chatInfo.groupName.charAt(0)}
</div>`
: ""}
</div>
` : ''
}
<div>
<div class="name">
<span style="float:left; padding-left: 8px; color: var(--chat-group);">
@ -165,24 +105,25 @@ class ChatSideNavHeads extends LitElement {
? this.chatInfo.groupName
: this.chatInfo.name !== undefined
? (this.chatInfo.alias || this.chatInfo.name)
: this.chatInfo.address.substr(0, 15)}
: this.chatInfo.address.substr(0, 15)
}
</span>
</div>
</div>
</div>
<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>
<vaadin-tooltip
for="willFollowIcon"
position="top"
hover-delay=${200}
hide-delay=${1}
text=${get('friends.friend11')}>
</vaadin-tooltip>
` : ''}
text=${get('friends.friend11')}
></vaadin-tooltip>
` : ''
}
</div>
</li>
<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) {
if (changedProperties.has('activeChatHeadUrl')) {
return true
}
if (changedProperties.has('chatInfo')) {
return true
}
return !!changedProperties.has('isImageLoaded');
return !!changedProperties.has('isImageLoaded')
}
getUrl(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)

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 '@material/mwc-button';
import '@material/mwc-dialog';
import '@material/mwc-checkbox';
import {connect} from 'pwa-helpers';
import {store} from '../../store';
import { addFriendsModalStyles } from '../../styles/core-css'
import '@material/mwc-button'
import '@material/mwc-checkbox'
import '@material/mwc-dialog'
import '@polymer/paper-spinner/paper-spinner-lite.js'
class AddFriendsModal extends connect(store)(LitElement) {
@ -23,273 +24,30 @@ class AddFriendsModal extends connect(store)(LitElement) {
mySelectedFeeds: { type: Array },
availableFeeedSchemas: { type: Array },
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() {
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;
return [addFriendsModalStyles]
}
.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;
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 {
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
}
}
render() {
return html`
<div class="modal-overlay ${this.isOpen ? '' : 'hidden'}">
<div class="modal-content">
<div class="inner-content">
<div style="text-align:center">
@ -331,9 +89,7 @@ class AddFriendsModal extends connect(store)(LitElement) {
id="name"
class="input"
?disabled=${true}
value=${this.userSelected
? this.userSelected.name
: ''}
value=${this.userSelected ? this.userSelected.name : ''}
/>
</div>
<div style="height:15px"></div>
@ -375,67 +131,42 @@ class AddFriendsModal extends connect(store)(LitElement) {
<p>${translate('friends.friend16')}</p>
</div>
<div>
${this.isLoadingSchemas ? html`
${this.isLoadingSchemas ?
html`
<div style="width:100%;display: flex; justify-content:center">
<paper-spinner-lite active></paper-spinner-lite>
</div>
` : ''}
` : ''
}
${this.availableFeeedSchemas.map((schema) => {
const isAlreadySelected = this.mySelectedFeeds.find(
(item) => item.name === schema.name
);
const isAlreadySelected = this.mySelectedFeeds.find((item) => item.name === schema.name);
let avatarImgApp;
const avatarUrl2 = `${this.nodeUrl}/arbitrary/THUMBNAIL/${schema.name}/qortal_avatar?async=true&apiKey=${this.myNode.apiKey}`;
avatarImgApp = html`<img
src="${avatarUrl2}"
style="max-width:100%; max-height:100%;"
onerror="this.onerror=null; this.src='/img/incognito.png';"
/>`;
avatarImgApp = html`<img src="${avatarUrl2}" style="max-width:100%; max-height:100%;" onerror="this.onerror=null; this.src='/img/incognito.png';"/>`;
return html`
<div
class="app-name"
style="background:${isAlreadySelected ? 'lightblue' : ''}"
@click=${() => {
const copymySelectedFeeds = [
...this.mySelectedFeeds,
];
const findIndex =
copymySelectedFeeds.findIndex(
(item) =>
item.name === schema.name
);
const copymySelectedFeeds = [...this.mySelectedFeeds];
const findIndex = copymySelectedFeeds.findIndex((item) => item.name === schema.name);
if (findIndex === -1) {
if (this.mySelectedFeeds.length > 4) return
copymySelectedFeeds.push({
name: schema.name,
identifier: schema.identifier,
service: schema.service,
});
this.mySelectedFeeds =
copymySelectedFeeds;
copymySelectedFeeds.push({name: schema.name, identifier: schema.identifier, service: schema.service});
this.mySelectedFeeds = copymySelectedFeeds;
} else {
this.mySelectedFeeds =
copymySelectedFeeds.filter(
(item) =>
item.name !==
schema.name
);
this.mySelectedFeeds = copymySelectedFeeds.filter((item) => item.name !== schema.name);
}
}}
>
<div class="avatar">${avatarImgApp}</div>
<span
style="color:${isAlreadySelected ? 'var(--white)': 'var(--black)'};font-size:16px"
>${schema.name}</span
>
<span style="color:${isAlreadySelected ? 'var(--white)' : 'var(--black)'};font-size:16px">${schema.name}</span>
</div>
`;
`
})}
</div>
</div>
<div
style="display:flex;justify-content:space-between;align-items:center;margin-top:20px"
>
<div style="display:flex;justify-content:space-between;align-items:center;margin-top:20px">
<button
class="modal-button-red"
?disabled="${this.isLoading}"
@ -447,36 +178,118 @@ class AddFriendsModal extends connect(store)(LitElement) {
>
${translate('general.close')}
</button>
${this.editContent
? html`
<button
?disabled="${this.isLoading}"
class="modal-button-red"
@click=${() => {
this.removeFriend();
}}
>
${this.editContent ?
html`
<button ?disabled="${this.isLoading}" class="modal-button-red" @click=${() => {this.removeFriend();}}>
${translate('friends.friend14')}
</button>
`
: ''}
<button
?disabled="${this.isLoading}"
class="modal-button"
@click=${() => {
this.addFriend();
}}
>
${this.editContent
? translate('friends.friend10')
: translate('friends.friend2')}
` : ''
}
<button ?disabled="${this.isLoading}" class="modal-button" @click=${() => {this.addFriend();}}>
${this.editContent ? translate('friends.friend10') : translate('friends.friend2')}
</button>
</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 axios from 'axios';
import '@material/mwc-menu';
import '@material/mwc-list/mwc-list-item.js';
import {RequestQueueWithPromise} from '../../../../plugins/plugins/utils/queue';
import '../../../../plugins/plugins/core/components/TimeAgo';
import {connect} from 'pwa-helpers';
import {store} from '../../store';
import ShortUniqueId from 'short-unique-id';
import { html, LitElement } from 'lit'
import { connect } from 'pwa-helpers'
import { store } from '../../store'
import { RequestQueueWithPromise } from '../../../../plugins/plugins/utils/classes'
import { avatarComponentStyles } from '../../styles/core-css'
import axios from 'axios'
import ShortUniqueId from 'short-unique-id'
import '../../../../plugins/plugins/core/components/TimeAgo'
import '@material/mwc-menu'
import '@material/mwc-list/mwc-list-item.js'
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 AvatarComponent extends connect(store)(LitElement) {
static get properties() {
@ -18,284 +19,210 @@ export class AvatarComponent extends connect(store)(LitElement) {
resource: { type: Object },
isReady: { type: Boolean },
status: { type: Object },
name: { type: String },
};
name: { type: String }
}
}
static get styles() {
return css`
* {
--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;
}
`;
return [avatarComponentStyles]
}
constructor() {
super();
super()
this.resource = {
identifier: '',
name: '',
service: '',
};
service: ''
}
this.status = {
status: '',
};
this.isReady = false;
this.nodeUrl = this.getNodeUrl();
this.myNode = this.getMyNode();
this.isFetching = false;
this.uid = new ShortUniqueId();
status: ''
}
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
];
}
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();
this.isReady = false
this.nodeUrl = this.getNodeUrl()
this.myNode = this.getMyNode()
this.isFetching = false
this.uid = new ShortUniqueId()
}
render() {
return html`
<div>
${this.status.status !== 'READY'
? html`
<mwc-icon style="user-select:none;"
>account_circle</mwc-icon
>
`
: ''}
${this.status.status === 'READY'
? html`
<div
style="height: 24px;width: 24px;overflow: hidden;"
>
${this.status.status !== 'READY' ?
html`
<mwc-icon style="user-select:none;">account_circle</mwc-icon>
` : ''
}
${this.status.status === 'READY' ?
html`
<div style="height: 24px;width: 24px;overflow: hidden;">
<img
src="${this
.nodeUrl}/arbitrary/THUMBNAIL/${this
.name}/qortal_avatar?async=true&apiKey=${this
.myNode.apiKey}"
src="${this.nodeUrl}/arbitrary/THUMBNAIL/${this.name}/qortal_avatar?async=true&apiKey=${this.myNode.apiKey}"
style="width:100%; height:100%;border-radius:50%"
onerror="this.onerror=null; this.src='/img/incognito.png';"
/>
</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 {connect} from 'pwa-helpers';
import '@vaadin/item';
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 '@material/mwc-icon';
import {get} from '../../../translate';
import '../../../../plugins/plugins/core/components/TimeAgo.js';
import '../notification-view/popover.js';
import ShortUniqueId from 'short-unique-id';
import { html, LitElement } from 'lit'
import { connect } from 'pwa-helpers'
import { store } from '../../store'
import { setNewTab } from '../../redux/app/app-actions'
import { get } from '../../../translate'
import { beginnerChecklistStyles } from '../../styles/core-css'
import ShortUniqueId from 'short-unique-id'
import '../notification-view/popover'
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'
class BeginnerChecklist extends connect(store)(LitElement) {
static properties = {
static get properties() {
return {
notifications: { type: Array },
showChecklist: { type: Boolean },
theme: { type: String, reflect: true },
isSynced: { type: Boolean },
hasName: { type: Boolean },
hasTourFinished: { type: Boolean },
};
theme: { type: String, reflect: true }
}
}
static get styles() {
return [beginnerChecklistStyles]
}
constructor() {
super();
this.showChecklist = false;
this.initialFetch = false;
this.theme = localStorage.getItem('qortalTheme')
? localStorage.getItem('qortalTheme')
: 'light';
this.isSynced = false;
this.hasName = null;
this.nodeUrl = this.getNodeUrl();
this.myNode = this.getMyNode();
this.hasTourFinished = null;
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);
super()
this.showChecklist = false
this.initialFetch = false
this.isSynced = false
this.hasName = null
this.nodeUrl = this.getNodeUrl()
this.myNode = this.getMyNode()
this.hasTourFinished = null
this._controlTourFinished = this._controlTourFinished.bind(this)
this.uid = new ShortUniqueId()
this.theme = localStorage.getItem('qortalTheme') ? localStorage.getItem('qortalTheme') : 'light'
}
render() {
return this.hasName === false || this.hasTourFinished === false
? html`
return this.hasName === false || this.hasTourFinished === false ?
html`
<div class="layout">
<popover-component
for="popover-checklist"
message=${get('tour.tour16')}
></popover-component>
<div
id="popover-checklist"
@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>
<popover-component for="popover-checklist" message=${get('tour.tour16')}></popover-component>
<div id="popover-checklist" @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
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="task-list-item">
<p>Are you synced?</p>
${this.syncPercentage === 100
? html`
<mwc-icon
id="checklist-general-icon"
style="color: green; user-select:none"
>task_alt</mwc-icon
>
${this.syncPercentage === 100 ?
html`
<mwc-icon id="checklist-general-icon" style="color: green; user-select:none">
task_alt
</mwc-icon>
`
: html`
<mwc-icon
id="checklist-general-icon"
style="color: red; user-select:none"
>radio_button_unchecked</mwc-icon
>
`}
<mwc-icon id="checklist-general-icon" style="color: red; user-select:none">
radio_button_unchecked
</mwc-icon>
`
}
</div>
<div
class="task-list-item"
style="cursor:pointer"
@ -206,139 +86,129 @@ class BeginnerChecklist extends connect(store)(LitElement) {
title: 'Name Registration',
icon: 'vaadin:user-check',
mwcicon: 'manage_accounts',
pluginNumber:
'plugin-qCmtXAQmtu',
pluginNumber: 'plugin-qCmtXAQmtu',
menus: [],
parent: false,
parent: false
},
openExisting: true,
openExisting: true
})
);
this.handleBlur();
}}
>
<p>Do you have a name registered?</p>
${this.hasName
? html`
<mwc-icon
id="checklist-general-icon"
style="color: green; user-select:none"
>task_alt</mwc-icon
>
${this.hasName ?
html`
<mwc-icon id="checklist-general-icon" style="color: green; user-select:none">
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>
`
: '';
: ''
}
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() {
this.showChecklist = !this.showChecklist;
this.showChecklist = !this.showChecklist
if (this.showChecklist) {
requestAnimationFrame(() => {
this.shadowRoot.getElementById('checklist-panel').focus();
});
this.shadowRoot.getElementById('checklist-panel').focus()
})
}
}
static styles = css`
.layout {
display: flex;
flex-direction: column;
align-items: center;
position: relative;
// Standard functions
getApiKey() {
const coreNode = store.getState().app.nodeConfig.knownNodes[store.getState().app.nodeConfig.node]
return coreNode.apiKey
}
.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;
isEmptyArray(arr) {
if (!arr) { return true }
return arr.length === 0
}
.nocount {
display: none;
round(number) {
return (Math.round(parseFloat(number) * 1e8) / 1e8).toFixed(8)
}
}
.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;
}
.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);
window.customElements.define('beginner-checklist', BeginnerChecklist)

View File

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

View File

@ -1,7 +1,8 @@
import {css, html, LitElement} from 'lit'
import {store} from '../../store'
import { html, LitElement } from 'lit'
import { connect } from 'pwa-helpers'
import { store } from '../../store'
import { translate } from '../../../translate'
import { coreSyncStatusStyles } from '../../styles/core-css'
class CoreSyncStatus extends connect(store)(LitElement) {
static get properties() {
@ -12,6 +13,10 @@ class CoreSyncStatus extends connect(store)(LitElement) {
}
}
static get styles() {
return [coreSyncStatusStyles]
}
constructor() {
super()
this.nodeInfos = []
@ -19,69 +24,6 @@ class CoreSyncStatus extends connect(store)(LitElement) {
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() {
return html`
<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 { feedItemStyles } from '../../styles/core-css'
import axios from 'axios'
import '@material/mwc-menu';
import '@material/mwc-list/mwc-list-item.js'
import {RequestQueueWithPromise} from '../../../../plugins/plugins/utils/queue';
import ShortUniqueId from 'short-unique-id'
import '../../../../plugins/plugins/core/components/TimeAgo'
import {connect} from 'pwa-helpers';
import {store} from '../../store';
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);
import '@material/mwc-menu'
import '@material/mwc-list/mwc-list-item.js'
const requestQueue = new RequestQueueWithPromise(3)
const requestQueueRawData = new RequestQueueWithPromise(3)
const requestQueueStatus = new RequestQueueWithPromise(3)
export class FeedItem extends connect(store)(LitElement) {
static get properties() {
@ -24,127 +24,15 @@ export class FeedItem extends connect(store)(LitElement) {
feedItem: { type: Object },
appName: { type: String },
link: { type: String }
};
}
}
static get styles() {
return css`
* {
--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);
}
}
`;
return [feedItemStyles]
}
constructor() {
super();
super()
this.resource = {
identifier: "",
name: "",
@ -159,33 +47,76 @@ export class FeedItem extends connect(store)(LitElement) {
this.hasCalledWhenDownloaded = false
this.isFetching = false
this.uid = new ShortUniqueId()
this.observer = new IntersectionObserver(entries => {
for (const entry of entries) {
if (entry.isIntersecting && this.status.status !== 'READY') {
this._fetchImage();
this._fetchImage()
// Stop observing after the image has started loading
this.observer.unobserve(this);
this.observer.unobserve(this)
}
}
});
})
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
}
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;
getMyNode() {
return store.getState().app.nodeConfig.knownNodes[store.getState().app.nodeConfig.node]
}
async fetchResource() {
@ -201,89 +132,79 @@ getMyNode(){
}
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;
const pattern = /\$\$\{([a-zA-Z0-9_\.]+)\}\$\$/g
for (const key in display) {
const value = display[key];
const value = display[key]
display[key] = value.replace(pattern, (match, p1) => {
if (p1.startsWith('rawdata.')) {
const dataKey = p1.split('.')[1];
const dataKey = p1.split('.')[1]
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.')) {
const resourceKey = p1.split('.')[1];
const resourceKey = p1.split('.')[1]
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() {
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') {
const rawData = await this.getRawData()
const object = {
...this.resource.schema.display
}
this.updateDisplayWithPlaceholders(object, {}, rawData.data)
this.feedItem = object
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
) {
if (res.percentLoaded === percentLoaded && res.percentLoaded !== 100) {
timer = timer - 5
} else {
timer = 24
@ -300,12 +221,14 @@ getMyNode(){
isCalling = false
this.fetchResource()
}, 25000)
return
}
percentLoaded = res.percentLoaded
}
this.status = res
if (this.status.status === 'DOWNLOADED') {
await this.fetchResource()
}
@ -323,7 +246,7 @@ getMyNode(){
this.status = res
this.isReady = true
}
}, 5000) // 1 second interval
}, 5000) // 5 second interval
}
async _fetchImage() {
@ -333,13 +256,6 @@ getMyNode(){
} catch (error) { /* empty */ }
}
firstUpdated(){
this.observer.observe(this);
}
async goToFeedLink() {
try {
let newQuery = this.link
@ -380,14 +296,13 @@ getMyNode(){
}
}
async extractComponents(url) {
if (!url.startsWith("qortal://")) {
return null
}
url = url.replace(/^(qortal\:\/\/)/, "")
if (url.includes("/")) {
let parts = url.split("/")
const service = parts[0].toUpperCase()
@ -426,84 +341,20 @@ getMyNode(){
return null
}
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>
`
: ''
// Standard functions
getApiKey() {
const coreNode = store.getState().app.nodeConfig.knownNodes[store.getState().app.nodeConfig.node]
return coreNode.apiKey
}
${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 {css, html, LitElement} from 'lit';
import {createPopper} from '@popperjs/core';
import '@material/mwc-icon';
import { html, LitElement } from 'lit'
import { connect } from 'pwa-helpers'
import { store } from '../../store'
import { createPopper } from '@popperjs/core'
import { setNewTab, setSideEffectAction } from '../../redux/app/app-actions'
import { translate } from '../../../translate'
import {store} from '../../store';
import {connect} from 'pwa-helpers';
import {setNewTab, setSideEffectAction} from '../../redux/app/app-actions';
import ShortUniqueId from 'short-unique-id';
import { friendItemActionsStyles } from '../../styles/core-css'
import ShortUniqueId from 'short-unique-id'
import '@material/mwc-icon'
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() {
return {
for: { type: String, reflect: true },
message: { type: String },
openEditFriend: { attribute: false },
name: { type: String },
closeSidePanel: { attribute: false, type: Object },
};
closeSidePanel: { attribute: false, type: Object }
}
}
static get styles() {
return [friendItemActionsStyles]
}
constructor() {
super();
this.message = '';
this.nodeUrl = this.getNodeUrl();
this.uid = new ShortUniqueId();
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 '';
}
super()
this.message = ''
this.nodeUrl = this.getNodeUrl()
this.uid = new ShortUniqueId()
this.getUserAddress = this.getUserAddress.bind(this)
}
render() {
return html`
<div id="parent-div" tabindex="0" @blur=${this.handleBlur}>
<span class="close-icon" @click="${this.closePopover}"
><mwc-icon style="color: var(--black)"
>close</mwc-icon
></span
>
<span class="close-icon" @click="${this.closePopover}">
<mwc-icon style="color: var(--black)">close</mwc-icon>
</span>
<div class="action-parent">
<div
class="send-message-button"
@ -164,16 +60,15 @@ export class FriendItemActions extends connect(store)(LitElement) {
myPlugObj: {
url: 'q-chat',
domain: 'core',
page: 'messaging/q-chat/index.html',
page: 'q-chat/index.html',
title: 'Q-Chat',
icon: 'vaadin:chat',
mwcicon: 'forum',
pluginNumber: 'plugin-qhsyOnpRhT',
menus: [],
parent: false,
parent: false
},
openExisting: true,
openExisting: true
})
);
store.dispatch(
@ -181,8 +76,8 @@ export class FriendItemActions extends connect(store)(LitElement) {
type: 'openPrivateChat',
data: {
address,
name: this.name,
},
name: this.name
}
})
);
this.closePopover();
@ -208,9 +103,9 @@ export class FriendItemActions extends connect(store)(LitElement) {
icon: 'vaadin:mailbox',
mwcicon: 'mail_outline',
menus: [],
parent: false,
parent: false
},
openExisting: true,
openExisting: true
})
);
this.closePopover();
@ -223,26 +118,89 @@ export class FriendItemActions extends connect(store)(LitElement) {
<div
class="send-message-button"
@click="${() => {
const customEvent = new CustomEvent(
'open-visiting-profile',
{
detail: this.name,
}
);
const customEvent = new CustomEvent('open-visiting-profile', { detail: this.name });
window.dispatchEvent(customEvent);
this.closePopover();
this.closeSidePanel();
}}"
>
<mwc-icon style="color: var(--black)"
>person</mwc-icon
>
<mwc-icon style="color: var(--black)">person</mwc-icon>
${translate('profile.profile18')}
</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 '@material/mwc-icon';
import './friends-view'
import {friendsViewStyles} from './friends-view-css';
import {connect} from 'pwa-helpers';
import {store} from '../../store';
import './feed-item'
import { html, LitElement } from 'lit'
import { store } from '../../store'
import { connect } from 'pwa-helpers'
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'
const perEndpointCount = 20
const totalDesiredCount = 100
const maxResultsInMemory = 300
const perEndpointCount = 20;
const totalDesiredCount = 100;
const maxResultsInMemory = 300;
class FriendsFeed extends connect(store)(LitElement) {
static get properties() {
return {
@ -21,46 +20,92 @@ class FriendsFeed extends connect(store)(LitElement) {
isLoading: { type: Boolean },
hasFetched: { type: Boolean },
mySelectedFeeds: { type: Array }
};
}
}
static get styles() {
return [friendsViewStyles]
}
constructor() {
super()
this.feed = []
this.feedToRender = []
this.nodeUrl = this.getNodeUrl();
this.myNode = this.getMyNode();
this.nodeUrl = this.getNodeUrl()
this.myNode = this.getMyNode()
this.endpoints = []
this.endpointOffsets = [] // Initialize offsets for each endpoint to 0
this.loadAndMergeData = this.loadAndMergeData.bind(this)
this.hasInitialFetch = false
this.observerHandler = this.observerHandler.bind(this);
this.observerHandler = this.observerHandler.bind(this)
this.elementObserver = this.elementObserver.bind(this)
this.mySelectedFeeds = []
this.getSchemas = this.getSchemas.bind(this)
this.hasFetched = false
this._updateFeeds = this._updateFeeds.bind(this)
}
static get styles() {
return [friendsViewStyles];
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>
`
}
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() {
const myNode =
store.getState().app.nodeConfig.knownNodes[
window.parent.reduxStore.getState().app.nodeConfig.node
]
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[
window.parent.reduxStore.getState().app.nodeConfig.node
]
return store.getState().app.nodeConfig.knownNodes[store.getState().app.nodeConfig.node]
}
_updateFeeds(event) {
@ -71,7 +116,8 @@ class FriendsFeed extends connect(store)(LitElement) {
connectedCallback() {
super.connectedCallback()
window.addEventListener('friends-my-selected-feeds-event', this._updateFeeds) }
window.addEventListener('friends-my-selected-feeds-event', this._updateFeeds)
}
disconnectedCallback() {
window.removeEventListener('friends-my-selected-feeds-event', this._updateFeeds)
@ -81,97 +127,73 @@ class FriendsFeed extends connect(store)(LitElement) {
async getSchemas() {
this.mySelectedFeeds = JSON.parse(localStorage.getItem('friends-my-selected-feeds') || "[]")
const schemas = this.mySelectedFeeds
const getAllSchemas = (schemas || []).map(
async (schema) => {
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 data = await res.json()
if (data.error) return false
return data
} catch (error) {
console.log(error);
console.log(error)
return false
}
}
);
const res = await Promise.all(getAllSchemas);
)
const res = await Promise.all(getAllSchemas)
return res.filter((item) => !!item)
}
getFeedOnInterval() {
let interval = null;
let stop = false;
const getAnswer = async () => {
let interval = null
let stop = false
const getAnswer = async () => {
if (!stop) {
stop = true;
stop = true
try {
await this.reFetchFeedData()
} catch (error) { }
stop = false;
stop = false
}
}
};
interval = setInterval(getAnswer, 900000);
interval = setInterval(getAnswer, 900000)
}
async getEndpoints() {
const dynamicVars = {
}
const dynamicVars = { }
const schemas = await this.getSchemas()
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) {
this.endpoints = []
this.endpointOffsets = Array(this.endpoints.length).fill(0);
this.endpointOffsets = Array(this.endpoints.length).fill(0)
return
}
const baseurl = `${this.nodeUrl}/arbitrary/resources/search?reverse=true&mode=ALL&exactmatchnames=true&${names}`
let formEndpoints = []
schemas.forEach((schema) => {
const feedData = schema.feed[0]
if (feedData) {
const copyFeedData = { ...feedData }
const fullUrl = constructUrl(baseurl, copyFeedData.search, dynamicVars);
const fullUrl = constructUrl(baseurl, copyFeedData.search, dynamicVars)
if (fullUrl) {
formEndpoints.push({
url: fullUrl, schemaName: schema.name, schema: copyFeedData
})
}
}
})
this.endpoints = formEndpoints
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)
}
this.endpointOffsets = Array(this.endpoints.length).fill(0)
}
getMoreFeed() {
@ -190,115 +212,112 @@ this.getFeedOnInterval()
}
}
elementObserver() {
const options = {
rootMargin: '0px',
threshold: 1,
};
threshold: 1
}
// identify an element to observe
const elementToObserve = this.downObserverElement;
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);
observer.observe(elementToObserve)
}
observerHandler(entries) {
if (!entries[0].isIntersecting) {
} else {
if (this.feedToRender.length < 20) {
return;
return
}
this.getMoreFeed();
this.getMoreFeed()
}
}
async fetchDataFromEndpoint(endpointIndex, count) {
const offset = this.endpointOffsets[endpointIndex];
const url = `${this.endpoints[endpointIndex].url}&limit=${count}&offset=${offset}`;
const offset = this.endpointOffsets[endpointIndex]
const url = `${this.endpoints[endpointIndex].url}&limit=${count}&offset=${offset}`
const res = await fetch(url)
const data = await res.json()
return data.map((i) => {
return {
...this.endpoints[endpointIndex],
...i
}
})
}
async initialLoad() {
let results = [];
let totalFetched = 0;
let i = 0;
let madeProgress = true;
let exhaustedEndpoints = new Set();
let results = []
let totalFetched = 0
let i = 0
let madeProgress = true
let exhaustedEndpoints = new Set()
while (totalFetched < totalDesiredCount && madeProgress) {
madeProgress = false;
madeProgress = false
this.isLoading = true
for (i = 0; i < this.endpoints.length; 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 (remainingCount <= 0) {
break;
}
let fetchCount = Math.min(perEndpointCount, remainingCount);
let data = await this.fetchDataFromEndpoint(i, fetchCount);
let fetchCount = Math.min(perEndpointCount, remainingCount)
let data = await this.fetchDataFromEndpoint(i, fetchCount)
// 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) {
madeProgress = true;
madeProgress = true
}
if (data.length < fetchCount) {
exhaustedEndpoints.add(i);
exhaustedEndpoints.add(i)
}
results = results.concat(data);
totalFetched += data.length;
results = results.concat(data)
totalFetched += data.length
}
if (exhaustedEndpoints.size === this.endpoints.length) {
break;
break
}
}
this.isLoading = false
this.hasFetched = true;
this.hasFetched = true
// Trim the results if somehow they are over the totalDesiredCount
return results.slice(0, totalDesiredCount);
return results.slice(0, totalDesiredCount)
}
trimDataToLimit(data, limit) {
return data.slice(0, limit);
return data.slice(0, limit)
}
mergeData(newData, existingData) {
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));
return uniqueNewData.concat(existingData);
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))
return uniqueNewData.concat(existingData)
}
async addExtraData(data) {
let newData = []
for (let item of data) {
@ -310,11 +329,13 @@ this.getFeedOnInterval()
}
}
let newResource = {
identifier: newItem.identifier,
service: newItem.service,
name: newItem.name
}
if (newItem.schema) {
const resource = newItem
@ -325,51 +346,49 @@ this.getFeedOnInterval()
}
}
return newData
}
async reFetchFeedData() {
// Resetting offsets to start fresh.
this.endpointOffsets = Array(this.endpoints.length).fill(0);
this.endpointOffsets = Array(this.endpoints.length).fill(0)
await this.getEndpoints()
const oldIdentifiers = new Set(this.feed.map(item => item.identifier));
const newData = await this.initialLoad();
const oldIdentifiers = new Set(this.feed.map(item => item.identifier))
const newData = await this.initialLoad()
// 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) {
// 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
this.feed = [...enhancedNewData, ...this.feed];
this.feed = [...enhancedNewData, ...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 = this.trimDataToLimit(this.feed, maxResultsInMemory); // Trim to the maximum allowed in memory
this.feedToRender = this.feed.slice(0, 20);
this.hasInitialFetch = true;
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.feedToRender = this.feed.slice(0, 20)
this.hasInitialFetch = true
const created = trulyNewData[0].created;
let value = localStorage.getItem('lastSeenFeed');
const created = trulyNewData[0].created
let value = localStorage.getItem('lastSeenFeed')
if (((+value || 0) < created)) {
this.setHasNewFeed(true);
this.setHasNewFeed(true)
}
}
}
removeDuplicates(array) {
const seenIds = new Set();
const seenIds = new Set()
return array.filter(item => {
if (!seenIds.has(item.identifier)) {
seenIds.add(item.identifier);
return true;
seenIds.add(item.identifier)
return true
}
return false;
});
return false
})
}
async loadAndMergeData() {
let allData = this.feed
const newData = await this.initialLoad();
@ -390,87 +409,53 @@ this.getFeedOnInterval()
}
}
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>
`;
// 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
}
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) {
if (typeof value !== 'string') return value;
const pattern = /\$\$\{([a-zA-Z0-9_]+)\}\$\$/g; // Adjusted pattern to capture $${name}$$ with curly braces
if (typeof value !== 'string') return value
const pattern = /\$\$\{([a-zA-Z0-9_]+)\}\$\$/g // Adjusted pattern to capture $${name}$$ with curly braces
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) {
let queryStrings = [];
let queryStrings = []
for (const [key, value] of Object.entries(search)) {
const substitutedValue = substituteDynamicVar(value, dynamicVars);
queryStrings.push(`${key}=${encodeURIComponent(substitutedValue)}`);
const substitutedValue = substituteDynamicVar(value, dynamicVars)
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) {
const dataSource = { resource, customParams };
const dataSource = { resource, customParams }
return template.replace(/\$\$\{(.*?)\}\$\$/g, (match, p1) => {
const keys = p1.split('.');
let value = dataSource;
const keys = p1.split('.')
let value = dataSource
for (let key of keys) {
if (value[key] !== undefined) {
value = value[key];
value = value[key]
} 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 '@material/mwc-icon'
import './friends-side-panel.js'
import '@vaadin/tooltip'
import { html, LitElement } from 'lit'
import { connect } from 'pwa-helpers'
import { store } from '../../store'
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() {
return {
isOpen: { type: Boolean },
@ -12,45 +15,16 @@ class FriendsSidePanelParent extends LitElement {
}
}
static get styles() {
return [friendsSidePanelParentStyles]
}
constructor() {
super()
this.isOpen = 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() {
return html`
<mwc-icon
@ -62,7 +36,8 @@ class FriendsSidePanelParent extends LitElement {
this.hasNewFeed = false
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
</mwc-icon>
@ -72,12 +47,33 @@ class FriendsSidePanelParent extends LitElement {
hover-delay=${400}
hide-delay=${1}
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>
`
}
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 '@material/mwc-icon';
import { html, LitElement } from 'lit'
import { connect } from 'pwa-helpers'
import { store } from '../../store'
import { translate } from '../../../translate'
import { friendsSidePanelStyles } from '../../styles/core-css'
import './friends-view'
import './friends-feed'
import {translate} from '../../../translate'
import '@material/mwc-icon'
class FriendsSidePanel extends LitElement {
class FriendsSidePanel extends connect(store)(LitElement) {
static get properties() {
return {
setIsOpen: { attribute: false },
@ -13,7 +16,11 @@ class FriendsSidePanel extends LitElement {
setHasNewFeed: { attribute: false },
closeSidePanel: { attribute: false, type: Object },
openSidePanel: { attribute: false, type: Object }
};
}
}
static get styles() {
return [friendsSidePanelStyles]
}
constructor() {
@ -21,102 +28,6 @@ class FriendsSidePanel extends LitElement {
this.selected = 'friends'
this.closeSidePanel = this.closeSidePanel.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() {
@ -128,14 +39,13 @@ class FriendsSidePanel extends LitElement {
<span @click=${() => this.selected = 'feed'} class="${this.selected === 'feed' ? 'active' : 'default'}">${translate('friends.friend13')}</span>
</div>
<div style="display:flex;gap:15px;align-items:center">
<mwc-icon @click=${()=> {
this.refreshFeed()
}} style="color: var(--black); cursor:pointer;">refresh</mwc-icon>
<mwc-icon style="cursor:pointer" @click=${()=> {
this.setIsOpen(false)
}}>close</mwc-icon>
<mwc-icon @click=${() => { this.refreshFeed(); }} style="color: var(--black); cursor:pointer;">
refresh
</mwc-icon>
<mwc-icon style="cursor:pointer" @click=${() => { this.setIsOpen(false); }}>
close
</mwc-icon>
</div>
</div>
<div class="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'}">
<friends-feed .setHasNewFeed=${(val) => this.setHasNewFeed(val)}></friends-feed>
</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 {connect} from 'pwa-helpers';
import '@material/mwc-button';
import '@material/mwc-dialog';
import '@polymer/paper-spinner/paper-spinner-lite.js';
import '@polymer/paper-progress/paper-progress.js';
import '@material/mwc-icon';
import { html, LitElement } from 'lit'
import { store } from '../../store'
import { connect } from 'pwa-helpers'
import { parentEpml } from '../show-plugin'
import { translate } from '../../../translate'
import { friendsViewStyles } from '../../styles/core-css'
import './add-friends-modal'
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/icons'
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';
import '@vaadin/button'
class FriendsView extends connect(store)(LitElement) {
static get properties() {
@ -38,279 +36,35 @@ class FriendsView extends connect(store)(LitElement) {
refreshFeed: { attribute: false },
closeSidePanel: { attribute: false, type: Object },
openSidePanel: { attribute: false, type: Object }
};
}
}
static get styles() {
return [friendsViewStyles];
return [friendsViewStyles]
}
constructor() {
super();
this.error = false;
this.observerHandler = this.observerHandler.bind(this);
this.viewElement = '';
this.downObserverElement = '';
this.myAddress =
window.parent.reduxStore.getState().app.selectedAddress.address;
this.errorMessage = '';
this.successMessage = '';
this.friendList = [];
this.userSelected = {};
this.isLoading = false;
super()
this.error = false
this.observerHandler = this.observerHandler.bind(this)
this.viewElement = ''
this.downObserverElement = ''
this.myAddress = store.getState().app.selectedAddress.address
this.errorMessage = ''
this.successMessage = ''
this.friendList = []
this.userSelected = {}
this.isLoading = false
this.userFoundModalOpen = false
this.userFound = [];
this.nodeUrl = this.getNodeUrl();
this.myNode = this.getMyNode();
this.userFound = []
this.nodeUrl = this.getNodeUrl()
this.myNode = this.getMyNode()
this.isOpenAddFriendsModal = false
this.editContent = null
this.addToFriendList = this.addToFriendList.bind(this)
this._updateFriends = this._updateFriends.bind(this)
this._updateFeed = this._updateFeed.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() {
@ -331,22 +85,20 @@ class FriendsView extends connect(store)(LitElement) {
this.userSearch()
}
}}
/>
>
<vaadin-icon
@click=${this.userSearch}
slot="icon"
icon="vaadin:search"
class="search-icon">
class="search-icon"
>
</vaadin-icon>
</div>
<div class="search-results-div">
<chat-search-results
.onClickFunc=${(result) => {
this.userSelected = result;
this.isOpenAddFriendsModal = true
this.userFound = [];
this.userFoundModalOpen = false;
}}
@ -356,21 +108,21 @@ class FriendsView extends connect(store)(LitElement) {
}}
.searchResults=${this.userFound}
?isOpen=${this.userFoundModalOpen}
?loading=${this.isLoading}>
?loading=${this.isLoading}
>
</chat-search-results>
</div>
${this.friendList.map((item) => {
return html`<chat-side-nav-heads
return html`
<chat-side-nav-heads
activeChatHeadUrl=""
.setActiveChatHeadUrl=${(val) => {
}}
.setActiveChatHeadUrl=${(val) => { }}
.chatInfo=${item}
.openEditFriend=${(val) => this.openEditFriend(val)}
.closeSidePanel=${this.closeSidePanel}
></chat-side-nav-heads>`;
>
</chat-side-nav-heads>
`
})}
<div id="downObserver"></div>
</div>
@ -387,8 +139,271 @@ class FriendsView extends connect(store)(LitElement) {
.mySelectedFeeds=${this.mySelectedFeeds}
>
</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 '@material/mwc-button';
import '@material/mwc-icon';
import '@vaadin/tooltip';
import '@material/mwc-dialog';
import '@material/mwc-checkbox';
import {connect} from 'pwa-helpers';
import {store} from '../../store';
import '@polymer/paper-spinner/paper-spinner-lite.js';
import {parentEpml} from '../show-plugin';
import { profileModalUpdateStyles } from '../../styles/core-css'
import '@material/mwc-button'
import '@material/mwc-checkbox'
import '@material/mwc-dialog'
import '@material/mwc-icon'
import '@polymer/paper-spinner/paper-spinner-lite.js'
import '@vaadin/tooltip'
class ProfileModalUpdate extends connect(store)(LitElement) {
static get properties() {
@ -31,412 +32,49 @@ class ProfileModalUpdate extends connect(store)(LitElement) {
newCustomDataKey: { type: String },
newCustomDataValue: { type: String },
isSaving: { type: Boolean }
};
}
}
static get styles() {
return [profileModalUpdateStyles]
}
constructor() {
super();
this.isOpen = false;
this.isLoading = false;
this.nodeUrl = this.getNodeUrl();
this.myNode = this.getMyNode();
this.tagline = '';
this.bio = '';
this.walletList = ['btc', 'ltc', 'doge', 'dgb', 'rvn', 'arrr'];
let wallets = {};
this.isOpen = false
this.isLoading = false
this.nodeUrl = this.getNodeUrl()
this.myNode = this.getMyNode()
this.tagline = ''
this.bio = ''
this.walletList = ['btc', 'ltc', 'doge', 'dgb', 'rvn', 'arrr']
let wallets = {}
this.walletList.forEach((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]
}
wallets[item] = ''
})
this.wallets = wallets
this.walletsUi = new Map()
let coinProp = {
wallet: null
}
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 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.walletList.forEach((c, i) => {
this.walletsUi.set(c, { ...coinProp })
})
this.walletsUi.get('btc').wallet = store.getState().app.selectedAddress.btcWallet
this.walletsUi.get('ltc').wallet = store.getState().app.selectedAddress.ltcWallet
this.walletsUi.get('doge').wallet = store.getState().app.selectedAddress.dogeWallet
this.walletsUi.get('dgb').wallet = store.getState().app.selectedAddress.dgbWallet
this.walletsUi.get('rvn').wallet = store.getState().app.selectedAddress.rvnWallet
this.hasFetchedArrr = false
this.isOpenCustomDataModal = false
this.customData = {}
this.newCustomDataKey = ""
this.newCustomDataField = {};
this.newCustomDataValue = ""
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', '');
}
this.isSaving = false
this.addPrivate = this.addPrivate.bind(this)
this.checkForPrivate = this.checkForPrivate.bind(this)
}
render() {
@ -491,9 +129,7 @@ class ProfileModalUpdate extends connect(store)(LitElement) {
<div style="display: flex;flex-direction: column;">
${Object.keys(this.wallets).map((key) => {
return html`
<div
style="display:flex;justify-content:center;flex-direction:column"
>
<div style="display:flex;justify-content:center;flex-direction:column">
<label
for=${key}
id="taglineLabel"
@ -501,9 +137,7 @@ class ProfileModalUpdate extends connect(store)(LitElement) {
>
${key}
</label>
<div
style="display:flex;gap:15px;align-items:center"
>
<div style="display:flex;gap:15px;align-items:center">
<input
id=${key}
placeholder=${key + ' ' + get('settings.address')}
@ -516,65 +150,51 @@ class ProfileModalUpdate extends connect(store)(LitElement) {
};
}}
/>
<mwc-icon
id=${`${key}-upload`}
@click=${() =>
this.fillAddress(key)}
style="color:var(--black);cursor:pointer"
>upload_2</mwc-icon
>
upload_2
</mwc-icon>
<vaadin-tooltip
for=${`${key}-upload`}
position="bottom"
hover-delay=${200}
hide-delay=${1}
text=${translate('profile.profile21')}
>
</vaadin-tooltip>
></vaadin-tooltip>
</div>
</div>
`;
`
})}
</div>
<div style="display: flex;flex-direction: column;">
${Object.keys(this.customData).map((key) => {
return html`
<div
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;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>
<mwc-icon
@click=${() =>
this.updateCustomData(key,this.customData[key])}
@click=${() => this.updateCustomData(key, this.customData[key])}
style="color:var(--black);cursor:pointer"
>edit</mwc-icon
>
edit
</mwc-icon>
<mwc-icon
@click=${() =>
this.removeCustomData(key)}
@click=${() => this.removeCustomData(key)}
style="color:var(--black);cursor:pointer"
>remove</mwc-icon
>
remove
</mwc-icon>
</div>
</div>
`;
`
})}
</div>
</div>
<div
style="display:flex;justify-content:space-between;align-items:center;margin-top:20px"
>
<div style="display:flex;justify-content:space-between;align-items:center;margin-top:20px">
<button
class="modal-button-red"
?disabled="${this.isLoading}"
@ -586,9 +206,7 @@ class ProfileModalUpdate extends connect(store)(LitElement) {
>
${translate('general.close')}
</button>
<div style="display:flex;gap:10px;align-items:center">
<button
?disabled="${this.isLoading}"
class="modal-button"
@ -615,7 +233,6 @@ class ProfileModalUpdate extends connect(store)(LitElement) {
</div>
</div>
</div>
<!-- add custom vars -->
<div
class="modal-overlay ${this.isOpenCustomDataModal
@ -627,17 +244,12 @@ class ProfileModalUpdate extends connect(store)(LitElement) {
<div class="inner-content">
<div style="display:flex; justify-content:flex-end">
<div class="checkbox-row" style="font-size:16px">
<label for="isPrivate" style="color: var(--black);">
${get('profile.profile23')}
</label>
<mwc-checkbox id="isPrivate" @change=${(e) => this.addPrivate(e)} ?checked=${this.checkForPrivate()}>
</mwc-checkbox>
<label for="isPrivate" style="color: var(--black);">${get('profile.profile23')}</label>
<mwc-checkbox id="isPrivate" @change=${(e) => this.addPrivate(e)} ?checked=${this.checkForPrivate()}></mwc-checkbox>
</div>
</div>
<div style="height:15px"></div>
<div
style="display:flex;justify-content:center;flex-direction:column"
>
<div style="display:flex;justify-content:center;flex-direction:column">
<label
for="key-name"
id="taglineLabel"
@ -645,9 +257,7 @@ class ProfileModalUpdate extends connect(store)(LitElement) {
>
${translate('profile.profile9')}
</label>
<div
style="display:flex;gap:15px;align-items:center"
>
<div style="display:flex;gap:15px;align-items:center">
<input
id="key-name"
placeholder=${translate(
@ -668,19 +278,9 @@ class ProfileModalUpdate extends connect(store)(LitElement) {
<div style="display: flex;flex-direction: column;">
${Object.keys(this.newCustomDataField).map((key) => {
return html`
<div
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"
>
<div 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">
<input
id=${key}
placeholder=${translate('profile.profile13')}
@ -693,16 +293,15 @@ class ProfileModalUpdate extends connect(store)(LitElement) {
};
}}
/>
<mwc-icon
@click=${() =>
this.removeField(key)}
@click=${() => this.removeField(key)}
style="color:var(--black);cursor:pointer"
>remove</mwc-icon
>
remove
</mwc-icon>
</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" : ""}`}>
@ -736,9 +335,7 @@ class ProfileModalUpdate extends connect(store)(LitElement) {
</button>
</div>
</div>
<div
style="display:flex;justify-content:space-between;align-items:center;margin-top:20px"
>
<div style="display:flex;justify-content:space-between;align-items:center;margin-top:20px">
<button
class="modal-button-red"
?disabled="${this.isLoading}"
@ -750,7 +347,6 @@ class ProfileModalUpdate extends connect(store)(LitElement) {
>
${translate('general.close')}
</button>
<button
?disabled="${this.isSaving}"
class="modal-button"
@ -763,8 +359,204 @@ class ProfileModalUpdate extends connect(store)(LitElement) {
</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'
registerTranslateConfig({
@ -22,47 +25,7 @@ class LanguageSelector extends LitElement {
}
static get styles() {
return [
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;
}
`
]
return [languageSelectorStyles]
}
constructor() {
@ -103,7 +66,7 @@ class LanguageSelector extends LitElement {
firstUpdated() {
const myElement = this.shadowRoot.getElementById('languageSelect')
myElement.addEventListener("change", () => {
myElement.addEventListener('change', () => {
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 {store} from '../../store.js'
import {get, translate} from '../../../translate'
import {createWallet} from '../../../../crypto/api/createWallet.js'
import {doLogin, doLogout, doSelectAddress} from '../../redux/app/app-actions.js'
import {doStoreWallet} from '../../redux/user/user-actions.js'
import {checkApiKey} from '../../apiKeyUtils.js'
import { store } from '../../store'
import { createWallet } from '../../../../crypto/api/createWallet'
import { doLogin, doLogout, doSelectAddress } from '../../redux/app/app-actions'
import { doStoreWallet } from '../../redux/user/user-actions'
import { checkApiKey } from '../../apiKeyUtils'
import { createAccountSectionStyles } from '../../styles/core-css'
import FileSaver from 'file-saver'
import ripple from '../../functional-components/loading-ripple.js'
import snackbar from '../../functional-components/snackbar.js'
import '../../functional-components/random-sentence-generator.js'
import ripple from '../../functional-components/loading-ripple'
import snackbar from '../../functional-components/snackbar'
import '../../functional-components/random-sentence-generator'
import '@material/mwc-button'
import '@material/mwc-checkbox'
import '@material/mwc-textfield'
import '@material/mwc-icon'
import '@material/mwc-dialog'
import '@material/mwc-formfield'
import '@material/mwc-icon'
import '@material/mwc-textfield'
import '@polymer/iron-pages'
import '@polymer/paper-button/paper-button.js'
import '@polymer/paper-input/paper-input-container.js'
import '@polymer/paper-input/paper-input.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/text-field/vaadin-text-field.js'
// Multi language support
import { get, translate } from '../../../translate'
let lastPassword = ''
@ -54,32 +56,7 @@ class CreateAccountSection extends connect(store)(LitElement) {
}
static get styles() {
return [
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;
}
`
]
return [createAccountSectionStyles]
}
constructor() {
@ -116,44 +93,54 @@ class CreateAccountSection extends connect(store)(LitElement) {
next: e => {
// Create account and login :)
this.createAccountLoading = true
const nameInput = this.shadowRoot.getElementById('nameInput').value
const password = this.shadowRoot.getElementById('password').value
const rePassword = this.shadowRoot.getElementById('rePassword').value
if (password === '') {
let snackbar1string = get("login.pleaseenter")
snackbar.add({
labelText: `${snackbar1string}`,
dismiss: true
})
return
}
if (password != rePassword) {
let snackbar2string = get("login.notmatch")
snackbar.add({
labelText: `${snackbar2string}`,
dismiss: true
})
return
}
if (password.length < 8 && lastPassword !== password) {
let snackbar3string = get("login.lessthen8")
snackbar.add({
labelText: `${snackbar3string}`,
dismiss: true
})
lastPassword = password
return
}
if (this.saveAccount === true && nameInput === '') {
let snackbar4string = get("login.entername")
snackbar.add({
labelText: `${snackbar4string}`,
dismiss: true
})
return
}
@ -161,32 +148,31 @@ class CreateAccountSection extends connect(store)(LitElement) {
this._pass = password
let seedObj = {}
const seedPhrase = this.shadowRoot.getElementById('randSentence').parsedString
seedObj = { seedPhrase: seedPhrase }
ripple.welcomeMessage = welcomeMessage
ripple.open({
x: e.clientX,
y: e.clientY
})
.then(() => createWallet('phrase', seedObj, status => {
ripple.open({ x: e.clientX, y: e.clientY }).then(() => createWallet('phrase', seedObj, status => {
ripple.loadingMessage = status
}))
.then(wallet => {
})).then(wallet => {
this._wallet = wallet
return ripple.fade()
})
.then(() => {
}).then(() => {
this.selectPage('backup')
this.updateNext()
})
.catch(e => {
}).catch(e => {
snackbar.add({
labelText: e,
dismiss: true
})
console.error('== Error == \n', e)
store.dispatch(doLogout())
ripple.close()
})
},
@ -199,6 +185,7 @@ class CreateAccountSection extends connect(store)(LitElement) {
next: e => {
if (!this.isDownloadedBackup) {
let snackbar5string = get("login.downloaded")
snackbar.add({
labelText: `${snackbar5string}`,
dismiss: true
@ -206,29 +193,28 @@ class CreateAccountSection extends connect(store)(LitElement) {
} else {
if (this.saveAccount) {
ripple.welcomeMessage = this.renderPrepareText()
ripple.open({
x: e.clientX,
y: e.clientY
})
.then(() => {
ripple.open({ x: e.clientX, y: e.clientY}).then(() => {
store.dispatch(doStoreWallet(this._wallet, this._pass, this._name, () => {
ripple.loadingMessage = this.renderLoadingText()
}))
.then(() => {
})).then(() => {
store.dispatch(doLogin(this._wallet))
store.dispatch(doSelectAddress(this._wallet.addresses[0]))
checkApiKey(this.nodeConfig);
this.cleanup()
return ripple.fade()
}).catch(err => {
console.error(err)
})
.catch(err => console.error(err))
}).catch(err => {
console.error(err)
})
} else {
store.dispatch(doLogin(this._wallet))
store.dispatch(doSelectAddress(this._wallet.addresses[0]))
checkApiKey()
this.cleanup()
}
}
@ -238,6 +224,7 @@ class CreateAccountSection extends connect(store)(LitElement) {
}
}
}
this.pageIndexes = {
info: 0,
password: 1,
@ -307,20 +294,24 @@ class CreateAccountSection extends connect(store)(LitElement) {
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)
}
@media only screen and (max-width: ${getComputedStyle(document.body).getPropertyValue('--layout-breakpoint-tablet')}) {
/* Mobile */
#createAccountSection {
max-width: 100%;
height: calc(var(--window-height) - 56px);
}
#infoContent {
height: auto;
min-height: calc(var(--window-height) - 96px)
}
#nav {
flex-shrink: 0;
padding-top: 8px;
@ -335,6 +326,7 @@ class CreateAccountSection extends connect(store)(LitElement) {
from {
opacity: 0;
}
to {
opacity: 1;
}
@ -360,7 +352,6 @@ class CreateAccountSection extends connect(store)(LitElement) {
padding: 0;
}
</style>
<div id="createAccountSection" class="flex column">
<iron-pages selected="${this.selectedPage}" attr-for-selected="page" id="createAccountPages">
<div page="info">
@ -371,11 +362,15 @@ class CreateAccountSection extends connect(store)(LitElement) {
${translate("login.createwelcome")}
</p>
<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 style="color: var(--black); margin-bottom: 0; text-align: center;">
${translate("login.clicknext")}
</p><br>
</p>
<br>
</div>
<mwc-dialog id="mySeedDialog">
<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
template="adverb verb noun adjective noun adverb verb noun adjective noun adjective verbed adjective noun"
id="randSentence"
>
</random-sentence-generator>
></random-sentence-generator>
</div>
<!--
--- --- --- --- --- --- --- --- --- --- --- --- --- -
@ -405,7 +399,8 @@ class CreateAccountSection extends connect(store)(LitElement) {
sooo 243*3387*403*2353*3387*403*2353*403*2353 ~ 2^92
--- --- --- --- --- --- --- --- --- --- --- --- --- -
-->
</div><br>
</div>
<br>
<div class="horizontal-center">
<mwc-button raised label="${translate("login.saveseed")}" icon="save" @click=${() => this.downloadSeedphrase()}></mwc-button>
</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-dialog>
</div>
<div page="password">
<div id="saveContent" class="section-content">
<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 page="backup">
<div id="downloadBackup" class="section-content">
<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); margin: 0;">
${translate("login.backup")}
</p><br>
<p style="color: var(--black); margin: 0;">${translate("login.backup")}</p>
<br>
<div id="download-area">
<div style="line-height: 40px;">
<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;">
<mwc-button><mwc-icon>cloud_download</mwc-icon>&nbsp; ${translate("general.save")}</mwc-button>
<slot id="trigger" name="inputTrigger" @click=${() =>
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>
</div>
</div>
@ -519,15 +515,19 @@ class CreateAccountSection extends connect(store)(LitElement) {
if (!this.shadowRoot.querySelector('#createAccountPages') || !newPage) {
return
}
const pages = this.shadowRoot.querySelector('#createAccountPages').children
// Run the animation on the newly selected page
const newIndex = this.pageIndexes[newPage]
if (!pages[newIndex].className.includes('animated')) {
pages[newIndex].className += ' animated'
}
if (typeof oldPage !== 'undefined') {
const oldIndex = this.pageIndexes[oldPage]
// Stop the animation of hidden pages
pages[oldIndex].classList.remove('animated')
}
@ -535,6 +535,7 @@ class CreateAccountSection extends connect(store)(LitElement) {
selectPage(newPage) {
const oldPage = this.selectedPage
this.selectedPage = newPage
this._pageChange(newPage, oldPage)
}
@ -552,6 +553,7 @@ class CreateAccountSection extends connect(store)(LitElement) {
this.backHidden = true
this.nextText = this.renderContinueText()
}
this.updatedProperty()
}
@ -583,24 +585,27 @@ class CreateAccountSection extends connect(store)(LitElement) {
this.nodeConfig = state.app.nodeConfig
}
createAccount() {
}
async downloadBackup(wallet) {
let backupname = ""
const state = store.getState()
const data = await wallet.generateSaveWalletData(this._pass, state.config.crypto.kdfThreads, () => { })
const dataString = JSON.stringify(data)
const blob = new Blob([dataString], { type: 'text/plain;charset=utf-8' })
backupname = "qortal_backup_" + wallet.addresses[0].address + ".json"
await this.saveFileToDisk(blob, backupname)
}
async downloadSeedphrase() {
let seedname = ""
const seed = this.shadowRoot.getElementById('randSentence').parsedString
const blob = new Blob([seed], { type: 'text/plain;charset=utf-8' })
seedname = "qortal_seedphrase.txt"
await this.saveFileToDisk(blob, seedname)
}
@ -609,16 +614,21 @@ class CreateAccountSection extends connect(store)(LitElement) {
const fileHandle = await self.showSaveFilePicker({
suggestedName: fileName,
types: [{
description: "File",
description: "File"
}]
})
const writeFile = async (fileHandle, contents) => {
const writable = await fileHandle.createWritable()
await writable.write(contents)
await writable.close()
}
writeFile(fileHandle, blob).then(() => console.log("FILE SAVED"))
let snack4string = get("general.save")
snackbar.add({
labelText: `${snack4string} ${fileName}`,
dismiss: true
@ -627,6 +637,7 @@ class CreateAccountSection extends connect(store)(LitElement) {
if (error.name === 'AbortError') {
return
}
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 {store} from '../../store.js'
import {checkApiKey} from '../../apiKeyUtils.js'
import {translate} from '../../../translate'
import {doLogin, doSelectAddress} from '../../redux/app/app-actions.js'
import {doRemoveWallet, doStoreWallet} from '../../redux/user/user-actions.js'
import {createWallet} from '../../../../crypto/api/createWallet.js'
import snackbar from '../../functional-components/snackbar.js'
import '../../custom-elements/frag-file-input.js'
import ripple from '../../functional-components/loading-ripple.js'
import { store } from '../../store'
import { checkApiKey } from '../../apiKeyUtils'
import { doLogin, doSelectAddress } from '../../redux/app/app-actions'
import { doRemoveWallet, doStoreWallet } from '../../redux/user/user-actions'
import { createWallet } from '../../../../crypto/api/createWallet'
import { createAccountSectionStyles } from '../../styles/core-css'
import ripple from '../../functional-components/loading-ripple'
import snackbar from '../../functional-components/snackbar'
import '../../functional-components/frag-file-input'
import '@material/mwc-button'
import '@material/mwc-checkbox'
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/password-field/vaadin-password-field.js'
// Multi language support
import { translate } from '../../../translate'
class LoginSection extends connect(store)(LitElement) {
static get properties() {
return {
@ -51,36 +53,7 @@ class LoginSection extends connect(store)(LitElement) {
}
static get styles() {
return [
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;
}
`
]
return [createAccountSectionStyles]
}
constructor() {
@ -136,8 +109,7 @@ class LoginSection extends connect(store)(LitElement) {
overflow: visible;
}
#walletsPage {
}
#walletsPage {}
#wallets {
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')}) {
/* Mobile */
#wallets {
height: 100%;
@ -278,16 +251,11 @@ class LoginSection extends connect(store)(LitElement) {
${this.loginOptions.map(({ page, linkText, icon }) => html`
<div class="login-option" @click=${() => { this.selectedPage = page }}>
<paper-ripple></paper-ripple>
<div>
<mwc-icon class='loginIcon'>${icon}</mwc-icon>
</div>
<div>
<span style="color: var(--black)">${linkText}</span>
</div>
<div><mwc-icon class='loginIcon'>${icon}</mwc-icon></div>
<div><span style="color: var(--black)">${linkText}</span></div>
</div>
`)}
</div>
<div page="storedWallet" id="walletsPage">
<div style="text-align: center; padding-left:0;">
<h1 style="padding:0; color: var(--black);">${translate("login.youraccounts")}</h1>
@ -314,17 +282,10 @@ class LoginSection extends connect(store)(LitElement) {
</div>
<br>
<p>${translate("login.areyousure")}</p>
<mwc-button
slot="primaryAction"
@click="${(e) => this.removeWallet(this.myToDeleteWallet)}"
>
<mwc-button slot="primaryAction" @click="${(e) => this.removeWallet(this.myToDeleteWallet)}">
${translate("general.yes")}
</mwc-button>
<mwc-button
slot="secondaryAction"
dialogAction="cancel"
class="red"
>
<mwc-button slot="secondaryAction" dialogAction="cancel" class="red">
${translate("general.no")}
</mwc-button>
</mwc-dialog>
@ -332,7 +293,6 @@ class LoginSection extends connect(store)(LitElement) {
`)}
</div>
</div>
<div page="phrase" id="phrasePage">
<div style="padding:0;">
<div style="display:flex;">
@ -341,7 +301,6 @@ class LoginSection extends connect(store)(LitElement) {
</div>
</div>
</div>
<div page="seed" id="seedPage">
<div>
<div style="display: flex;">
@ -350,7 +309,6 @@ class LoginSection extends connect(store)(LitElement) {
</div>
</div>
</div>
<div page="unlockStored" id="unlockStoredPage">
<div style="text-align:center;">
<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>
</div>
</div>
<div page="backedUpSeed">
${!this.backedUpSeedLoading ? html`
<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>
`}
</div>
<div page="unlockBackedUpSeed">
<h3 style="text-align: center; color: var(--black);">${translate("login.decrypt")}</h3>
</div>
</iron-pages>
<iron-collapse style="" ?opened=${this.showName(this.selectedPage)} id="passwordCollapse">
<div style="display:flex;">
<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>
</div>
</iron-collapse>
<iron-collapse style="" ?opened=${this.showPassword(this.selectedPage)} id="passwordCollapse">
<div style="display:flex;">
<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>
</div>
</iron-collapse>
<div style="text-align: right; color: var(--mdc-theme-error)">
${this.loginErrorMessage}
</div>
@ -400,13 +353,14 @@ class LoginSection extends connect(store)(LitElement) {
` : ''}
</div>
</div>
</div>
`
}
firstUpdated() {
this.loadingRipple = ripple
const pages = this.shadowRoot.querySelector('#loginPages')
pages.addEventListener('selected-item-changed', () => {
if (!pages.selectedItem) {
// ...
@ -475,10 +429,13 @@ class LoginSection extends connect(store)(LitElement) {
removeWallet(walletAddress) {
delete store.getState().user.storedWallets[walletAddress]
this.wallets = store.getState().user.storedWallets
store.dispatch(
doRemoveWallet(walletAddress)
)
this.cleanup()
}
@ -503,14 +460,13 @@ class LoginSection extends connect(store)(LitElement) {
}
emitNext(e) {
this.dispatchEvent(new CustomEvent('next', {
detail: {}
}))
this.dispatchEvent(new CustomEvent('next', { detail: {} }))
}
loadBackup(file) {
let error = ''
let pf
this.selectedPage = 'unlockBackedUpSeed'
try {
@ -521,6 +477,7 @@ class LoginSection extends connect(store)(LitElement) {
try {
const requiredFields = ['address0', 'salt', 'iv', 'version', 'encryptedSeed', 'mac', 'kdfThreads']
for (const field of requiredFields) {
if (!(field in pf)) throw new Error(field + ' not found in JSON')
}
@ -532,38 +489,23 @@ class LoginSection extends connect(store)(LitElement) {
snackbar.add({
labelText: error
})
this.selectedPage = 'backedUpSeed'
return
}
this.backedUpWalletJSON = pf
}
showName(selectedPage) {
return (
this.saveInBrowser && [
'unlockBackedUpSeed',
'seed',
'phrase'
].includes(selectedPage)
) ||
(
[
''
].includes(selectedPage)
)
return (this.saveInBrowser && ['unlockBackedUpSeed', 'seed', 'phrase'].includes(selectedPage)) || ([''].includes(selectedPage))
}
showPassword(selectedPage) {
let willBeShown = (
this.saveInBrowser && [
'unlockBackedUpSeed',
'seed',
'phrase'
].includes(selectedPage)
) || (['unlockBackedUpSeed', 'unlockStored'].includes(selectedPage))
let willBeShown = (this.saveInBrowser && ['unlockBackedUpSeed', 'seed', 'phrase'].includes(selectedPage)) || (['unlockBackedUpSeed', 'unlockStored'].includes(selectedPage))
if (willBeShown)
this.shadowRoot.getElementById('password').focus()
if (willBeShown) this.shadowRoot.getElementById('password').focus()
return willBeShown
}
@ -574,6 +516,7 @@ class LoginSection extends connect(store)(LitElement) {
const seed = this.shadowRoot.querySelector('#v1SeedInput').value
const name = this.shadowRoot.getElementById('nameInput').value
const password = this.shadowRoot.getElementById('password').value
return {
seed,
password,
@ -583,6 +526,7 @@ class LoginSection extends connect(store)(LitElement) {
storedWallet: () => {
const wallet = this.selectedWallet
const password = this.shadowRoot.getElementById('password').value
return {
wallet,
password
@ -590,12 +534,15 @@ class LoginSection extends connect(store)(LitElement) {
},
phrase: () => {
const seedPhrase = this.shadowRoot.querySelector('#existingSeedPhraseInput').value
if (seedPhrase == "") {
if (seedPhrase == '') {
throw new Error('Please enter a seedphrase')
return
}
const name = this.shadowRoot.getElementById('nameInput').value
const password = this.shadowRoot.getElementById('password').value
return {
seedPhrase,
name,
@ -606,6 +553,7 @@ class LoginSection extends connect(store)(LitElement) {
const wallet = this.backedUpWalletJSON
const name = this.shadowRoot.getElementById('nameInput').value
const password = this.shadowRoot.getElementById('password').value
return {
password,
wallet,
@ -621,6 +569,7 @@ class LoginSection extends connect(store)(LitElement) {
login(e) {
let type = this.selectedPage === 'unlockStored' ? 'storedWallet' : this.selectedPage
type = type === 'unlockBackedUpSeed' ? 'backedUpSeed' : 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 {store} from '../../store.js'
import {stateAwait} from '../../stateAwait.js'
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 { store } from '../../store'
import { stateAwait } from '../../stateAwait'
import {
addAutoLoadImageChat,
addChatLastSeen,
@ -32,9 +19,23 @@ import {
setNewTab,
setSideEffectAction,
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.reduxAction = {
addAutoLoadImageChat: addAutoLoadImageChat,
removeAutoLoadImageChat: removeAutoLoadImageChat,
@ -71,16 +72,6 @@ class LoginView extends connect(store)(LitElement) {
}
}
static get styles() {
return [
css``
]
}
getPreSelectedPage() {
return 'welcome'
}
constructor() {
super()
this.selectedPage = this.getPreSelectedPage()
@ -95,50 +86,10 @@ class LoginView extends connect(store)(LitElement) {
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() {
return html`
<style>
canvas {
display: block;
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')}) {
/* Desktop/tablet */
.login-card {
max-width: 460px;
}
#loginContainerPages [page] {
border-radius: 4px;
}
#loginContainerPages [page="welcome"] {
}
#loginContainerPages [page="welcome"] {}
}
@media only screen and (max-width: ${getComputedStyle(document.body).getPropertyValue('--layout-breakpoint-tablet')}) {
/* Mobile */
.qortal-logo {
display: none;
visibility: hidden;
}
.login-card {
width: 100%;
margin: 0;
top: 0;
max-width: 100%;
}
.backButton {
text-align: left;
padding-left: 12px;
}
.login-card h5 {
margin-top: 0px;
margin-left: 0px;
@ -288,6 +245,7 @@ class LoginView extends connect(store)(LitElement) {
opacity: 0;
transform: translateX(-20%);
}
to {
opacity: 1;
transform: translateX(0);
@ -299,13 +257,15 @@ class LoginView extends connect(store)(LitElement) {
overflow: hidden;
max-height: 0;
}
to {
overflow: hidden;
max-height: var(--window-height);
}
}
iron-pages .animated, .animated {
iron-pages .animated,
.animated {
animation-duration: ${animationDuration}s;
animation-name: grow-up;
}
@ -325,7 +285,9 @@ class LoginView extends connect(store)(LitElement) {
</style>
<div class="login-page" ?hidden=${this.loggedIn}>
<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-center-container">
<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>
</iron-pages>
<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-icon>keyboard_arrow_left</mwc-icon>${this.selectedPageElement.backText}
<mwc-button
@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 @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
@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>
</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() {
const selectedNodeIndexOnStart = localStorage.getItem('mySelectedNode')
const catchSavedNodes = JSON.parse(localStorage.getItem('myQortalNodes'))
@ -370,6 +392,7 @@ class LoginView extends connect(store)(LitElement) {
stateChanged(state) {
if (this.loggedIn && !state.app.loggedIn) this.cleanup()
this.loggedIn = state.app.loggedIn
this.config = state.config
this.nodeConfig = state.app.nodeConfig

View File

@ -1,56 +1,28 @@
import {css, html, LitElement} from 'lit'
import {translate} from '../../../translate'
import { html, LitElement } from 'lit'
import { welcomePageStyles } from '../../styles/core-css'
import '@material/mwc-button'
// Multi language support
import { translate } from '../../../translate'
class WelcomePage extends LitElement {
static get properties() {
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 },
welcomeMessage: { type: String },
theme: { type: String, reflect: true }
}
}
static get styles() {
return css`
* {
--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;
}
`
return [welcomePageStyles]
}
constructor() {
super()
this.hideNav = true
this.nextText = ''
this.welcomeMessage = 'Welcome to Qortal'
this.theme = localStorage.getItem('qortalTheme') ? localStorage.getItem('qortalTheme') : 'light'
}
firstUpdated() {}
render() {
return html`
<div class="welcome-page">
@ -60,10 +32,6 @@ class WelcomePage extends LitElement {
`
}
back() {}
next() {}
navigate(page) {
this.dispatchEvent(new CustomEvent('navigate', {
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 { store } from '../../store.js'
import { doLogout } from '../../redux/app/app-actions.js'
import {translate} from '../../../translate'
import '@polymer/paper-dialog/paper-dialog.js'
import { logoutViewStyles } from '../../styles/core-css'
import '@material/mwc-button'
import '@polymer/paper-dialog/paper-dialog.js'
// Multi language support
import { translate } from '../../../translate'
class LogoutView extends connect(store)(LitElement) {
static get properties() {
@ -15,22 +17,7 @@ class LogoutView extends connect(store)(LitElement) {
}
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);
}
.decline {
--mdc-theme-primary: var(--mdc-theme-error)
}
.buttons {
text-align:right;
}
`
return [logoutViewStyles]
}
constructor() {
@ -49,7 +36,7 @@ class LogoutView extends connect(store)(LitElement) {
<h2 style="color: var(--black);">${translate("logout.confirmlogout")}</h2>
</div>
<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>
</div>
</paper-dialog>
@ -62,10 +49,12 @@ class LogoutView extends connect(store)(LitElement) {
async confirm(e) {
store.dispatch(doLogout())
e.stopPropagation()
}
decline(e) {
decline() {
this.shadowRoot.getElementById('userLogoutDialog').close()
this.requestUpdate()
}
}

View File

@ -1,16 +1,14 @@
import { html, LitElement } from 'lit'
import {installRouter} from 'pwa-helpers/router.js'
import { connect } from 'pwa-helpers'
import {store} from '../store.js'
import {doNavigate} from '../redux/app/app-actions.js'
import { store } from '../store'
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 '../plugins/streams.js'
import {loadPlugins} from '../plugins/load-plugins.js'
import '../styles/app-styles.js'
import './login-view/login-view.js'
import './app-view.js'
import './login-view/login-view'
import './app-view'
import '../plugins/streams'
import '../styles/app-styles'
installRouter((location) => store.dispatch(doNavigate(location)))
@ -22,44 +20,10 @@ class MainApp extends connect(store)(LitElement) {
}
}
static get styles() {
return []
}
render() {
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() {
super.connectedCallback()
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)

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'
registerTranslateConfig({
@ -22,40 +25,7 @@ class NewSelector extends LitElement {
}
static get styles() {
return [
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;
}
`
]
return [newSelectorStyles]
}
constructor() {
@ -100,15 +70,16 @@ class NewSelector extends LitElement {
firstUpdated() {
const myElement = this.shadowRoot.getElementById('languageNew')
myElement.addEventListener("change", () => {
myElement.addEventListener('change', () => {
this.selectElement()
})
myElement.addEventListener("click", () => {
myElement.addEventListener('click', () => {
const element1 = localStorage.getItem('qortalLanguage')
const element2 = this.shadowRoot.getElementById('languageNew').value
if (element1 === element2) {
myElement.style.display = "none"
myElement.style.display = 'none'
}
})
@ -117,9 +88,11 @@ class NewSelector extends LitElement {
selectElement() {
const selectedLanguage = localStorage.getItem('qortalLanguage')
let element = this.shadowRoot.getElementById('languageNew')
element.value = selectedLanguage
element.style.display = "none"
element.style.display = 'none'
}
changeLanguage(event) {
@ -129,10 +102,11 @@ class NewSelector extends LitElement {
toggleMenu() {
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 {
mySwitchDisplay.style.display = "none"
mySwitchDisplay.style.display = 'none'
}
}
}

View File

@ -1,160 +1,68 @@
import {css, html, LitElement} from 'lit';
import {connect} from 'pwa-helpers';
import { html, LitElement } from 'lit'
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';
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';
// Multi language support
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) {
static properties = {
static get properties() {
return {
notifications: { type: Array },
showNotifications: { type: Boolean },
notificationCount: { type: Boolean },
theme: { type: String, reflect: true },
currentNotification: { type: Object },
};
theme: { type: String, reflect: true }
}
}
static get styles() {
return [notificationBellGeneralStyles]
}
constructor() {
super();
this.notifications = [];
this.showNotifications = false;
this.notificationCount = false;
this.initialFetch = false;
this.theme = localStorage.getItem('qortalTheme')
? localStorage.getItem('qortalTheme')
: '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;
}
super()
this.notifications = []
this.showNotifications = false
this.notificationCount = false
this.initialFetch = false
this.currentNotification = null
this.theme = localStorage.getItem('qortalTheme') ? localStorage.getItem('qortalTheme') : 'light'
}
render() {
const hasOngoing = this.notifications.find(
(notification) => notification.status !== 'confirmed'
);
)
return html`
<div class="layout">
<popover-component
for="popover-notification"
message=${get('notifications.explanation')}
></popover-component>
<div
id="popover-notification"
@click=${() => this._toggleNotifications()}
>
${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>
<popover-component for="popover-notification" message=${get('notifications.explanation')}></popover-component>
<div id="popover-notification" @click=${() => this._toggleNotifications()}>
${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>
${hasOngoing
? html`
<span
class="count"
style="cursor:pointer"
@click=${() => this._toggleNotifications()}
>
<mwc-icon
style="color: var(--black);font-size:18px"
>pending</mwc-icon
>
${hasOngoing ? html`
<span class="count" style="cursor:pointer" @click=${() => this._toggleNotifications()}>
<mwc-icon style="color: var(--black);font-size:18px">pending</mwc-icon>
</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">
${this.notifications.length === 0 ? html`
<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}
type=${notification.type}
signature=${notification.reference
.signature}
.signature
}
></notification-item-tx>
`
)}
</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() {
@ -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 {
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);
window.customElements.define('notification-bell-general', NotificationBellGeneral)
class NotificationItemTx extends connect(store)(LitElement) {
static properties = {
static get properties() {
return {
status: { type: String },
type: { type: String },
timestamp: { type: Number },
signature: { type: String },
changeStatus: { attribute: false },
};
changeStatus: { attribute: false }
}
}
static get styles() {
return [notificationItemTxStyles]
}
constructor() {
super();
this.nodeUrl = this.getNodeUrl();
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();
super()
this.nodeUrl = this.getNodeUrl()
this.myNode = this.getMyNode()
}
render() {
@ -345,187 +198,66 @@ class NotificationItemTx extends connect(store)(LitElement) {
${translate('walletpage.wchange35')}: ${this.type}
</p>
<p style="margin-bottom:5px">
${translate('tubespage.schange28')}:
${this.status === 'confirming'
? translate('notifications.notify1')
: translate('notifications.notify2')}
${translate('tubespage.schange28')}: ${this.status === 'confirming' ? translate('notifications.notify1') : translate('notifications.notify2')}
</p>
${this.status !== 'confirmed'
? html`
<div class="centered">
<div class="loader">Loading...</div>
${this.status !== 'confirmed' ? html`<div class="centered"><div class="loader">Loading...</div></div>` : ''}
<div style="display:flex;justify-content:space-between;align-items:center">
<message-time timestamp=${this.timestamp} style="color:red;font-size:12px"></message-time>
${this.status === 'confirmed' ? html`<mwc-icon style="color: green;">done</mwc-icon>` : ''}
</div>
</div>
</div>
`
: ''}
<div
style="display:flex;justify-content:space-between;align-items:center"
>
<message-time
timestamp=${this.timestamp}
style="color:red;font-size:12px"
></message-time>
${this.status === 'confirmed'
? html`
<mwc-icon style="color: green;"
>done</mwc-icon
>
`
: ''}
</div>
</div>
</div>
`;
}
firstUpdated() {
this.getStatus()
}
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 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)
}
_toggleNotifications() {
if (this.notifications.length === 0) return;
this.showNotifications = !this.showNotifications;
if (this.notifications.length === 0) return
this.showNotifications = !this.showNotifications
}
}
static styles = css`
.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);
window.customElements.define('notification-item-tx', NotificationItemTx)

View File

@ -1,25 +1,29 @@
import { css, html, LitElement } from 'lit'
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/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) {
static properties = {
static get properties() {
return {
notifications: { type: Array },
showNotifications: { type: Boolean },
notificationCount: { type: Boolean },
theme: { type: String, reflect: true },
theme: { type: String, reflect: true }
}
}
static get styles() {
return [notificationBellStyles]
}
constructor() {
@ -31,132 +35,13 @@ class NotificationBell extends connect(store)(LitElement) {
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() {
return html`
<div class="layout">
${this.notificationCount ? html`
<mwc-icon @click=${() => this._toggleNotifications()} id="notification-mail-icon" style="color: green;cursor:pointer;user-select:none"
>mail</mwc-icon
>
<mwc-icon @click=${() => this._toggleNotifications()} id="notification-mail-icon" style="color: green;cursor:pointer;user-select:none">
mail
</mwc-icon>
<vaadin-tooltip
for="notification-mail-icon"
position="bottom"
@ -164,11 +49,10 @@ class NotificationBell extends connect(store)(LitElement) {
hide-delay=${1}
text="Q-Mail">
</vaadin-tooltip>
` : html`
<mwc-icon @click=${() => this._openTabQmail()} id="notification-mail-icon" style="color: var(--black); cursor:pointer;user-select:none"
>mail</mwc-icon
>
<mwc-icon @click=${() => this._openTabQmail()} id="notification-mail-icon" style="color: var(--black); cursor:pointer;user-select:none">
mail
</mwc-icon>
<vaadin-tooltip
for="notification-mail-icon"
position="bottom"
@ -176,17 +60,16 @@ class NotificationBell extends connect(store)(LitElement) {
hide-delay=${1}
text="Q-Mail">
</vaadin-tooltip>
`}
${this.notificationCount ? html`
<span class="count">${this.notifications.length}</span>
` : ''}
<div class="popover-panel" ?hidden=${!this.showNotifications}>
<div class="notifications-list">
${this.notifications.map(notification => html`
<div class="notification-item" @click=${() => {
<div
class="notification-item"
@click=${() => {
const query = `?service=APP&name=Q-Mail`
store.dispatch(setNewTab({
url: `qdn/browser/index.html${query}`,
@ -204,7 +87,8 @@ class NotificationBell extends connect(store)(LitElement) {
}))
this.showNotifications = false
this.notifications = []
}}>
}}
>
<div>
<p>Q-Mail</p>
<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() {
if (this.notifications.length === 0) return
this.showNotifications = !this.showNotifications
}
_openTabQmail() {
const query = `?service=APP&name=Q-Mail`
store.dispatch(setNewTab({
url: `qdn/browser/index.html${query}`,
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 {
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)
window.customElements.define('notification-bell', NotificationBell)

View File

@ -1,44 +1,30 @@
// popover-component.js
import {css, html, LitElement} from 'lit';
import {createPopper} from '@popperjs/core';
import { css, html, LitElement } from 'lit'
import { createPopper } from '@popperjs/core'
import { popoverComponentStyles } from '../../styles/core-css'
import '@material/mwc-icon'
export class PopoverComponent extends 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)
}
`;
static properties = {
static get properties() {
return {
for: { type: String, reflect: true },
message: { type: String }
};
constructor() {
super();
this.message = '';
}
}
firstUpdated() {
// We'll defer the popper attachment to the openPopover() method to ensure target availability
static get styles() {
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) {
@ -46,31 +32,25 @@ export class PopoverComponent extends LitElement {
this.popperInstance = createPopper(target, this, {
placement: 'bottom',
strategy: 'fixed'
});
})
}
}
openPopover(target) {
this.attachToTarget(target);
this.style.display = 'block';
this.attachToTarget(target)
this.style.display = 'block'
}
closePopover() {
this.style.display = 'none';
this.style.display = 'none'
if (this.popperInstance) {
this.popperInstance.destroy();
this.popperInstance = null;
}
this.requestUpdate();
this.popperInstance.destroy()
this.popperInstance = null
}
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>
`;
this.requestUpdate()
}
}
customElements.define('popover-component', PopoverComponent);
window.customElements.define('popover-component', PopoverComponent)

View File

@ -1,97 +1,23 @@
import {css, html, LitElement} from 'lit'
import {svgMoon, svgSun} from '../../assets/js/svg.js'
import { html, LitElement } from 'lit'
import { svgMoon, svgSun } from '../../assets/js/svg'
import { qortThemeToggleStyles } from '../styles/core-css'
class QortThemeToggle extends LitElement {
static get properties() {
return {
theme: {
type: String,
reflect: true
}
theme: { type: String, reflect: true }
}
}
constructor() {
super();
this.theme = localStorage.getItem('qortalTheme') ? localStorage.getItem('qortalTheme') : 'light';
super()
this.theme = localStorage.getItem('qortalTheme') ? localStorage.getItem('qortalTheme') : 'light'
}
static styles = [
css`
:host {
display: inline-block;
position: relative;
width: 54px;
height: 32px;
transform: translateY(-2px);
static get styles() {
return [qortThemeToggleStyles]
}
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() {
return html`
<input type="checkbox" @change=${() => this.toggleTheme()}/>
@ -100,35 +26,37 @@ class QortThemeToggle extends LitElement {
<span class="sun">${svgSun}</span>
<span class="moon">${svgMoon}</span>
</div>
`;
`
}
firstUpdated() {
this.initTheme();
this.initTheme()
}
toggleTheme() {
if (this.theme === 'light') {
this.theme = 'dark';
this.theme = 'dark'
} else {
this.theme = 'light';
this.theme = 'light'
}
this.dispatchEvent(
new CustomEvent('qort-theme-change', {
bubbles: true,
composed: true,
detail: this.theme,
}),
);
detail: this.theme
})
)
window.localStorage.setItem('qortalTheme', this.theme);
this.initTheme();
window.localStorage.setItem('qortalTheme', this.theme)
this.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 {get, translate} from '../../translate'
import snackbar from '../functional-components/snackbar.js'
import { html, LitElement } from 'lit'
import { searchModalStyles } from '../styles/core-css'
import snackbar from '../functional-components/snackbar'
import '@polymer/paper-icon-button/paper-icon-button.js'
import '@polymer/iron-icons/iron-icons.js'
import '@polymer/paper-dialog/paper-dialog.js'
import '@vaadin/text-field'
// Multi language support
import { get, translate } from '../../translate'
class SearchModal extends LitElement {
static get properties() {
return {
@ -15,56 +17,16 @@ class SearchModal extends LitElement {
}
}
static get styles() {
return [searchModalStyles]
}
constructor() {
super()
this.searchContentString = ''
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() {
return html`
<div style="display: inline;">
@ -92,6 +54,7 @@ class SearchModal extends LitElement {
}
firstUpdated() {
// ...
}
openSearch() {
@ -110,19 +73,24 @@ class SearchModal extends LitElement {
openUserInfo() {
const checkvalue = this.shadowRoot.getElementById('searchContent').value
if (checkvalue.length < 3) {
let snackbar1string = get("publishpage.pchange20")
let snackbar2string = get("welcomepage.wcchange4")
snackbar.add({
labelText: `${snackbar1string} ${snackbar2string}`,
dismiss: true
})
this.shadowRoot.getElementById('searchContent').value = this.searchContentString
this.shadowRoot.getElementById('searchContent').value = this.searchContentString
} else {
let sendInfoAddress = this.shadowRoot.getElementById('searchContent').value
const infoDialog = document.getElementById('main-app').shadowRoot.querySelector('app-view').shadowRoot.querySelector('user-info-view')
infoDialog.openUserInfo(sendInfoAddress)
this.shadowRoot.getElementById('searchContent').value = this.searchContentString
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 {store} from '../../store.js'
import { store } from '../../store'
import { accountViewStyles } from '../../styles/core-css'
// Multi language support
import { get, translate } from '../../../translate'
class AccountView extends connect(store)(LitElement) {
static get properties() {
return {
accountInfo: { type: Object },
theme: { type: String, reflect: true },
switchAvatar: { type: String }
switchAvatar: { type: String },
theme: { type: String, reflect: true }
}
}
static get styles() {
return css`
.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%;
}
`
return [accountViewStyles]
}
constructor() {
super()
this.accountInfo = store.getState().app.accountInfo
this.theme = localStorage.getItem('qortalTheme') ? localStorage.getItem('qortalTheme') : 'light'
this.switchAvatar = ''
this.theme = localStorage.getItem('qortalTheme') ? localStorage.getItem('qortalTheme') : 'light'
}
render() {
@ -92,9 +48,10 @@ class AccountView extends connect(store)(LitElement) {
firstUpdated() {
this.getSwitchAvatar()
setInterval(() => {
this.getSwitchAvatar()
}, 2000)
}, 10000)
}
getAvatar() {
@ -105,7 +62,8 @@ class AccountView extends connect(store)(LitElement) {
const avatarName = this.accountInfo.names[0].name
const avatarNode = store.getState().app.nodeConfig.knownNodes[store.getState().app.nodeConfig.node]
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';">`
}
} else if (this.switchAvatar === 'dark') {
@ -115,7 +73,8 @@ class AccountView extends connect(store)(LitElement) {
const avatarName = this.accountInfo.names[0].name
const avatarNode = store.getState().app.nodeConfig.knownNodes[store.getState().app.nodeConfig.node]
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';">`
}
}
@ -125,11 +84,6 @@ class AccountView extends connect(store)(LitElement) {
this.switchAvatar = localStorage.getItem('qortalTheme')
}
getApiKey() {
const apiNode = store.getState().app.nodeConfig.knownNodes[store.getState().app.nodeConfig.node]
return apiNode.apiKey
}
stateChanged(state) {
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 {store} from '../../store.js'
import {Epml} from '../../epml.js'
import {addTradeBotRoutes} from '../../tradebot/addTradeBotRoutes.js'
import {get, translate} from '../../../translate'
import snackbar from '../../functional-components/snackbar.js'
import { store } from '../../store'
import { Epml } from '../../epml'
import { addTradeBotRoutes } from '../../tradebot/addTradeBotRoutes'
import { exportKeysStyles } from '../../styles/core-css'
import FileSaver from 'file-saver'
import '@material/mwc-dialog'
import snackbar from '../../functional-components/snackbar'
import '@material/mwc-button'
import '@material/mwc-dialog'
import '@material/mwc-icon'
// Multi language support
import { get, translate } from '../../../translate'
const parentEpml = new Epml({ type: 'WINDOW', source: window.parent })
class ExportKeys extends connect(store)(LitElement) {
@ -51,211 +53,7 @@ class ExportKeys extends connect(store)(LitElement) {
}
static get styles() {
return css`
* {
--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);
}
}
`
return [exportKeysStyles]
}
constructor() {
@ -405,7 +203,16 @@ class ExportKeys extends connect(store)(LitElement) {
</mwc-button>
</mwc-dialog>
<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>
</mwc-dialog>
<mwc-dialog id="okDialog" scrimClickAction="" escapeKeyAction="">
@ -426,6 +233,7 @@ class ExportKeys extends connect(store)(LitElement) {
async firstUpdated() {
addTradeBotRoutes(parentEpml)
parentEpml.imReady()
await this.fetchArrrWalletAddress()
await this.checkArrrWalletPrivateKey()
}
@ -510,6 +318,7 @@ class ExportKeys extends connect(store)(LitElement) {
async repairLtcWallet() {
this.shadowRoot.querySelector('#repairLTCDialog').close()
this.shadowRoot.querySelector('#pleaseWaitDialog').show()
let resRepair = await parentEpml.request('apiCall', {
url: `/crosschain/ltc/repair?apiKey=${this.getApiKey()}`,
method: 'POST',
@ -518,24 +327,32 @@ class ExportKeys extends connect(store)(LitElement) {
if (resRepair != null && resRepair.error != 128) {
this.shadowRoot.querySelector('#pleaseWaitDialog').close()
await this.openOkDialog()
} else {
this.shadowRoot.querySelector('#pleaseWaitDialog').close()
await this.openErrorDialog()
}
}
async openOkDialog() {
const okDelay = ms => new Promise(res => setTimeout(res, ms))
this.shadowRoot.querySelector('#okDialog').show()
await okDelay(3000)
this.shadowRoot.querySelector('#okDialog').close()
}
async openErrorDialog() {
const errorDelay = ms => new Promise(res => setTimeout(res, ms))
this.shadowRoot.querySelector('#errorDialog').show()
await errorDelay(3000)
this.shadowRoot.querySelector('#errorDialog').close()
}
@ -552,12 +369,15 @@ class ExportKeys extends connect(store)(LitElement) {
}
async exportKey(cMasterKey, cName, cAddress) {
let exportname = ""
let exportname = ''
const myPrivateMasterKey = cMasterKey
const myCoinName = cName
const myCoinAddress = cAddress
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)
}
@ -566,16 +386,21 @@ class ExportKeys extends connect(store)(LitElement) {
const fileHandle = await self.showSaveFilePicker({
suggestedName: fileName,
types: [{
description: "File",
description: "File"
}]
})
const writeFile = async (fileHandle, contents) => {
const writable = await fileHandle.createWritable()
await writable.write(contents)
await writable.close()
}
writeFile(fileHandle, blob).then(() => console.log("FILE SAVED"))
let snack4string = get("general.save")
snackbar.add({
labelText: `${snack4string} ${fileName}`,
dismiss: true
@ -584,6 +409,7 @@ class ExportKeys extends connect(store)(LitElement) {
if (error.name === 'AbortError') {
return
}
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 {store} from '../../store.js'
import {allowShowSyncIndicator, removeShowSyncIndicator} from '../../redux/app/app-actions.js'
import {doSetQChatNotificationConfig} from '../../redux/user/user-actions.js'
import {translate} from '../../../translate'
import { store } from '../../store'
import { allowShowSyncIndicator, removeShowSyncIndicator } from '../../redux/app/app-actions'
import { doSetQChatNotificationConfig } from '../../redux/user/user-actions'
import { notificationsViewStyles } from '../../styles/core-css'
import isElectron from 'is-electron'
import '@material/mwc-checkbox'
// Multi language support
import { translate } from '../../../translate'
class NotificationsView extends connect(store)(LitElement) {
static get properties() {
return {
@ -18,130 +20,28 @@ class NotificationsView extends connect(store)(LitElement) {
}
}
static get styles() {
return [notificationsViewStyles]
}
constructor() {
super()
this.notificationConfig = {}
this.q_chatConfig = {}
this.theme = localStorage.getItem('qortalTheme') ? localStorage.getItem('qortalTheme') : 'light'
this.appNotificationList = [] // Fetch the list of apps from local storage
this.appNotificationList = []
}
firstUpdated() {
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() {
return html`
<div class="sub-main">
<div class="notification-box">
<div class="content-box">
<h4>Q-Chat ${translate("settings.notifications")}</h4>
<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>
<label
@ -151,7 +51,6 @@ class NotificationsView extends connect(store)(LitElement) {
${translate("settings.playsound")}
</label>
</div>
<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>
<label
@ -184,27 +83,29 @@ class NotificationsView extends connect(store)(LitElement) {
}
getAppsFromStorage() {
// Your method to fetch the list of apps from local storage
// Example:
const address = store.getState().app.selectedAddress.address
const id = `appNotificationList-${address}`
const data = localStorage.getItem(id)
return data ? Object.keys(JSON.parse(data)) : []
}
removeApp(appName) {
// Remove the app from local storage
this.removeAppFromStorage(appName);
this.removeAppFromStorage(appName)
// 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) {
// Your method to remove the app from local storage
const address = store.getState().app.selectedAddress.address
const id = `appNotificationList-${address}`;
const data = JSON.parse(localStorage.getItem(id) || '{}');
delete data[appName];
const id = `appNotificationList-${address}`
const data = JSON.parse(localStorage.getItem(id) || '{}')
delete data[appName]
localStorage.setItem(id, JSON.stringify(data));
}
@ -243,13 +144,14 @@ class NotificationsView extends connect(store)(LitElement) {
playSound: !valueObject.value,
showNotification: this.q_chatConfig.showNotification
}
store.dispatch(doSetQChatNotificationConfig(data))
} if (valueObject.type === 'SHOW_NOTIFICATION') {
let data = {
playSound: this.q_chatConfig.playSound,
showNotification: !valueObject.value
}
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 {store} from '../../store.js'
import {translate} from '../../../translate'
import '@material/mwc-textfield'
import { store } from '../../store'
import { qrLoginViewStyles } from '../../styles/core-css'
import '../../../../plugins/plugins/core/components/QortalQrcodeGenerator'
import '@material/mwc-icon'
import '@material/mwc-textfield'
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) {
static get properties() {
return {
theme: { type: String, reflect: true },
savedWalletDataJson: { type: String },
translateDescriptionKey: { type: String }, // Description text
translateButtonKey: { type: String }, // Button text
translateDescriptionKey: { type: String },
translateButtonKey: { type: String }
}
}
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);
}
.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;
}
`
return [qrLoginViewStyles]
}
constructor() {
@ -101,7 +45,6 @@ class QRLoginView extends connect(store)(LitElement) {
<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>
<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>
</div>
@ -114,25 +57,33 @@ class QRLoginView extends connect(store)(LitElement) {
const state = store.getState()
const address0 = state.app.wallet._addresses[0].address
const savedWalletData = state.user.storedWallets && state.user.storedWallets[address0]
return !!savedWalletData
}
async setSavedWalletDataJson() {
const state = store.getState()
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
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
data = await state.app.wallet.generateSaveWalletData(password, state.config.crypto.kdfThreads, () => { })
}
this.savedWalletDataJson = JSON.stringify(data)
}
async showQRCode() {
await this.setSavedWalletDataJson()
let el = this.shadowRoot.getElementById('login-qr-code')
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 {store} from '../../store.js'
import { store } from '../../store'
import {
allowQAPPAutoAuth,
allowQAPPAutoFriendsList,
@ -9,16 +9,18 @@ import {
removeQAPPAutoFriendsList,
removeQAPPAutoLists,
setIsOpenDevDialog
} from '../../redux/app/app-actions.js'
import {get, translate} from '../../../translate'
import snackbar from '../../functional-components/snackbar.js'
} from '../../redux/app/app-actions'
import { securityViewStyles } from '../../styles/core-css'
import FileSaver from 'file-saver'
import snackbar from '../../functional-components/snackbar'
import '@material/mwc-checkbox'
import '@material/mwc-textfield'
import '@material/mwc-icon'
import '@material/mwc-textfield'
import '@vaadin/password-field/vaadin-password-field.js'
// Multi language support
import { get, translate } from '../../../translate'
class SecurityView extends connect(store)(LitElement) {
static get properties() {
return {
@ -29,79 +31,7 @@ class SecurityView extends connect(store)(LitElement) {
}
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);
--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;
}
`
return [securityViewStyles]
}
constructor() {
@ -125,8 +55,7 @@ class SecurityView extends connect(store)(LitElement) {
id="downloadBackupPassword"
helper-text="${translate("login.passwordhint")}"
autofocus
>
</vaadin-password-field>
></vaadin-password-field>
</div>
<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>
@ -134,8 +63,7 @@ class SecurityView extends connect(store)(LitElement) {
style="width: 100%; color: var(--black);"
label="${translate("login.confirmpass")}"
id="rePassword"
>
</vaadin-password-field>
></vaadin-password-field>
</div>
<div style="text-align: center; color: var(--mdc-theme-error); text-transform: uppercase; font-size: 15px;">
${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>
</div>
<div class="checkbox-row">
<button
class="add-dev-button"
title="${translate('tabmenu.tm18')}"
@click=${this.openDevDialog}
>
<button class="add-dev-button" title="${translate('tabmenu.tm18')}" @click=${this.openDevDialog}>
${translate('tabmenu.tm38')}
</button>
</div>
@ -176,9 +100,6 @@ class SecurityView extends connect(store)(LitElement) {
`
}
stateChanged(state) {
}
checkForAuth(e) {
if (e.target.checked) {
store.dispatch(removeQAPPAutoAuth(false))
@ -225,13 +146,17 @@ class SecurityView extends connect(store)(LitElement) {
async downloadBackup() {
let backupname = ''
this.backupErrorMessage = ''
const state = store.getState()
const password = this.shadowRoot.getElementById('downloadBackupPassword').value
const data = await state.app.wallet.generateSaveWalletData(password, state.config.crypto.kdfThreads, () => { })
const dataString = JSON.stringify(data)
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)
}
@ -240,26 +165,33 @@ class SecurityView extends connect(store)(LitElement) {
const fileHandle = await self.showSaveFilePicker({
suggestedName: fileName,
types: [{
description: "File",
description: "File"
}]
})
const writeFile = async (fileHandle, contents) => {
const writable = await fileHandle.createWritable()
await writable.write(contents)
await writable.close()
}
writeFile(fileHandle, blob).then(() => console.log("FILE SAVED"))
let snack4string = get("general.save")
snackbar.add({
labelText: `${snack4string} ${fileName}`,
dismiss: true
})
this.shadowRoot.getElementById('downloadBackupPassword').value = ''
this.shadowRoot.getElementById('rePassword').value = ''
} catch (error) {
if (error.name === 'AbortError') {
return
}
FileSaver.saveAs(blob, fileName)
this.shadowRoot.getElementById('downloadBackupPassword').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 {store} from '../../store.js'
import {translate} from '../../../translate'
import '@polymer/paper-dialog/paper-dialog.js'
import { store } from '../../store'
import { userSettingsStyles } from '../../styles/core-css'
import './account-view'
import './export-keys'
import './notifications-view'
import './qr-login-view'
import './security-view'
import '@material/mwc-button'
import '@polymer/paper-dialog/paper-dialog.js'
import './account-view.js'
import './security-view.js'
import './notifications-view.js'
import './qr-login-view.js'
import './export-keys.js'
// Multi language support
import { translate } from '../../../translate'
class UserSettings extends connect(store)(LitElement) {
static get properties() {
@ -23,189 +24,7 @@ class UserSettings extends connect(store)(LitElement) {
}
static get styles() {
return css`
: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;
}
}
`
return [userSettingsStyles]
}
constructor() {

File diff suppressed because it is too large Load Diff

View File

@ -1,19 +1,20 @@
import {css, html, LitElement} from 'lit';
import {connect} from 'pwa-helpers';
import {store} from '../store.js';
import {get, translate} from '../../translate'
import {asyncReplace} from 'lit/directives/async-replace.js';
import '../functional-components/my-button.js';
import {routes} from '../plugins/routes.js';
import { html, LitElement } from 'lit'
import { asyncReplace } from 'lit/directives/async-replace.js'
import { connect } from 'pwa-helpers'
import { store } from '../store.js'
import { routes } from '../plugins/routes'
import { startMintingStyles } from '../styles/core-css'
import '../functional-components/my-button'
import "@material/mwc-button"
import '@material/mwc-dialog'
// Multi language support
import { get, translate } from '../../translate'
async function* countDown(count, callback) {
while (count > 0) {
yield count--;
await new Promise((r) => setTimeout(r, 1000));
yield count--
await new Promise((r) => setTimeout(r, 1000))
if (count === 0) {
callback()
}
@ -30,187 +31,56 @@ class StartMinting extends connect(store)(LitElement) {
status: { type: Number },
timer: { type: Number },
privateRewardShareKey: { type: String }
};
}
}
static get styles() {
return [
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);
}
`,
];
return [startMintingStyles]
}
constructor() {
super();
this.addressInfo = {};
this.mintingAccountData = [];
this.errorMsg = '';
this.openDialogRewardShare = false;
this.status = 0;
this.privateRewardShareKey = "";
this.address = this.getAddress();
this.nonce = this.getNonce();
super()
this.addressInfo = {}
this.mintingAccountData = []
this.errorMsg = ''
this.openDialogRewardShare = false
this.status = 0
this.privateRewardShareKey = ''
this.address = this.getAddress()
this.nonce = this.getNonce()
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() {
return html` ${this.renderStartMintingButton()} `;
return html`${this.renderStartMintingButton()}`
}
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() {
@ -230,124 +100,130 @@ const nonce = selectedAddress && selectedAddress.nonce;
}
async getMintingAcccounts() {
const myNode = store.getState().app.nodeConfig.knownNodes[store.getState().app.nodeConfig.node];
const nodeUrl = myNode.protocol + '://' + myNode.domain + ':' + myNode.port;
const url = `${nodeUrl}/admin/mintingaccounts`;
const myNode = store.getState().app.nodeConfig.knownNodes[store.getState().app.nodeConfig.node]
const nodeUrl = myNode.protocol + '://' + myNode.domain + ':' + myNode.port
const url = `${nodeUrl}/admin/mintingaccounts`
try {
const res = await fetch(url);
this.mintingAccountData = await res.json();
const res = await fetch(url)
this.mintingAccountData = await res.json()
} catch (error) {
this.errorMsg = this.renderErrorMsg1();
this.errorMsg = this.renderErrorMsg1()
}
}
async changeStatus(value) {
const myNode = store.getState().app.nodeConfig.knownNodes[store.getState().app.nodeConfig.node];
const nodeUrl = myNode.protocol + '://' + myNode.domain + ':' + myNode.port;
this.status = value
const myNode = store.getState().app.nodeConfig.knownNodes[store.getState().app.nodeConfig.node]
const nodeUrl = myNode.protocol + '://' + myNode.domain + ':' + myNode.port
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.
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 url = `${nodeUrl}/admin/mintingaccounts?apiKey=${myNode.apiKey}`;
const url = `${nodeUrl}/admin/mintingaccounts?apiKey=${myNode.apiKey}`
return await fetch(url, {
method: 'DELETE',
body: publicKey,
});
};
body: publicKey
})
}
const addMintingAccount = async (sponsorshipKeyValue) => {
const url = `${nodeUrl}/admin/mintingaccounts?apiKey=${myNode.apiKey}`;
const url = `${nodeUrl}/admin/mintingaccounts?apiKey=${myNode.apiKey}`
return await fetch(url, {
method: 'POST',
body: sponsorshipKeyValue,
});
};
body: sponsorshipKeyValue
})
}
try {
if (
findMintingAccountFromOtherUser &&
findMintingAccountFromOtherUser.publicKey &&
findMintingAccountFromOtherUser.publicKey[0]
) {
if (findMintingAccountFromOtherUser && findMintingAccountFromOtherUser.publicKey && findMintingAccountFromOtherUser.publicKey[0]) {
await removeMintingAccount(
findMintingAccountFromOtherUser.publicKey[0]
);
)
}
} catch (error) {
this.errorMsg = this.renderErrorMsg2();
return;
this.errorMsg = this.renderErrorMsg2()
return
}
try {
await addMintingAccount(this.privateRewardShareKey);
await addMintingAccount(this.privateRewardShareKey)
await routes.showSnackBar({
data: translate('becomeMinterPage.bchange19'),
});
this.status = 5;
await this.getMintingAcccounts();
data: translate('becomeMinterPage.bchange19')
})
this.status = 5
await this.getMintingAcccounts()
} catch (error) {
this.errorMsg = this.renderErrorMsg3();
this.errorMsg = this.renderErrorMsg3()
}
}
async confirmRelationship() {
const myNode =
store.getState().app.nodeConfig.knownNodes[
store.getState().app.nodeConfig.node
];
const nodeUrl = myNode.protocol + '://' + myNode.domain + ':' + myNode.port;
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
this.status = 2
const getAnswer = async () => {
const rewardShares = async (minterAddr) => {
const url = `${nodeUrl}/addresses/rewardshares?minters=${minterAddr}&recipients=${minterAddr}`;
const res = await fetch(url);
return await res.json();
};
const url = `${nodeUrl}/addresses/rewardshares?minters=${minterAddr}&recipients=${minterAddr}`
const res = await fetch(url)
return await res.json()
}
if (!stop) {
stop = true;
stop = true
try {
const address = this.address
const myRewardShareArray = await rewardShares(address);
const myRewardShareArray = await rewardShares(address)
if (myRewardShareArray.length > 0) {
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
}
};
interval = setInterval(getAnswer, 5000);
}
interval = setInterval(getAnswer, 5000)
}
renderStartMintingButton() {
const myNode = store.getState().app.nodeConfig.knownNodes[store.getState().app.nodeConfig.node];
const nodeUrl = myNode.protocol + '://' + myNode.domain + ':' + myNode.port;
const mintingAccountData = this.mintingAccountData;
const myNode = store.getState().app.nodeConfig.knownNodes[store.getState().app.nodeConfig.node]
const nodeUrl = myNode.protocol + '://' + myNode.domain + ':' + myNode.port
const mintingAccountData = this.mintingAccountData
const addressInfo = window.parent.reduxStore.getState().app.accountInfo.addressInfo
const address = this.address
const nonce = this.nonce
const publicAddress = this.base58PublicKey
const findMintingAccount = mintingAccountData.find((ma) => ma.mintingAccount === address);
const isMinterButKeyMintingKeyNotAssigned = addressInfo && addressInfo.error !== 124 && addressInfo.level >= 1 && !findMintingAccount;
const findMintingAccount = mintingAccountData.find((ma) => ma.mintingAccount === address)
const isMinterButKeyMintingKeyNotAssigned = addressInfo && addressInfo.error !== 124 && addressInfo.level >= 1 && !findMintingAccount
const makeTransactionRequest = async (lastRef) => {
let mylastRef = lastRef;
let rewarddialog1 = get('transactions.rewarddialog1');
let rewarddialog2 = get('transactions.rewarddialog2');
let rewarddialog3 = get('transactions.rewarddialog3');
let rewarddialog4 = get('transactions.rewarddialog4');
let mylastRef = lastRef
let rewarddialog1 = get('transactions.rewarddialog1')
let rewarddialog2 = get('transactions.rewarddialog2')
let rewarddialog3 = get('transactions.rewarddialog3')
let rewarddialog4 = get('transactions.rewarddialog4')
return await routes.transaction({
data: {
@ -360,90 +236,93 @@ const nonce = selectedAddress && selectedAddress.nonce;
rewarddialog1: rewarddialog1,
rewarddialog2: rewarddialog2,
rewarddialog3: rewarddialog3,
rewarddialog4: rewarddialog4,
rewarddialog4: rewarddialog4
}
},
},
disableModal: true,
});
};
disableModal: true
})
}
const getTxnRequestResponse = (txnResponse) => {
let err6string = get('rewardsharepage.rchange21');
let err6string = get('rewardsharepage.rchange21')
if (txnResponse && txnResponse.extraData && txnResponse.extraData.rewardSharePrivateKey &&
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) {
throw txnResponse;
throw txnResponse
} else if (txnResponse.success === true && txnResponse.data && !txnResponse.data.error) {
return err6string;
return err6string
} else {
throw txnResponse;
throw txnResponse
}
}
};
const createSponsorshipKey = async () => {
this.status = 1;
let lastRef = await getLastRef();
let myTransaction = await makeTransactionRequest(lastRef);
getTxnRequestResponse(myTransaction);
this.status = 1
let lastRef = await getLastRef()
let myTransaction = await makeTransactionRequest(lastRef)
getTxnRequestResponse(myTransaction)
if (myTransaction && myTransaction.extraData) {
return myTransaction.extraData.rewardSharePrivateKey;
return myTransaction.extraData.rewardSharePrivateKey
}
}
};
const getLastRef = async () => {
const url = `${nodeUrl}/addresses/lastreference/${address}`;
const res = await fetch(url);
return await res.text();
};
const url = `${nodeUrl}/addresses/lastreference/${address}`
const res = await fetch(url)
return await res.text()
}
const startMinting = async () => {
this.openDialogRewardShare = true
this.errorMsg = '';
this.errorMsg = ''
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) {
this.errorMsg = translate("startminting.smchange10")
return;
return
}
try {
this.privateRewardShareKey = await createSponsorshipKey();
this.privateRewardShareKey = await createSponsorshipKey()
await this.confirmRelationship(publicAddress)
} catch (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`
${isMinterButKeyMintingKeyNotAssigned ? html`
<div class="start-minting-wrapper">
<my-button label="${translate('becomeMinterPage.bchange18')}"
<my-button
label="${translate('becomeMinterPage.bchange18')}"
?isLoading=${false}
.onClick=${async () => {
await startMinting();
if (this.errorMsg) {
await routes.showSnackBar({
data: this.errorMsg,
data: this.errorMsg
});
}
}}
>
</my-button>
></my-button>
</div>
<!-- Dialog for tracking the progress of starting minting -->
${this.openDialogRewardShare ? html`
<div class="dialogCustom">
<div class="dialogCustomInner">
@ -457,73 +336,50 @@ const nonce = selectedAddress && selectedAddress.nonce;
<div class="dialog-container">
<ul>
<li class="row between">
<p>
1. ${translate("startminting.smchange5")}
</p>
<p>1. ${translate("startminting.smchange5")}</p>
<div class=${`smallLoading marginLoader ${this.status !== 1 && 'hide'}`}></div>
</li>
<li class=${`row between ${this.status < 2 && 'inactiveText'}`}>
<p>
2. ${translate("startminting.smchange6")}
</p>
<p>2. ${translate("startminting.smchange6")}</p>
<div class=${`smallLoading marginLoader ${this.status !== 2 && 'hide'}`}></div>
</li>
<li class=${`row between ${this.status < 3 && 'inactiveText'}`}>
<p>
3. ${translate("startminting.smchange7")}
</p>
<p>3. ${translate("startminting.smchange7")}</p>
<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>
</li>
<li class=${`row between ${this.status < 4 && 'inactiveText'}`}>
<p>
4. ${translate("startminting.smchange8")}
</p>
<p>4. ${translate("startminting.smchange8")}</p>
<div class=${`smallLoading marginLoader ${this.status !== 4 && 'hide'}`}></div>
</li>
<li class=${`row between ${this.status < 5 && 'inactiveText'}`}>
<p>
5. ${translate("startminting.smchange9")}
</p>
<p>5. ${translate("startminting.smchange9")}</p>
</li>
</ul>
<div class="warning column">
<p>
Warning: do not close the Qortal UI until completion!
</p>
<p>Warning: do not close the Qortal UI until completion!</p>
<p class="message-error">${this.errorMsg}</p>
</div>
</div>
<div class="modalFooter">
${this.errorMsg || this.status === 5 ? html`
<mwc-button
slot="primaryAction"
@click=${() => {
this.openDialogRewardShare = false
this.errorMsg = ''
}}
class="red"
>
<mwc-button slot="primaryAction" @click=${() => { this.openDialogRewardShare = false; this.errorMsg = '';}} class="red">
${translate("general.close")}
</mwc-button>
` : ''}
</div>
</div>
</div>
` : ""}
` : ''}
`;
` : ''}
`
}
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 {translate} from '../../translate'
import { html, LitElement } from 'lit'
import { themeToggleStyles } from '../styles/core-css'
import '@polymer/paper-icon-button/paper-icon-button.js'
import '@polymer/iron-icons/image-icons.js'
import '@polymer/iron-icons/iron-icons.js'
// Multi language support
import { translate } from '../../translate'
class ThemeToggle extends LitElement {
static get properties() {
return {
@ -11,58 +14,15 @@ class ThemeToggle extends LitElement {
}
}
static get styles() {
return [themeToggleStyles]
}
constructor() {
super()
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() {
return html`
<div style="display: inline;">
@ -87,16 +47,17 @@ class ThemeToggle extends LitElement {
this.dispatchEvent(new CustomEvent('qort-theme-change', {
bubbles: true,
composed: true,
detail: this.theme,
detail: this.theme
}))
window.localStorage.setItem('qortalTheme', this.theme)
this.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 {store} from '../store.js'
import { store } from '../store'
import { walletProfileStyles } from '../styles/core-css'
// Multi language support
import { translate } from '../../translate'
class WalletProfile extends connect(store)(LitElement) {
@ -14,56 +17,7 @@ class WalletProfile extends connect(store)(LitElement) {
}
static get styles() {
return css`
#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;
}
`
return [walletProfileStyles]
}
constructor() {
@ -85,8 +39,16 @@ class WalletProfile extends connect(store)(LitElement) {
<div id="child inline-block-child" class="full-info-logo">${this.getAvatar()}</div>
&nbsp;&nbsp;&nbsp;
<div id="inline-block-child">
<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>
<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>
</div>
</div>
@ -96,7 +58,9 @@ class WalletProfile extends connect(store)(LitElement) {
`
}
firstUpdated() {}
firstUpdated() {
// ...
}
getAvatar() {
if (this.accountInfo.names.length === 0) {
@ -104,16 +68,12 @@ class WalletProfile extends connect(store)(LitElement) {
} else {
const avatarNode = store.getState().app.nodeConfig.knownNodes[store.getState().app.nodeConfig.node]
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';" />`
}
}
getApiKey() {
const apiNode = store.getState().app.nodeConfig.knownNodes[store.getState().app.nodeConfig.node]
return apiNode.apiKey
}
stateChanged(state) {
this.wallet = state.app.wallet
this.nodeConfig = state.app.nodeConfig

View File

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

View File

@ -1,12 +1,13 @@
import { css, html, LitElement } from 'lit'
import { connect } from 'pwa-helpers'
import {store} from '../store.js'
import {get, translate} from '../../translate'
import {listenForRequest} from '../transactionRequest.js'
import '@polymer/paper-dialog/paper-dialog.js'
import { store } from '../store'
import { listenForRequest } from '../transactionRequest'
import { confirmTransactionDialogStyles } from '../styles/core-css'
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) {
static get properties() {
@ -17,35 +18,7 @@ class ConfirmTransactionDialog extends connect(store)(LitElement) {
}
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);
}
.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);
}
`
return [confirmTransactionDialogStyles]
}
constructor() {
@ -55,7 +28,7 @@ class ConfirmTransactionDialog extends connect(store)(LitElement) {
}
this.txInfo = html``
listenForRequest(args => this.requestTransaction(args))
this.theme = localStorage.getItem('qortalTheme') ? localStorage.getItem('qortalTheme') : 'light';
this.theme = localStorage.getItem('qortalTheme') ? localStorage.getItem('qortalTheme') : 'light'
}
render() {
@ -73,8 +46,8 @@ class ConfirmTransactionDialog extends connect(store)(LitElement) {
`
}
stateChanged(state) {
this.loggedIn = state.app.loggedIn
firstUpdated() {
// ...
}
requestTransaction(transaction) {
@ -98,6 +71,10 @@ class ConfirmTransactionDialog extends connect(store)(LitElement) {
const rejecterror = get("transactions.declined")
this._reject(new Error(rejecterror))
}
stateChanged(state) {
this.loggedIn = state.app.loggedIn
}
}
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']
@ -7,17 +8,13 @@ let rippleElement
class LoadingRipple extends LitElement {
static get properties() {
return {
welcomeMessage: {
type: String,
attribute: 'welcome-message',
reflectToAttribute: true
},
loadingMessage: {
type: String,
attribute: 'loading-message',
reflectToAttribute: true
welcomeMessage: { type: String, attribute: 'welcome-message', reflectToAttribute: true },
loadingMessage: { type: String, attribute: 'loading-message', reflectToAttribute: true}
}
}
static get styles() {
return [loadingRippleStyles]
}
constructor() {
@ -26,83 +23,6 @@ class LoadingRipple extends LitElement {
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() {
return html`
<div id="rippleWrapper">
@ -126,7 +46,6 @@ class LoadingRipple extends LitElement {
this._rippleContentWrapper = this.shadowRoot.getElementById('rippleContentWrapper')
}
// duh
open(origin) {
this._rippleWrapper.style.top = origin.y + 'px'
this._rippleWrapper.style.left = origin.x + 'px'
@ -135,53 +54,64 @@ class LoadingRipple extends LitElement {
return new Promise((resolve, reject) => {
this._ripple.classList.add('activating')
let isOpened = false
const doneOpeningEvent = () => {
if (isOpened) return
// Clear events
TRANSITION_EVENT_NAMES.forEach(name => this._ripple.removeEventListener(name, doneOpeningEvent))
this._ripple.classList.add('activating-done')
isOpened = true
resolve()
}
TRANSITION_EVENT_NAMES.forEach(name => this._ripple.addEventListener(name, doneOpeningEvent))
})
}
// Fades out
fade() {
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-done')
this._ripple.classList.remove('disabling')
resolve()
})
}
// un-ripples...
close() {
return new Promise((resolve, reject) => {
let rippleClosed = false
this._ripple.classList.add('error')
this._ripple.classList.remove('activating')
this._ripple.classList.remove('activating-done')
const rippleClosedEvent = () => {
if (rippleClosed) return
rippleClosed = true
TRANSITION_EVENT_NAMES.forEach(name => this._ripple.removeEventListener(name, rippleClosedEvent))
// Reset the ripple
this._ripple.classList.remove('error')
this.rippleIsOpen = false
resolve()
}
TRANSITION_EVENT_NAMES.forEach(name => this._ripple.addEventListener(name, rippleClosedEvent))
})
}
stateChanged(state) {
// this.loggedIn = state.app.loggedIn
// ...
}
}
@ -195,7 +125,6 @@ setTimeout(() => {
const ripple = document.getElementById('ripple-node')
const mainApp = document.getElementById('main-app')
const shadow = mainApp.shadowRoot
// console.log(shadow)
rippleElement = shadow.appendChild(ripple)
}, 500) // Should just keep checking for the main-app and it's shadow and then append once it's there
export default rippleElement

View File

@ -1,47 +1,39 @@
import {css, html, LitElement} from 'lit';
import '@vaadin/button';
import '@polymer/paper-spinner/paper-spinner-lite.js';
import { html, LitElement } from 'lit'
import { myButtonStyles } from '../styles/core-css'
import '@vaadin/button'
import '@polymer/paper-spinner/paper-spinner-lite.js'
export class MyButton extends LitElement {
static properties = {
static get properties() {
return {
onClick: { type: Function },
isLoading: { type: Boolean },
label: { type: String },
};
static styles = css`
vaadin-button {
height: 100%;
margin: 0;
cursor: pointer;
min-width: 80px;
background-color: #03a9f4;
color: white;
label: { type: String }
}
}
vaadin-button:hover {
opacity: 0.8;
static get styles() {
return [myButtonStyles]
}
`;
constructor() {
super();
this.onClick = () => {};
this.isLoading = false;
this.label = '';
super()
this.onClick = () => { }
this.isLoading = false
this.label = ''
}
render() {
return html`
<vaadin-button
?disabled="${this.isLoading}"
@click="${this.onClick}"
>
${this.isLoading === false
? html`${this.label}`
: html`<paper-spinner-lite active></paper-spinner-lite>`}
<vaadin-button ?disabled="${this.isLoading}" @click="${this.onClick}">
${this.isLoading === false ? html`${this.label}` : html`<paper-spinner-lite active></paper-spinner-lite>`}
</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 {store} from '../store.js'
import {testApiKey} from '../apiKeyUtils.js'
import {get, translate} from '../../translate'
import '@material/mwc-dialog'
import { store } from '../store'
import { testApiKey } from '../apiKeyUtils'
import { mykeyPageStyles } from '../styles/core-css'
import snackbar from './snackbar'
import '@material/mwc-button'
import '@material/mwc-select'
import '@material/mwc-textfield'
import '@material/mwc-dialog'
import '@material/mwc-icon'
import '@material/mwc-textfield'
import snackbar from './snackbar.js'
// Multi language support
import { get, translate } from '../../translate'
let mykeyDialog
@ -23,34 +23,13 @@ class MykeyPage extends connect(store)(LitElement) {
}
static get styles() {
return css`
* {
--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;
}
`
return [mykeyPageStyles]
}
constructor() {
super()
this.nodeConfig = {}
this.theme = localStorage.getItem('qortalTheme') ? localStorage.getItem('qortalTheme') : 'light';
this.theme = localStorage.getItem('qortalTheme') ? localStorage.getItem('qortalTheme') : 'light'
}
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>
<p style="margin-top: 45px;">${translate("apipage.achange3")}</p>
</div>
<mwc-button
slot="secondaryAction"
dialogAction="close"
class="red"
>
<mwc-button slot="secondaryAction" dialogAction="close" class="red">
${translate("apipage.achange4")}
</mwc-button>
<mwc-button
slot="primaryAction"
@click="${this.addMykey}"
>
<mwc-button slot="primaryAction" @click="${this.addMykey}">
${translate("apipage.achange5")}
</mwc-button>
</mwc-dialog>
`
}
stateChanged(state) {
this.config = state.config
this.nodeConfig = state.app.nodeConfig
firstUpdated() {
// ...
}
show() {
@ -88,35 +59,47 @@ class MykeyPage extends connect(store)(LitElement) {
async addMykey() {
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) {
selectedNode.apiKey = mykeyInput;
this.nodeConfig.knownNodes[this.nodeConfig.node] = selectedNode;
localStorage.setItem('myQortalNodes', JSON.stringify(this.nodeConfig.knownNodes));
selectedNode.apiKey = mykeyInput
this.nodeConfig.knownNodes[this.nodeConfig.node] = selectedNode
localStorage.setItem('myQortalNodes', JSON.stringify(this.nodeConfig.knownNodes))
let snackbar1 = get("apipage.achange6")
snackbar.add({
labelText: `${snackbar1}`,
dismiss: true
})
this.shadowRoot.getElementById('mykeyInput').value = ''
this.shadowRoot.querySelector('#mykeyDialog').close()
} else {
let snackbar2 = get("apipage.achange7")
snackbar.add({
labelText: `${snackbar2}`,
dismiss: true
})
this.shadowRoot.getElementById('mykeyInput').value = ''
this.shadowRoot.querySelector('#mykeyDialog').close()
}
}
stateChanged(state) {
this.config = state.config
this.nodeConfig = state.app.nodeConfig
}
}
window.customElements.define('mykey-page', MykeyPage)
const mykey = document.createElement('mykey-page')
mykeyDialog = document.body.appendChild(mykey)
export default mykeyDialog

View File

@ -1,38 +1,18 @@
// Author: irontiga <irontiga@gmail.com>
'use strict'
import { html, LitElement } from 'lit'
import * as WORDLISTS from './wordlists.js'
import * as WORDLISTS from './wordlists'
class RandomSentenceGenerator extends LitElement {
static get properties() {
return {
template: {
type: String,
attribute: 'template'
},
parsedString: {
type: String
},
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'
}
template: { type: String, attribute: 'template' },
parsedString: { type: String },
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
}
render() {
return html`
${this.parsedString}
`
}
firstUpdated() {
// ...
}
updated(changedProperties) {
let regen = false
if (changedProperties.has('template')) {
regen = true
}
if (changedProperties.has('maxWordLength')) {
console.dir(this.maxWordLength)
if (this.maxWordLength) {
const wl = { ...this._wordlists }
for (const partOfSpeech in this._wordlists) {
console.log(this._wordlists[partOfSpeech])
if (Array.isArray(this._wordlists[partOfSpeech])) {
wl[partOfSpeech] = this._wordlists[partOfSpeech].filter(word => word.length <= this.maxWordLength)
}
}
this._wordlists = wl
}
regen = true
}
if (regen) this.generate()
}
@ -83,13 +80,16 @@ class RandomSentenceGenerator extends LitElement {
if (entropy > 1074) {
throw new Error('Javascript can not handle that much entropy!')
}
let randNum = 0
const crypto = window.crypto || window.msCrypto
if (crypto) {
const entropy256 = Math.ceil(entropy / 8)
let buffer = new Uint8Array(entropy256)
crypto.getRandomValues(buffer)
randNum = buffer.reduce((num, value) => {
@ -97,8 +97,10 @@ class RandomSentenceGenerator extends LitElement {
}, 1) / Math.pow(256, entropy256)
} else {
console.warn('Secure RNG not found. Using Math.random')
randNum = Math.random()
}
return randNum
}
@ -127,7 +129,9 @@ class RandomSentenceGenerator extends LitElement {
parse(template) {
const split = template.split(/[\s]/g)
let entropy = 1
const final = split.map(word => {
const lower = word.toLowerCase()
@ -139,22 +143,20 @@ class RandomSentenceGenerator extends LitElement {
const replacement = this.getWord(partOfSpeech)
word = replacement.word + word.slice(partOfSpeech.length) // Append the rest of the "word" (punctuation)
entropy = entropy * replacement.entropy
return true
}
})
return word
})
this.templateEntropy = Math.floor(Math.log(entropy) / Math.log(8))
return final.join(' ')
}
render() {
return html`
${this.parsedString}
`
}
}
customElements.define('random-sentence-generator', RandomSentenceGenerator)
window.customElements.define('random-sentence-generator', 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 {store} from '../store.js'
import {doAddNode, doEditNode, doLoadNodeConfig, doRemoveNode, doSetNode} from '../redux/app/app-actions.js'
import {get, registerTranslateConfig, translate, use} from '../../translate'
import snackbar from './snackbar.js'
import '../components/language-selector.js'
import '../custom-elements/frag-file-input.js'
import { store } from '../store'
import { doAddNode, doEditNode, doLoadNodeConfig, doRemoveNode, doSetNode } from '../redux/app/app-actions'
import { settingsPageStyles } from '../styles/core-css'
import FileSaver from 'file-saver'
import '@material/mwc-dialog'
import snackbar from './snackbar'
import './frag-file-input'
import '../components/language-selector'
import '@material/mwc-button'
import '@material/mwc-select'
import '@material/mwc-textfield'
import '@material/mwc-dialog'
import '@material/mwc-icon'
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({
loader: (lang) => fetch(`/language/${lang}.json`).then((res) => res.json())
@ -35,132 +37,23 @@ class SettingsPage extends connect(store)(LitElement) {
return {
lastSelected: { type: Number },
nodeConfig: { type: Object },
theme: { type: String, reflect: true },
isBeingEdited: { type: Boolean },
dropdownOpen: { type: Boolean }
dropdownOpen: { type: Boolean },
theme: { type: String, reflect: true }
}
}
static get styles() {
return css`
* {
--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;
}
`
return [settingsPageStyles]
}
constructor() {
super()
this.theme = localStorage.getItem('qortalTheme') ? localStorage.getItem('qortalTheme') : 'light'
this.nodeConfig = {}
this.isBeingEdited = false
this.isBeingEditedIndex = null
this.dropdownOpen = false
this.theme = localStorage.getItem('qortalTheme') ? localStorage.getItem('qortalTheme') : 'light'
}
render() {
@ -176,20 +69,21 @@ class SettingsPage extends connect(store)(LitElement) {
<div class="selected">
<div class="selected-left-side">
<mwc-icon style="margin-right: 10px">link</mwc-icon>
${this.selectedItem ? html
`
${this.selectedItem ?
html `
<div>
<span class="name">${this.selectedItem.name}</span>
<span>${this.selectedItem.protocol + '://' + this.selectedItem.domain + ':' + this.selectedItem.port}</span>
</div>
` : html`${translate('settings.selectnode')}`
` : html`
${translate('settings.selectnode')}
`
}
</div>
<mwc-icon>expand_more</mwc-icon>
</div>
<ul class="${this.dropdownOpen ? 'open' : ''}">
${this.nodeConfig.knownNodes.map(
(n, index) => html`
${this.nodeConfig.knownNodes.map((n, index) => html`
<li @click="${(e) => this.handleSelection(e, n, index)}">
<div class="list-parent">
<div>
@ -200,7 +94,7 @@ class SettingsPage extends connect(store)(LitElement) {
<mwc-button
outlined
@click="${(e) => {
e.stopPropagation();
e.stopPropagation()
const currentValues = this.nodeConfig.knownNodes[index]
const dialog = this.shadowRoot.querySelector('#addNodeDialog')
@ -225,19 +119,15 @@ class SettingsPage extends connect(store)(LitElement) {
</div>
</div>
</li>
`
)}
`)}
</ul>
</div>
<p style="margin-top: 30px; text-align: center;">
${translate('settings.nodehint')}
</p>
<center>
<mwc-button
outlined
@click="${() => this.shadowRoot.querySelector('#addNodeDialog').show()}"
><mwc-icon class="buttongreen">add</mwc-icon
>
<mwc-button outlined @click="${() => this.shadowRoot.querySelector('#addNodeDialog').show()}">
<mwc-icon class="buttongreen">add</mwc-icon>
${translate('settings.addcustomnode')}
</mwc-button>
</center>
@ -261,8 +151,7 @@ class SettingsPage extends connect(store)(LitElement) {
<br />
<center>
<div id="main">
<mwc-icon class="globe">language</mwc-icon
>&nbsp;<language-selector></language-selector>
<mwc-icon class="globe">language</mwc-icon>&nbsp;<language-selector></language-selector>
</div>
</center>
</div>
@ -270,7 +159,6 @@ class SettingsPage extends connect(store)(LitElement) {
${translate('general.close')}
</mwc-button>
</mwc-dialog>
<mwc-dialog id="addNodeDialog">
<div style="text-align: center;">
<h2>${translate('settings.addcustomnode')}</h2>
@ -293,7 +181,6 @@ class SettingsPage extends connect(store)(LitElement) {
${translate('settings.addandsave')}
</mwc-button>
</mwc-dialog>
<mwc-dialog id="importQortalNodesListDialog">
<div style="text-align:center">
<h2>${translate('settings.import')}</h2>
@ -326,6 +213,7 @@ class SettingsPage extends connect(store)(LitElement) {
firstUpdated() {
const checkNode = localStorage.getItem('mySelectedNode')
if (checkNode === null || checkNode.length === 0) {
localStorage.setItem('mySelectedNode', 0)
} else {
@ -364,6 +252,29 @@ class SettingsPage extends connect(store)(LitElement) {
this.dropdownOpen = false
this.requestUpdate()
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)
const selectedNodeIndexOnNewStart = localStorage.getItem('mySelectedNode')
@ -408,10 +319,13 @@ class SettingsPage extends connect(store)(LitElement) {
}
var renewNodes = []
renewNodes.push(obj1, obj2)
localStorage.setItem('myQortalNodes', JSON.stringify(renewNodes))
let snack1string = get('settings.snack1')
snackbar.add({
labelText: `${snack1string}`,
dismiss: true
@ -427,8 +341,8 @@ class SettingsPage extends connect(store)(LitElement) {
const selectedNode = this.nodeConfig.knownNodes[selectedNodeIndex]
const selectedName = `${selectedNode.name}`
const selectedNodeUrl = `${selectedNode.protocol + '://' + selectedNode.domain + ':' + selectedNode.port}`
const index = parseInt(selectedNodeIndex)
if (isNaN(index)) return
store.dispatch(doSetNode(selectedNodeIndex))
@ -462,13 +376,23 @@ class SettingsPage extends connect(store)(LitElement) {
store.dispatch(doAddNode(nodeObject))
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) {
var savedNodes = []
savedNodes.push(nodeObject)
localStorage.setItem('myQortalNodes', JSON.stringify(savedNodes))
let snack3string = get('settings.snack3')
snackbar.add({
labelText: `${snack3string}`,
dismiss: true
@ -478,14 +402,16 @@ class SettingsPage extends connect(store)(LitElement) {
this.shadowRoot.getElementById('protocolList').value = ''
this.shadowRoot.getElementById('domainInput').value = ''
this.shadowRoot.getElementById('portInput').value = ''
this.shadowRoot.querySelector('#addNodeDialog').close()
} else {
var stored = JSON.parse(localStorage.getItem('myQortalNodes'))
stored.push(nodeObject)
localStorage.setItem('myQortalNodes', JSON.stringify(stored))
let snack3string = get('settings.snack3');
let snack3string = get('settings.snack3')
snackbar.add({
labelText: `${snack3string}`,
dismiss: true
@ -495,9 +421,10 @@ class SettingsPage extends connect(store)(LitElement) {
this.shadowRoot.getElementById('protocolList').value = ''
this.shadowRoot.getElementById('domainInput').value = ''
this.shadowRoot.getElementById('portInput').value = ''
this.shadowRoot.querySelector('#addNodeDialog').close()
}
this.handleAddNodeSelection(choosedNode, choosedIndex)
}
}
@ -505,11 +432,15 @@ class SettingsPage extends connect(store)(LitElement) {
event.stopPropagation()
let stored = JSON.parse(localStorage.getItem('myQortalNodes'))
stored.splice(index, 1)
localStorage.setItem('myQortalNodes', JSON.stringify(stored))
store.dispatch(doRemoveNode(index))
let snack6string = get('settings.snack6')
snackbar.add({
labelText: `${snack6string}`,
dismiss: true
@ -530,16 +461,21 @@ class SettingsPage extends connect(store)(LitElement) {
protocol: protocolList,
domain: domainInput,
port: portInput,
enableManagement: true,
enableManagement: true
}
let stored = JSON.parse(localStorage.getItem('myQortalNodes'))
const copyStored = [...stored]
copyStored[index] = nodeObject
localStorage.setItem('myQortalNodes', JSON.stringify(copyStored))
store.dispatch(doEditNode(index, nodeObject))
let snack7string = get('settings.snack7')
snackbar.add({
labelText: `${snack7string}`,
dismiss: true
@ -551,7 +487,6 @@ class SettingsPage extends connect(store)(LitElement) {
this.shadowRoot.getElementById('portInput').value = ''
this.isBeingEdited = false
this.isBeingEditedIndex = null
this.shadowRoot.querySelector('#addNodeDialog').close()
}
}
@ -566,26 +501,25 @@ class SettingsPage extends connect(store)(LitElement) {
renderExportNodesListButton() {
return html`
<mwc-button
dense
unelevated
label="${translate('settings.export')}"
@click="${() => this.exportQortalNodesList()}"
>
</mwc-button>
<mwc-button dense unelevated label="${translate('settings.export')}" @click="${() => this.exportQortalNodesList()}"></mwc-button>
`
}
exportQortalNodesList() {
let nodelist = ''
const qortalNodesList = JSON.stringify(
localStorage.getItem('myQortalNodes')
);
)
const qortalNodesListSave = JSON.parse(qortalNodesList || '[]')
const blob = new Blob([qortalNodesListSave], {
type: 'text/plain;charset=utf-8',
type: 'text/plain;charset=utf-8'
})
nodelist = 'qortal.nodes'
this.saveFileToDisk(blob, nodelist)
}
@ -595,16 +529,20 @@ class SettingsPage extends connect(store)(LitElement) {
suggestedName: fileName,
types: [{
description: 'File'
}],
}]
})
const writeFile = async (fileHandle, contents) => {
const writable = await fileHandle.createWritable()
await writable.write(contents)
await writable.close()
}
writeFile(fileHandle, blob).then(() => console.log('FILE SAVED'))
let snack4string = get('settings.snack4')
snackbar.add({
labelText: `${snack4string} qortal.nodes`,
dismiss: true
@ -613,29 +551,28 @@ class SettingsPage extends connect(store)(LitElement) {
if (error.name === 'AbortError') {
return
}
FileSaver.saveAs(blob, fileName)
}
}
renderImportNodesListButton() {
return html`
<mwc-button
dense
unelevated
label="${translate('settings.import')}"
@click="${() => this.openImportNodesDialog()}"
>
</mwc-button>
<mwc-button dense unelevated label="${translate('settings.import')}" @click="${() => this.openImportNodesDialog()}"></mwc-button>
`
}
async importQortalNodesList(file) {
localStorage.removeItem('myQortalNodes')
const newItems = JSON.parse(file || '[]')
localStorage.setItem('myQortalNodes', JSON.stringify(newItems))
this.shadowRoot.querySelector('#importQortalNodesListDialog').close()
let snack5string = get('settings.snack5')
snackbar.add({
labelText: `${snack5string}`,
dismiss: true,
@ -657,5 +594,4 @@ window.customElements.define('settings-page', SettingsPage)
const settings = document.createElement('settings-page')
settingsDialog = document.body.appendChild(settings)
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 {sideMenuItemStyle} from './side-menu-item-style.js'
import { sideMenuItemStyles } from '../styles/core-css'
import '@vaadin/icon'
import '@vaadin/icons'
import '@polymer/paper-tooltip'
@ -19,9 +19,7 @@ export class SideMenuItem extends LitElement {
}
static get styles() {
return css`
${sideMenuItemStyle}
`
return [sideMenuItemStyles]
}
constructor() {
@ -42,6 +40,7 @@ export class SideMenuItem extends LitElement {
if (!this.hasChildren()) {
return
}
this.collapseExpandIcon = document.createElement("vaadin-icon")
this.collapseExpandIcon.id = "collapse-button"
this.shadowRoot.getElementById("content").appendChild(this.collapseExpandIcon)
@ -67,26 +66,29 @@ export class SideMenuItem extends LitElement {
_tooltipTemplate() {
return html`
${this._getLevel === 0 && this.compact ? html`
${this._getLevel === 0 && this.compact ?
html`
<paper-tooltip for="itemLink" position="right" animation-delay="0">
${this.label}
</paper-tooltip>
`
: undefined}
` : undefined
}
`
}
_childrenTemplate() {
return html`
${this.expanded ? html`
${this.compact ? html`
${this.expanded ?
html`
${this.compact ?
html`
<div id="overlay"><slot></slot></div>
`
: html`
` : html`
<slot></slot>
`}
`
: undefined}
}
` : undefined
}
`
}
@ -110,14 +112,14 @@ export class SideMenuItem extends LitElement {
this._markParentWithSelectedChild()
}
}
});
})
}
_onCompactChanged() {
this.expanded = false;
this.expanded = false
if (this.collapseExpandIcon == null) {
return;
return
}
if (!this.compact) {
@ -129,13 +131,13 @@ export class SideMenuItem extends LitElement {
_onExpandedChanged() {
if (this.collapseExpandIcon == null) {
return;
return
}
if (this.expanded) {
this._onHandleExpanded();
this._onHandleExpanded()
} else {
this._onHandleCollapsed();
this._onHandleCollapsed()
}
}
@ -166,12 +168,14 @@ export class SideMenuItem extends LitElement {
this.selected = true
} else {
this.expanded = !this.expanded
e.preventDefault()
}
}
_outsideClickListener(event) {
const eventPath = event.composedPath()
if (eventPath.indexOf(this) < 0) {
this.expanded = false
}
@ -179,11 +183,13 @@ export class SideMenuItem extends LitElement {
_changeSelectedState(selected, sourceEvent) {
this.selected = selected
let evt = new CustomEvent("side-menu-item-select", {
bubbles: true,
cancelable: true,
detail: { sourceEvent: sourceEvent }
});
})
this.dispatchEvent(evt)
}
@ -195,19 +201,21 @@ export class SideMenuItem extends LitElement {
let element = this.parentElement;
while (element instanceof SideMenuItem) {
element.setAttribute('hasSelectedChild', true)
element = element.parentElement;
element = element.parentElement
}
}
get _getLevel() {
let level = 0
let element = this.parentElement
while (element instanceof SideMenuItem) {
level++;
level++
element = element.parentElement
}
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 {
static get properties() {
@ -10,21 +11,7 @@ class SideMenu extends LitElement {
}
static get styles() {
return css`
nav {
padding: 0;
}
:host {
list-style: none;
width: 100%;
position: relative;
}
:host([compact]) {
width: auto;
}
`
return [sideMenuStyles]
}
constructor() {
@ -56,23 +43,26 @@ class SideMenu extends LitElement {
if (this.compact) {
element.expanded = false
}
element.selected = false
element.hasChildren() ? element.removeAttribute('hasSelectedChild') : undefined
});
})
}
updated(changedProperties) {
changedProperties.forEach((oldValue, propName) => {
if (propName === "compact") {
this.items.forEach(item => (item.compact = this.compact))
let evt = new CustomEvent("side-menu-compact-change", {
bubbles: true,
cancelable: true
})
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'
let queueElement
@ -6,34 +6,20 @@ let queueElement
class SnackQueue extends LitElement {
static get properties() {
return {
busy: {
type: Boolean,
attribute: 'busy',
reflectToAttribute: true
},
currentSnack: {
type: Object,
attribute: 'current-snack',
reflectToAttribute: true
},
_queue: {
type: Array
},
busy: { type: Boolean, attribute: 'busy', reflectToAttribute: true },
currentSnack: { type: Object, attribute: 'current-snack', reflectToAttribute: true },
_queue: { type: Array },
_labelText: { type: String },
_stacked: { type: Boolean },
_leading: { type: Boolean },
_closeOnEscape: { type: Boolean },
_timeoutMs: { type: Number },
action: {},
_dismiss: {},
_dismissIcon: { type: String }
_dismissIcon: { type: String },
action: {}
}
}
static get styles() {
return css``
}
constructor() {
super()
this._queue = []
@ -45,9 +31,11 @@ class SnackQueue extends LitElement {
return html`
<mwc-snackbar id="snack" labelText="${this._labelText}" ?stacked=${this._stacked} ?leading=${this._leading} ?closeOnEscape=${this._closeOnEscape} timeoutMs=${this._timeoutMs}>
${this._action}
${this._dismiss ? html`
${this._dismiss ?
html`
<mwc-icon-button icon="${this._dismissIcon}" slot="dismiss"></mwc-icon-button>
` : ''}
` : ''
}
</mwc-snackbar>
`
}

View File

@ -1,5 +1,3 @@
// Sourced from https://gist.github.com/letsgetrandy/1e05a68ea74ba6736eb5
export const EXCEPTIONS = {
'are': 'were',
'eat': 'ate',
@ -16,21 +14,27 @@ export const getPastTense = (verb, exceptions = EXCEPTIONS) => {
if (exceptions[verb]) {
return exceptions[verb]
}
if ((/e$/i).test(verb)) {
return verb + 'd'
}
if ((/[aeiou]c$/i).test(verb)) {
return verb + 'ked'
}
// for american english only
if ((/el$/i).test(verb)) {
return verb + 'ed'
}
if ((/[aeio][aeiou][dlmnprst]$/).test(verb)) {
return verb + 'ed'
}
if ((/[aeiou][bdglmnprst]$/i).test(verb)) {
return verb.replace(/(.+[aeiou])([bdglmnprst])/, '$1$2$2ed')
}
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 {doLoadConfigFromAPI} from './redux/config/config-actions.js'
import {doInitWorkers, doLoadNodeConfig} from './redux/app/app-actions.js'
import {doLoadNotificationConfig} from './redux/user/user-actions.js'
import './persistState.js'
import { store } from './store'
import { doLoadConfigFromAPI } from './redux/config/config-actions'
import { doInitWorkers, doLoadNodeConfig } from './redux/app/app-actions'
import { doLoadNotificationConfig } from './redux/user/user-actions'
import { initApi } from 'qortal-ui-crypto'
import './persistState'
initApi(store)
@ -20,15 +18,15 @@ const workerInitChecker = () => {
store.dispatch(doLoadNodeConfig())
if (state.app.workers.ready) {
workerInitSubscription()
} else {
if (!state.app.workers.loading) store.dispatch(doInitWorkers(state.config.crypto.kdfThreads, state.config.user.constants.workerURL))
}
}
}
workerInitChecker()
const workerInitSubscription = store.subscribe(workerInitChecker)
if (!store.getState().config.loaded) {

View File

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

View File

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

View File

@ -1,23 +1,24 @@
import config from './config'
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 config from './config'
import snackbar from '../functional-components/snackbar'
let initial = 0
let _state
const notificationCheck = function () {
if (window.Notification && Notification.permission === 'granted') {
// ...
return true
} else if (window.Notification && Notification.permission !== 'denied') {
Notification.requestPermission().then(permission => {
if (permission === 'granted') {
dispatcher(_state)
_state = ''
return true
} else {
initial = initial + 1
snackbar.add({
labelText: 'Notification is disabled, Enable it to recieve notifications.',
dismiss: true
@ -41,21 +42,21 @@ const notificationCheck = function () {
* @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 }
*/
export const doNewMessage = function (req) {
const newMessage = () => {
let data
if (req.type && req.type === 'qapp') {
data = req
} else if (req.groupId) {
const title = `${req.groupName}`
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 }
} else {
const title = `${req.senderName === undefined ? req.sender : req.senderName}`
const body = 'New Message'
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 {
dispatcher(notificationState)
}
} else {
_state = notificationState
}
}
const page = window.top.location.href
if (req.type && req.type === 'qapp-local-notification') {
try {
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'
export const dispatcher = function (notificationState) {
switch (notificationState.type) {
case NEW_MESSAGE:
return newMessage(notificationState.data)
case NEW_MESSAGE_NOTIFICATION_QAPP:
return newMessageNotificationQapp(notificationState.data)
case NEW_MESSAGE_NOTIFICATION_QAPP_LOCAL:
return newMessageNotificationQappLocal(notificationState.data)
default:
}
}

View File

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

View File

@ -1,20 +1,17 @@
import {store} from './store.js'
import {saveStateToLocalStorage} from './localStorageHelpers.js'
const keys = [
'config',
'user'
]
import { store } from './store'
import { saveStateToLocalStorage } from './localStorageHelpers'
const keys = ['config', 'user']
const oldReducers = {}
const oldState = store.getState()
for (const key of keys) {
oldReducers[key] = oldState[key]
}
store.subscribe(() => {
const newState = store.getState()
keys.forEach(key => {
if (newState[key] !== oldState[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 => {
Object.entries(routes).forEach(([route, handler]) => {

View File

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

View File

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

View File

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

View File

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

View File

@ -1,4 +1,4 @@
import {store} from '../store.js'
import { store } from '../store'
import { EpmlStream } from 'epml'
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 coinBalancesActionStream = new EpmlStream(COIN_BALANCES_ACTION, () => store.getState().app.coinBalances)
let oldState = {
app: {}
}
@ -57,6 +54,7 @@ store.subscribe(() => {
textColor: state.app.selectedAddress.textColor
})
}
if (oldState.app.chatHeads !== state.app.chatHeads) {
chatHeadsStateStream.emit(state.app.chatHeads)
}
@ -74,6 +72,5 @@ store.subscribe(() => {
coinBalancesActionStream.emit(state.app.coinBalances)
}
oldState = state
})

View File

@ -25,7 +25,7 @@ import {
REMOVE_QAPP_FRIENDS_LIST,
ALLOW_SHOW_SYNC_INDICATOR,
REMOVE_SHOW_SYNC_INDICATOR
} from '../app-action-types.js'
} from '../app-action-types'
export const doUpdateBlockInfo = (blockObj) => {
return (dispatch, getState) => {
@ -119,6 +119,7 @@ export const removeQAPPAutoAuth = (payload) => {
payload
}
}
export const allowQAPPAutoLists = (payload) => {
return {
type: ALLOW_QAPP_AUTO_LISTS,
@ -153,6 +154,7 @@ export const setChatLastSeen = (payload) => {
payload
}
}
export const addChatLastSeen = (payload) => {
return {
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 {INIT_WORKERS} from '../app-action-types.js'
import { INIT_WORKERS } from '../app-action-types'
Epml.registerPlugin(EpmlWorkerPlugin)
export const doInitWorkers = (numberOfWorkers, workerURL) => {
const workers = []
return (dispatch, getState) => {
dispatch(initWorkers())
try {
for (let i = 0; i < numberOfWorkers; i++) {
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))
})
} catch (e) {
@ -22,6 +23,7 @@ export const doInitWorkers = (numberOfWorkers, workerURL) => {
}
}
}
const initWorkers = (status, payload) => {
return {
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 => {
return (dispatch, getState) => {

View File

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

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