4
1
mirror of https://github.com/Qortal/qortal-ui.git synced 2025-01-27 13:22:14 +00:00

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'),
}
...config.build,
options: {
...config.build.options,
outputDir: path.join(__dirname, '/builtWWW'),
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");
buildDefalutPlugins()
})
build(buildConfig.options, buildConfig.outputs, buildConfig.outputOptions, buildConfig.inputOptions, inlineConfigs).then(() => {
console.log("Building and Bundling Plugins")
buildDefalutPlugins()
})

View File

@ -2,13 +2,13 @@ const path = require('path')
const defaultConfig = require('./default.config.js')
const build = {
options: {
outputDir: path.join(__dirname, '../build'),
imgDir: path.join(__dirname, '../img')
},
aliases: {
'qortal-ui-crypto': path.join(__dirname, '../crypto/api.js')
}
options: {
outputDir: path.join(__dirname, '../build'),
imgDir: path.join(__dirname, '../img')
},
aliases: {
'qortal-ui-crypto': path.join(__dirname, '../crypto/api.js')
}
}
module.exports = build
module.exports = build

View File

@ -1,8 +1,8 @@
const defaultConfig = require('./default.config.js')
module.exports = {
name: 'Qortal',
symbol: 'Qort',
addressVersion: 58, // Q for Qortal
logo: '/img/QORT_LOGO.svg'
}
name: 'Qortal',
symbol: 'Qort',
addressVersion: 58, // Q for Qortal
logo: '/img/QORT_LOGO.svg'
}

View File

@ -1,27 +1,33 @@
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 (typeof newObj[key] === 'object') {
storeObj[key] = checkKeys(storeObj[key], newObj[key])
} else {
storeObj[key] = newObj[key]
}
}
return storeObj
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 (typeof newObj[key] === 'object') {
storeObj[key] = checkKeys(storeObj[key], newObj[key])
} else {
storeObj[key] = newObj[key]
}
}
return storeObj
}
const getConfig = customConfig => {
config = checkKeys(config, customConfig)
return config
config = checkKeys(config, customConfig)
return config
}
module.exports = getConfig(userConfig)
module.exports = getConfig(userConfig)

View File

@ -1,3 +1,3 @@
const defaultConfig = require('./default.config.js')
module.exports = {}
module.exports = {}

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
module.exports = defaultConfig

View File

@ -1 +1 @@
module.exports = {}
module.exports = {}

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

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

View File

@ -4,132 +4,132 @@ const { makeSourceAbsolute } = require('../tooling/utils.js')
const srcDir = '../src'
const options = {
inputFile: path.join(__dirname, '../src/main.js'),
outputDir: path.join(__dirname, '../build'),
sassOutputDir: path.join(__dirname, '../build/styles.bundle.css'),
imgDir: path.join(__dirname, '../img')
inputFile: path.join(__dirname, '../src/main.js'),
outputDir: path.join(__dirname, '../build'),
sassOutputDir: path.join(__dirname, '../build/styles.bundle.css'),
imgDir: path.join(__dirname, '../img')
}
const aliases = {
'qortal-ui-crypto': '../../crypto/api.js'
'qortal-ui-crypto': '../../crypto/api.js'
}
const apiComponents = {
api: {
file: '../../crypto/api.js',
className: 'api'
}
api: {
file: '../../crypto/api.js',
className: 'api'
}
}
const functionalComponents = {
'loading-ripple': {
file: 'functional-components/loading-ripple.js',
className: 'LoadingRipple'
},
'confirm-transaction-dialog': {
file: 'functional-components/confirm-transaction-dialog',
className: 'ConfirmTransactionDialog'
}
'loading-ripple': {
file: 'functional-components/loading-ripple.js',
className: 'LoadingRipple'
},
'confirm-transaction-dialog': {
file: 'functional-components/confirm-transaction-dialog',
className: 'ConfirmTransactionDialog'
}
}
const inlineComponents = [
{
className: 'worker',
input: path.join(__dirname, srcDir, 'worker.js'),
output: 'worker.js'
},
{
className: 'PluginMainJSLoader',
input: path.join(__dirname, srcDir, '/plugins/plugin-mainjs-loader.js'),
output: 'plugins/plugin-mainjs-loader.js'
}
{
className: 'worker',
input: path.join(__dirname, srcDir, 'worker.js'),
output: 'worker.js'
},
{
className: 'PluginMainJSLoader',
input: path.join(__dirname, srcDir, '/plugins/plugin-mainjs-loader.js'),
output: 'plugins/plugin-mainjs-loader.js'
}
]
const elementComponents = {
'main-app': {
file: 'components/main-app.js',
className: 'MainApp',
children: {
'app-styles': {
file: 'styles/app-styles.js',
className: 'AppStyles',
children: {
'app-theme': {
className: 'AppTheme',
file: 'styles/app-theme.js'
}
}
},
'app-view': {
file: 'components/app-view.js',
className: 'AppView',
children: {
'show-plugin': {
file: 'components/show-plugin.js',
className: 'ShowPlugin'
},
'wallet-profile': {
file: 'components/wallet-profile.js',
className: 'WalletProfile'
},
'app-info': {
file: 'components/app-info.js',
className: 'AppInfo'
}
}
},
'login-view': {
file: 'components/login-view/login-view.js',
className: 'LoginView',
children: {
'create-account-section': {
file: 'components/login-view/create-account-section.js',
className: 'CreateAccountSection'
},
'login-section': {
file: 'components/login-view/login-section.js',
className: 'LoginSection'
}
}
},
'settings-view': {
file: 'components/settings-view/user-settings.js',
className: 'UserSettings',
children: {
'account-view': {
file: 'components/settings-view/account-view.js',
className: 'AccountView'
},
'security-view': {
file: 'components/settings-view/security-view.js',
className: 'SecurityView'
},
'qr-login-view': {
file: 'components/settings-view/qr-login-view.js',
className: 'QRLoginView'
},
'notifications-view': {
file: 'components/settings-view/notifications-view.js',
className: 'NotificationsView'
}
}
},
'user-info-view': {
file: 'components/user-info-view/user-info-view.js',
className: 'UserInfoView'
}
}
}
'main-app': {
file: 'components/main-app.js',
className: 'MainApp',
children: {
'app-styles': {
file: 'styles/app-styles.js',
className: 'AppStyles',
children: {
'app-theme': {
className: 'AppTheme',
file: 'styles/app-theme.js'
}
}
},
'app-view': {
file: 'components/app-view.js',
className: 'AppView',
children: {
'show-plugin': {
file: 'components/show-plugin.js',
className: 'ShowPlugin'
},
'wallet-profile': {
file: 'components/wallet-profile.js',
className: 'WalletProfile'
},
'app-info': {
file: 'components/app-info.js',
className: 'AppInfo'
}
}
},
'login-view': {
file: 'components/login-view/login-view.js',
className: 'LoginView',
children: {
'create-account-section': {
file: 'components/login-view/create-account-section.js',
className: 'CreateAccountSection'
},
'login-section': {
file: 'components/login-view/login-section.js',
className: 'LoginSection'
}
}
},
'settings-view': {
file: 'components/settings-view/user-settings.js',
className: 'UserSettings',
children: {
'account-view': {
file: 'components/settings-view/account-view.js',
className: 'AccountView'
},
'security-view': {
file: 'components/settings-view/security-view.js',
className: 'SecurityView'
},
'qr-login-view': {
file: 'components/settings-view/qr-login-view.js',
className: 'QRLoginView'
},
'notifications-view': {
file: 'components/settings-view/notifications-view.js',
className: 'NotificationsView'
}
}
},
'user-info-view': {
file: 'components/user-info-view/user-info-view.js',
className: 'UserInfoView'
}
}
}
}
makeSourceAbsolute(path.join(__dirname, srcDir), elementComponents)
makeSourceAbsolute(path.join(__dirname, srcDir), functionalComponents)
module.exports = {
options,
elementComponents,
functionalComponents,
inlineComponents,
apiComponents,
aliases
}
options,
elementComponents,
functionalComponents,
inlineComponents,
apiComponents,
aliases
}

View File

@ -1,11 +1,11 @@
const coin = {
name: 'Qortal',
symbol: 'QORT',
addressCount: 1,
addressVersion: 58,
decimals: 100000000,
logo: '/img/QORT_LOGO.png',
icon: '/img/QORT_LOGO.png'
name: 'Qortal',
symbol: 'QORT',
addressCount: 1,
addressVersion: 58,
decimals: 100000000,
logo: '/img/QORT_LOGO.png',
icon: '/img/QORT_LOGO.png'
}
module.exports = coin
module.exports = coin

View File

@ -1,11 +1,11 @@
const crypto = {
kdfThreads: 16,
staticSalt: '4ghkVQExoneGqZqHTMMhhFfxXsVg2A75QeS1HCM5KAih', // Base58 encoded
bcryptRounds: 11, // Note it's kinda bcryptRounds * log.2.16, cause it runs on all 16 threads
bcryptVersion: '2a',
get staticBcryptSalt () {
return `$${this.bcryptVersion}$${this.bcryptRounds}$IxVE941tXVUD4cW0TNVm.O`
}
kdfThreads: 16,
staticSalt: '4ghkVQExoneGqZqHTMMhhFfxXsVg2A75QeS1HCM5KAih', // Base58 encoded
bcryptRounds: 11, // Note it's kinda bcryptRounds * log.2.16, cause it runs on all 16 threads
bcryptVersion: '2a',
get staticBcryptSalt() {
return `$${this.bcryptVersion}$${this.bcryptRounds}$IxVE941tXVUD4cW0TNVm.O`
}
}
module.exports = crypto
module.exports = crypto

View File

@ -1,40 +1,41 @@
const styles = {
breakpoints: {
desktop: '',
laptop: '',
tablet: '',
mobile: ''
},
theme: {
colors: {
primary: '#03a9f4', /* Sets the text color to the theme primary color. */
primaryBg: '#e8eaf6', /* Sets the background color to the theme primary color. */
onPrimary: '#fff', /* Sets the text color to the color configured for text on the primary color. */
breakpoints: {
desktop: '',
laptop: '',
tablet: '',
mobile: ''
},
theme: {
colors: {
primary: '#03a9f4', /* Sets the text color to the theme primary color. */
primaryBg: '#e8eaf6', /* Sets the background color to the theme primary color. */
onPrimary: '#fff', /* Sets the text color to the color configured for text on the primary color. */
secondary: '#03a9f4', /* Sets the text color to the theme secondary color. */
secondaryBg: '#fce4ec', /* Sets the background color to the theme secondary color. */
onSecondary: '#fff', /* Sets the text color to the color configured for text on the secondary color. */
secondary: '#03a9f4', /* Sets the text color to the theme secondary color. */
secondaryBg: '#fce4ec', /* Sets the background color to the theme secondary color. */
onSecondary: '#fff', /* Sets the text color to the color configured for text on the secondary color. */
surface: '#fff', /* Sets the background color to the surface background color. */
onSurface: '#333', /* Sets the text color to the color configured for text on the surface color. */
background: '#eee', /* Sets the background color to the theme background color. */
surface: '#fff', /* Sets the background color to the surface background color. */
onSurface: '#333', /* Sets the text color to the color configured for text on the surface color. */
background: '#eee', /* Sets the background color to the theme background color. */
warning: '#FFA000',
error: '#F44336'
},
warning: '#FFA000',
error: '#F44336'
},
addressColors: [
'#256480',
'#002530',
'#02564e',
'#d32f2f',
'#795548',
'#004d40',
'#006064',
'#9c27b0',
'#2196f3',
'#d81b60'
]
}
addressColors: [
'#256480',
'#002530',
'#02564e',
'#d32f2f',
'#795548',
'#004d40',
'#006064',
'#9c27b0',
'#2196f3',
'#d81b60'
]
}
}
module.exports = styles
module.exports = styles

View File

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

View File

@ -1,21 +1,22 @@
let config = require('./config.js')
const checkKeys = (storeObj, newObj) => {
for (const key in newObj) {
if (!Object.prototype.hasOwnProperty.call(storeObj, key)) return
for (const key in newObj) {
if (!Object.prototype.hasOwnProperty.call(storeObj, key)) return
if (typeof newObj[key] === 'object') {
storeObj[key] = checkKeys(storeObj[key], newObj[key])
} else {
storeObj[key] = newObj[key]
}
}
return storeObj
if (typeof newObj[key] === 'object') {
storeObj[key] = checkKeys(storeObj[key], newObj[key])
} else {
storeObj[key] = newObj[key]
}
}
return storeObj
}
const getConfig = customConfig => {
config = checkKeys(config, customConfig)
return config
config = checkKeys(config, customConfig)
return config
}
module.exports = getConfig
module.exports = getConfig

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,139 +1,121 @@
<!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">
<link rel="apple-touch-icon" sizes="76x76" href="/img/favicon/apple-icon-76x76.png">
<link rel="apple-touch-icon" sizes="114x114" href="/img/favicon/apple-icon-114x114.png">
<link rel="apple-touch-icon" sizes="120x120" href="/img/favicon/apple-icon-120x120.png">
<link rel="apple-touch-icon" sizes="144x144" href="/img/favicon/apple-icon-144x144.png">
<link rel="apple-touch-icon" sizes="152x152" href="/img/favicon/apple-icon-152x152.png">
<link rel="apple-touch-icon" sizes="180x180" href="/img/favicon/apple-icon-180x180.png">
<link rel="icon" type="image/png" sizes="192x192" href="/img/favicon/android-icon-192x192.png">
<link rel="icon" type="image/png" sizes="32x32" href="/img/favicon/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="96x96" href="/img/favicon/favicon-96x96.png">
<link rel="icon" type="image/png" sizes="16x16" href="/img/favicon/favicon-16x16.png">
<link rel="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;
padding: 0;
background: var(--plugback);
overflow: hidden;
}
</style>
<link rel="stylesheet" href="/build/styles.bundle.css">
<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') {
newtheme = 'dark';
} else {
newtheme = 'light';
}
document.querySelector('html').setAttribute('theme', newtheme);
const memory = new WebAssembly.Memory({ initial: 256, maximum: 256 });
const heap = new Uint8Array(memory.buffer);
const sbrk = function (size, heap) {
let brk = 512 * 1024; // stack top
let old = brk;
brk += size;
if (brk > heap.length)
throw new Error("heap exhausted");
return old;
};
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);
});
}
const path = window.parent.location.origin + '/memory-pow/memory-pow.wasm.full'
loadWebAssembly(path)
.then(wasmModule => {
window.sbrk = sbrk
window.memory = memory
window.heap = heap
window.powInstance = wasmModule.instance;
window.computePow = wasmModule.exports.compute2;
});
</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>
<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">
<link rel="apple-touch-icon" sizes="76x76" href="/img/favicon/apple-icon-76x76.png">
<link rel="apple-touch-icon" sizes="114x114" href="/img/favicon/apple-icon-114x114.png">
<link rel="apple-touch-icon" sizes="120x120" href="/img/favicon/apple-icon-120x120.png">
<link rel="apple-touch-icon" sizes="144x144" href="/img/favicon/apple-icon-144x144.png">
<link rel="apple-touch-icon" sizes="152x152" href="/img/favicon/apple-icon-152x152.png">
<link rel="apple-touch-icon" sizes="180x180" href="/img/favicon/apple-icon-180x180.png">
<link rel="icon" type="image/png" sizes="192x192" href="/img/favicon/android-icon-192x192.png">
<link rel="icon" type="image/png" sizes="32x32" href="/img/favicon/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="96x96" href="/img/favicon/favicon-96x96.png">
<link rel="icon" type="image/png" sizes="16x16" href="/img/favicon/favicon-16x16.png">
<link rel="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;
padding: 0;
background: var(--plugback);
overflow: hidden;
}
</style>
<link rel="stylesheet" href="/build/styles.bundle.css">
<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') {
newtheme = 'dark';
} else {
newtheme = 'light';
}
document.querySelector('html').setAttribute('theme', newtheme);
const memory = new WebAssembly.Memory({ initial: 256, maximum: 256 });
const heap = new Uint8Array(memory.buffer);
const sbrk = function (size, heap) {
let brk = 512 * 1024; // stack top
let old = brk;
brk += size;
if (brk > heap.length)
throw new Error("heap exhausted");
return old;
};
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);
});
}
const path = window.parent.location.origin + '/memory-pow/memory-pow.wasm.full'
loadWebAssembly(path)
.then(wasmModule => {
window.sbrk = sbrk
window.memory = memory
window.heap = heap
window.powInstance = wasmModule.instance;
window.computePow = wasmModule.exports.compute2;
});
</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

@ -3,32 +3,33 @@ const Hapi = require('@hapi/hapi')
const Inert = require('@hapi/inert')
function serverFactory(routes, address, port, tls) {
this.server = new Hapi.Server({
routes: {
files: {
relativeTo: Path.join(__dirname, '../')
}
},
address: address,
port: port,
tls: tls
})
this.server = new Hapi.Server({
routes: {
files: {
relativeTo: Path.join(__dirname, '../')
}
},
address: address,
port: port,
tls: tls
})
this.startServer = async () => {
try {
await this.server.register([Inert])
this.startServer = async () => {
try {
await this.server.register([Inert])
this.server.route(routes)
this.server.route(routes)
await this.server.start()
await this.server.start()
delete this.startServer
return this.server
} catch (e) {
console.error(e)
throw e
}
}
delete this.startServer
return this.server
} catch (e) {
console.error(e)
throw e
}
}
}
module.exports = serverFactory
module.exports = serverFactory

View File

@ -1,106 +1,106 @@
const path = require('path')
const routesOptions = {
security: {
hsts: {
maxAge: 15768000,
includeSubDomains: true,
preload: true
},
xframe: 'sameorigin'
}
security: {
hsts: {
maxAge: 15768000,
includeSubDomains: true,
preload: true
},
xframe: 'sameorigin'
}
}
const createRoutes = config => [
{
method: 'GET',
path: '/img/{param*}',
handler: {
directory: {
path: config.build.options.imgDir,
redirectToSlash: true,
index: true
}
},
options: routesOptions
},
{
method: 'GET',
path: '/language/{param*}',
handler: {
directory: {
path: path.join(__dirname, '../../language'),
redirectToSlash: true,
index: true
}
},
options: routesOptions
},
{
method: 'GET',
path: '/font/{param*}',
handler: {
directory: {
path: path.join(__dirname, '../../font'),
redirectToSlash: true,
index: true
}
},
options: routesOptions
},
{
method: 'GET',
path: '/sound/{param*}',
handler: {
directory: {
path: path.join(__dirname, '../../sound/'),
redirectToSlash: true,
index: true
}
},
options: routesOptions
},
{
method: 'GET',
path: '/emoji/{param*}',
handler: {
directory: {
path: path.join(__dirname, '../../emoji/'),
redirectToSlash: true,
index: true
}
},
options: routesOptions
},
{
method: 'GET',
path: '/memory-pow/{param*}',
handler: {
directory: {
path: path.join(__dirname, '../../memory-pow/'),
redirectToSlash: true,
index: true
}
},
options: routesOptions
},
{
method: 'GET',
path: '/getConfig',
handler: (request, h) => {
const response = {
config: {
...config
}
}
{
method: 'GET',
path: '/img/{param*}',
handler: {
directory: {
path: config.build.options.imgDir,
redirectToSlash: true,
index: true
}
},
options: routesOptions
},
{
method: 'GET',
path: '/language/{param*}',
handler: {
directory: {
path: path.join(__dirname, '../../language'),
redirectToSlash: true,
index: true
}
},
options: routesOptions
},
{
method: 'GET',
path: '/font/{param*}',
handler: {
directory: {
path: path.join(__dirname, '../../font'),
redirectToSlash: true,
index: true
}
},
options: routesOptions
},
{
method: 'GET',
path: '/sound/{param*}',
handler: {
directory: {
path: path.join(__dirname, '../../sound/'),
redirectToSlash: true,
index: true
}
},
options: routesOptions
},
{
method: 'GET',
path: '/emoji/{param*}',
handler: {
directory: {
path: path.join(__dirname, '../../emoji/'),
redirectToSlash: true,
index: true
}
},
options: routesOptions
},
{
method: 'GET',
path: '/memory-pow/{param*}',
handler: {
directory: {
path: path.join(__dirname, '../../memory-pow/'),
redirectToSlash: true,
index: true
}
},
options: routesOptions
},
{
method: 'GET',
path: '/getConfig',
handler: (request, h) => {
const response = {
config: {
...config
}
}
delete response.config.user.tls
delete response.config.build
delete response.config.user.tls
delete response.config.build
return JSON.stringify(response)
},
options: routesOptions
}
return JSON.stringify(response)
},
options: routesOptions
}
]
module.exports = createRoutes
module.exports = createRoutes

View File

@ -1,141 +1,140 @@
const path = require('path')
const createCommonRoutes = require('./createCommonRoutes.js')
const createPrimaryRoutes = (config, plugins) => {
const routes = createCommonRoutes(config)
const routes = createCommonRoutes(config)
let myPlugins = plugins
let myPlugins = plugins
const pluginFolders = {}
const pluginFolders = {}
const routesOptions = {
security: {
hsts: {
maxAge: 15768000,
includeSubDomains: true,
preload: true
},
xframe: 'sameorigin'
}
}
const routesOptions = {
security: {
hsts: {
maxAge: 15768000,
includeSubDomains: true,
preload: true
},
xframe: 'sameorigin'
}
}
plugins.reduce((obj, plugin) => {
obj[plugin.name] = plugin.folder
return obj
}, pluginFolders)
plugins.reduce((obj, plugin) => {
obj[plugin.name] = plugin.folder
return obj
}, pluginFolders)
routes.push(
{
method: 'GET',
path: '/',
handler: (request, reply) => {
return reply.redirect('/app')
},
options: routesOptions
},
{
method: 'GET',
path: '/{path*}',
handler: (request, h) => {
const filePath = path.join(__dirname, '../../public/index.html')
const response = h.file(filePath, {
confine: true
})
response.header('Access-Control-Allow-Origin', request.info.host)
return response
},
options: routesOptions
},
{
method: 'GET',
path: '/getPlugins',
handler: (request, h) => {
return { plugins: myPlugins.map(p => p.name) }
},
options: routesOptions
},
{
method: 'GET',
path: '/build/{param*}',
handler: {
directory: {
path: config.build.options.outputDir,
redirectToSlash: true,
index: true
}
},
options: routesOptions
},
{
method: 'GET',
path: '/src/{param*}',
handler: {
directory: {
path: path.join(__dirname, '../../src'),
redirectToSlash: true,
index: true
}
},
options: routesOptions
},
{
method: 'GET',
path: '/plugin/{path*}',
handler: (request, h) => {
routes.push(
{
method: 'GET',
path: '/',
handler: (request, reply) => {
return reply.redirect('/app')
},
options: routesOptions
},
{
method: 'GET',
path: '/{path*}',
handler: (request, h) => {
const filePath = path.join(__dirname, '../../public/index.html')
const response = h.file(filePath, {
confine: true
})
response.header('Access-Control-Allow-Origin', request.info.host)
return response
},
options: routesOptions
},
{
method: 'GET',
path: '/getPlugins',
handler: (request, h) => {
return { plugins: myPlugins.map(p => p.name) }
},
options: routesOptions
},
{
method: 'GET',
path: '/build/{param*}',
handler: {
directory: {
path: config.build.options.outputDir,
redirectToSlash: true,
index: true
}
},
options: routesOptions
},
{
method: 'GET',
path: '/src/{param*}',
handler: {
directory: {
path: path.join(__dirname, '../../src'),
redirectToSlash: true,
index: true
}
},
options: routesOptions
},
{
method: 'GET',
path: '/plugin/{path*}',
handler: (request, h) => {
const plugin = request.params.path.split('/')[0]
const filePath = path.join(pluginFolders[plugin], '../', request.params.path)
const plugin = request.params.path.split('/')[0]
const filePath = path.join(pluginFolders[plugin], '../', request.params.path)
const response = h.file(filePath, {
confine: false
})
response.header('Access-Control-Allow-Origin', request.info.host)
return response
},
options: routesOptions
},
{
method: 'GET',
path: '/plugin/404',
handler: (request, h) => {
const response = h.file(path.join(config.server.primary.page404))
response.header('Access-Control-Allow-Origin', request.info.host)
return response
},
options: routesOptions
},
{
method: 'GET',
path: '/qortal-components/plugin-mainjs-loader.html',
handler: (request, h) => {
const response = h.file(path.join(__dirname, '../../src/plugins/plugin-mainjs-loader.html'), {
confine: false
})
response.header('Access-Control-Allow-Origin', request.info.host)
return response
},
options: routesOptions
},
{
method: 'GET',
path: '/qortal-components/plugin-mainjs-loader.js',
handler: (request, h) => {
const file = path.join(config.build.options.outputDir, '/plugins/plugin-mainjs-loader.js')
const response = h.file(filePath, {
confine: false
})
response.header('Access-Control-Allow-Origin', request.info.host)
return response
},
options: routesOptions
},
{
method: 'GET',
path: '/plugin/404',
handler: (request, h) => {
const response = h.file(path.join(config.server.primary.page404))
response.header('Access-Control-Allow-Origin', request.info.host)
return response
},
options: routesOptions
},
{
method: 'GET',
path: '/qortal-components/plugin-mainjs-loader.html',
handler: (request, h) => {
const response = h.file(path.join(__dirname, '../../src/plugins/plugin-mainjs-loader.html'), {
confine: false
})
response.header('Access-Control-Allow-Origin', request.info.host)
return response
},
options: routesOptions
},
{
method: 'GET',
path: '/qortal-components/plugin-mainjs-loader.js',
handler: (request, h) => {
const file = path.join(config.build.options.outputDir, '/plugins/plugin-mainjs-loader.js')
const response = h.file(file, {
confine: false
})
response.header('Access-Control-Allow-Origin', request.info.host)
return response
},
options: routesOptions
},
const response = h.file(file, {
confine: false
})
response.header('Access-Control-Allow-Origin', request.info.host)
return response
},
options: routesOptions
}
)
)
return routes
return routes
}
module.exports = createPrimaryRoutes
module.exports = createPrimaryRoutes

View File

@ -3,22 +3,20 @@ const ServerFactory = require('./ServerFactory.js')
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 => {
console.log(`Qortal UI Server started at ${server.info.uri} and listening on ${server.info.address}`)
})
.catch(e => {
console.error(e)
})
}
return this
}
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 => {
console.log(`Qortal UI Server started at ${server.info.uri} and listening on ${server.info.address}`)
}).catch(e => {
console.error(e)
})
}
return this
}
const serverExports = {
createServer
createServer
}
module.exports = serverExports
module.exports = serverExports

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'
// Attempt to generate an API key
const generateUrl = "/admin/apikey/generate";
let generateRes = await api.request(generateUrl, {
method: "POST"
});
let generateRes = await api.request(generateUrl, {
method: 'POST'
})
if (generateRes != null && generateRes.error == null && generateRes.length >= 8) {
console.log("Generated API key");
apiKey = generateRes;
if (generateRes != null && generateRes.error == null && generateRes.length >= 8) {
console.log('Generated API key')
// 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");
}
apiKey = generateRes
// Now test the API key
let testResult = await testApiKey(apiKey);
if (testResult === true) {
console.log("API key test passed");
}
else {
console.log("API key test failed");
mykey.show();
this.dispatchEvent(
// 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")
}
// Now test the API key
let testResult = await testApiKey(apiKey)
if (testResult === true) {
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
}),
);
}
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,147 +1,107 @@
import {css, html, LitElement} from 'lit'
import {connect} from 'pwa-helpers'
import {store} from '../store.js'
import {translate} from '../../translate'
import { html, LitElement } from 'lit'
import { connect } from 'pwa-helpers'
import { store } from '../store'
import { appInfoStyles } from '../styles/core-css'
// Multi language support
import { translate } from '../../translate'
class AppInfo extends connect(store)(LitElement) {
static get properties() {
return {
nodeInfo: { type: Array },
coreInfo: { type: Array },
nodeConfig: { type: Object },
theme: { type: String, reflect: true }
}
}
static get properties() {
return {
nodeInfo: { type: Array },
coreInfo: { type: Array },
nodeConfig: { type: Object },
theme: { type: String, reflect: true }
}
}
static get styles() {
return css`
* {
--mdc-theme-primary: rgb(3, 169, 244);
--paper-input-container-focus-color: var(--mdc-theme-primary);
}
static get styles() {
return [appInfoStyles]
}
.normal {
--mdc-theme-primary: rgb(3, 169, 244);
}
constructor() {
super()
this.nodeInfo = []
this.coreInfo = []
this.nodeConfig = {}
this.theme = localStorage.getItem('qortalTheme') ? localStorage.getItem('qortalTheme') : 'light'
}
#profileInMenu {
flex: 0 0 100px;
padding:12px;
border-top: 1px solid var(--border);
background: var(--sidetopbar);
}
render() {
return html`
<div id="profileInMenu">
<span class="info">${translate("appinfo.uiversion")}: ${this.nodeConfig.version ? this.nodeConfig.version : ''}</span>
${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 : ''}
</div>
`
}
.info {
margin: 0;
font-size: 14px;
font-weight: 100;
display: inline-block;
width: 100%;
padding-bottom: 8px;
color: var(--black);
}
firstUpdated() {
this.getNodeInfo()
this.getCoreInfo()
.blue {
color: #03a9f4;
margin: 0;
font-size: 14px;
font-weight: 200;
display: inline;
}
setInterval(() => {
this.getNodeInfo()
this.getCoreInfo()
}, 60000)
}
.black {
color: var(--black);
margin: 0;
font-size: 14px;
font-weight: 200;
display: inline;
}
`
}
async getNodeInfo() {
const appinfoNode = store.getState().app.nodeConfig.knownNodes[store.getState().app.nodeConfig.node]
const appinfoUrl = appinfoNode.protocol + '://' + appinfoNode.domain + ':' + appinfoNode.port
const url = `${appinfoUrl}/admin/status`
constructor() {
super()
this.nodeInfo = []
this.coreInfo = []
this.nodeConfig = {}
this.theme = localStorage.getItem('qortalTheme') ? localStorage.getItem('qortalTheme') : 'light'
}
await fetch(url).then(response => {
return response.json()
}).then(data => {
this.nodeInfo = data
}).catch(err => {
console.error('Request failed', err)
})
}
render() {
return html`
<div id="profileInMenu">
<span class="info">${translate("appinfo.uiversion")}: ${this.nodeConfig.version ? this.nodeConfig.version : ''}</span>
${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>
`
}
async getCoreInfo() {
const appinfoNode = store.getState().app.nodeConfig.knownNodes[store.getState().app.nodeConfig.node]
const appinfoUrl = appinfoNode.protocol + '://' + appinfoNode.domain + ':' + appinfoNode.port
const url = `${appinfoUrl}/admin/info`
firstUpdated() {
this.getNodeInfo()
this.getCoreInfo()
await fetch(url).then(response => {
return response.json()
}).then(data => {
this.coreInfo = data
}).catch(err => {
console.error('Request failed', err)
})
}
setInterval(() => {
this.getNodeInfo()
this.getCoreInfo()
}, 60000)
}
_renderStatus() {
if (this.nodeInfo.isMintingPossible === true && this.nodeInfo.isSynchronizing === true) {
this.cssStatus = 'blue'
return html`${translate("appinfo.minting")}`
} else if (this.nodeInfo.isMintingPossible === true && this.nodeInfo.isSynchronizing === false) {
this.cssStatus = 'blue'
return html`${translate("appinfo.minting")}`
} else if (this.nodeInfo.isMintingPossible === false && this.nodeInfo.isSynchronizing === true) {
this.cssStatus = 'black'
return html`(${translate("appinfo.synchronizing")}... ${this.nodeInfo.syncPercent !== undefined ? this.nodeInfo.syncPercent + '%' : ''})`
} else if (this.nodeInfo.isMintingPossible === false && this.nodeInfo.isSynchronizing === false) {
this.cssStatus = 'black'
return ''
} else {
return ''
}
}
async getNodeInfo() {
const appinfoNode = store.getState().app.nodeConfig.knownNodes[store.getState().app.nodeConfig.node]
const appinfoUrl = appinfoNode.protocol + '://' + appinfoNode.domain + ':' + appinfoNode.port
const url = `${appinfoUrl}/admin/status`
_renderCoreVersion() {
return html`<span class="info">${translate("appinfo.coreversion")}: ${this.coreInfo.buildVersion ? this.coreInfo.buildVersion : ''}</span>`
}
await fetch(url).then(response => {
return response.json()
}).then(data => {
this.nodeInfo = data
}).catch(err => {
console.error('Request failed', err)
})
}
async getCoreInfo() {
const appinfoNode = store.getState().app.nodeConfig.knownNodes[store.getState().app.nodeConfig.node]
const appinfoUrl = appinfoNode.protocol + '://' + appinfoNode.domain + ':' + appinfoNode.port
const url = `${appinfoUrl}/admin/info`
await fetch(url).then(response => {
return response.json()
}).then(data => {
this.coreInfo = data
}).catch(err => {
console.error('Request failed', err)
})
}
_renderStatus() {
if (this.nodeInfo.isMintingPossible === true && this.nodeInfo.isSynchronizing === true) {
this.cssStatus = 'blue'
return html`${translate("appinfo.minting")}`
} else if (this.nodeInfo.isMintingPossible === true && this.nodeInfo.isSynchronizing === false) {
this.cssStatus = 'blue'
return html`${translate("appinfo.minting")}`
} else if (this.nodeInfo.isMintingPossible === false && this.nodeInfo.isSynchronizing === true) {
this.cssStatus = 'black'
return html`(${translate("appinfo.synchronizing")}... ${this.nodeInfo.syncPercent !== undefined ? this.nodeInfo.syncPercent + '%' : ''})`
} else if (this.nodeInfo.isMintingPossible === false && this.nodeInfo.isSynchronizing === false) {
this.cssStatus = 'black'
return ''
} else {
return ''
}
}
_renderCoreVersion() {
return html`<span class="info">${translate("appinfo.coreversion")}: ${this.coreInfo.buildVersion ? this.coreInfo.buildVersion : ''}</span>`
}
stateChanged(state) {
this.nodeConfig = state.app.nodeConfig
}
stateChanged(state) {
this.nodeConfig = state.app.nodeConfig
}
}
window.customElements.define('app-info', AppInfo)
window.customElements.define('app-info', AppInfo)

File diff suppressed because it is too large Load Diff

View File

@ -1,26 +1,11 @@
import {css, html, LitElement} from 'lit'
import {connect} from 'pwa-helpers'
import {store} from '../store.js'
import { html, LitElement } from 'lit'
import { connect } from 'pwa-helpers'
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) {
}
render () {
return html`<style></style>`
}
}
window.customElements.define('my-element', MyElement)
window.customElements.define('my-element', MyElement)

View File

@ -1,11 +1,13 @@
import {css, html, LitElement} from 'lit'
import {store} from '../../store'
import {connect} from 'pwa-helpers'
import {translate} from '../../../translate'
import {parentEpml} from '../show-plugin'
import { html, LitElement } from 'lit'
import { connect } from 'pwa-helpers'
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,242 +21,112 @@ 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);
}
: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;
}
`;
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>
` : ''}
`
}
_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 = [
{
popover: {
title: get("tour.tour6"),
description: `
<div style="display:flex;justify-content:center;gap:15px">
<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>
<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>
<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>
`,
// ... other options
},
},
];
const step2 = elements['core-sync-status-id'];
const step3 = elements['tab'];
const step4 = elements['checklist'];
const elements = this.getElements()
let steps = [{
popover: {
title: get("tour.tour6"),
description: `
<div style="display:flex;justify-content:center;gap:15px">
<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>
<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>
<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>
`
}
}]
const step2 = elements['core-sync-status-id']
const step3 = elements['tab']
const step4 = elements['checklist']
if (step2) {
steps.push({
@ -261,58 +134,54 @@ class TourComponent extends connect(store)(LitElement) {
popover: {
title: get("tour.tour5"),
description: `
<div style="display:flex;gap:15px;align-items:center;margin-top:15px;margin-bottom:30px">
<p style="margin:0px;padding:0px">${get("tour.tour1")}</p>
</div>
<div style="display:flex;gap:15px;align-items:center;margin-top:15px;">
<span><img src="/img/synced.png" style="height: 24px; width: 24px; padding-top: 4px;" /></span>
<p style="margin:0px;padding:0px">${get("tour.tour2")}</p>
</div>
<div style="display:flex;gap:15px;align-items:center;margin-top:15px;">
<span><img src="/img/synced_minting.png" style="height: 24px; width: 24px; padding-top: 4px;" /></span>
<p style="margin:0px;padding:0px">${get("tour.tour3")}</p>
</div>
<div style="display:flex;gap:15px;align-items:center;margin-top:15px;margin-bottom:30px">
<span><img src="/img/syncing.png" style="height: 24px; width: 24px; padding-top: 4px;" /></span>
<p style="margin:0px;padding:0px">${get("tour.tour4")}</p>
</div>
<div style="display:flex;gap:15px;align-items:center;margin-top:15px;margin-bottom:30px">
<p style="margin:0px;padding:0px">${get("tour.tour1")}</p>
</div>
<div style="display:flex;gap:15px;align-items:center;margin-top:15px;">
<span><img src="/img/synced.png" style="height: 24px; width: 24px; padding-top: 4px;" /></span>
<p style="margin:0px;padding:0px">${get("tour.tour2")}</p>
</div>
<div style="display:flex;gap:15px;align-items:center;margin-top:15px;">
<span><img src="/img/synced_minting.png" style="height: 24px; width: 24px; padding-top: 4px;" /></span>
<p style="margin:0px;padding:0px">${get("tour.tour3")}</p>
</div>
<div style="display:flex;gap:15px;align-items:center;margin-top:15px;margin-bottom:30px">
<span><img src="/img/syncing.png" style="height: 24px; width: 24px; padding-top: 4px;" /></span>
<p style="margin:0px;padding:0px">${get("tour.tour4")}</p>
</div>
`,
},
});
`
}
})
}
if (step3) {
steps.push({
element: step3,
popover: {
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>
</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>
</div>
`,
},
});
}
if (step4) {
steps.push(
{
element: step4,
popover: {
title: get("tour.tour11"),
description: get("tour.tour12"),
},
<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>
</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>
</div>
`
}
);this.hasViewedTour
})
}
let currentStepIndex = 0;
if (step4) {
steps.push({ element: step4, popover: { title: get("tour.tour11"), description: get("tour.tour12")}})
this.hasViewedTour
}
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() {
@ -48,4 +45,4 @@ class CheckForUpdate extends LitElement {
}
}
window.customElements.define('check-for-update', CheckForUpdate)
window.customElements.define('check-for-update', CheckForUpdate)

View File

@ -1,82 +1,58 @@
import {Sha256} from 'asmcrypto.js'
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')
return old
function sbrk(size, heap) {
let brk = 512 * 1024 // stack top
let old = brk
brk += size
if (brk > heap.length) throw new Error('heap exhausted')
return old
}
self.addEventListener('message', async e => {
const response = await computePow(e.data.chatBytes, e.data.path, e.data.difficulty)
postMessage(response)
const response = await computePow(e.data.chatBytes, e.data.path, e.data.difficulty)
postMessage(response)
})
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 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);
});
}
loadWebAssembly(path)
.then(wasmModule => {
response = {
nonce : wasmModule.exports.compute2(hashPtr, workBufferPtr, workBufferLength, difficulty),
chatBytesArray
}
resolve()
});
})
return response
}
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 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)
})
}
loadWebAssembly(path)
.then(wasmModule => {
response = {
nonce: wasmModule.exports.compute2(hashPtr, workBufferPtr, workBufferLength, difficulty),
chatBytesArray
}
resolve()
})
})
return response
}

View File

@ -1,92 +1,68 @@
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')
return old
function sbrk(size, heap) {
let brk = 512 * 1024 // stack top
let old = brk
brk += size
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 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]
}
)
const convertedBytesArray = new Uint8Array(_convertedBytesArray)
const convertedBytesHash = new Sha256()
.process(convertedBytesArray)
.finish().result
const hashPtr = sbrk(32, heap)
const hashAry = new Uint8Array(
memory.buffer,
hashPtr,
32
)
hashAry.set(convertedBytesHash)
const difficulty = 14
const workBufferLength = 8 * 1024 * 1024
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)
})
}
loadWebAssembly(path)
.then(wasmModule => {
response = {
nonce: wasmModule.exports.compute2(hashPtr, workBufferPtr, workBufferLength, difficulty),
let response = null
await new Promise((resolve, reject)=> {
const _convertedBytesArray = Object.keys(convertedBytes).map(
function (key) {
return convertedBytes[key]
}
)
const convertedBytesArray = new Uint8Array(_convertedBytesArray)
const convertedBytesHash = new Sha256()
.process(convertedBytesArray)
.finish().result
const hashPtr = sbrk(32, heap)
const hashAry = new Uint8Array(
memory.buffer,
hashPtr,
32
)
hashAry.set(convertedBytesHash)
const difficulty = 14
const workBufferLength = 8 * 1024 * 1024
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);
});
}
loadWebAssembly(path)
.then(wasmModule => {
response = {
nonce : wasmModule.exports.compute2(hashPtr, workBufferPtr, workBufferLength, difficulty),
}
resolve()
});
})
return response
}
resolve()
})
})
return response
}

View File

@ -1,320 +1,303 @@
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.fetchBalance = this.fetchBalance.bind(this)
this._updateCoinList = this._updateCoinList.bind(this)
this.stop = false
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
async updateArrrWalletBalance() {
let _url = `/crosschain/arrr/walletbalance?apiKey=${this.myNode.apiKey}`
let _body = store.getState().app.selectedAddress.arrrWallet.seed58
await parentEpml.request('apiCall', {
url: _url,
method: 'POST',
body: _body,
}).then((res) => {
if (isNaN(Number(res))) {
//...
} else {
this.arrrWalletBalance = (Number(res) / 1e8).toFixed(8)
store.dispatch(
setCoinBalances({
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')
})
}
_updateCoinList(event) {
const copyCoinList = {...this.coinList}
const coin = event.detail
if(!copyCoinList[coin]){
try {
if(coin === 'ltc'){
this.updateLtcWalletBalance()
} else if(coin === 'qort'){
this.updateQortWalletBalance()
} else if(coin === 'doge'){
this.updateDogeWalletBalance()
} else if(coin === 'btc'){
this.updateBtcWalletBalance()
} else if(coin === 'dgb'){
this.updateDgbWalletBalance()
} else if(coin === 'rvn'){
this.updateRvnWalletBalance()
}else if(coin === 'arrr'){
this.updateArrrWalletBalance()
}
} catch (error) {
}
}
copyCoinList[coin] = Date.now() + 120000;
this.coinList = copyCoinList
this.requestUpdate()
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
async fetchCoins(arrayOfCoins){
const getCoinBalances = (arrayOfCoins || []).map(
async (coin) => {
if(coin === 'ltc'){
await this.updateLtcWalletBalance()
} else if(coin === 'qort'){
await this.updateQortWalletBalance()
} else if(coin === 'doge'){
await this.updateDogeWalletBalance()
} else if(coin === 'btc'){
await this.updateBtcWalletBalance()
} else if(coin === 'dgb'){
await this.updateDgbWalletBalance()
} else if(coin === 'rvn'){
await this.updateRvnWalletBalance()
}else if(coin === 'arrr'){
await this.updateArrrWalletBalance()
}
})
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')
})
}
await Promise.all(getCoinBalances);
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)
})
)
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]
}
}).catch(() => {
console.log('error')
})
}
if(item < currentDate){
delete copyObject[key]
} else {
arrayOfCoins.push(key)
}
}
if(!this.stop){
this.stop = true
await this.fetchCoins(arrayOfCoins)
this.stop = false
}
this.coinList = copyObject
} catch (error) {
this.stop = false
}
}
async updateDogeWalletBalance() {
let _url = `/crosschain/doge/walletbalance?apiKey=${this.myNode.apiKey}`
let _body = store.getState().app.selectedAddress.dogeWallet.derivedMasterPublicKey
connectedCallback() {
super.connectedCallback();
this.intervalID = setInterval(this.fetchBalance, 45000);
window.addEventListener(
'ping-coin-controller-with-coin',
this._updateCoinList
);
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}`
let _body = store.getState().app.selectedAddress.arrrWallet.seed58
await parentEpml.request('apiCall', {
url: _url,
method: 'POST',
body: _body,
}).then((res) => {
if (isNaN(Number(res))) {
//...
} else {
this.arrrWalletBalance = (Number(res) / 1e8).toFixed(8)
store.dispatch(
setCoinBalances({
type: 'arrr',
fullValue: Number(res)
})
)
}
}).catch(() => {
console.log('error')
})
}
_updateCoinList(event) {
const copyCoinList = { ...this.coinList }
const coin = event.detail
if (!copyCoinList[coin]) {
try {
if (coin === 'qort') {
this.updateQortWalletBalance()
} 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') {
this.updateRvnWalletBalance()
} else if (coin === 'arrr') {
this.updateArrrWalletBalance()
}
} catch (error) { }
}
copyCoinList[coin] = Date.now() + 120000
this.coinList = copyCoinList
this.requestUpdate()
}
async fetchCoins(arrayOfCoins) {
const getCoinBalances = (arrayOfCoins || []).map(async (coin) => {
if (coin === 'qort') {
await this.updateQortWalletBalance()
} 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') {
await this.updateRvnWalletBalance()
} else if (coin === 'arrr') {
await this.updateArrrWalletBalance()
}
})
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]
if (item < currentDate) {
delete copyObject[key]
} else {
arrayOfCoins.push(key)
}
}
if (!this.stop) {
this.stop = true
await this.fetchCoins(arrayOfCoins)
this.stop = false
}
this.coinList = copyObject
} catch (error) {
this.stop = false
}
}
connectedCallback() {
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,221 +1,204 @@
import {css, html, LitElement} from 'lit'
import {get} from '../../../translate'
import '@material/mwc-icon'
import '@vaadin/tooltip';
import { html, LitElement } from 'lit'
import { connect } from 'pwa-helpers'
import { store } from '../../store'
import { get } from '../../../translate'
import { chatSideNavHeadsStyles } from '../../styles/core-css'
import './friend-item-actions'
import '@material/mwc-icon'
import '@vaadin/tooltip'
class ChatSideNavHeads extends LitElement {
static get properties() {
return {
selectedAddress: { type: Object },
config: { type: Object },
chatInfo: { type: Object },
iconName: { type: String },
activeChatHeadUrl: { type: String },
isImageLoaded: { type: Boolean },
setActiveChatHeadUrl: {attribute: false},
openEditFriend: {attribute: false},
closeSidePanel: {attribute: false, type: Object}
}
}
class ChatSideNavHeads extends connect(store)(LitElement) {
static get properties() {
return {
selectedAddress: { type: Object },
config: { type: Object },
chatInfo: { type: Object },
iconName: { type: String },
activeChatHeadUrl: { type: String },
isImageLoaded: { type: Boolean },
setActiveChatHeadUrl: { attribute: false },
openEditFriend: { attribute: false },
closeSidePanel: { attribute: false, type: Object }
}
}
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;
}
static get styles() {
return [chatSideNavHeadsStyles]
}
li:hover {
background-color: var(--lightChatHeadHover);
}
.active {
background: var(--menuactive);
border-left: 4px solid #3498db;
}
.img-icon {
font-size:40px;
color: var(--chat-group);
}
.status {
color: #92959e;
}
.clearfix {
display: flex;
align-items: center;
}
.clearfix:after {
visibility: hidden;
display: block;
font-size: 0;
content: " ";
clear: both;
height: 0;
}
`
}
constructor() {
super()
this.selectedAddress = {}
this.config = {
user: {
node: {
}
}
}
this.chatInfo = {}
this.iconName = ''
this.activeChatHeadUrl = ''
this.isImageLoaded = false
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 = ""
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}`;
avatarImg = this.createImage(avatarUrl)
}
return html`
<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');
if (popover) {
popover.openPopover(target);
constructor() {
super()
this.selectedAddress = {}
this.config = {
user: {
node: {
}
}} 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
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(--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
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(--chatHeadTextActive)"
: "var(--chatHeadText)"}; font-weight:bold; display: flex; justify-content: center; align-items: center; text-transform: capitalize"
>
${this.chatInfo.groupName.charAt(0)}
</div>`
: ""}
<div>
<div class="name">
<span style="float:left; padding-left: 8px; color: var(--chat-group);">
${this.chatInfo.groupName
? this.chatInfo.groupName
: this.chatInfo.name !== undefined
? (this.chatInfo.alias || this.chatInfo.name)
: this.chatInfo.address.substr(0, 15)}
</span>
</div>
</div>
}
}
this.chatInfo = {}
this.iconName = ''
this.activeChatHeadUrl = ''
this.isImageLoaded = false
this.imageFetches = 0
}
</div>
<div style="display:flex; align-items: center">
${this.chatInfo.willFollow ? html`
<mwc-icon id="willFollowIcon" style="color: var(--black)">connect_without_contact</mwc-icon>
<vaadin-tooltip
render() {
let avatarImg = ''
for="willFollowIcon"
position="top"
hover-delay=${200}
hide-delay=${1}
text=${get('friends.friend11')}>
</vaadin-tooltip>
` : ''}
</div>
</li>
<friend-item-actions
for=${`friend-item-parent-${this.chatInfo.name}`}
message=${get('notifications.explanation')}
.openEditFriend=${()=> {
this.openEditFriend(this.chatInfo)
}}
name=${this.chatInfo.name}
.closeSidePanel=${this.closeSidePanel}
></friend-item-actions>
`
}
if (this.chatInfo.name) {
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) => {
const target = e.target
const popover = this.shadowRoot.querySelector('friend-item-actions');
if (popover) {
popover.openPopover(target);
}
}}
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
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(--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
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(--chatHeadTextActive)"
: "var(--chatHeadText)"}; font-weight:bold; display: flex; justify-content: center; align-items: center; text-transform: capitalize"
>
${this.chatInfo.groupName.charAt(0)}
</div>
` : ''
}
<div>
<div class="name">
<span style="float:left; padding-left: 8px; color: var(--chat-group);">
${this.chatInfo.groupName
? this.chatInfo.groupName
: this.chatInfo.name !== undefined
? (this.chatInfo.alias || this.chatInfo.name)
: this.chatInfo.address.substr(0, 15)
}
</span>
</div>
</div>
</div>
<div style="display:flex; align-items: center">
${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>
` : ''
}
</div>
</li>
<friend-item-actions
for=${`friend-item-parent-${this.chatInfo.name}`}
message=${get('notifications.explanation')}
.openEditFriend=${() => {
this.openEditFriend(this.chatInfo)
}}
name=${this.chatInfo.name}
.closeSidePanel=${this.closeSidePanel}
></friend-item-actions>
`
}
firstUpdated() {
// ...
}
shouldUpdate(changedProperties) {
if(changedProperties.has('activeChatHeadUrl')){
return true
}
if(changedProperties.has('chatInfo')){
return true
}
return !!changedProperties.has('isImageLoaded');
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
}
getUrl(chatUrl) {
this.setActiveChatHeadUrl(chatUrl)
}
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')
}
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)
window.customElements.define('chat-side-nav-heads', ChatSideNavHeads)

View File

@ -1,10 +1,11 @@
import {css, html, LitElement} from 'lit';
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 { html, LitElement } from 'lit'
import { connect } from 'pwa-helpers'
import { store } from '../../store'
import { translate, } from '../../../translate'
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) {
@ -21,199 +22,195 @@ class AddFriendsModal extends connect(store)(LitElement) {
editContent: { type: Object },
onClose: { attribute: false },
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;
availableFeeedSchemas: { type: Array },
isLoadingSchemas: { type: Boolean }
}
}
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;
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;
}
`;
return [addFriendsModalStyles]
}
firstUpdated() {}
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">
<h1>
${this.editContent
? translate('friends.friend10')
: translate('friends.friend2')}
</h1>
<hr />
</div>
<p>${translate('friends.friend3')}</p>
<div class="checkbox-row">
<label
for="willFollow"
id="willFollowLabel"
style="color: var(--black);"
>
${translate('friends.friend5')}
</label>
<mwc-checkbox
style="margin-right: -15px;"
id="willFollow"
@change=${(e) => {
this.willFollow = e.target.checked;
}}
?checked=${this.willFollow}
></mwc-checkbox>
</div>
<div style="height:15px"></div>
<div style="display: flex;flex-direction: column;">
<label
for="name"
id="nameLabel"
style="color: var(--black);"
>
${translate('login.name')}
</label>
<input
id="name"
class="input"
?disabled=${true}
value=${this.userSelected ? this.userSelected.name : ''}
/>
</div>
<div style="height:15px"></div>
<div style="display: flex;flex-direction: column;">
<label
for="alias"
id="aliasLabel"
style="color: var(--black);"
>
${translate('friends.friend6')}
</label>
<input
id="alias"
placeholder=${translate('friends.friend7')}
class="input"
.value=${this.alias}
@change=${(e) => {
this.alias = e.target.value
}}
/>
</div>
<div style="height:15px"></div>
<div style="margin-bottom:0;">
<textarea
class="input"
@change=${(e) => {
this.notes = e.target.value
}}
.value=${this.notes}
?disabled=${this.isLoading}
id="messageBoxAddFriend"
placeholder="${translate('friends.friend4')}"
rows="3"
></textarea>
</div>
<div style="height:15px"></div>
<h2>${translate('friends.friend15')}</h2>
<div style="margin-bottom:0;">
<p>${translate('friends.friend16')}</p>
</div>
<div>
${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);
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';"/>`;
return html`
<div
class="app-name"
style="background:${isAlreadySelected ? 'lightblue' : ''}"
@click=${() => {
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;
} else {
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>
</div>
`
})}
</div>
</div>
<div style="display:flex;justify-content:space-between;align-items:center;margin-top:20px">
<button
class="modal-button-red"
?disabled="${this.isLoading}"
@click="${() => {
this.setIsOpen(false);
this.clearFields();
this.onClose();
}}"
>
${translate('general.close')}
</button>
${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>
</div>
</div>
</div>
`
}
firstUpdated() {
// ...
}
getNodeUrl() {
const myNode =
store.getState().app.nodeConfig.knownNodes[
window.parent.reduxStore.getState().app.nodeConfig.node
];
return myNode.protocol + '://' + myNode.domain + ':' + myNode.port;
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]
}
clearFields() {
this.alias = '';
this.willFollow = true;
this.notes = '';
this.alias = ''
this.willFollow = true
this.notes = ''
}
addFriend() {
@ -223,10 +220,10 @@ class AddFriendsModal extends connect(store)(LitElement) {
notes: this.notes,
willFollow: this.willFollow,
mySelectedFeeds: this.mySelectedFeeds
})
});
this.clearFields();
this.onClose();
this.clearFields()
this.onClose()
}
removeFriend() {
@ -239,244 +236,60 @@ class AddFriendsModal extends connect(store)(LitElement) {
mySelectedFeeds: this.mySelectedFeeds
},
true
);
this.clearFields();
this.onClose();
)
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 ?? '';
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
) {
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();
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 = [];
this.availableFeeedSchemas = []
} else {
this.availableFeeedSchemas = data.filter(
(item) => item.identifier === 'ui_schema_feed'
);
this.availableFeeedSchemas = data.filter((item) => item.identifier === 'ui_schema_feed')
}
this.userFoundModalOpen = true;
} catch (error) {} finally {
this.isLoadingSchemas= false
this.userFoundModalOpen = true
} catch (error) {
} finally {
this.isLoadingSchemas = false
}
}
render() {
return html`
<div class="modal-overlay ${this.isOpen ? '' : 'hidden'}">
// Standard functions
getApiKey() {
const coreNode = store.getState().app.nodeConfig.knownNodes[store.getState().app.nodeConfig.node]
return coreNode.apiKey
}
<div class="modal-content">
<div class="inner-content">
<div style="text-align:center">
<h1>
${this.editContent
? translate('friends.friend10')
: translate('friends.friend2')}
</h1>
<hr />
</div>
<p>${translate('friends.friend3')}</p>
<div class="checkbox-row">
<label
for="willFollow"
id="willFollowLabel"
style="color: var(--black);"
>
${translate('friends.friend5')}
</label>
<mwc-checkbox
style="margin-right: -15px;"
id="willFollow"
@change=${(e) => {
this.willFollow = e.target.checked;
}}
?checked=${this.willFollow}
></mwc-checkbox>
</div>
<div style="height:15px"></div>
<div style="display: flex;flex-direction: column;">
<label
for="name"
id="nameLabel"
style="color: var(--black);"
>
${translate('login.name')}
</label>
<input
id="name"
class="input"
?disabled=${true}
value=${this.userSelected
? this.userSelected.name
: ''}
/>
</div>
<div style="height:15px"></div>
<div style="display: flex;flex-direction: column;">
<label
for="alias"
id="aliasLabel"
style="color: var(--black);"
>
${translate('friends.friend6')}
</label>
<input
id="alias"
placeholder=${translate('friends.friend7')}
class="input"
.value=${this.alias}
@change=${(e) => {
this.alias = e.target.value
}}
/>
</div>
<div style="height:15px"></div>
<div style="margin-bottom:0;">
<textarea
class="input"
@change=${(e) => {
this.notes = e.target.value
}}
.value=${this.notes}
?disabled=${this.isLoading}
id="messageBoxAddFriend"
placeholder="${translate('friends.friend4')}"
rows="3"
></textarea>
</div>
<div style="height:15px"></div>
<h2>${translate('friends.friend15')}</h2>
<div style="margin-bottom:0;">
<p>${translate('friends.friend16')}</p>
</div>
<div>
${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
);
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';"
/>`;
return html`
<div
class="app-name"
style="background:${isAlreadySelected ? 'lightblue': ''}"
@click=${() => {
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;
} else {
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
>
</div>
`;
})}
</div>
</div>
<div
style="display:flex;justify-content:space-between;align-items:center;margin-top:20px"
>
<button
class="modal-button-red"
?disabled="${this.isLoading}"
@click="${() => {
this.setIsOpen(false);
this.clearFields();
this.onClose();
}}"
>
${translate('general.close')}
</button>
${this.editContent
? html`
<button
?disabled="${this.isLoading}"
class="modal-button-red"
@click=${() => {
this.removeFriend();
}}
>
${translate('friends.friend14')}
</button>
`
: ''}
isEmptyArray(arr) {
if (!arr) { return true }
return arr.length === 0
}
<button
?disabled="${this.isLoading}"
class="modal-button"
@click=${() => {
this.addFriend();
}}
>
${this.editContent
? translate('friends.friend10')
: translate('friends.friend2')}
</button>
</div>
</div>
</div>
`;
round(number) {
return (Math.round(parseFloat(number) * 1e8) / 1e8).toFixed(8)
}
}
customElements.define('add-friends-modal', AddFriendsModal);
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();
}
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;
status: ''
}
}
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;"
>
<img
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>
`
: ''}
${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}"
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
}
}
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)
}
}
customElements.define('avatar-component', AvatarComponent);
window.customElements.define('avatar-component', AvatarComponent)

View File

@ -1,344 +1,214 @@
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 = {
notifications: { type: Array },
showChecklist: { type: Boolean },
theme: { type: String, reflect: true },
isSynced: { type: Boolean },
hasName: { type: Boolean },
hasTourFinished: { type: Boolean },
};
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();
static get properties() {
return {
notifications: { type: Array },
showChecklist: { type: Boolean },
isSynced: { type: Boolean },
hasName: { type: Boolean },
hasTourFinished: { type: Boolean },
theme: { type: String, reflect: true }
}
}
_controlTourFinished() {
this.hasTourFinished = true;
static get styles() {
return [beginnerChecklistStyles]
}
constructor() {
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`
<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>
</div>
<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>
`
: html`
<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"
@click=${() => {
store.dispatch(
setNewTab({
url: `group-management`,
id: this.uid.rnd(),
myPlugObj: {
url: 'name-registration',
domain: 'core',
page: 'name-registration/index.html',
title: 'Name Registration',
icon: 'vaadin:user-check',
mwcicon: 'manage_accounts',
pluginNumber: 'plugin-qCmtXAQmtu',
menus: [],
parent: false
},
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>
` : 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'
);
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
);
super.connectedCallback()
window.addEventListener('send-tour-finished', this._controlTourFinished)
}
disconnectedCallback() {
window.removeEventListener(
'send-tour-finished',
this._controlTourFinished
);
super.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;
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
];
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();
if (!recipient) return ''
this.hasName = Array.isArray(getNames) && getNames.length > 0;
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 '';
return ''
}
}
stateChanged(state) {
if (
state.app.nodeStatus &&
state.app.nodeStatus.syncPercent !== this.syncPercentage
) {
this.syncPercentage = state.app.nodeStatus.syncPercent;
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 (!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 &&
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;
this.hasName = true
}
}
handleBlur() {
setTimeout(() => {
if (!this.shadowRoot.contains(document.activeElement)) {
this.showChecklist = false;
this.showChecklist = false
}
}, 0);
}
render() {
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>
</div>
<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
>
`
: html`
<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"
@click=${() => {
store.dispatch(
setNewTab({
url: `group-management`,
id: this.uid.rnd(),
myPlugObj: {
url: 'name-registration',
domain: 'core',
page: 'name-registration/index.html',
title: 'Name Registration',
icon: 'vaadin:user-check',
mwcicon: 'manage_accounts',
pluginNumber:
'plugin-qCmtXAQmtu',
menus: [],
parent: false,
},
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
>
`
: html`
<mwc-icon
id="checklist-general-icon"
style="color: red; user-select:none"
>radio_button_unchecked</mwc-icon
>
`}
</div>
</div>
</div>
</div>
`
: '';
}, 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;
}
.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;
}
`;
round(number) {
return (Math.round(parseFloat(number) * 1e8) / 1e8).toFixed(8)
}
}
customElements.define('beginner-checklist', BeginnerChecklist);
window.customElements.define('beginner-checklist', BeginnerChecklist)

View File

@ -1,91 +1,67 @@
import {Sha256} from 'asmcrypto.js'
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')
return old
function sbrk(size, heap) {
let brk = 512 * 1024 // stack top
let old = brk
brk += size
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 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]
}
)
const convertedBytesArray = new Uint8Array(_convertedBytesArray)
const convertedBytesHash = new Sha256()
.process(convertedBytesArray)
.finish().result
const hashPtr = sbrk(32, heap)
const hashAry = new Uint8Array(
memory.buffer,
hashPtr,
32
)
hashAry.set(convertedBytesHash)
const difficulty = 14
const workBufferLength = 8 * 1024 * 1024
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)
})
}
loadWebAssembly(path)
.then(wasmModule => {
response = {
nonce: wasmModule.exports.compute2(hashPtr, workBufferPtr, workBufferLength, difficulty),
let response = null
await new Promise((resolve, reject)=> {
const _convertedBytesArray = Object.keys(convertedBytes).map(
function (key) {
return convertedBytes[key]
}
)
const convertedBytesArray = new Uint8Array(_convertedBytesArray)
const convertedBytesHash = new Sha256()
.process(convertedBytesArray)
.finish().result
const hashPtr = sbrk(32, heap)
const hashAry = new Uint8Array(
memory.buffer,
hashPtr,
32
)
hashAry.set(convertedBytesHash)
const difficulty = 14
const workBufferLength = 8 * 1024 * 1024
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);
});
}
loadWebAssembly(path)
.then(wasmModule => {
response = {
nonce : wasmModule.exports.compute2(hashPtr, workBufferPtr, workBufferLength, difficulty),
}
resolve()
});
})
return response
}
}
resolve()
})
})
return response
}

View File

@ -1,7 +1,8 @@
import {css, html, LitElement} from 'lit'
import {store} from '../../store'
import {connect} from 'pwa-helpers'
import {translate} from '../../../translate'
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">
@ -136,7 +78,7 @@ class CoreSyncStatus extends connect(store)(LitElement) {
<span><img src="/img/syncing.png" style="height: 24px; width: 24px; padding-top: 4px;"></span>
<div class="bottom">
<h3>${translate("walletprofile.wp3")}</h3>
<h4 class="lineHeight">${translate("appinfo.coreversion")}: <span style="color: #03a9f4">${this.coreInfos.buildVersion ? (this.coreInfos.buildVersion).substring(0,12) : ''}</span></h4>
<h4 class="lineHeight">${translate("appinfo.coreversion")}: <span style="color: #03a9f4">${this.coreInfos.buildVersion ? (this.coreInfos.buildVersion).substring(0, 12) : ''}</span></h4>
<h4 class="lineHeight">${translate("appinfo.synchronizing")}... <span style="color: #03a9f4">${this.nodeInfos.syncPercent !== undefined ? this.nodeInfos.syncPercent + '%' : ''}</span></h4>
<h4 class="lineHeight">${translate("appinfo.blockheight")}: <span style="color: #03a9f4">${this.nodeInfos.height ? this.nodeInfos.height : ''}</span></h4>
<h4 class="lineHeight">${translate("appinfo.peers")}: <span style="color: #03a9f4">${this.nodeInfos.numberOfConnections ? this.nodeInfos.numberOfConnections : ''}</span></h4>
@ -150,7 +92,7 @@ class CoreSyncStatus extends connect(store)(LitElement) {
<span><img src="/img/synced.png" style="height: 24px; width: 24px; padding-top: 4px;"></span>
<div class="bottom">
<h3>${translate("walletprofile.wp3")}</h3>
<h4 class="lineHeight">${translate("appinfo.coreversion")}: <span style="color: #03a9f4">${this.coreInfos.buildVersion ? (this.coreInfos.buildVersion).substring(0,12) : ''}</span></h4>
<h4 class="lineHeight">${translate("appinfo.coreversion")}: <span style="color: #03a9f4">${this.coreInfos.buildVersion ? (this.coreInfos.buildVersion).substring(0, 12) : ''}</span></h4>
<h4 class="lineHeight">${translate("walletprofile.wp4")} ${translate("walletprofile.wp2")}</h4>
<h4 class="lineHeight">${translate("appinfo.blockheight")}: <span style="color: #03a9f4">${this.nodeInfos.height ? this.nodeInfos.height : ''}</span></h4>
<h4 class="lineHeight">${translate("appinfo.peers")}: <span style="color: #03a9f4">${this.nodeInfos.numberOfConnections ? this.nodeInfos.numberOfConnections : ''}</span></h4>
@ -164,7 +106,7 @@ class CoreSyncStatus extends connect(store)(LitElement) {
<span><img src="/img/synced.png" style="height: 24px; width: 24px; padding-top: 4px;"></span>
<div class="bottom">
<h3>${translate("walletprofile.wp3")}</h3>
<h4 class="lineHeight">${translate("appinfo.coreversion")}: <span style="color: #03a9f4">${this.coreInfos.buildVersion ? (this.coreInfos.buildVersion).substring(0,12) : ''}</span></h4>
<h4 class="lineHeight">${translate("appinfo.coreversion")}: <span style="color: #03a9f4">${this.coreInfos.buildVersion ? (this.coreInfos.buildVersion).substring(0, 12) : ''}</span></h4>
<h4 class="lineHeight">${translate("walletprofile.wp4")} ${translate("walletprofile.wp2")}</h4>
<h4 class="lineHeight">${translate("appinfo.blockheight")}: <span style="color: #03a9f4">${this.nodeInfos.height ? this.nodeInfos.height : ''}</span></h4>
<h4 class="lineHeight">${translate("appinfo.peers")}: <span style="color: #03a9f4">${this.nodeInfos.numberOfConnections ? this.nodeInfos.numberOfConnections : ''}</span></h4>
@ -178,7 +120,7 @@ class CoreSyncStatus extends connect(store)(LitElement) {
<span><img src="/img/synced_minting.png" style="height: 24px; width: 24px; padding-top: 4px;"></span>
<div class="bottom">
<h3>${translate("walletprofile.wp3")}</h3>
<h4 class="lineHeight">${translate("appinfo.coreversion")}: <span style="color: #03a9f4">${this.coreInfos.buildVersion ? (this.coreInfos.buildVersion).substring(0,12) : ''}</span></h4>
<h4 class="lineHeight">${translate("appinfo.coreversion")}: <span style="color: #03a9f4">${this.coreInfos.buildVersion ? (this.coreInfos.buildVersion).substring(0, 12) : ''}</span></h4>
<h4 class="lineHeight">${translate("walletprofile.wp4")} <span style="color: #03a9f4">( ${translate("walletprofile.wp1")} )</span></h4>
<h4 class="lineHeight">${translate("appinfo.blockheight")}: <span style="color: #03a9f4">${this.nodeInfos.height ? this.nodeInfos.height : ''}</span></h4>
<h4 class="lineHeight">${translate("appinfo.peers")}: <span style="color: #03a9f4">${this.nodeInfos.numberOfConnections ? this.nodeInfos.numberOfConnections : ''}</span></h4>
@ -192,7 +134,7 @@ class CoreSyncStatus extends connect(store)(LitElement) {
<span><img src="/img/synced_minting.png" style="height: 24px; width: 24px; padding-top: 4px;"></span>
<div class="bottom">
<h3>${translate("walletprofile.wp3")}</h3>
<h4 class="lineHeight">${translate("appinfo.coreversion")}: <span style="color: #03a9f4">${this.coreInfos.buildVersion ? (this.coreInfos.buildVersion).substring(0,12) : ''}</span></h4>
<h4 class="lineHeight">${translate("appinfo.coreversion")}: <span style="color: #03a9f4">${this.coreInfos.buildVersion ? (this.coreInfos.buildVersion).substring(0, 12) : ''}</span></h4>
<h4 class="lineHeight">${translate("walletprofile.wp4")} <span style="color: #03a9f4">( ${translate("walletprofile.wp1")} )</span></h4>
<h4 class="lineHeight">${translate("appinfo.blockheight")}: <span style="color: #03a9f4">${this.nodeInfos.height ? this.nodeInfos.height : ''}</span></h4>
<h4 class="lineHeight">${translate("appinfo.peers")}: <span style="color: #03a9f4">${this.nodeInfos.numberOfConnections ? this.nodeInfos.numberOfConnections : ''}</span></h4>
@ -206,7 +148,7 @@ class CoreSyncStatus extends connect(store)(LitElement) {
<span><img src="/img/syncing.png" style="height: 24px; width: 24px; padding-top: 4px;"></span>
<div class="bottom">
<h3>${translate("walletprofile.wp3")}</h3>
<h4 class="lineHeight">${translate("appinfo.coreversion")}: <span style="color: #03a9f4">${this.coreInfos.buildVersion ? (this.coreInfos.buildVersion).substring(0,12) : ''}</span></h4>
<h4 class="lineHeight">${translate("appinfo.coreversion")}: <span style="color: #03a9f4">${this.coreInfos.buildVersion ? (this.coreInfos.buildVersion).substring(0, 12) : ''}</span></h4>
<h4 class="lineHeight">${translate("appinfo.synchronizing")}... <span style="color: #03a9f4">${this.nodeInfos.syncPercent !== undefined ? this.nodeInfos.syncPercent + '%' : ''}</span></h4>
<h4 class="lineHeight">${translate("appinfo.blockheight")}: <span style="color: #03a9f4">${this.nodeInfos.height ? this.nodeInfos.height : ''}</span></h4>
<h4 class="lineHeight">${translate("appinfo.peers")}: <span style="color: #03a9f4">${this.nodeInfos.numberOfConnections ? this.nodeInfos.numberOfConnections : ''}</span></h4>
@ -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
}
isEmptyArray(arr) {
if (!arr) { return true }
return arr.length === 0
}
round(number) {
return (Math.round(parseFloat(number) * 1e8) / 1e8).toFixed(8)
}
}
customElements.define('core-sync-status', CoreSyncStatus)
window.customElements.define('core-sync-status', CoreSyncStatus)

View File

@ -1,366 +1,282 @@
import {css, html, LitElement} from 'lit';
import {translate,} from '../../../translate'
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() {
return {
resource: { type: Object },
isReady: { type: Boolean},
status: {type: Object},
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;
static get properties() {
return {
resource: { type: Object },
isReady: { type: Boolean },
status: { type: Object },
feedItem: { type: Object },
appName: { type: String },
link: { type: String }
}
}
.app-name {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
}
static get styles() {
return [feedItemStyles]
}
mwc-menu {
position: absolute;
}
constructor() {
super()
this.resource = {
identifier: "",
name: "",
service: ""
}
this.status = {
status: ''
}
this.isReady = false
this.nodeUrl = this.getNodeUrl()
this.myNode = this.getMyNode()
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()
// Stop observing after the image has started loading
this.observer.unobserve(this)
}
}
})
this.feedItem = null
}
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>
`
}
@-webkit-keyframes loadingAnimation {
0% {
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
-webkit-transform: rotate(360deg);
transform: rotate(360deg);
}
}
firstUpdated() {
this.observer.observe(this)
}
@keyframes loadingAnimation {
0% {
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
}
100% {
-webkit-transform: rotate(360deg);
transform: rotate(360deg);
}
}
`;
}
getNodeUrl() {
const myNode = store.getState().app.nodeConfig.knownNodes[store.getState().app.nodeConfig.node]
return myNode.protocol + '://' + myNode.domain + ':' + myNode.port
}
constructor() {
super();
this.resource = {
identifier: "",
name: "",
service: ""
}
this.status = {
status: ''
}
this.isReady = false
this.nodeUrl = this.getNodeUrl()
this.myNode = this.getMyNode()
this.hasCalledWhenDownloaded = false
this.isFetching = false
this.uid = new ShortUniqueId()
getMyNode() {
return store.getState().app.nodeConfig.knownNodes[store.getState().app.nodeConfig.node]
}
this.observer = new IntersectionObserver(entries => {
for (const entry of entries) {
if (entry.isIntersecting && this.status.status !== 'READY') {
this._fetchImage();
// Stop observing after the image has started loading
this.observer.unobserve(this);
}
}
});
this.feedItem = null
}
getNodeUrl(){
const myNode = window.parent.reduxStore.getState().app.nodeConfig.knownNodes[window.parent.reduxStore.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
return myNode.protocol + '://' + myNode.domain + ':' + myNode.port
}
getMyNode(){
return window.parent.reduxStore.getState().app.nodeConfig.knownNodes[window.parent.reduxStore.getState().app.nodeConfig.node]
}
} catch (error) {
this.isFetching = false
}
}
getApiKey() {
const myNode =
window.parent.reduxStore.getState().app.nodeConfig.knownNodes[
window.parent.reduxStore.getState().app.nodeConfig.node
];
return myNode.apiKey;
}
async fetchVideoUrl() {
await this.fetchResource()
}
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
async getRawData() {
const url = `${this.nodeUrl}/arbitrary/${this.resource.service}/${this.resource.name}/${this.resource.identifier}?apiKey=${this.myNode.apiKey}`
} catch (error) {
this.isFetching = false
}
}
return await requestQueueRawData.enqueue(() => {
return axios.get(url)
})
}
async fetchVideoUrl() {
updateDisplayWithPlaceholders(display, resource, rawdata) {
const pattern = /\$\$\{([a-zA-Z0-9_\.]+)\}\$\$/g
await this.fetchResource()
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 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'
// }
// })
async fetchStatus() {
let isCalling = false
let percentLoaded = 0
let timer = 24
// const responseData2 = await response2.json()
// return responseData2
}
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}`)
})
updateDisplayWithPlaceholders(display, resource, rawdata) {
const pattern = /\$\$\{([a-zA-Z0-9_\.]+)\}\$\$/g;
if (response && response.data && response.data.status === 'READY') {
const rawData = await this.getRawData()
for (const key in display) {
const value = display[key];
const object = {
...this.resource.schema.display
}
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;
});
}
}
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}`)
})
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
const res = data.data
return
}
const intervalId = setInterval(async () => {
if (isCalling) return
isCalling = true
isCalling = false
if (res.localChunkCount) {
if (res.percentLoaded) {
if (res.percentLoaded === percentLoaded && res.percentLoaded !== 100) {
timer = timer - 5
} else {
timer = 24
}
if (timer < 0) {
timer = 24
isCalling = true
this.status = {
...res,
status: 'REFETCHING'
}
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
setTimeout(() => {
isCalling = false
this.fetchResource()
}, 25000)
isCalling = false
if (res.localChunkCount) {
if (res.percentLoaded) {
if (
res.percentLoaded === percentLoaded &&
res.percentLoaded !== 100
) {
timer = timer - 5
} else {
timer = 24
}
if (timer < 0) {
timer = 24
isCalling = true
this.status = {
...res,
status: 'REFETCHING'
}
return
}
percentLoaded = res.percentLoaded
}
setTimeout(() => {
isCalling = false
this.fetchResource()
}, 25000)
return
}
percentLoaded = res.percentLoaded
}
this.status = res
this.status = res
if(this.status.status === 'DOWNLOADED'){
await this.fetchResource()
}
}
if (this.status.status === 'DOWNLOADED') {
await this.fetchResource()
}
}
// check if progress is 100% and clear interval if true
if (res.status === 'READY') {
const rawData = await this.getRawData()
const object = {
...this.resource.schema.display
}
this.updateDisplayWithPlaceholders(object, {},rawData.data)
this.feedItem = object
clearInterval(intervalId)
this.status = res
this.isReady = true
}
}, 5000) // 1 second interval
}
// check if progress is 100% and clear interval if true
if (res.status === 'READY') {
const rawData = await this.getRawData()
const object = {
...this.resource.schema.display
}
this.updateDisplayWithPlaceholders(object, {}, rawData.data)
this.feedItem = object
clearInterval(intervalId)
this.status = res
this.isReady = true
}
}, 5000) // 5 second interval
}
async _fetchImage() {
try {
await this.fetchVideoUrl()
await this.fetchStatus()
} catch (error) { /* empty */ }
}
async _fetchImage() {
try {
await this.fetchVideoUrl()
await this.fetchStatus()
} catch (error) { /* empty */ }
}
firstUpdated(){
this.observer.observe(this);
async goToFeedLink() {
try {
let newQuery = this.link
if (newQuery.endsWith('/')) {
newQuery = newQuery.slice(0, -1)
}
const res = await this.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}`
}
}
async goToFeedLink(){
try {
let newQuery = this.link
if (newQuery.endsWith('/')) {
newQuery = newQuery.slice(0, -1)
}
const res = await this.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}`
}
store.dispatch(setNewTab({
store.dispatch(setNewTab({
url: `qdn/browser/index.html${query}`,
id: this.uid.rnd(),
myPlugObj: {
@ -373,137 +289,72 @@ getMyNode(){
"menus": [],
"parent": false
},
openExisting: true
openExisting: true
}))
} catch (error) {
console.log({error})
}
}
} catch (error) {
console.log({ error })
}
}
async extractComponents(url) {
if (!url.startsWith("qortal://")) {
return null
}
url = url.replace(/^(qortal\:\/\/)/, "")
async extractComponents(url) {
if (!url.startsWith("qortal://")) {
return null
}
if (url.includes("/")) {
let parts = url.split("/")
const service = parts[0].toUpperCase()
parts.shift()
const name = parts[0]
parts.shift()
let identifier
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
if (parts.length > 0) {
identifier = parts[0] // Do not shift yet
// Check if a resource exists with this service, name and identifier combination
const myNode = store.getState().app.nodeConfig.knownNodes[store.getState().app.nodeConfig.node]
const nodeUrl = myNode.protocol + '://' + myNode.domain + ':' + myNode.port
const url = `${nodeUrl}/arbitrary/resource/status/${service}/${name}/${identifier}?apiKey=${myNode.apiKey}}`
if (parts.length > 0) {
identifier = parts[0] // Do not shift yet
// Check if a resource exists with this service, name and identifier combination
const myNode = store.getState().app.nodeConfig.knownNodes[store.getState().app.nodeConfig.node]
const nodeUrl = myNode.protocol + '://' + myNode.domain + ':' + myNode.port
const url = `${nodeUrl}/arbitrary/resource/status/${service}/${name}/${identifier}?apiKey=${myNode.apiKey}}`
const res = await fetch(url);
const data = await res.json();
if (data.totalChunkCount > 0) {
// Identifier exists, so don't include it in the path
parts.shift()
}
else {
identifier = null
}
}
const res = await fetch(url);
const data = await res.json();
if (data.totalChunkCount > 0) {
// Identifier exists, so don't include it in the path
parts.shift()
}
else {
identifier = null
}
}
const path = parts.join("/")
const path = parts.join("/")
const components = {}
components["service"] = service
components["name"] = name
components["identifier"] = identifier
components["path"] = path
return components
}
return null
}
const components = {}
components["service"] = service
components["name"] = name
components["identifier"] = identifier
components["path"] = path
return components
}
return null
// 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)
}
}
render() {
let avatarImg
const avatarUrl = `${this.nodeUrl}/arbitrary/THUMBNAIL/${this.resource.name}/qortal_avatar?async=true&apiKey=${this.myNode.apiKey}`;
avatarImg = html`<img
src="${avatarUrl}"
style="width:100%; height:100%;"
onerror="this.onerror=null; this.src='/img/incognito.png';"
/>`;
let avatarImgApp
const avatarUrl2 = `${this.nodeUrl}/arbitrary/THUMBNAIL/${this.appName}/qortal_avatar?async=true&apiKey=${this.myNode.apiKey}`;
avatarImgApp = html`<img
src="${avatarUrl2}"
style="width:100%; height:100%;"
onerror="this.onerror=null; this.src='/img/incognito.png';"
/>`;
return html`
<div
class=${[
`image-container`,
this.status.status !== 'READY'
? 'defaultSize'
: '',
this.status.status !== 'READY'
? 'hideImg'
: '',
].join(' ')}
style=" box-sizing: border-box;"
>
${
this.status.status !== 'READY'
? html`
<div
style="display:flex;flex-direction:column;width:100%;height:100%;justify-content:center;align-items:center; box-sizing: border-box;"
>
<div
class=${`smallLoading`}
></div>
<p style="color: var(--black)">${`${Math.round(this.status.percentLoaded || 0
).toFixed(0)}% `}${translate('chatpage.cchange94')}</p>
</div>
`
: ''
}
${this.status.status === 'READY' && this.feedItem ? html`
<div class="parent-feed-item" style="position:relative" @click=${this.goToFeedLink}>
<div style="display:flex;gap:10px;margin-bottom:5px">
<div class="avatar">
${avatarImg}</div> <span class="feed-item-name">${this.resource.name}</span>
</div>
<div>
<p>${this.feedItem.title}</p>
</div>
<div class="app-name">
<div class="avatarApp">
${avatarImgApp}
</div>
<message-time
timestamp=${this
.resource
.created}
></message-time>
</div>
</div>
` : ''}
</div>
`
}
}
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 {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 { 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 { 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'
})
}
}
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)
}
}
customElements.define('friend-item-actions', FriendItemActions);
window.customElements.define('friend-item-actions', FriendItemActions)

View File

@ -1,476 +1,461 @@
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 { 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 {translate} from '../../../translate'
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() {
static get properties() {
return {
feed: {type: Array},
setHasNewFeed: {attribute:false},
isLoading: {type: Boolean},
hasFetched: {type: Boolean},
mySelectedFeeds: {type: Array}
};
}
constructor(){
super()
this.feed = []
this.feedToRender = []
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.elementObserver = this.elementObserver.bind(this)
this.mySelectedFeeds = []
this.getSchemas = this.getSchemas.bind(this)
this.hasFetched = false
this._updateFeeds = this._updateFeeds.bind(this)
feed: { type: Array },
setHasNewFeed: { attribute: false },
isLoading: { type: Boolean },
hasFetched: { type: Boolean },
mySelectedFeeds: { type: Array }
}
}
static get styles() {
return [friendsViewStyles];
return [friendsViewStyles]
}
constructor() {
super()
this.feed = []
this.feedToRender = []
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.elementObserver = this.elementObserver.bind(this)
this.mySelectedFeeds = []
this.getSchemas = this.getSchemas.bind(this)
this.hasFetched = false
this._updateFeeds = this._updateFeeds.bind(this)
}
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) {
_updateFeeds(event) {
this.mySelectedFeeds = event.detail
this.reFetchFeedData()
this.requestUpdate()
this.reFetchFeedData()
this.requestUpdate()
}
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)
super.disconnectedCallback()
}
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 res = await fetch(url)
const data = await res.json()
if(data.error) return false
return data
} catch (error) {
console.log(error);
return false
}
}
);
const res = await Promise.all(getAllSchemas);
return res.filter((item)=> !!item)
}
async getSchemas() {
this.mySelectedFeeds = JSON.parse(localStorage.getItem('friends-my-selected-feeds') || "[]")
const schemas = this.mySelectedFeeds
getFeedOnInterval(){
let interval = null;
let stop = false;
const getAnswer = async () => {
if (!stop) {
stop = true;
const getAllSchemas = (schemas || []).map(
async (schema) => {
try {
await this.reFetchFeedData()
} catch (error) {}
stop = false;
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)
return false
}
}
};
interval = setInterval(getAnswer, 900000);
)
}
async getEndpoints(){
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('&');
if(names.length === 0){
this.endpoints= []
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);
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)
}
const res = await Promise.all(getAllSchemas)
return res.filter((item) => !!item)
}
getMoreFeed(){
if(!this.hasInitialFetch) return
if(this.feedToRender.length === this.feed.length ) return
this.feedToRender = this.feed.slice(0, this.feedToRender.length + 20)
this.requestUpdate()
}
getFeedOnInterval() {
let interval = null
let stop = false
async refresh(){
try {
await this.getEndpoints()
await this.reFetchFeedData()
} catch (error) {
const getAnswer = async () => {
if (!stop) {
stop = true
try {
await this.reFetchFeedData()
} catch (error) { }
stop = false
}
}
}
}
interval = setInterval(getAnswer, 900000)
}
async getEndpoints() {
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('&')
if (names.length === 0) {
this.endpoints = []
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)
if (fullUrl) {
formEndpoints.push({
url: fullUrl, schemaName: schema.name, schema: copyFeedData
})
}
}
})
this.endpoints = formEndpoints
this.endpointOffsets = Array(this.endpoints.length).fill(0)
}
getMoreFeed() {
if (!this.hasInitialFetch) return
if (this.feedToRender.length === this.feed.length) return
this.feedToRender = this.feed.slice(0, this.feedToRender.length + 20)
this.requestUpdate()
}
async refresh() {
try {
await this.getEndpoints()
await this.reFetchFeedData()
} catch (error) {
}
}
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 res = await fetch(url)
const data = await res.json()
return data.map((i)=> {
return {
...this.endpoints[endpointIndex],
...i
}
})
async fetchDataFromEndpoint(endpointIndex, count) {
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()
async initialLoad() {
let results = [];
let totalFetched = 0;
let i = 0;
let madeProgress = true;
let exhaustedEndpoints = new Set();
while (totalFetched < totalDesiredCount && madeProgress) {
madeProgress = false
this.isLoading = true
for (i = 0; i < this.endpoints.length; i++) {
if (exhaustedEndpoints.has(i)) {
continue
}
while (totalFetched < totalDesiredCount && madeProgress) {
madeProgress = false;
this.isLoading = true
for (i = 0; i < this.endpoints.length; i++) {
if (exhaustedEndpoints.has(i)) {
continue;
}
const remainingCount = totalDesiredCount - totalFetched
const remainingCount = totalDesiredCount - totalFetched;
// If we've already reached the desired count, break
if (remainingCount <= 0) {
break;
}
// 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
// Increment the offset for this endpoint by the number of items fetched
this.endpointOffsets[i] += data.length;
if (data.length > 0) {
madeProgress = true
}
if (data.length > 0) {
madeProgress = true;
}
if (data.length < fetchCount) {
exhaustedEndpoints.add(i)
}
if (data.length < fetchCount) {
exhaustedEndpoints.add(i);
}
results = results.concat(data)
totalFetched += data.length
}
results = results.concat(data);
totalFetched += data.length;
}
if (exhaustedEndpoints.size === this.endpoints.length) {
break
}
}
if (exhaustedEndpoints.size === this.endpoints.length) {
break;
}
}
this.isLoading = false
this.hasFetched = true;
// Trim the results if somehow they are over the totalDesiredCount
return results.slice(0, totalDesiredCount);
}
this.isLoading = false
this.hasFetched = true
// Trim the results if somehow they are over the totalDesiredCount
return results.slice(0, totalDesiredCount)
}
trimDataToLimit(data, 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)
}
async addExtraData(data) {
let newData = []
for (let item of data) {
let newItem = {
...item,
schema: {
...item.schema,
customParams: { ...item.schema.customParams }
trimDataToLimit(data, 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);
}
let newResource = {
identifier: newItem.identifier,
service: newItem.service,
name: newItem.name
}
if (newItem.schema) {
const resource = newItem
async addExtraData(data){
let newData = []
for (let item of data) {
let newItem = {
...item,
schema: {
...item.schema,
customParams: {...item.schema.customParams}
}
}
let newResource = {
identifier: newItem.identifier,
service: newItem.service,
name: newItem.name
}
if(newItem.schema){
const resource = newItem
let clickValue1 = newItem.schema.click;
let clickValue1 = newItem.schema.click;
newItem.link = replacePlaceholders(clickValue1, resource, newItem.schema.customParams)
newData.push(newItem)
}
}
return newData
newData.push(newItem)
}
}
return newData
}
}
async reFetchFeedData() {
// Resetting offsets to start fresh.
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();
async reFetchFeedData() {
// Resetting offsets to start fresh.
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()
// Filter out items that are already in the feed
const trulyNewData = newData.filter(item => !oldIdentifiers.has(item.identifier));
// Filter out items that are already in the feed
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);
if (trulyNewData.length > 0) {
// Adding extra data and merging with old data
const enhancedNewData = await this.addExtraData(trulyNewData)
// Merge new data with old data immutably
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;
// Merge new data with old data immutably
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
const created = trulyNewData[0].created;
let value = localStorage.getItem('lastSeenFeed');
if (((+value || 0) < created)) {
this.setHasNewFeed(true);
}
}
}
removeDuplicates(array) {
const seenIds = new Set();
return array.filter(item => {
if (!seenIds.has(item.identifier)) {
seenIds.add(item.identifier);
return true;
}
return false;
});
}
async loadAndMergeData() {
let allData = this.feed
const newData = await this.initialLoad();
allData = await this.addExtraData(newData)
allData = this.mergeData(newData, allData);
allData.sort((a, b) => new Date(b.created) - new Date(a.created)); // Sort by timestamp, most recent first
allData = this.trimDataToLimit(allData, maxResultsInMemory); // Trim to the maximum allowed in memory
allData = this.removeDuplicates(allData)
this.feed = [...allData]
this.feedToRender = this.feed.slice(0,20)
this.hasInitialFetch = true
if(allData.length > 0){
const created = allData[0].created
let value = localStorage.getItem('lastSeenFeed')
const created = trulyNewData[0].created
let value = localStorage.getItem('lastSeenFeed')
if (((+value || 0) < created)) {
this.setHasNewFeed(true)
}
}
}
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>
`;
}
}
removeDuplicates(array) {
const seenIds = new Set()
return array.filter(item => {
if (!seenIds.has(item.identifier)) {
seenIds.add(item.identifier)
return true
}
return false
})
}
async loadAndMergeData() {
let allData = this.feed
const newData = await this.initialLoad();
allData = await this.addExtraData(newData)
allData = this.mergeData(newData, allData);
allData.sort((a, b) => new Date(b.created) - new Date(a.created)); // Sort by timestamp, most recent first
allData = this.trimDataToLimit(allData, maxResultsInMemory); // Trim to the maximum allowed in memory
allData = this.removeDuplicates(allData)
this.feed = [...allData]
this.feedToRender = this.feed.slice(0, 20)
this.hasInitialFetch = true
if (allData.length > 0) {
const created = allData[0].created
let value = localStorage.getItem('lastSeenFeed')
if (((+value || 0) < created)) {
this.setHasNewFeed(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)
}
}
customElements.define('friends-feed', FriendsFeed);
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
return value.replace(pattern, (match, p1) => {
return dynamicVars[p1] !== undefined ? dynamicVars[p1] : match;
});
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
})
}
export function constructUrl(base, search, dynamicVars) {
let queryStrings = [];
for (const [key, value] of Object.entries(search)) {
const substitutedValue = substituteDynamicVar(value, dynamicVars);
queryStrings.push(`${key}=${encodeURIComponent(substitutedValue)}`);
}
return queryStrings.length > 0 ? `${base}&${queryStrings.join('&')}` : base;
let queryStrings = []
for (const [key, value] of Object.entries(search)) {
const substitutedValue = substituteDynamicVar(value, dynamicVars)
queryStrings.push(`${key}=${encodeURIComponent(substitutedValue)}`)
}
return queryStrings.length > 0 ? `${base}&${queryStrings.join('&')}` : base
}
export function replacePlaceholders(template, resource, customParams) {
const dataSource = { resource, customParams };
return template.replace(/\$\$\{(.*?)\}\$\$/g, (match, p1) => {
const keys = p1.split('.');
let value = dataSource;
for (let key of keys) {
if (value[key] !== undefined) {
value = value[key];
} else {
return match; // Return placeholder unchanged
}
}
return value;
});
}
const dataSource = { resource, customParams }
return template.replace(/\$\$\{(.*?)\}\$\$/g, (match, p1) => {
const keys = p1.split('.')
let value = dataSource
for (let key of keys) {
if (value[key] !== undefined) {
value = value[key]
} else {
return match // Return placeholder unchanged
}
}
return value
})
}

View File

@ -1,68 +1,43 @@
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 { friendsSidePanelParentStyles } from '../../styles/core-css'
import './friends-side-panel'
import '@material/mwc-icon'
import './friends-side-panel.js'
import '@vaadin/tooltip'
import {translate} from '../../../translate'
class FriendsSidePanelParent extends LitElement {
class FriendsSidePanelParent extends connect(store)(LitElement) {
static get properties() {
return {
isOpen: {type: Boolean},
hasNewFeed: {type: Boolean}
isOpen: { type: Boolean },
hasNewFeed: { type: Boolean }
}
}
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
id="friends-icon"
@click=${()=> {
@click=${() => {
this.isOpen = !this.isOpen
if(this.isOpen && this.hasNewFeed) {
if (this.isOpen && this.hasNewFeed) {
localStorage.setItem('lastSeenFeed', Date.now())
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>
<friends-side-panel .setHasNewFeed=${(val)=> this.setHasNewFeed(val)} ?isOpen=${this.isOpen} .setIsOpen=${(val)=> this.isOpen = val}></friends-side-panel>
></vaadin-tooltip>
<friends-side-panel .setHasNewFeed=${(val) => this.setHasNewFeed(val)} ?isOpen=${this.isOpen} .setIsOpen=${(val) => this.isOpen = val}></friends-side-panel>
`
}
firstUpdated() {
// ...
}
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)
}
}
customElements.define('friends-side-panel-parent', FriendsSidePanelParent)
window.customElements.define('friends-side-panel-parent', FriendsSidePanelParent)

View File

@ -1,158 +1,94 @@
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 {
static get properties() {
class FriendsSidePanel extends connect(store)(LitElement) {
static get properties() {
return {
setIsOpen: { attribute: false},
isOpen: {type: Boolean},
selected: {type: String},
setHasNewFeed: {attribute: false},
closeSidePanel: {attribute: false, type: Object},
openSidePanel: {attribute: false, type: Object}
};
setIsOpen: { attribute: false },
isOpen: { type: Boolean },
selected: { type: String },
setHasNewFeed: { attribute: false },
closeSidePanel: { attribute: false, type: Object },
openSidePanel: { attribute: false, type: Object }
}
}
constructor(){
static get styles() {
return [friendsSidePanelStyles]
}
constructor() {
super()
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() {
return html`
<div class="parent">
<div class="header">
<div style="display:flex;align-items:center;gap:10px">
<span @click=${()=> this.selected = 'friends'} class="${this.selected === 'friends' ? 'active' : 'default'}">${translate('friends.friend12')}</span>
<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>
</div>
</div>
<div class="content">
<div class="${this.selected === 'friends' ? 'active-content' : 'default-content'}">
<friends-view .openSidePanel=${this.openSidePanel} .closeSidePanel=${this.closeSidePanel} .refreshFeed=${()=>this.refreshFeed()}></friends-view>
<div class="header">
<div style="display:flex;align-items:center;gap:10px">
<span @click=${() => this.selected = 'friends'} class="${this.selected === 'friends' ? 'active' : 'default'}">${translate('friends.friend12')}</span>
<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>
</div>
</div>
<div class="${this.selected === 'feed' ? 'active-content' : 'default-content'}">
<friends-feed .setHasNewFeed=${(val)=> this.setHasNewFeed(val)}></friends-feed>
<div class="content">
<div class="${this.selected === 'friends' ? 'active-content' : 'default-content'}">
<friends-view .openSidePanel=${this.openSidePanel} .closeSidePanel=${this.closeSidePanel} .refreshFeed=${() => this.refreshFeed()}></friends-view>
</div>
<div class="${this.selected === 'feed' ? 'active-content' : 'default-content'}">
<friends-feed .setHasNewFeed=${(val) => this.setHasNewFeed(val)}></friends-feed>
</div>
</div>
</div>
</div>
</div>
`;
`
}
firstUpdated() {
// ...
}
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)
}
}
customElements.define('friends-side-panel', FriendsSidePanel);
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() {
@ -29,94 +27,159 @@ class FriendsView extends connect(store)(LitElement) {
setUserName: { attribute: false },
friendList: { type: Array },
userSelected: { type: Object },
isLoading: {type: Boolean},
userFoundModalOpen: {type: Boolean},
userFound: { type: Array},
isOpenAddFriendsModal: {type: Boolean},
editContent: {type: Object},
mySelectedFeeds: {type: Array},
refreshFeed: {attribute: false},
closeSidePanel: {attribute: false, type: Object},
openSidePanel: {attribute:false, type: Object}
};
isLoading: { type: Boolean },
userFoundModalOpen: { type: Boolean },
userFound: { type: Array },
isOpenAddFriendsModal: { type: Boolean },
editContent: { type: Object },
mySelectedFeeds: { type: Array },
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)
}
render() {
return html`
<div class="container">
<div id="viewElement" class="container-body" style=${"position: relative"}>
<p class="group-name">My Friends</p>
<div class="search-field">
<input
type="text"
class="name-input"
?disabled=${this.isLoading}
id="sendTo"
placeholder="${translate("friends.friend1")}"
value=${this.userSelected.name ? this.userSelected.name : ''}
@keypress=${(e) => {
if (e.key === 'Enter') {
this.userSearch()
}
}}
>
<vaadin-icon
@click=${this.userSearch}
slot="icon"
icon="vaadin:search"
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;
}}
.closeFunc=${() => {
this.userFoundModalOpen = false;
this.userFound = [];
}}
.searchResults=${this.userFound}
?isOpen=${this.userFoundModalOpen}
?loading=${this.isLoading}
>
</chat-search-results>
</div>
${this.friendList.map((item) => {
return html`
<chat-side-nav-heads
activeChatHeadUrl=""
.setActiveChatHeadUrl=${(val) => { }}
.chatInfo=${item}
.openEditFriend=${(val) => this.openEditFriend(val)}
.closeSidePanel=${this.closeSidePanel}
>
</chat-side-nav-heads>
`
})}
<div id="downObserver"></div>
</div>
</div>
<add-friends-modal
?isOpen=${this.isOpenAddFriendsModal}
.setIsOpen=${(val) => {
this.isOpenAddFriendsModal = val
}}
.userSelected=${this.userSelected}
.onSubmit=${(val, isRemove) => this.addToFriendList(val, isRemove)}
.editContent=${this.editContent}
.onClose=${() => this.onClose()}
.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[
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]
}
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') || "[]")
}
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;
_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
@ -134,7 +197,6 @@ class FriendsView extends connect(store)(LitElement) {
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()
}
@ -142,18 +204,21 @@ class FriendsView extends connect(store)(LitElement) {
const options = {
root: this.viewElement,
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) {
@ -163,112 +228,121 @@ class FriendsView extends connect(store)(LitElement) {
if (this.friendList.length < 20) {
return;
}
this.getMoreFriends();
this.getMoreFriends()
}
}
async userSearch() {
const nameValue = this.shadowRoot.getElementById('sendTo').value
if(!nameValue) {
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 = []
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}`)
} 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
]
async myFollowName(name) {
let items = [
name
]
let namesJsonString = JSON.stringify({ "items": items })
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}`
})
}
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 })
async unFollowName(name) {
let items = [
name
]
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}
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){
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) {
if (!copyVal.willFollow || isRemove) {
await this.unFollowName(copyVal.name)
} else if(copyVal.willFollow){
} else if (copyVal.willFollow) {
await this.myFollowName(copyVal.name)
}
this.setMySelectedFeeds(val.mySelectedFeeds)
await new Promise((res)=> {
setTimeout(()=> {
await new Promise((res) => {
setTimeout(() => {
res()
},50)
}, 50)
})
this.userSelected = {};
this.userSelected = {}
this.shadowRoot.getElementById('sendTo').value = ''
this.isLoading = false;
this.isLoading = false
this.isOpenAddFriendsModal = false
this.editContent = null
this.setMyFriends(this.friendList)
if(!isRemove && this.friendList.length === 1){
if (!isRemove && this.friendList.length === 1) {
this.refreshFeed()
}
}
setMyFriends(friendList){
setMyFriends(friendList) {
localStorage.setItem('friends-my-friend-list', JSON.stringify(friendList));
const tempSettingsData= JSON.parse(localStorage.getItem('temp-settings-data') || "{}")
const tempSettingsData = JSON.parse(localStorage.getItem('temp-settings-data') || "{}")
const newTemp = {
...tempSettingsData,
userLists: {
@ -277,18 +351,20 @@ class FriendsView extends connect(store)(LitElement) {
}
}
localStorage.setItem('temp-settings-data', JSON.stringify(newTemp));
localStorage.setItem('temp-settings-data', JSON.stringify(newTemp))
this.dispatchEvent(
new CustomEvent('temp-settings-data-event', {
bubbles: true,
composed: true
}),
);
bubbles: true,
composed: true
})
)
}
setMySelectedFeeds(mySelectedFeeds){
setMySelectedFeeds(mySelectedFeeds) {
this.mySelectedFeeds = mySelectedFeeds
const tempSettingsData= JSON.parse(localStorage.getItem('temp-settings-data') || "{}")
const tempSettingsData = JSON.parse(localStorage.getItem('temp-settings-data') || "{}")
const newTemp = {
...tempSettingsData,
friendsFeed: {
@ -297,98 +373,37 @@ class FriendsView extends connect(store)(LitElement) {
}
}
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}
localStorage.setItem('temp-settings-data', JSON.stringify(newTemp))
localStorage.setItem('friends-my-selected-feeds', JSON.stringify(mySelectedFeeds))
}
onClose(){
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() {
return html`
<div class="container">
<div id="viewElement" class="container-body" style=${"position: relative"}>
<p class="group-name">My Friends</p>
<div class="search-field">
<input
type="text"
class="name-input"
?disabled=${this.isLoading}
id="sendTo"
placeholder="${translate("friends.friend1")}"
value=${this.userSelected.name ? this.userSelected.name: ''}
@keypress=${(e) => {
if(e.key === 'Enter'){
this.userSearch()
}
}}
/>
// Standard functions
getApiKey() {
const coreNode = store.getState().app.nodeConfig.knownNodes[store.getState().app.nodeConfig.node]
return coreNode.apiKey
}
<vaadin-icon
@click=${this.userSearch}
slot="icon"
icon="vaadin:search"
class="search-icon">
</vaadin-icon>
isEmptyArray(arr) {
if (!arr) { return true }
return arr.length === 0
}
</div>
<div class="search-results-div">
<chat-search-results
.onClickFunc=${(result) => {
this.userSelected = result;
this.isOpenAddFriendsModal = true
this.userFound = [];
this.userFoundModalOpen = false;
}}
.closeFunc=${() => {
this.userFoundModalOpen = false;
this.userFound = [];
}}
.searchResults=${this.userFound}
?isOpen=${this.userFoundModalOpen}
?loading=${this.isLoading}>
</chat-search-results>
</div>
${this.friendList.map((item) => {
return html`<chat-side-nav-heads
activeChatHeadUrl=""
.setActiveChatHeadUrl=${(val) => {
}}
.chatInfo=${item}
.openEditFriend=${(val)=> this.openEditFriend(val)}
.closeSidePanel=${this.closeSidePanel}
></chat-side-nav-heads>`;
})}
<div id="downObserver"></div>
</div>
</div>
<add-friends-modal
?isOpen=${this.isOpenAddFriendsModal}
.setIsOpen=${(val)=> {
this.isOpenAddFriendsModal = val
}}
.userSelected=${this.userSelected}
.onSubmit=${(val, isRemove)=> this.addToFriendList(val, isRemove)}
.editContent=${this.editContent}
.onClose=${()=> this.onClose()}
.mySelectedFeeds=${this.mySelectedFeeds}
>
</add-friends-modal>
`;
round(number) {
return (Math.round(parseFloat(number) * 1e8) / 1e8).toFixed(8)
}
}
customElements.define('friends-view', FriendsView);
window.customElements.define('friends-view', FriendsView)

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,125 +1,88 @@
import {css, html, LitElement} from 'lit'
import {registerTranslateConfig, translate, use} from '../../translate'
import { html, LitElement } from 'lit'
import { languageSelectorStyles } from '../styles/core-css'
// Multi language support
import { registerTranslateConfig, translate, use } from '../../translate'
registerTranslateConfig({
loader: lang => fetch(`/language/${lang}.json`).then(res => res.json())
loader: lang => fetch(`/language/${lang}.json`).then(res => res.json())
})
const checkLanguage = localStorage.getItem('qortalLanguage')
if (checkLanguage === null || checkLanguage.length === 0) {
localStorage.setItem('qortalLanguage', 'us')
use('us')
localStorage.setItem('qortalLanguage', 'us')
use('us')
} else {
use(checkLanguage)
use(checkLanguage)
}
class LanguageSelector extends LitElement {
static get properties() {
return {
theme: { type: String, reflect: true }
}
}
static get properties() {
return {
theme: { type: String, reflect: true }
}
}
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;
}
static get styles() {
return [languageSelectorStyles]
}
*:focus {
outline: none;
}
constructor() {
super()
this.theme = localStorage.getItem('qortalTheme') ? localStorage.getItem('qortalTheme') : 'light'
}
select option {
color: var(--black);
background: var(--white);
line-height: 34px;
}
`
]
}
render() {
return html`
<div style="display: inline;">
<select id="languageSelect" @change="${this.changeLanguage}">
<option value="us">US - ${translate("selectmenu.english")}</option>
<option value="de">DE - ${translate("selectmenu.german")}</option>
<option value="es">ES - ${translate("selectmenu.spanish")}</option>
<option value="et">ET - ${translate("selectmenu.estonian")}</option>
<option value="fi">FI - ${translate("selectmenu.finnish")}</option>
<option value="fr">FR - ${translate("selectmenu.french")}</option>
<option value="hr">HR - ${translate("selectmenu.croatian")}</option>
<option value="hu">HU - ${translate("selectmenu.hungarian")}</option>
<option value="hindi">IN - ${translate("selectmenu.hindi")}</option>
<option value="it">IT - ${translate("selectmenu.italian")}</option>
<option value="jp">JP - ${translate("selectmenu.japanese")}</option>
<option value="ko">KO - ${translate("selectmenu.korean")}</option>
<option value="nl">NL - ${translate("selectmenu.dutch")}</option>
<option value="no">NO - ${translate("selectmenu.norwegian")}</option>
<option value="pl">PL - ${translate("selectmenu.polish")}</option>
<option value="pt">PT - ${translate("selectmenu.portuguese")}</option>
<option value="rs">RS - ${translate("selectmenu.serbian")}</option>
<option value="ro">RO - ${translate("selectmenu.romanian")}</option>
<option value="ru">RU - ${translate("selectmenu.russian")}</option>
<option value="zht">ZHT - ${translate("selectmenu.chinese2")}</option>
<option value="zhc">ZHC - ${translate("selectmenu.chinese1")}</option>
</select>
</div>
`
}
constructor() {
super()
this.theme = localStorage.getItem('qortalTheme') ? localStorage.getItem('qortalTheme') : 'light'
}
firstUpdated() {
const myElement = this.shadowRoot.getElementById('languageSelect')
render() {
return html`
<div style="display: inline;">
<select id="languageSelect" @change="${this.changeLanguage}">
<option value="us">US - ${translate("selectmenu.english")}</option>
<option value="de">DE - ${translate("selectmenu.german")}</option>
<option value="es">ES - ${translate("selectmenu.spanish")}</option>
<option value="et">ET - ${translate("selectmenu.estonian")}</option>
<option value="fi">FI - ${translate("selectmenu.finnish")}</option>
<option value="fr">FR - ${translate("selectmenu.french")}</option>
<option value="hr">HR - ${translate("selectmenu.croatian")}</option>
<option value="hu">HU - ${translate("selectmenu.hungarian")}</option>
<option value="hindi">IN - ${translate("selectmenu.hindi")}</option>
<option value="it">IT - ${translate("selectmenu.italian")}</option>
<option value="jp">JP - ${translate("selectmenu.japanese")}</option>
<option value="ko">KO - ${translate("selectmenu.korean")}</option>
<option value="nl">NL - ${translate("selectmenu.dutch")}</option>
<option value="no">NO - ${translate("selectmenu.norwegian")}</option>
<option value="pl">PL - ${translate("selectmenu.polish")}</option>
<option value="pt">PT - ${translate("selectmenu.portuguese")}</option>
<option value="rs">RS - ${translate("selectmenu.serbian")}</option>
<option value="ro">RO - ${translate("selectmenu.romanian")}</option>
<option value="ru">RU - ${translate("selectmenu.russian")}</option>
<option value="zht">ZHT - ${translate("selectmenu.chinese2")}</option>
<option value="zhc">ZHC - ${translate("selectmenu.chinese1")}</option>
</select>
</div>
`
}
myElement.addEventListener('change', () => {
this.selectElement()
})
firstUpdated() {
const myElement = this.shadowRoot.getElementById('languageSelect')
this.selectElement()
}
myElement.addEventListener("change", () => {
this.selectElement()
})
selectElement() {
const selectedLanguage = localStorage.getItem('qortalLanguage')
let element = this.shadowRoot.getElementById('languageSelect')
element.value = selectedLanguage
}
this.selectElement()
}
selectElement() {
const selectedLanguage = localStorage.getItem('qortalLanguage')
let element = this.shadowRoot.getElementById('languageSelect')
element.value = selectedLanguage
}
changeLanguage(event) {
use(event.target.value)
localStorage.setItem('qortalLanguage', event.target.value)
}
changeLanguage(event) {
use(event.target.value)
localStorage.setItem('qortalLanguage', event.target.value)
}
}
window.customElements.define('language-selector', LanguageSelector)
window.customElements.define('language-selector', LanguageSelector)

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,20 +1,7 @@
import {css, 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 { html, LitElement } from 'lit'
import { connect } from 'pwa-helpers'
import { store } from '../../store'
import { stateAwait } from '../../stateAwait'
import {
addAutoLoadImageChat,
addChatLastSeen,
@ -32,352 +19,388 @@ 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,
addChatLastSeen: addChatLastSeen,
allowQAPPAutoAuth: allowQAPPAutoAuth,
removeQAPPAutoAuth: removeQAPPAutoAuth,
allowQAPPAutoLists: allowQAPPAutoLists,
removeQAPPAutoLists: removeQAPPAutoLists,
addTabInfo: addTabInfo,
setTabNotifications: setTabNotifications,
setNewTab: setNewTab,
setNewNotification: setNewNotification,
setSideEffectAction: setSideEffectAction,
allowQAPPAutoFriendsList: allowQAPPAutoFriendsList,
removeQAPPAutoFriendsList: removeQAPPAutoFriendsList,
allowShowSyncIndicator: allowShowSyncIndicator,
removeShowSyncIndicator: removeShowSyncIndicator
addAutoLoadImageChat: addAutoLoadImageChat,
removeAutoLoadImageChat: removeAutoLoadImageChat,
addChatLastSeen: addChatLastSeen,
allowQAPPAutoAuth: allowQAPPAutoAuth,
removeQAPPAutoAuth: removeQAPPAutoAuth,
allowQAPPAutoLists: allowQAPPAutoLists,
removeQAPPAutoLists: removeQAPPAutoLists,
addTabInfo: addTabInfo,
setTabNotifications: setTabNotifications,
setNewTab: setNewTab,
setNewNotification: setNewNotification,
setSideEffectAction: setSideEffectAction,
allowQAPPAutoFriendsList: allowQAPPAutoFriendsList,
removeQAPPAutoFriendsList: removeQAPPAutoFriendsList,
allowShowSyncIndicator: allowShowSyncIndicator,
removeShowSyncIndicator: removeShowSyncIndicator
}
const animationDuration = 0.7 // Seconds
class LoginView extends connect(store)(LitElement) {
static get properties() {
return {
loggedIn: { type: Boolean },
selectedPage: { type: String },
pages: { type: Object },
rippleIsOpen: { type: Boolean },
config: { type: Object },
rippleLoadingMessage: { type: String },
selectedPageElement: {},
nodeConfig: { type: Object },
theme: { type: String, reflect: true }
}
}
static get properties() {
return {
loggedIn: { type: Boolean },
selectedPage: { type: String },
pages: { type: Object },
rippleIsOpen: { type: Boolean },
config: { type: Object },
rippleLoadingMessage: { type: String },
selectedPageElement: {},
nodeConfig: { type: Object },
theme: { type: String, reflect: true }
}
}
static get styles() {
return [
css``
]
}
constructor() {
super()
this.selectedPage = this.getPreSelectedPage()
this.selectedPageElement = {}
this.rippleIsOpen = false
this.pages = {
welcome: 0,
'create-account': 1,
login: 2
}
this.rippleLoadingMessage = 'Getting information'
this.theme = localStorage.getItem('qortalTheme') ? localStorage.getItem('qortalTheme') : 'light'
}
getPreSelectedPage() {
return 'welcome'
}
render() {
return html`
<style>
constructor() {
super()
this.selectedPage = this.getPreSelectedPage()
this.selectedPageElement = {}
this.rippleIsOpen = false
this.pages = {
welcome: 0,
'create-account': 1,
login: 2
}
this.rippleLoadingMessage = 'Getting information'
this.theme = localStorage.getItem('qortalTheme') ? localStorage.getItem('qortalTheme') : 'light'
}
canvas {
display: block;
vertical-align: bottom;
}
firstUpdated() {
.login-page {
background: var(--background);
background-repeat: no-repeat;
background-attachment: fixed;
background-position: center;
height: var(--window-height);
width: 100vw;
max-width: 100vw;
max-height: var(--window-height);
position: absolute;
top: 0;
left: 0;
z-index: 1;
}
stateAwait(state => {
return 'primary' in state.config.styles.theme.colors
}).catch(e => console.error(e))
.login-card-container {
max-width: 1240px;
max-height: var(--window-height);
margin-right: auto;
margin-left: auto;
width: calc(100vw);
}
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', () => {
.qortal-logo {
margin-left: auto;
margin-right: auto;
width: 200px;
max-width: 40%;
z-index: 1;
}
if (!loginContainerPages.selectedItem) {
.login-card-center-container {
max-width: 100%;
max-height: var(--window-height);
display: flex;
align-items: center;
justify-content: center;
height: var(--window-height);
overflow: hidden;
}
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(() => {
#loginContainerPages {
display: inline;
}
this.selectedPageElement = loginContainerPages.selectedItem
#loginContainerPages [page] {
background: none;
padding: 0;
}
this.selectedPageElement.addEventListener('navigate', navigate)
this.selectedPageElement.addEventListener('updatedProperty', updatedProperty)
setTimeout(() => loginCard.classList.remove('animated'), animationDuration * 1000)
}, 1)
}
})
}
.login-card {
min-width: 340px;
border-bottom: 2px solid var(--mdc-theme-primary);
border-top: 2px solid var(--mdc-theme-primary);
text-align: center;
z-index: 0;
padding: 0;
border: 0;
border-radius: 4px;
}
render() {
return html`
<style>
canvas {
display: block;
vertical-align: bottom;
}
.login-card p {
margin-top: 0;
font-size: 1rem;
font-style: italic;
}
.login-page {
background: var(--background);
background-repeat: no-repeat;
background-attachment: fixed;
background-position: center;
height: var(--window-height);
width:100vw;
max-width:100vw;
max-height:var(--window-height);
position:absolute;
top:0;
left:0;
z-index:1;
}
.login-card h1 {
margin-bottom: 12px;
font-size: 64px;
}
.login-card-container {
max-width:1240px;
max-height:var(--window-height);
margin-right: auto;
margin-left: auto;
width: calc(100vw);
}
.login-card h5 {
margin-top: -16px;
margin-left: 100px;
font-size: 14px;
color: var(--black);
}
.qortal-logo {
margin-left: auto;
margin-right: auto;
width:200px;
max-width:40%;
z-index:1;
}
.login-card h6 {
font-size: 12px;
color: var(--mdc-theme-primary);
}
.login-card-center-container {
max-width:100%;
max-height:var(--window-height);
display: flex;
align-items: center;
justify-content: center;
height: var(--window-height);
overflow:hidden;
}
.login-card iron-pages {
height: 100%;
margin-top: -16px;
}
#loginContainerPages {
display:inline;
}
.backButton {
padding-top: 18px;
text-align: center;
}
#loginContainerPages [page] {
background: none;
padding:0;
}
#login-pages-nav {
text-align: left;
padding: 12px 0 8px 0;
}
.login-card {
min-width: 340px;
border-bottom: 2px solid var(--mdc-theme-primary);
border-top: 2px solid var(--mdc-theme-primary);
text-align:center;
z-index:0;
padding:0;
border: 0;
border-radius: 4px;
}
#nav-next {
float: right;
}
.login-card p {
margin-top: 0;
font-size: 1rem;
font-style: italic;
}
@media only screen and (min-width: ${getComputedStyle(document.body).getPropertyValue('--layout-breakpoint-tablet')}) {
.login-card h1 {
margin-bottom:12px;
font-size:64px;
}
/* Desktop/tablet */
.login-card {
max-width: 460px;
}
.login-card h5 {
margin-top: -16px;
margin-left: 100px;
font-size: 14px;
color: var(--black);
}
#loginContainerPages [page] {
border-radius: 4px;
}
.login-card h6 {
font-size: 12px;
color: var(--mdc-theme-primary);
}
#loginContainerPages [page="welcome"] {}
}
.login-card iron-pages {
height:100%;
margin-top: -16px;
}
@media only screen and (max-width: ${getComputedStyle(document.body).getPropertyValue('--layout-breakpoint-tablet')}) {
.backButton {
padding-top:18px;
text-align:center;
}
/* Mobile */
.qortal-logo {
display: none;
visibility: hidden;
}
#login-pages-nav {
text-align: left;
padding: 12px 0 8px 0;
}
.login-card {
width: 100%;
margin: 0;
top: 0;
max-width: 100%;
}
#nav-next {
float: right;
}
.backButton {
text-align: left;
padding-left: 12px;
}
@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"] {
}
}
.login-card h5 {
margin-top: 0px;
margin-left: 0px;
font-size: 14px;
color: var(--black);
}
}
@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;
font-size: 14px;
color: var(--black);
}
}
@keyframes fade {
from {
opacity: 0;
transform: translateX(-20%);
}
@keyframes fade {
from {
opacity: 0;
transform: translateX(-20%);
}
to {
opacity: 1;
transform: translateX(0);
}
}
to {
opacity: 1;
transform: translateX(0);
}
}
@keyframes grow-up {
from {
overflow:hidden;
max-height:0;
}
to {
overflow:hidden;
max-height:var(--window-height);
}
}
@keyframes grow-up {
from {
overflow: hidden;
max-height: 0;
}
iron-pages .animated, .animated {
animation-duration: ${animationDuration}s;
animation-name: grow-up;
}
to {
overflow: hidden;
max-height: var(--window-height);
}
}
div[page] > paper-icon-button {
margin:12px;
}
iron-pages .animated,
.animated {
animation-duration: ${animationDuration}s;
animation-name: grow-up;
}
.corner-box {
border-color: var(--mdc-theme-primary) !important;
}
div[page]>paper-icon-button {
margin: 12px;
}
[hidden] {
visibility: hidden;
display: none;
}
</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>
<div class="login-card-container">
<div class="login-card-center-container">
<div class="login-card" id="login-card">
<img class="qortal-logo" src="${this.config.coin.logo}">
<h5 ?hidden="${this.selectedPage != "welcome"}">UI: v${this.nodeConfig.version ? this.nodeConfig.version : ''}</h5>
${this.renderSelectedNodeOnStart()}
<iron-pages selected="${this.selectedPage}" attr-for-selected="page" id="loginContainerPages">
<welcome-page @next=${e => this.selectedPageElement.next(e)} page="welcome"></welcome-page>
<create-account-section @next=${e => this.selectedPageElement.next(e)} page="create-account"></create-account-section>
<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>
<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>
</div>
</div>
</div>
`
}
.corner-box {
border-color: var(--mdc-theme-primary) !important;
}
renderSelectedNodeOnStart() {
const selectedNodeIndexOnStart = localStorage.getItem('mySelectedNode')
const catchSavedNodes = JSON.parse(localStorage.getItem('myQortalNodes'))
const selectedNodeOnStart = catchSavedNodes[selectedNodeIndexOnStart]
const selectedNameOnStart = `${selectedNodeOnStart.name}`
const selectedNodeUrlOnStart = `${selectedNodeOnStart.protocol + '://' + selectedNodeOnStart.domain +':' + selectedNodeOnStart.port}`
[hidden] {
visibility: hidden;
display: none;
}
</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>
<div class="login-card-container">
<div class="login-card-center-container">
<div class="login-card" id="login-card">
<img class="qortal-logo" src="${this.config.coin.logo}">
<h5 ?hidden="${this.selectedPage != "welcome"}">UI: v${this.nodeConfig.version ? this.nodeConfig.version : ''}</h5>
${this.renderSelectedNodeOnStart()}
<iron-pages selected="${this.selectedPage}" attr-for-selected="page" id="loginContainerPages">
<welcome-page @next=${e => this.selectedPageElement.next(e)} page="welcome"></welcome-page>
<create-account-section @next=${e => this.selectedPageElement.next(e)} page="create-account"></create-account-section>
<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>
<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>
</div>
</div>
</div>
`
}
let connectString = get('settings.snack2')
firstUpdated() {
stateAwait(state => {
return 'primary' in state.config.styles.theme.colors
}).catch(e => console.error(e))
return html`<h6>${connectString} : ${selectedNameOnStart} ${selectedNodeUrlOnStart}</h6>`
}
const loginContainerPages = this.shadowRoot.querySelector('#loginContainerPages')
const loginCard = this.shadowRoot.querySelector('#login-card')
selectPage(newPage) {
this.selectedPage = newPage
}
const navigate = e => {
this.selectPage(e.detail.page)
}
stateChanged(state) {
if (this.loggedIn && !state.app.loggedIn) this.cleanup()
this.loggedIn = state.app.loggedIn
this.config = state.config
this.nodeConfig = state.app.nodeConfig
}
const updatedProperty = e => {
const selectedPageElement = this.selectedPageElement
cleanup() {
this.selectedPage = 'welcome'
}
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'))
const selectedNodeOnStart = catchSavedNodes[selectedNodeIndexOnStart]
const selectedNameOnStart = `${selectedNodeOnStart.name}`
const selectedNodeUrlOnStart = `${selectedNodeOnStart.protocol + '://' + selectedNodeOnStart.domain + ':' + selectedNodeOnStart.port}`
let connectString = get('settings.snack2')
return html`<h6>${connectString} : ${selectedNameOnStart} ${selectedNodeUrlOnStart}</h6>`
}
selectPage(newPage) {
this.selectedPage = newPage
}
stateChanged(state) {
if (this.loggedIn && !state.app.loggedIn) this.cleanup()
this.loggedIn = state.app.loggedIn
this.config = state.config
this.nodeConfig = state.app.nodeConfig
}
cleanup() {
this.selectedPage = 'welcome'
}
}
window.customElements.define('login-view', LoginView)
window.customElements.define('login-view', LoginView)

View File

@ -1,76 +1,44 @@
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 properties() {
return {
hideNav: { type: Boolean, notify: true },
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);
}
static get styles() {
return [welcomePageStyles]
}
.button-outline {
margin: 6px;
width: 90%;
max-width:90vw;
border-top: 0;
border-bottom: 0;
}
constructor() {
super()
this.hideNav = true
this.theme = localStorage.getItem('qortalTheme') ? localStorage.getItem('qortalTheme') : 'light'
}
.welcome-page {
padding: 12px 0;
overflow: hidden;
}
`
}
render() {
return html`
<div class="welcome-page">
<mwc-button class="button-outline" @click=${() => this.navigate('login')} outlined>${translate("login.login")}</mwc-button>
<mwc-button class="button-outline" @click=${() => this.navigate('create-account')} outlined>${translate("login.createaccount")}</mwc-button>
</div>
`
}
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">
<mwc-button class="button-outline" @click=${() => this.navigate('login')} outlined>${translate("login.login")}</mwc-button>
<mwc-button class="button-outline" @click=${() => this.navigate('create-account')} outlined>${translate("login.createaccount")}</mwc-button>
</div>
`
}
back() {}
next() {}
navigate(page) {
this.dispatchEvent(new CustomEvent('navigate', {
detail: { page },
bubbles: true,
composed: true
}))
}
navigate(page) {
this.dispatchEvent(new CustomEvent('navigate', {
detail: { page },
bubbles: true,
composed: true
}))
}
}
window.customElements.define('welcome-page', WelcomePage)
window.customElements.define('welcome-page', WelcomePage)

View File

@ -1,72 +1,61 @@
import {css, 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 { html, LitElement } from 'lit'
import { connect } from 'pwa-helpers'
import { store } from '../../store.js'
import { doLogout } from '../../redux/app/app-actions.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() {
return {
theme: { type: String, reflect: true }
}
}
static get properties() {
return {
theme: { type: String, reflect: true }
}
}
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);
}
static get styles() {
return [logoutViewStyles]
}
.decline {
--mdc-theme-primary: var(--mdc-theme-error)
}
constructor() {
super()
this.theme = localStorage.getItem('qortalTheme') ? localStorage.getItem('qortalTheme') : 'light'
}
.buttons {
text-align:right;
}
`
}
render() {
return html`
<paper-dialog style="background: var(--white);" id="userLogoutDialog" modal>
<div style="text-align: center;">
<h2 style="color: var(--black);">Qortal UI</h2>
<hr>
</div>
<div style="text-align: center;">
<h2 style="color: var(--black);">${translate("logout.confirmlogout")}</h2>
</div>
<div class="buttons">
<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>
`
}
constructor() {
super()
this.theme = localStorage.getItem('qortalTheme') ? localStorage.getItem('qortalTheme') : 'light'
}
openLogout() {
this.shadowRoot.getElementById('userLogoutDialog').open()
}
render() {
return html`
<paper-dialog style="background: var(--white);" id="userLogoutDialog" modal>
<div style="text-align: center;">
<h2 style="color: var(--black);">Qortal UI</h2>
<hr>
</div>
<div style="text-align: center;">
<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='confirm' @click=${e => this.confirm(e)} dialog-confirm autofocus>${translate("general.yes")}</mwc-button>
</div>
</paper-dialog>
`
}
async confirm(e) {
store.dispatch(doLogout())
e.stopPropagation()
}
openLogout() {
this.shadowRoot.getElementById('userLogoutDialog').open()
}
async confirm(e) {
store.dispatch(doLogout())
}
decline(e) {
this.shadowRoot.getElementById('userLogoutDialog').close()
}
decline() {
this.shadowRoot.getElementById('userLogoutDialog').close()
this.requestUpdate()
}
}
window.customElements.define('logout-view', LogoutView)
window.customElements.define('logout-view', LogoutView)

View File

@ -1,77 +1,66 @@
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 { html, LitElement } from 'lit'
import { connect } from 'pwa-helpers'
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)))
class MainApp extends connect(store)(LitElement) {
static get properties() {
return {
name: { type: String },
loggedIn: { type: Boolean }
}
}
static get properties() {
return {
name: { type: String },
loggedIn: { type: Boolean }
}
}
static get styles() {
return []
}
render() {
return html`${this.renderViews(this.loggedIn)}`
}
render() {
return html`${this.renderViews(this.loggedIn)}`
}
connectedCallback() {
super.connectedCallback()
this.initial = 0
/**
* Dynamic renderViews method to introduce conditional rendering of views based on user's logged in state.
* @param {Boolean} isLoggedIn
*/
if (!isElectron()) {
} else {
window.addEventListener('contextmenu', (event) => {
event.preventDefault()
window.electronAPI.showMyMenu()
})
}
}
renderViews(isLoggedIn) {
if (isLoggedIn) {
return html`
<app-view></app-view>
`
} else {
return html`
<login-view></login-view>
`
}
}
/**
* 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()
}
_loadPlugins() {
loadPlugins()
}
connectedCallback() {
super.connectedCallback()
this.initial = 0
if (!isElectron()) {
} else {
window.addEventListener('contextmenu', (event) => {
event.preventDefault()
window.electronAPI.showMyMenu()
})
}
}
stateChanged(state) {
this.loggedIn = state.app.loggedIn
if (this.loggedIn === true && this.initial === 0) {
this.initial = this.initial + 1
this._loadPlugins()
}
document.title = state.config.coin.name
}
}
window.customElements.define('main-app', MainApp)
window.customElements.define('main-app', MainApp)

View File

@ -1,140 +1,114 @@
import {css, html, LitElement} from 'lit'
import {registerTranslateConfig, translate, use} from '../../translate'
import { html, LitElement } from 'lit'
import { newSelectorStyles } from '../styles/core-css'
// Multi language support
import { registerTranslateConfig, translate, use } from '../../translate'
registerTranslateConfig({
loader: lang => fetch(`/language/${lang}.json`).then(res => res.json())
loader: lang => fetch(`/language/${lang}.json`).then(res => res.json())
})
const checkLanguage = localStorage.getItem('qortalLanguage')
if (checkLanguage === null || checkLanguage.length === 0) {
localStorage.setItem('qortalLanguage', 'us')
use('us')
localStorage.setItem('qortalLanguage', 'us')
use('us')
} else {
use(checkLanguage)
use(checkLanguage)
}
class NewSelector extends LitElement {
static get properties() {
return {
theme: { type: String, reflect: true }
}
}
static get properties() {
return {
theme: { type: String, reflect: true }
}
}
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;
}
static get styles() {
return [newSelectorStyles]
}
*:focus {
outline: none;
}
constructor() {
super()
this.theme = localStorage.getItem('qortalTheme') ? localStorage.getItem('qortalTheme') : 'light'
}
select option {
color: var(--black);
background: var(--white);
line-height: 34px;
}
render() {
return html`
<div style="display: inline;">
<span>
<img src="/img/${translate("selectmenu.languageflag")}-flag-round-icon-32.png" style="width: 24px; height: 24px; padding-top: 4px;" @click=${() => this.toggleMenu()}>
</span>
<select id="languageNew" style="display: none;" size="20" tabindex="0" @change="${this.changeLanguage}">
<option value="us">US - ${translate("selectmenu.english")}</option>
<option value="de">DE - ${translate("selectmenu.german")}</option>
<option value="es">ES - ${translate("selectmenu.spanish")}</option>
<option value="et">ET - ${translate("selectmenu.estonian")}</option>
<option value="fi">FI - ${translate("selectmenu.finnish")}</option>
<option value="fr">FR - ${translate("selectmenu.french")}</option>
<option value="hr">HR - ${translate("selectmenu.croatian")}</option>
<option value="hu">HU - ${translate("selectmenu.hungarian")}</option>
<option value="hindi">IN - ${translate("selectmenu.hindi")}</option>
<option value="it">IT - ${translate("selectmenu.italian")}</option>
<option value="jp">JP - ${translate("selectmenu.japanese")}</option>
<option value="ko">KO - ${translate("selectmenu.korean")}</option>
<option value="nl">NL - ${translate("selectmenu.dutch")}</option>
<option value="no">NO - ${translate("selectmenu.norwegian")}</option>
<option value="pl">PL - ${translate("selectmenu.polish")}</option>
<option value="pt">PT - ${translate("selectmenu.portuguese")}</option>
<option value="rs">RS - ${translate("selectmenu.serbian")}</option>
<option value="ro">RO - ${translate("selectmenu.romanian")}</option>
<option value="ru">RU - ${translate("selectmenu.russian")}</option>
<option value="zht">ZHT - ${translate("selectmenu.chinese2")}</option>
<option value="zhc">ZHC - ${translate("selectmenu.chinese1")}</option>
</select>
</div>
select option:hover {
color: var(--white);
background: var(--black);
line-height: 34px;
cursor: pointer;
}
`
]
}
`
}
constructor() {
super()
this.theme = localStorage.getItem('qortalTheme') ? localStorage.getItem('qortalTheme') : 'light'
}
firstUpdated() {
const myElement = this.shadowRoot.getElementById('languageNew')
render() {
return html`
<div style="display: inline;">
<span>
<img src="/img/${translate("selectmenu.languageflag")}-flag-round-icon-32.png" style="width: 24px; height: 24px; padding-top: 4px;" @click=${() => this.toggleMenu()}>
</span>
<select id="languageNew" style="display: none;" size="20" tabindex="0" @change="${this.changeLanguage}">
<option value="us">US - ${translate("selectmenu.english")}</option>
<option value="de">DE - ${translate("selectmenu.german")}</option>
<option value="es">ES - ${translate("selectmenu.spanish")}</option>
<option value="et">ET - ${translate("selectmenu.estonian")}</option>
<option value="fi">FI - ${translate("selectmenu.finnish")}</option>
<option value="fr">FR - ${translate("selectmenu.french")}</option>
<option value="hr">HR - ${translate("selectmenu.croatian")}</option>
<option value="hu">HU - ${translate("selectmenu.hungarian")}</option>
<option value="hindi">IN - ${translate("selectmenu.hindi")}</option>
<option value="it">IT - ${translate("selectmenu.italian")}</option>
<option value="jp">JP - ${translate("selectmenu.japanese")}</option>
<option value="ko">KO - ${translate("selectmenu.korean")}</option>
<option value="nl">NL - ${translate("selectmenu.dutch")}</option>
<option value="no">NO - ${translate("selectmenu.norwegian")}</option>
<option value="pl">PL - ${translate("selectmenu.polish")}</option>
<option value="pt">PT - ${translate("selectmenu.portuguese")}</option>
<option value="rs">RS - ${translate("selectmenu.serbian")}</option>
<option value="ro">RO - ${translate("selectmenu.romanian")}</option>
<option value="ru">RU - ${translate("selectmenu.russian")}</option>
<option value="zht">ZHT - ${translate("selectmenu.chinese2")}</option>
<option value="zhc">ZHC - ${translate("selectmenu.chinese1")}</option>
</select>
</div>
myElement.addEventListener('change', () => {
this.selectElement()
})
`
}
myElement.addEventListener('click', () => {
const element1 = localStorage.getItem('qortalLanguage')
const element2 = this.shadowRoot.getElementById('languageNew').value
firstUpdated() {
const myElement = this.shadowRoot.getElementById('languageNew')
if (element1 === element2) {
myElement.style.display = 'none'
}
})
myElement.addEventListener("change", () => {
this.selectElement()
})
this.selectElement()
}
myElement.addEventListener("click", () => {
const element1 = localStorage.getItem('qortalLanguage')
const element2 = this.shadowRoot.getElementById('languageNew').value
if (element1 === element2) {
myElement.style.display = "none"
}
})
selectElement() {
const selectedLanguage = localStorage.getItem('qortalLanguage')
this.selectElement()
}
let element = this.shadowRoot.getElementById('languageNew')
selectElement() {
const selectedLanguage = localStorage.getItem('qortalLanguage')
let element = this.shadowRoot.getElementById('languageNew')
element.value = selectedLanguage
element.style.display = "none"
}
element.value = selectedLanguage
element.style.display = 'none'
}
changeLanguage(event) {
use(event.target.value)
localStorage.setItem('qortalLanguage', event.target.value)
}
changeLanguage(event) {
use(event.target.value)
localStorage.setItem('qortalLanguage', event.target.value)
}
toggleMenu() {
let mySwitchDisplay = this.shadowRoot.getElementById('languageNew')
if(mySwitchDisplay.style.display == "none") {
mySwitchDisplay.style.display = "block"
} else {
mySwitchDisplay.style.display = "none"
}
}
toggleMenu() {
let mySwitchDisplay = this.shadowRoot.getElementById('languageNew')
if (mySwitchDisplay.style.display == 'none') {
mySwitchDisplay.style.display = 'block'
} else {
mySwitchDisplay.style.display = 'none'
}
}
}
window.customElements.define('new-selector', NewSelector)
window.customElements.define('new-selector', NewSelector)

View File

@ -1,163 +1,71 @@
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 {setNewNotification} from '../../redux/app/app-actions.js';
import '@material/mwc-icon';
import {get, translate} from '../../../translate'
import {repeat} from 'lit/directives/repeat.js';
import '../../../../plugins/plugins/core/components/TimeAgo.js';
import './popover.js';
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'
// Multi language support
import { get, translate } from '../../../translate'
class NotificationBellGeneral extends connect(store)(LitElement) {
static properties = {
notifications: { type: Array },
showNotifications: { type: Boolean },
notificationCount: { type: Boolean },
theme: { type: String, reflect: true },
currentNotification: { type: Object },
};
static get properties() {
return {
notifications: { type: Array },
showNotifications: { type: Boolean },
notificationCount: { type: Boolean },
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
>
</span>
`
: ''}
<div
id="notification-panel"
class="popover-panel"
style="visibility:${this.showNotifications
? 'visibile'
: 'hidden'}"
tabindex="0"
@blur=${this.handleBlur}
>
${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 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>
<p style="font-size: 16px; width: 100%; text-align:center;margin-top:20px;">${translate('notifications.notify3')}</p>
` : ''}
${repeat(
this.notifications,
@ -166,18 +74,82 @@ class NotificationBellGeneral extends connect(store)(LitElement) {
<notification-item-tx
.changeStatus=${(val1, val2) =>
this.changeStatus(val1, val2)}
status=${notification.status}
timestamp=${notification.timestamp}
type=${notification.type}
signature=${notification.reference
.signature}
status=${notification.status}
timestamp=${notification.timestamp}
type=${notification.type}
signature=${notification.reference
.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,153 +160,34 @@ 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 = {
status: { type: String },
type: { type: String },
timestamp: { type: Number },
signature: { type: String },
changeStatus: { attribute: false },
};
static get properties() {
return {
status: { type: String },
type: { type: String },
timestamp: { type: Number },
signature: { type: String },
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() {
return html`
<div class="notification-item" @click=${() => {}}>
<div class="notification-item" @click=${() => { }}>
<div>
<p style="margin-bottom:10px; font-weight:bold">
${translate('transpage.tchange1')}
@ -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>
</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
>
`
: ''}
${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>
`;
`
}
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,329 +1,262 @@
import {css, html, LitElement} from 'lit'
import {connect} from 'pwa-helpers'
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 get properties() {
return {
notifications: { type: Array },
showNotifications: { type: Boolean },
notificationCount: { type: Boolean },
theme: { type: String, reflect: true }
}
}
static properties = {
notifications: { type: Array },
showNotifications: { type: Boolean },
notificationCount: { type: Boolean },
theme: { type: String, reflect: true },
}
static get styles() {
return [notificationBellStyles]
}
constructor() {
super()
this.notifications = []
this.showNotifications = false
this.notificationCount = false
this.initialFetch = false
this.theme = localStorage.getItem('qortalTheme') ? localStorage.getItem('qortalTheme') : 'light'
}
constructor() {
super()
this.notifications = []
this.showNotifications = false
this.notificationCount = false
this.initialFetch = false
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
}
})
}
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>
<vaadin-tooltip
for="notification-mail-icon"
position="bottom"
hover-delay=${400}
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>
<vaadin-tooltip
for="notification-mail-icon"
position="bottom"
hover-delay=${400}
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=${() => {
const query = `?service=APP&name=Q-Mail`
store.dispatch(setNewTab({
url: `qdn/browser/index.html${query}`,
id: 'q-mail-notification',
myPlugObj: {
"url": "myapp",
"domain": "core",
"page": `qdn/browser/index.html${query}`,
"title": "Q-Mail",
"icon": "vaadin:mailbox",
"mwcicon": "mail_outline",
"menus": [],
"parent": false
}
}))
this.showNotifications = false
this.notifications = []
}}
>
<div>
<p>Q-Mail</p>
<message-time timestamp=${notification.created} style="color:red;font-size:12px"></message-time>
</div>
<div>
<p>${notification.name}</p>
</div>
</div>
`)}
</div>
</div>
</div>
`
}
getApiKey() {
const apiNode = window.parent.reduxStore.getState().app.nodeConfig.knownNodes[window.parent.reduxStore.getState().app.nodeConfig.node]
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
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
let interval = null
let stop = false
const getNewMail = async () => {
const getNewMail = async () => {
const getMail = async (recipientName, recipientAddress) => {
const query = `qortal_qmail_${recipientName.slice(
0,
20
)}_${recipientAddress.slice(-6)}_mail_`
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'
}
})
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 (!stop && !this.showNotifications) {
stop = true
if (!name || !address) return
const mailArray = await getMail(name, address)
let notificationsToShow = []
if (mailArray.length > 0) {
const lastVisited = localStorage.getItem("Q-Mail-last-visited")
try {
const address = window.parent.reduxStore.getState().app?.selectedAddress?.address;
const name = window.parent.reduxStore.getState().app?.accountInfo?.names[0]?.name
if (lastVisited) {
mailArray.forEach((mail) => {
if (mail.created > lastVisited) notificationsToShow.push(mail)
})
} else {
notificationsToShow = mailArray
}
if (!name || !address) return
}
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
const mailArray = await getMail(name, address)
this.notificationCount = this.notifications.length !== 0;
let notificationsToShow = []
if (!this.initialFetch) this.initialFetch = true
} catch (error) {
console.error(error)
}
stop = false
}
}
try {
if (mailArray.length > 0) {
const lastVisited = localStorage.getItem("Q-Mail-last-visited")
setTimeout(() => {
getNewMail()
}, 5000)
if (lastVisited) {
mailArray.forEach((mail) => {
if (mail.created > lastVisited) notificationsToShow.push(mail)
})
} else {
notificationsToShow = mailArray
}
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
>
<vaadin-tooltip
for="notification-mail-icon"
position="bottom"
hover-delay=${400}
hide-delay=${1}
text="Q-Mail">
</vaadin-tooltip>
if (!this.initialFetch && notificationsToShow.length > 0) {
const mail = notificationsToShow[0]
const urlPic = `${nodeUrl}/arbitrary/THUMBNAIL/${mail.name}/qortal_avatar?async=true}`
` : html`
<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"
hover-delay=${400}
hide-delay=${1}
text="Q-Mail">
</vaadin-tooltip>
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.notificationCount ? html`
<span class="count">${this.notifications.length}</span>
` : ''}
this.notifications = notificationsToShow
<div class="popover-panel" ?hidden=${!this.showNotifications}>
<div class="notifications-list">
${this.notifications.map(notification => html`
<div class="notification-item" @click=${() => {
const query = `?service=APP&name=Q-Mail`
store.dispatch(setNewTab({
url: `qdn/browser/index.html${query}`,
id: 'q-mail-notification',
myPlugObj: {
"url": "myapp",
"domain": "core",
"page": `qdn/browser/index.html${query}`,
"title": "Q-Mail",
"icon": "vaadin:mailbox",
"mwcicon": "mail_outline",
"menus": [],
"parent": false
}
}))
this.showNotifications = false
this.notifications = []
}}>
<div>
<p>Q-Mail</p>
<message-time timestamp=${notification.created} style="color:red;font-size:12px"></message-time>
</div>
<div>
<p>${notification.name}</p>
</div>
</div>
`)}
</div>
</div>
</div>
`
}
this.notificationCount = this.notifications.length !== 0
_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',
myPlugObj: {
"url": "myapp",
"domain": "core",
"page": `qdn/browser/index.html${query}`,
"title": "Q-Mail",
"icon": "vaadin:mailbox",
"mwcicon": "mail_outline",
"menus": [],
"parent": false
}
}))
}
if (!this.initialFetch) this.initialFetch = true
} catch (error) {
console.error(error)
}
static styles = css`
.layout {
display: flex;
flex-direction: column;
align-items: center;
position: relative;
}
stop = false
}
}
.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;
}
try {
setTimeout(() => {
getNewMail()
}, 5000)
.nocount {
display: none;
}
interval = setInterval(getNewMail, 60000)
} catch (error) {
console.error(error)
}
}
.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;
}
_toggleNotifications() {
if (this.notifications.length === 0) return
this.showNotifications = !this.showNotifications
}
.popover-panel::-webkit-scrollbar {
width: 11px;
}
_openTabQmail() {
const query = `?service=APP&name=Q-Mail`
.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;
}
`
store.dispatch(setNewTab({
url: `qdn/browser/index.html${query}`,
id: 'q-mail-notification',
myPlugObj: {
"url": "myapp",
"domain": "core",
"page": `qdn/browser/index.html${query}`,
"title": "Q-Mail",
"icon": "vaadin:mailbox",
"mwcicon": "mail_outline",
"menus": [],
"parent": false
}
}))
}
}
customElements.define('notification-bell', NotificationBell)
window.customElements.define('notification-bell', NotificationBell)

View File

@ -1,76 +1,56 @@
// 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;
}
static get properties() {
return {
for: { type: String, reflect: true },
message: { type: String }
}
}
.close-icon {
cursor: pointer;
float: right;
margin-left: 10px;
color: var(--black)
}
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>
`
}
static properties = {
for: { type: String, reflect: true },
message: { type: String }
};
attachToTarget(target) {
if (!this.popperInstance && target) {
this.popperInstance = createPopper(target, this, {
placement: 'bottom',
strategy: 'fixed'
})
}
}
constructor() {
super();
this.message = '';
}
openPopover(target) {
this.attachToTarget(target)
this.style.display = 'block'
}
firstUpdated() {
// We'll defer the popper attachment to the openPopover() method to ensure target availability
}
closePopover() {
this.style.display = 'none'
attachToTarget(target) {
if (!this.popperInstance && target) {
this.popperInstance = createPopper(target, this, {
placement: 'bottom',
strategy: 'fixed'
});
}
}
if (this.popperInstance) {
this.popperInstance.destroy()
this.popperInstance = null
}
openPopover(target) {
this.attachToTarget(target);
this.style.display = 'block';
}
closePopover() {
this.style.display = 'none';
if (this.popperInstance) {
this.popperInstance.destroy();
this.popperInstance = null;
}
this.requestUpdate();
}
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,134 +1,62 @@
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
}
}
}
static get properties() {
return {
theme: { type: String, reflect: true }
}
}
constructor() {
super();
this.theme = localStorage.getItem('qortalTheme') ? localStorage.getItem('qortalTheme') : 'light';
}
constructor() {
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;
}
render() {
return html`
<input type="checkbox" @change=${() => this.toggleTheme()}/>
<div class="slider"></div>
<div class="icon">
<span class="sun">${svgSun}</span>
<span class="moon">${svgMoon}</span>
</div>
`
}
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()}/>
<div class="slider"></div>
<div class="icon">
<span class="sun">${svgSun}</span>
<span class="moon">${svgMoon}</span>
</div>
`;
}
firstUpdated() {
this.initTheme();
}
firstUpdated() {
this.initTheme()
}
toggleTheme() {
if (this.theme === 'light') {
this.theme = 'dark';
} else {
this.theme = 'light';
}
this.dispatchEvent(
new CustomEvent('qort-theme-change', {
bubbles: true,
composed: true,
detail: this.theme,
}),
);
toggleTheme() {
if (this.theme === 'light') {
this.theme = 'dark'
} else {
this.theme = 'light'
}
window.localStorage.setItem('qortalTheme', this.theme);
this.initTheme();
}
this.dispatchEvent(
new CustomEvent('qort-theme-change', {
bubbles: true,
composed: true,
detail: this.theme
})
)
initTheme() {
document.querySelector('html').setAttribute('theme', this.theme);
}
window.localStorage.setItem('qortalTheme', this.theme)
this.initTheme()
}
initTheme() {
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,23 +73,28 @@ 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()
}
}
}
window.customElements.define('search-modal', SearchModal)
window.customElements.define('search-modal', SearchModal)

View File

@ -1,138 +1,92 @@
import {css, html, LitElement} from 'lit'
import {connect} from 'pwa-helpers'
import {store} from '../../store.js'
import {get, translate} from '../../../translate'
import { html, LitElement } from 'lit'
import { connect } from 'pwa-helpers'
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 }
}
}
static get properties() {
return {
accountInfo: { type: Object },
switchAvatar: { type: String },
theme: { type: String, reflect: true }
}
}
static get styles() {
return css`
static get styles() {
return [accountViewStyles]
}
.sub-main {
position: relative;
text-align: center;
}
constructor() {
super()
this.accountInfo = store.getState().app.accountInfo
this.switchAvatar = ''
this.theme = localStorage.getItem('qortalTheme') ? localStorage.getItem('qortalTheme') : 'light'
}
.center-box {
position: relative;
top: 45%;
left: 50%;
transform: translate(-50%, 0%);
text-align: center;
}
render() {
return html`
<div class="sub-main">
<div class="center-box">
<div class="img-icon">${this.getAvatar()}</div>
<span id="accountName">
${this.accountInfo.names.length !== 0 ? this.accountInfo.names[0].name : get("chatpage.cchange15")}
</span>
<div class="content-box">
<span class="title">${translate("settings.address")}: </span>
<span class="value">${store.getState().app.selectedAddress.address}</span>
<br/>
<span class="title">${translate("settings.publickey")}: </span>
<span class="value">${store.getState().app.selectedAddress.base58PublicKey}</span>
</div>
</div>
</div>
`
}
.img-icon {
display: block;
margin-top: 10px;
}
firstUpdated() {
this.getSwitchAvatar()
.content-box {
border: 1px solid #a1a1a1;
padding: 10px 25px;
text-align: left;
display: inline-block;
}
setInterval(() => {
this.getSwitchAvatar()
}, 10000)
}
.title {
font-weight: 600;
font-size: 15px;
display: block;
line-height: 32px;
opacity: 0.66;
}
getAvatar() {
if (this.switchAvatar === 'light') {
if (this.accountInfo.names.length === 0) {
return html`<img src="/img/noavatar_light.png" style="width:150px; height:150px; border-radius: 25%;">`
} else {
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`
.value {
font-size: 16px;
display: inline-block;
}
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') {
if (this.accountInfo.names.length === 0) {
return html`<img src="/img/noavatar_dark.png" style="width:150px; height:150px; border-radius: 25%;">`
} else {
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`
#accountName {
margin: 0;
font-size: 24px;
font-weight:500;
display: inline-block;
width:100%;
}
`
}
return html`<img src="${url}" style="width:150px; height:150px; border-radius: 25%;" onerror="this.src='/img/noavatar_dark.png';">`
}
}
}
constructor() {
super()
this.accountInfo = store.getState().app.accountInfo
this.theme = localStorage.getItem('qortalTheme') ? localStorage.getItem('qortalTheme') : 'light'
this.switchAvatar = ''
}
getSwitchAvatar() {
this.switchAvatar = localStorage.getItem('qortalTheme')
}
render() {
return html`
<div class="sub-main">
<div class="center-box">
<div class="img-icon">${this.getAvatar()}</div>
<span id="accountName">
${this.accountInfo.names.length !== 0 ? this.accountInfo.names[0].name : get("chatpage.cchange15")}
</span>
<div class="content-box">
<span class="title">${translate("settings.address")}: </span>
<span class="value">${store.getState().app.selectedAddress.address}</span>
<br/>
<span class="title">${translate("settings.publickey")}: </span>
<span class="value">${store.getState().app.selectedAddress.base58PublicKey}</span>
</div>
</div>
</div>
`
}
firstUpdated() {
this.getSwitchAvatar()
setInterval(() => {
this.getSwitchAvatar()
}, 2000)
}
getAvatar() {
if (this.switchAvatar === 'light') {
if (this.accountInfo.names.length === 0) {
return html`<img src="/img/noavatar_light.png" style="width:150px; height:150px; border-radius: 25%;">`
} else {
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()}`
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') {
if (this.accountInfo.names.length === 0) {
return html`<img src="/img/noavatar_dark.png" style="width:150px; height:150px; border-radius: 25%;">`
} else {
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()}`
return html`<img src="${url}" style="width:150px; height:150px; border-radius: 25%;" onerror="this.src='/img/noavatar_dark.png';">`
}
}
}
getSwitchAvatar() {
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
}
stateChanged(state) {
this.accountInfo = state.app.accountInfo
}
}
window.customElements.define('account-view', AccountView)
window.customElements.define('account-view', AccountView)

View File

@ -1,597 +1,423 @@
import {css, 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 { html, LitElement } from 'lit'
import { connect } from 'pwa-helpers'
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) {
static get properties() {
return {
theme: { type: String, reflect: true },
backupErrorMessage: { type: String },
btcPMK: { type: String },
ltcPMK: { type: String },
dogePMK: { type: String },
dgbPMK: { type: String },
rvnPMK: { type: String },
arrrPMK: { type: String },
btcWALLET: { type: String },
ltcWALLET: { type: String },
dogeWALLET: { type: String },
dgbWALLET: { type: String },
rvnWALLET: { type: String },
arrrWALLET: { type: String },
btcName: { type: String },
ltcName: { type: String },
dogeName: { type: String },
dgbName: { type: String },
rvnName: { type: String },
arrrName: { type: String },
btcShort: { type: String },
ltcShort: { type: String },
dogeShort: { type: String },
dgbShort: { type: String },
rvnShort: { type: String },
arrrShort: { type: String },
enableArrr: { type: Boolean },
dWalletAddress: { type: String },
dPrivateKey: { type: String },
dCoinName: { type: String },
dCoinShort: { type: String }
}
}
static get properties() {
return {
theme: { type: String, reflect: true },
backupErrorMessage: { type: String },
btcPMK: { type: String },
ltcPMK: { type: String },
dogePMK: { type: String },
dgbPMK: { type: String },
rvnPMK: { type: String },
arrrPMK: { type: String },
btcWALLET: { type: String },
ltcWALLET: { type: String },
dogeWALLET: { type: String },
dgbWALLET: { type: String },
rvnWALLET: { type: String },
arrrWALLET: { type: String },
btcName: { type: String },
ltcName: { type: String },
dogeName: { type: String },
dgbName: { type: String },
rvnName: { type: String },
arrrName: { type: String },
btcShort: { type: String },
ltcShort: { type: String },
dogeShort: { type: String },
dgbShort: { type: String },
rvnShort: { type: String },
arrrShort: { type: String },
enableArrr: { type: Boolean },
dWalletAddress: { type: String },
dPrivateKey: { type: String },
dCoinName: { type: String },
dCoinShort: { type: String }
}
}
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);
}
static get styles() {
return [exportKeysStyles]
}
.center-box {
position: relative;
top: 45%;
left: 50%;
transform: translate(-50%, 0%);
text-align: center;
}
constructor() {
super()
this.theme = localStorage.getItem('qortalTheme') ? localStorage.getItem('qortalTheme') : 'light'
this.backupErrorMessage = ''
this.btcPMK = store.getState().app.selectedAddress.btcWallet.derivedMasterPrivateKey
this.btcWALLET = store.getState().app.selectedAddress.btcWallet.address
this.btcName = 'Bitcoin'
this.btcShort = 'btc'
this.ltcPMK = store.getState().app.selectedAddress.ltcWallet.derivedMasterPrivateKey
this.ltcWALLET = store.getState().app.selectedAddress.ltcWallet.address
this.ltcName = 'Litecoin'
this.ltcShort = 'ltc'
this.dogePMK = store.getState().app.selectedAddress.dogeWallet.derivedMasterPrivateKey
this.dogeWALLET = store.getState().app.selectedAddress.dogeWallet.address
this.dogeName = 'Dogecoin'
this.dogeShort = 'doge'
this.dgbPMK = store.getState().app.selectedAddress.dgbWallet.derivedMasterPrivateKey
this.dgbWALLET = store.getState().app.selectedAddress.dgbWallet.address
this.dgbName = 'Digibyte'
this.dgbShort = 'dgb'
this.rvnPMK = store.getState().app.selectedAddress.rvnWallet.derivedMasterPrivateKey
this.rvnWALLET = store.getState().app.selectedAddress.rvnWallet.address
this.rvnName = 'Ravencoin'
this.rvnShort = 'rvn'
this.arrrPMK = ''
this.arrrWALLET = ''
this.arrrName = 'Pirate Chain'
this.arrrShort = 'arrr'
this.enableArrr = false
this.dWalletAddress = ''
this.dPrivateKey = ''
this.dCoinName = ''
this.dCoinShort = 'btc'
}
.sub-main {
position: relative;
text-align: center;
height: auto;
width: 100%;
}
render() {
return html`
<div style="position: relative;">
<div class="center-box">
<p>
${translate("settings.exp4")}
</p>
</div>
<div class="sub-main">
<div class="center-box">
<div class="content-box">
<div style="display: flex; align-items: center; justify-content: center;">
<img src="/img/btc.png" style="width: 32px; height: 32px;">&nbsp;&nbsp;${this.btcWALLET}<br>
</div>
<div @click=${() => this.checkForPmkDownload(this.btcWALLET, this.btcPMK, this.btcName, this.btcShort)} class="export-button"> ${translate("settings.exp2")} </div>
</div>
<div class="content-box">
<div style="display: flex; align-items: center; justify-content: center;">
<img src="/img/ltc.png" style="width: 32px; height: 32px;">&nbsp;&nbsp;${this.ltcWALLET}<br>
</div>
<div @click=${() => this.checkForPmkDownload(this.ltcWALLET, this.ltcPMK, this.ltcName, this.ltcShort)} class="export-button"> ${translate("settings.exp2")} </div>
</div>
<div class="content-box">
<div style="display: flex; align-items: center; justify-content: center;">
<img src="/img/doge.png" style="width: 32px; height: 32px;">&nbsp;&nbsp;${this.dogeWALLET}<br>
</div>
<div @click=${() => this.checkForPmkDownload(this.dogeWALLET, this.dogePMK, this.dogeName, this.dogeShort)} class="export-button"> ${translate("settings.exp2")} </div>
</div>
<div class="content-box">
<div style="display: flex; align-items: center; justify-content: center;">
<img src="/img/dgb.png" style="width: 32px; height: 32px;">&nbsp;&nbsp;${this.dgbWALLET}<br>
</div>
<div @click=${() => this.checkForPmkDownload(this.dgbWALLET, this.dgbPMK, this.dgbName, this.dgbShort)} class="export-button"> ${translate("settings.exp2")} </div>
</div>
<div class="content-box">
<div style="display: flex; align-items: center; justify-content: center;">
<img src="/img/rvn.png" style="width: 32px; height: 32px;">&nbsp;&nbsp;${this.rvnWALLET}<br>
</div>
<div @click=${() => this.checkForPmkDownload(this.rvnWALLET, this.rvnPMK, this.rvnName, this.rvnShort)} class="export-button"> ${translate("settings.exp2")} </div>
</div>
<div class="content-box" style="display:${this.enableArrr ? 'block' : 'none'}">
<div style="display: flex; align-items: center; justify-content: center;">
<img src="/img/arrr.png" style="width: 32px; height: 32px;">&nbsp;&nbsp;${this.arrrWALLET}<br>
</div>
<div @click=${() => this.checkForPmkDownload(this.arrrWALLET, this.arrrPMK, this.arrrName, this.arrrShort)} class="export-button"> ${translate("settings.exp2")} </div>
</div>
</div>
</div>
<hr style="margin-top: 20px;">
<div class="button-row">
<button class="repair-button" title="${translate('nodepage.nchange38')}" @click="${() => this.openRepairLTCDialog()}">${translate("nodepage.nchange38")}</button>
</div>
</div>
<mwc-dialog id="savePkmDialog" scrimClickAction="" escapeKeyAction="">
<img src="/img/${this.dCoinShort}.png" style="width: 32px; height: 32px;">
<h3>${this.dCoinName} ${translate("settings.exp2")}</h3>
<hr>
<h4>${translate("settings.address")}: ${this.dWalletAddress}</h4>
<mwc-button
slot="primaryAction"
@click="${() => this.closeSavePkmDialog()}"
class="red"
>
${translate("general.close")}
</mwc-button>
<mwc-button
slot="secondaryAction"
@click="${() => this.exportKey(this.dPrivateKey, this.dCoinName, this.dWalletAddress)}"
>
${translate("settings.exp3")}
</mwc-button>
</mwc-dialog>
<mwc-dialog id="arrrWalletNotSynced" scrimClickAction="" escapeKeyAction="">
<img src="/img/arrr.png" style="width: 32px; height: 32px;">
<h3>${translate("settings.arrr1")}</h3>
<hr>
<h4>${translate("settings.arrr2")}</h4>
<mwc-button
slot="primaryAction"
@click="${() => this.closeArrrWalletNotSynced()}"
class="red"
>
${translate("general.close")}
</mwc-button>
</mwc-dialog>
<mwc-dialog id="needCoreUpdate" scrimClickAction="" escapeKeyAction="">
<img src="/img/arrr.png" style="width: 32px; height: 32px;">
<h3>${translate("settings.arrr3")}</h3>
<hr>
<h4>${translate("settings.arrr4")}</h4>
<mwc-button
slot="primaryAction"
@click="${() => this.closeNeedCoreUpdate()}"
class="red"
>
${translate("general.close")}
</mwc-button>
</mwc-dialog>
<mwc-dialog id="repairLTCDialog" scrimClickAction="" escapeKeyAction="">
<img src="/img/ltc.png" style="width: 32px; height: 32px;">
<h3>${translate("nodepage.nchange38")}</h3>
<hr>
<h4>${translate("nodepage.nchange39")}</h4>
<h4>${translate("nodepage.nchange40")}</h4>
<mwc-button slot="primaryAction" @click="${() => this.repairLtcWallet()}" class="green">
${translate("general.continue")}
</mwc-button>
<mwc-button slot="secondaryAction" @click="${() => this.closeRepairLTCDialog()}" class="red">
${translate("login.lp4")}
</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>
<h2>${translate("nodepage.nchange41")}</h2>
</mwc-dialog>
<mwc-dialog id="okDialog" scrimClickAction="" escapeKeyAction="">
<img src="/img/ltc.png" style="width: 32px; height: 32px;">
<h3>${translate("nodepage.nchange38")}</h3>
<hr>
<h3>${translate("nodepage.nchange42")}</h3>
</mwc-dialog>
<mwc-dialog id="errorDialog" scrimClickAction="" escapeKeyAction="">
<img src="/img/ltc.png" style="width: 32px; height: 32px;">
<h3>${translate("nodepage.nchange38")}</h3>
<hr>
<h3>${translate("nodepage.nchange43")}</h3>
</mwc-dialog>
`
}
.content-box {
text-align: center;
display: inline-block;
min-width: 400px;
margin-bottom: 10px;
margin-left: 10px;
margin-top: 20px;
}
async firstUpdated() {
addTradeBotRoutes(parentEpml)
parentEpml.imReady()
.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;
}
await this.fetchArrrWalletAddress()
await this.checkArrrWalletPrivateKey()
}
.red {
--mdc-theme-primary: #F44336;
}
async fetchArrrWalletAddress() {
let resAD = await parentEpml.request('apiCall', {
url: `/crosschain/arrr/walletaddress?apiKey=${this.getApiKey()}`,
method: 'POST',
body: `${store.getState().app.selectedAddress.arrrWallet.seed58}`
})
.green {
--mdc-theme-primary: #198754;
}
if (resAD != null && resAD.error != 1201) {
this.arrrWALLET = ''
this.enableArrr = true
this.arrrWALLET = resAD
} else {
this.arrrWALLET = ''
this.enableArrr = false
this.shadowRoot.querySelector('#arrrWalletNotSynced').show()
}
}
.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;
}
async checkArrrWalletPrivateKey() {
const myNode = store.getState().app.nodeConfig.knownNodes[store.getState().app.nodeConfig.node]
const nodeUrl = myNode.protocol + '://' + myNode.domain + ':' + myNode.port
const privateKeyUrl = `${nodeUrl}/crosschain/arrr/walletprivatekey?apiKey=${this.getApiKey()}`
.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;
}
await fetch(privateKeyUrl, {
method: 'POST',
body: `${store.getState().app.selectedAddress.arrrWallet.seed58}`
}).then(res => {
if (res.status === 404) {
this.arrrPMK = ''
this.enableArrr = false
this.shadowRoot.querySelector('#needCoreUpdate').show()
} else {
this.fetchArrrWalletPrivateKey()
}
})
}
.repair-button:hover {
opacity: 0.8;
cursor: pointer;
}
async fetchArrrWalletPrivateKey() {
let resPK = await parentEpml.request('apiCall', {
url: `/crosschain/arrr/walletprivatekey?apiKey=${this.getApiKey()}`,
method: 'POST',
body: `${store.getState().app.selectedAddress.arrrWallet.seed58}`
})
.lds-roller {
display: inline-block;
position: relative;
width: 80px;
height: 80px;
}
if (resPK != null && resPK.error != 1201) {
this.arrrPMK = ''
this.enableArrr = true
this.arrrPMK = resPK
} else {
this.arrrPMK = ''
this.enableArrr = false
this.shadowRoot.querySelector('#arrrWalletNotSynced').show()
}
}
.lds-roller div {
animation: lds-roller 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite;
transform-origin: 40px 40px;
}
closeArrrWalletNotSynced() {
this.shadowRoot.querySelector('#arrrWalletNotSynced').close()
}
.lds-roller div:after {
content: " ";
display: block;
position: absolute;
width: 7px;
height: 7px;
border-radius: 50%;
background: var(--black);
margin: -4px 0 0 -4px;
}
closeNeedCoreUpdate() {
this.arrrPMK = ''
this.enableArrr = false
this.shadowRoot.querySelector('#needCoreUpdate').close()
}
.lds-roller div:nth-child(1) {
animation-delay: -0.036s;
}
closeSavePkmDialog() {
this.shadowRoot.querySelector('#savePkmDialog').close()
}
.lds-roller div:nth-child(1):after {
top: 63px;
left: 63px;
}
openRepairLTCDialog() {
this.shadowRoot.querySelector('#repairLTCDialog').show()
}
.lds-roller div:nth-child(2) {
animation-delay: -0.072s;
}
closeRepairLTCDialog() {
this.shadowRoot.querySelector('#repairLTCDialog').close()
}
.lds-roller div:nth-child(2):after {
top: 68px;
left: 56px;
}
async repairLtcWallet() {
this.shadowRoot.querySelector('#repairLTCDialog').close()
this.shadowRoot.querySelector('#pleaseWaitDialog').show()
.lds-roller div:nth-child(3) {
animation-delay: -0.108s;
}
let resRepair = await parentEpml.request('apiCall', {
url: `/crosschain/ltc/repair?apiKey=${this.getApiKey()}`,
method: 'POST',
body: `${store.getState().app.selectedAddress.ltcWallet.derivedMasterPrivateKey}`
})
.lds-roller div:nth-child(3):after {
top: 71px;
left: 48px;
}
if (resRepair != null && resRepair.error != 128) {
this.shadowRoot.querySelector('#pleaseWaitDialog').close()
.lds-roller div:nth-child(4) {
animation-delay: -0.144s;
}
await this.openOkDialog()
} else {
this.shadowRoot.querySelector('#pleaseWaitDialog').close()
.lds-roller div:nth-child(4):after {
top: 72px;
left: 40px;
}
await this.openErrorDialog()
}
}
.lds-roller div:nth-child(5) {
animation-delay: -0.18s;
}
async openOkDialog() {
const okDelay = ms => new Promise(res => setTimeout(res, ms))
.lds-roller div:nth-child(5):after {
top: 71px;
left: 32px;
}
this.shadowRoot.querySelector('#okDialog').show()
.lds-roller div:nth-child(6) {
animation-delay: -0.216s;
}
await okDelay(3000)
.lds-roller div:nth-child(6):after {
top: 68px;
left: 24px;
}
this.shadowRoot.querySelector('#okDialog').close()
}
.lds-roller div:nth-child(7) {
animation-delay: -0.252s;
}
async openErrorDialog() {
const errorDelay = ms => new Promise(res => setTimeout(res, ms))
.lds-roller div:nth-child(7):after {
top: 63px;
left: 17px;
}
this.shadowRoot.querySelector('#errorDialog').show()
.lds-roller div:nth-child(8) {
animation-delay: -0.288s;
}
await errorDelay(3000)
.lds-roller div:nth-child(8):after {
top: 56px;
left: 12px;
}
this.shadowRoot.querySelector('#errorDialog').close()
}
@keyframes lds-roller {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
`
}
checkForPmkDownload(wAddress, wPkm, wName, wShort) {
this.dWalletAddress = ''
this.dPrivateKey = ''
this.dCoinName = ''
this.dCoinShort = ''
this.dWalletAddress = wAddress
this.dPrivateKey = wPkm
this.dCoinName = wName
this.dCoinShort = wShort
this.shadowRoot.querySelector('#savePkmDialog').show()
}
constructor() {
super()
this.theme = localStorage.getItem('qortalTheme') ? localStorage.getItem('qortalTheme') : 'light'
this.backupErrorMessage = ''
this.btcPMK = store.getState().app.selectedAddress.btcWallet.derivedMasterPrivateKey
this.btcWALLET = store.getState().app.selectedAddress.btcWallet.address
this.btcName = 'Bitcoin'
this.btcShort = 'btc'
this.ltcPMK = store.getState().app.selectedAddress.ltcWallet.derivedMasterPrivateKey
this.ltcWALLET = store.getState().app.selectedAddress.ltcWallet.address
this.ltcName = 'Litecoin'
this.ltcShort = 'ltc'
this.dogePMK = store.getState().app.selectedAddress.dogeWallet.derivedMasterPrivateKey
this.dogeWALLET = store.getState().app.selectedAddress.dogeWallet.address
this.dogeName = 'Dogecoin'
this.dogeShort = 'doge'
this.dgbPMK = store.getState().app.selectedAddress.dgbWallet.derivedMasterPrivateKey
this.dgbWALLET = store.getState().app.selectedAddress.dgbWallet.address
this.dgbName = 'Digibyte'
this.dgbShort = 'dgb'
this.rvnPMK = store.getState().app.selectedAddress.rvnWallet.derivedMasterPrivateKey
this.rvnWALLET = store.getState().app.selectedAddress.rvnWallet.address
this.rvnName = 'Ravencoin'
this.rvnShort = 'rvn'
this.arrrPMK = ''
this.arrrWALLET = ''
this.arrrName = 'Pirate Chain'
this.arrrShort = 'arrr'
this.enableArrr = false
this.dWalletAddress = ''
this.dPrivateKey = ''
this.dCoinName = ''
this.dCoinShort = 'btc'
}
async exportKey(cMasterKey, cName, cAddress) {
let exportname = ''
render() {
return html`
<div style="position: relative;">
<div class="center-box">
<p>
${translate("settings.exp4")}
</p>
</div>
<div class="sub-main">
<div class="center-box">
<div class="content-box">
<div style="display: flex; align-items: center; justify-content: center;">
<img src="/img/btc.png" style="width: 32px; height: 32px;">&nbsp;&nbsp;${this.btcWALLET}<br>
</div>
<div @click=${() => this.checkForPmkDownload(this.btcWALLET, this.btcPMK, this.btcName, this.btcShort)} class="export-button"> ${translate("settings.exp2")} </div>
</div>
<div class="content-box">
<div style="display: flex; align-items: center; justify-content: center;">
<img src="/img/ltc.png" style="width: 32px; height: 32px;">&nbsp;&nbsp;${this.ltcWALLET}<br>
</div>
<div @click=${() => this.checkForPmkDownload(this.ltcWALLET, this.ltcPMK, this.ltcName, this.ltcShort)} class="export-button"> ${translate("settings.exp2")} </div>
</div>
<div class="content-box">
<div style="display: flex; align-items: center; justify-content: center;">
<img src="/img/doge.png" style="width: 32px; height: 32px;">&nbsp;&nbsp;${this.dogeWALLET}<br>
</div>
<div @click=${() => this.checkForPmkDownload(this.dogeWALLET, this.dogePMK, this.dogeName, this.dogeShort)} class="export-button"> ${translate("settings.exp2")} </div>
</div>
<div class="content-box">
<div style="display: flex; align-items: center; justify-content: center;">
<img src="/img/dgb.png" style="width: 32px; height: 32px;">&nbsp;&nbsp;${this.dgbWALLET}<br>
</div>
<div @click=${() => this.checkForPmkDownload(this.dgbWALLET, this.dgbPMK, this.dgbName, this.dgbShort)} class="export-button"> ${translate("settings.exp2")} </div>
</div>
<div class="content-box">
<div style="display: flex; align-items: center; justify-content: center;">
<img src="/img/rvn.png" style="width: 32px; height: 32px;">&nbsp;&nbsp;${this.rvnWALLET}<br>
</div>
<div @click=${() => this.checkForPmkDownload(this.rvnWALLET, this.rvnPMK, this.rvnName, this.rvnShort)} class="export-button"> ${translate("settings.exp2")} </div>
</div>
<div class="content-box" style="display:${this.enableArrr ? 'block' : 'none'}">
<div style="display: flex; align-items: center; justify-content: center;">
<img src="/img/arrr.png" style="width: 32px; height: 32px;">&nbsp;&nbsp;${this.arrrWALLET}<br>
</div>
<div @click=${() => this.checkForPmkDownload(this.arrrWALLET, this.arrrPMK, this.arrrName, this.arrrShort)} class="export-button"> ${translate("settings.exp2")} </div>
</div>
</div>
</div>
<hr style="margin-top: 20px;">
<div class="button-row">
<button class="repair-button" title="${translate('nodepage.nchange38')}" @click="${() => this.openRepairLTCDialog()}">${translate("nodepage.nchange38")}</button>
</div>
</div>
<mwc-dialog id="savePkmDialog" scrimClickAction="" escapeKeyAction="">
<img src="/img/${this.dCoinShort}.png" style="width: 32px; height: 32px;">
<h3>${this.dCoinName} ${translate("settings.exp2")}</h3>
<hr>
<h4>${translate("settings.address")}: ${this.dWalletAddress}</h4>
<mwc-button
slot="primaryAction"
@click="${() => this.closeSavePkmDialog()}"
class="red"
>
${translate("general.close")}
</mwc-button>
<mwc-button
slot="secondaryAction"
@click="${() => this.exportKey(this.dPrivateKey, this.dCoinName, this.dWalletAddress)}"
>
${translate("settings.exp3")}
</mwc-button>
</mwc-dialog>
<mwc-dialog id="arrrWalletNotSynced" scrimClickAction="" escapeKeyAction="">
<img src="/img/arrr.png" style="width: 32px; height: 32px;">
<h3>${translate("settings.arrr1")}</h3>
<hr>
<h4>${translate("settings.arrr2")}</h4>
<mwc-button
slot="primaryAction"
@click="${() => this.closeArrrWalletNotSynced()}"
class="red"
>
${translate("general.close")}
</mwc-button>
</mwc-dialog>
<mwc-dialog id="needCoreUpdate" scrimClickAction="" escapeKeyAction="">
<img src="/img/arrr.png" style="width: 32px; height: 32px;">
<h3>${translate("settings.arrr3")}</h3>
<hr>
<h4>${translate("settings.arrr4")}</h4>
<mwc-button
slot="primaryAction"
@click="${() => this.closeNeedCoreUpdate()}"
class="red"
>
${translate("general.close")}
</mwc-button>
</mwc-dialog>
<mwc-dialog id="repairLTCDialog" scrimClickAction="" escapeKeyAction="">
<img src="/img/ltc.png" style="width: 32px; height: 32px;">
<h3>${translate("nodepage.nchange38")}</h3>
<hr>
<h4>${translate("nodepage.nchange39")}</h4>
<h4>${translate("nodepage.nchange40")}</h4>
<mwc-button slot="primaryAction" @click="${() => this.repairLtcWallet()}" class="green">
${translate("general.continue")}
</mwc-button>
<mwc-button slot="secondaryAction" @click="${() => this.closeRepairLTCDialog()}" class="red">
${translate("login.lp4")}
</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>
<h2>${translate("nodepage.nchange41")}</h2>
</mwc-dialog>
<mwc-dialog id="okDialog" scrimClickAction="" escapeKeyAction="">
<img src="/img/ltc.png" style="width: 32px; height: 32px;">
<h3>${translate("nodepage.nchange38")}</h3>
<hr>
<h3>${translate("nodepage.nchange42")}</h3>
</mwc-dialog>
<mwc-dialog id="errorDialog" scrimClickAction="" escapeKeyAction="">
<img src="/img/ltc.png" style="width: 32px; height: 32px;">
<h3>${translate("nodepage.nchange38")}</h3>
<hr>
<h3>${translate("nodepage.nchange43")}</h3>
</mwc-dialog>
`
}
const myPrivateMasterKey = cMasterKey
const myCoinName = cName
const myCoinAddress = cAddress
const blob = new Blob([`${myPrivateMasterKey}`], { type: 'text/plain;charset=utf-8' })
async firstUpdated() {
addTradeBotRoutes(parentEpml)
parentEpml.imReady()
await this.fetchArrrWalletAddress()
await this.checkArrrWalletPrivateKey()
}
exportname = 'Private_Master_Key_' + myCoinName + '_' + myCoinAddress + '.txt'
async fetchArrrWalletAddress() {
let resAD = await parentEpml.request('apiCall', {
url: `/crosschain/arrr/walletaddress?apiKey=${this.getApiKey()}`,
method: 'POST',
body: `${store.getState().app.selectedAddress.arrrWallet.seed58}`
})
await this.saveFileToDisk(blob, exportname)
}
if (resAD != null && resAD.error != 1201) {
this.arrrWALLET = ''
this.enableArrr = true
this.arrrWALLET = resAD
} else {
this.arrrWALLET = ''
this.enableArrr = false
this.shadowRoot.querySelector('#arrrWalletNotSynced').show()
}
}
async saveFileToDisk(blob, fileName) {
try {
const fileHandle = await self.showSaveFilePicker({
suggestedName: fileName,
types: [{
description: "File"
}]
})
async checkArrrWalletPrivateKey() {
const myNode = store.getState().app.nodeConfig.knownNodes[store.getState().app.nodeConfig.node]
const nodeUrl = myNode.protocol + '://' + myNode.domain + ':' + myNode.port
const privateKeyUrl = `${nodeUrl}/crosschain/arrr/walletprivatekey?apiKey=${this.getApiKey()}`
const writeFile = async (fileHandle, contents) => {
const writable = await fileHandle.createWritable()
await fetch(privateKeyUrl, {
method: 'POST',
body: `${store.getState().app.selectedAddress.arrrWallet.seed58}`
}).then(res => {
if (res.status === 404) {
this.arrrPMK = ''
this.enableArrr = false
this.shadowRoot.querySelector('#needCoreUpdate').show()
} else {
this.fetchArrrWalletPrivateKey()
}
})
}
await writable.write(contents)
await writable.close()
}
async fetchArrrWalletPrivateKey() {
let resPK = await parentEpml.request('apiCall', {
url: `/crosschain/arrr/walletprivatekey?apiKey=${this.getApiKey()}`,
method: 'POST',
body: `${store.getState().app.selectedAddress.arrrWallet.seed58}`
})
writeFile(fileHandle, blob).then(() => console.log("FILE SAVED"))
if (resPK != null && resPK.error != 1201) {
this.arrrPMK = ''
this.enableArrr = true
this.arrrPMK = resPK
} else {
this.arrrPMK = ''
this.enableArrr = false
this.shadowRoot.querySelector('#arrrWalletNotSynced').show()
}
}
let snack4string = get("general.save")
closeArrrWalletNotSynced() {
this.shadowRoot.querySelector('#arrrWalletNotSynced').close()
}
snackbar.add({
labelText: `${snack4string} ${fileName}`,
dismiss: true
})
} catch (error) {
if (error.name === 'AbortError') {
return
}
closeNeedCoreUpdate() {
this.arrrPMK = ''
this.enableArrr = false
this.shadowRoot.querySelector('#needCoreUpdate').close()
}
FileSaver.saveAs(blob, fileName)
}
}
closeSavePkmDialog() {
this.shadowRoot.querySelector('#savePkmDialog').close()
}
openRepairLTCDialog() {
this.shadowRoot.querySelector('#repairLTCDialog').show()
}
closeRepairLTCDialog() {
this.shadowRoot.querySelector('#repairLTCDialog').close()
}
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',
body: `${store.getState().app.selectedAddress.ltcWallet.derivedMasterPrivateKey}`
})
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()
}
checkForPmkDownload(wAddress, wPkm, wName, wShort) {
this.dWalletAddress = ''
this.dPrivateKey = ''
this.dCoinName = ''
this.dCoinShort = ''
this.dWalletAddress = wAddress
this.dPrivateKey = wPkm
this.dCoinName = wName
this.dCoinShort = wShort
this.shadowRoot.querySelector('#savePkmDialog').show()
}
async exportKey(cMasterKey, cName, cAddress) {
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"
await this.saveFileToDisk(blob, exportname)
}
async saveFileToDisk(blob, fileName) {
try {
const fileHandle = await self.showSaveFilePicker({
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("general.save")
snackbar.add({
labelText: `${snack4string} ${fileName}`,
dismiss: true
})
} catch (error) {
if (error.name === 'AbortError') {
return
}
FileSaver.saveAs(blob, fileName)
}
}
getApiKey() {
const apiNode = store.getState().app.nodeConfig.knownNodes[store.getState().app.nodeConfig.node]
getApiKey() {
const apiNode = store.getState().app.nodeConfig.knownNodes[store.getState().app.nodeConfig.node]
return apiNode.apiKey
}
}
}
window.customElements.define('export-keys', ExportKeys)
window.customElements.define('export-keys', ExportKeys)

View File

@ -1,258 +1,160 @@
import {css, 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 { html, LitElement } from 'lit'
import { connect } from 'pwa-helpers'
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 {
notificationConfig: { type: Object },
q_chatConfig: { type: Object },
theme: { type: String, reflect: true },
appNotificationList: { type: Array }
}
}
static get properties() {
return {
notificationConfig: { type: Object },
q_chatConfig: { type: Object },
theme: { type: String, reflect: true },
appNotificationList: { type: Array }
}
}
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
}
static get styles() {
return [notificationsViewStyles]
}
firstUpdated() {
this.appNotificationList = this.getAppsFromStorage()
}
constructor() {
super()
this.notificationConfig = {}
this.q_chatConfig = {}
this.theme = localStorage.getItem('qortalTheme') ? localStorage.getItem('qortalTheme') : 'light'
this.appNotificationList = []
}
static get styles() {
return css`
.sub-main {
position: relative;
text-align: center;
}
firstUpdated() {
this.appNotificationList = this.getAppsFromStorage()
}
.notification-box {
display: block;
position: relative;
top: 45%;
left: 50%;
transform: translate(-50%, 0%);
text-align: center;
}
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
for="qChatPlaySound"
@click=${() => this.shadowRoot.getElementById('qChatPlaySound').click()}
>
${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
for="qChatShowNotification"
@click=${() => this.shadowRoot.getElementById('qChatShowNotification').click()}
>
${translate("settings.shownotifications")}
</label>
</div>
</div>
<div class="content-box">
<h4>${translate("settings.qappNotification1")}</h4>
${this.appNotificationList.map((app) => html`
<div style="display: flex; justify-content: space-between; margin-top: 10px;">
${app}
<button class="remove-button" @click=${() => this.removeApp(app)}>Remove</button>
</div>
`)}
</div>
</div>
<div class="checkbox-row">
<label for="syncIndicator" id="syncIndicatorLabel" style="color: var(--black);">
${translate("settings.sync_indicator")}
</label>
<mwc-checkbox style="margin-right: -15px;" id="syncIndicator" @click=${(e) => this.checkForSyncMessages(e)} ?checked=${store.getState().app.showSyncIndicator}></mwc-checkbox>
</div>
${this.renderSetCoreButton()}
</div>
`
}
@media(min-width: 1400px) {
.notification-box {
display: grid;
grid-template-columns: 1fr 1fr;
grid-gap: 30px;
}
}
getAppsFromStorage() {
const address = store.getState().app.selectedAddress.address
const id = `appNotificationList-${address}`
const data = localStorage.getItem(id)
.checkbox-row {
position: relative;
display: flex;
align-items: center;
align-content: center;
font-family: Montserrat, sans-serif;
font-weight: 600;
color: var(--black);
}
return data ? Object.keys(JSON.parse(data)) : []
}
.content-box {
border: 1px solid #a1a1a1;
padding: 10px 25px;
text-align: left;
display: inline-block;
min-width: 350px;
min-height: 150px;
margin: 20px 0;
}
removeApp(appName) {
// Remove the app from local storage
this.removeAppFromStorage(appName)
h4 {
margin-bottom: 0;
}
// Update the apps list in the component
this.appNotificationList = this.appNotificationList.filter(app => app !== appName)
}
mwc-checkbox::shadow .mdc-checkbox::after, mwc-checkbox::shadow .mdc-checkbox::before {
background-color:var(--mdc-theme-primary)
}
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) || '{}')
label:hover {
cursor: pointer;
}
delete data[appName]
.title {
font-weight: 600;
font-size: 15px;
display: block;
line-height: 32px;
opacity: 0.66;
}
localStorage.setItem(id, JSON.stringify(data));
}
.value {
font-size: 16px;
display: inline-block;
}
renderSetCoreButton() {
if (!isElectron()) {
return html``
} else {
return html`
<div style="max-width: 500px; display: flex; justify-content: center; margin: auto;">
<div @click=${() => this.checkCoreSettings()} class="q-button">${translate("settings.core")}</div>
</div>
`
}
}
.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;
}
checkCoreSettings() {
window.electronAPI.setStartCore()
}
.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;
}
`
}
checkForSyncMessages(e) {
if (e.target.checked) {
store.dispatch(removeShowSyncIndicator(false))
} else {
store.dispatch(allowShowSyncIndicator(true))
}
}
render() {
return html`
<div class="sub-main">
<div class="notification-box">
<div class="content-box">
<h4> Q-Chat ${translate("settings.notifications")} </h4>
stateChanged(state) {
this.notificationConfig = state.user.notifications
this.q_chatConfig = this.notificationConfig.q_chat
}
<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
for="qChatPlaySound"
@click=${() => this.shadowRoot.getElementById('qChatPlaySound').click()}
>
${translate("settings.playsound")}
</label>
</div>
setQChatNotificationConfig(valueObject) {
if (valueObject.type === 'PLAY_SOUND') {
let data = {
playSound: !valueObject.value,
showNotification: this.q_chatConfig.showNotification
}
<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
for="qChatShowNotification"
@click=${() => this.shadowRoot.getElementById('qChatShowNotification').click()}
>
${translate("settings.shownotifications")}
</label>
</div>
</div>
<div class="content-box">
<h4>${translate("settings.qappNotification1")}</h4>
${this.appNotificationList.map((app) => html`
<div style="display: flex; justify-content: space-between; margin-top: 10px;">
${app}
<button class="remove-button" @click=${() => this.removeApp(app)}>Remove</button>
</div>
`)}
</div>
</div>
<div class="checkbox-row">
<label for="syncIndicator" id="syncIndicatorLabel" style="color: var(--black);">
${translate("settings.sync_indicator")}
</label>
<mwc-checkbox style="margin-right: -15px;" id="syncIndicator" @click=${(e) => this.checkForSyncMessages(e)} ?checked=${store.getState().app.showSyncIndicator}></mwc-checkbox>
</div>
${this.renderSetCoreButton()}
</div>
`
}
store.dispatch(doSetQChatNotificationConfig(data))
} if (valueObject.type === 'SHOW_NOTIFICATION') {
let data = {
playSound: this.q_chatConfig.playSound,
showNotification: !valueObject.value
}
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);
// Update the apps list in the component
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];
localStorage.setItem(id, JSON.stringify(data));
}
renderSetCoreButton() {
if (!isElectron()) {
return html``
} else {
return html`
<div style="max-width: 500px; display: flex; justify-content: center; margin: auto;">
<div @click=${() => this.checkCoreSettings()} class="q-button"> ${translate("settings.core")} </div>
</div>
`
}
}
checkCoreSettings() {
window.electronAPI.setStartCore()
}
checkForSyncMessages(e) {
if (e.target.checked) {
store.dispatch(removeShowSyncIndicator(false))
} else {
store.dispatch(allowShowSyncIndicator(true))
}
}
stateChanged(state) {
this.notificationConfig = state.user.notifications
this.q_chatConfig = this.notificationConfig.q_chat
}
setQChatNotificationConfig(valueObject) {
if (valueObject.type === 'PLAY_SOUND') {
let data = {
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))
}
}
store.dispatch(doSetQChatNotificationConfig(data))
}
}
}
window.customElements.define('notifications-view', NotificationsView)
window.customElements.define('notifications-view', NotificationsView)

View File

@ -1,140 +1,91 @@
import {css, html, LitElement} from 'lit'
import {connect} from 'pwa-helpers'
import {store} from '../../store.js'
import {translate} from '../../../translate'
import '@material/mwc-textfield'
import { html, LitElement } from 'lit'
import { connect } from 'pwa-helpers'
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
}
}
static get properties() {
return {
theme: { type: String, reflect: true },
savedWalletDataJson: { type: String },
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);
}
static get styles() {
return [qrLoginViewStyles]
}
.center-box {
position: relative;
top: 45%;
left: 50%;
transform: translate(-50%, 0%);
text-align: center;
}
constructor() {
super()
this.theme = localStorage.getItem('qortalTheme') ? localStorage.getItem('qortalTheme') : 'light'
this.translateDescriptionKey = 'settings.qr_login_description_' + (this.isWalletStored() ? '1' : '2')
this.translateButtonKey = 'settings.qr_login_button_' + (this.isWalletStored() ? '1' : '2')
}
.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;
}
render() {
return html`
<div style="position: relative;" >
<div class="center-box">
<p>
${translate(this.translateDescriptionKey)}
</p>
<div style="max-width: 500px; justify-content: center; margin: auto; display: ${this.isWalletStored() ? 'none' : 'flex'}">
<mwc-icon style="padding: 10px; padding-left:0; padding-top: 42px;">password</mwc-icon>
<vaadin-password-field id="newWalletPassword" style="width: 100%; color: var(--black);" label="${translate("settings.password")}" autofocus></vaadin-password-field>
</div>
<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>
</div>
</div>
`
}
.q-button.outlined {
background: unset;
border: 1px solid #03a9f4;
}
isWalletStored() {
const state = store.getState()
const address0 = state.app.wallet._addresses[0].address
const savedWalletData = state.user.storedWallets && state.user.storedWallets[address0]
:host([theme="light"]) .q-button.outlined {
color: #03a9f4;
}
return !!savedWalletData
}
#qr-toggle-button {
margin-left: 12px;
}
async setSavedWalletDataJson() {
const state = store.getState()
#login-qr-code {
margin: auto;
}
`
}
let data
constructor() {
super()
this.theme = localStorage.getItem('qortalTheme') ? localStorage.getItem('qortalTheme') : 'light'
this.translateDescriptionKey = 'settings.qr_login_description_' + (this.isWalletStored() ? '1' : '2')
this.translateButtonKey = 'settings.qr_login_button_' + (this.isWalletStored() ? '1' : '2')
}
if (this.isWalletStored()) {
const address0 = state.app.wallet._addresses[0].address
render() {
return html`
<div style="position: relative;" >
<div class="center-box">
<p>
${translate(this.translateDescriptionKey)}
</p>
<div style="max-width: 500px; justify-content: center; margin: auto; display: ${this.isWalletStored() ? 'none' : 'flex' }">
<mwc-icon style="padding: 10px; padding-left:0; padding-top: 42px;">password</mwc-icon>
<vaadin-password-field id="newWalletPassword" style="width: 100%; color: var(--black);" label="${translate("settings.password")}" autofocus></vaadin-password-field>
</div>
<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>
data = state.user.storedWallets[address0]
} else {
const password = this.shadowRoot.getElementById('newWalletPassword').value
<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>
</div>
</div>
`
}
data = await state.app.wallet.generateSaveWalletData(password, state.config.crypto.kdfThreads, () => { })
}
isWalletStored() {
const state = store.getState()
const address0 = state.app.wallet._addresses[0].address
const savedWalletData = state.user.storedWallets && state.user.storedWallets[address0]
return !!savedWalletData
}
this.savedWalletDataJson = JSON.stringify(data)
}
async setSavedWalletDataJson() {
const state = store.getState()
let data
if (this.isWalletStored()) { // if the wallet is stored, we use the existing encrypted backup
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
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()
async showQRCode() {
await this.setSavedWalletDataJson()
let el = this.shadowRoot.getElementById('login-qr-code')
el.style.display = 'flex'
}
let el = this.shadowRoot.getElementById('login-qr-code')
el.style.display = 'flex'
}
}
window.customElements.define('qr-login-view', QRLoginView)
window.customElements.define('qr-login-view', QRLoginView)

View File

@ -1,6 +1,6 @@
import {css, html, LitElement} from 'lit'
import {connect} from 'pwa-helpers'
import {store} from '../../store.js'
import { html, LitElement } from 'lit'
import { connect } from 'pwa-helpers'
import { store } from '../../store'
import {
allowQAPPAutoAuth,
allowQAPPAutoFriendsList,
@ -9,262 +9,194 @@ 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 {
theme: { type: String, reflect: true },
backupErrorMessage: { type: String },
closeSettings: {attribute: false}
}
}
static get properties() {
return {
theme: { type: String, reflect: true },
backupErrorMessage: { type: String },
closeSettings: { attribute: false }
}
}
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);
}
static get styles() {
return [securityViewStyles]
}
.center-box {
position: relative;
top: 45%;
left: 50%;
transform: translate(-50%, 0%);
text-align: center;
}
constructor() {
super()
this.theme = localStorage.getItem('qortalTheme') ? localStorage.getItem('qortalTheme') : 'light'
this.backupErrorMessage = ''
}
.checkbox-row {
position: relative;
display: flex;
align-items: center;
align-content: center;
font-family: Montserrat, sans-serif;
font-weight: 600;
color: var(--black);
}
render() {
return html`
<div style="position: relative;" >
<div class="center-box">
<p>
${translate("settings.choose")}
</p>
<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>
<vaadin-password-field
style="width: 100%; color: var(--black);"
label="${translate("settings.password")}"
id="downloadBackupPassword"
helper-text="${translate("login.passwordhint")}"
autofocus
></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>
<vaadin-password-field
style="width: 100%; color: var(--black);"
label="${translate("login.confirmpass")}"
id="rePassword"
></vaadin-password-field>
</div>
<div style="text-align: center; color: var(--mdc-theme-error); text-transform: uppercase; font-size: 15px;">
${this.backupErrorMessage}
</div>
<div style="max-width: 500px; display: flex; justify-content: center; margin: auto;">
<div @click=${() => this.checkForDownload()} class="q-button"> ${translate("settings.download")} </div>
</div>
</div>
<hr style="margin-top: 20px;">
<div class="checkbox-row">
<label for="authButton" id="authButtonLabel" style="color: var(--black);">
${get('browserpage.bchange26')}
</label>
<mwc-checkbox style="margin-right: -15px;" id="authButton" @click=${(e) => this.checkForAuth(e)} ?checked=${store.getState().app.qAPPAutoAuth}></mwc-checkbox>
</div>
<div class="checkbox-row">
<label for="authButton" id="authButtonLabel" style="color: var(--black);">
${get('browserpage.bchange39')}
</label>
<mwc-checkbox style="margin-right: -15px;" id="authButton" @click=${(e) => this.checkForLists(e)} ?checked=${store.getState().app.qAPPAutoLists}></mwc-checkbox>
</div>
<div class="checkbox-row">
<label for="authButton" id="authButtonLabel" style="color: var(--black);">
${get('browserpage.bchange53')}
</label>
<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}>
${translate('tabmenu.tm38')}
</button>
</div>
</div>
`
}
.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;
}
checkForAuth(e) {
if (e.target.checked) {
store.dispatch(removeQAPPAutoAuth(false))
} else {
store.dispatch(allowQAPPAutoAuth(true))
}
}
.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;
}
checkForLists(e) {
if (e.target.checked) {
store.dispatch(removeQAPPAutoLists(false))
} else {
store.dispatch(allowQAPPAutoLists(true))
}
}
.add-dev-button:hover {
opacity: 0.8;
cursor: pointer;
}
`
}
checkForFriends(e) {
if (e.target.checked) {
store.dispatch(removeQAPPAutoFriendsList(false))
} else {
store.dispatch(allowQAPPAutoFriendsList(true))
}
}
constructor() {
super()
this.theme = localStorage.getItem('qortalTheme') ? localStorage.getItem('qortalTheme') : 'light'
this.backupErrorMessage = ''
}
checkForDownload() {
const checkPass = this.shadowRoot.getElementById('downloadBackupPassword').value
const rePass = this.shadowRoot.getElementById('rePassword').value
render() {
return html`
<div style="position: relative;" >
<div class="center-box">
<p>
${translate("settings.choose")}
</p>
<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>
<vaadin-password-field
style="width: 100%; color: var(--black);"
label="${translate("settings.password")}"
id="downloadBackupPassword"
helper-text="${translate("login.passwordhint")}"
autofocus
>
</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>
<vaadin-password-field
style="width: 100%; color: var(--black);"
label="${translate("login.confirmpass")}"
id="rePassword"
>
</vaadin-password-field>
</div>
<div style="text-align: center; color: var(--mdc-theme-error); text-transform: uppercase; font-size: 15px;">
${this.backupErrorMessage}
</div>
<div style="max-width: 500px; display: flex; justify-content: center; margin: auto;">
<div @click=${() => this.checkForDownload()} class="q-button"> ${translate("settings.download")} </div>
</div>
</div>
<hr style="margin-top: 20px;">
<div class="checkbox-row">
<label for="authButton" id="authButtonLabel" style="color: var(--black);">
${get('browserpage.bchange26')}
</label>
<mwc-checkbox style="margin-right: -15px;" id="authButton" @click=${(e) => this.checkForAuth(e)} ?checked=${store.getState().app.qAPPAutoAuth}></mwc-checkbox>
</div>
<div class="checkbox-row">
<label for="authButton" id="authButtonLabel" style="color: var(--black);">
${get('browserpage.bchange39')}
</label>
<mwc-checkbox style="margin-right: -15px;" id="authButton" @click=${(e) => this.checkForLists(e)} ?checked=${store.getState().app.qAPPAutoLists}></mwc-checkbox>
</div>
<div class="checkbox-row">
<label for="authButton" id="authButtonLabel" style="color: var(--black);">
${get('browserpage.bchange53')}
</label>
<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}
>
${translate('tabmenu.tm38')}
</button>
</div>
</div>
`
}
if (checkPass === '') {
this.backupErrorMessage = get("login.pleaseenter")
} else if (checkPass.length < 5) {
this.backupErrorMessage = get("login.lessthen8-2")
} else if (checkPass != rePass) {
this.backupErrorMessage = get("login.notmatch")
} else {
this.downloadBackup()
}
}
stateChanged(state) {
}
openDevDialog() {
this.closeSettings()
store.dispatch(setIsOpenDevDialog(true))
}
checkForAuth(e) {
if (e.target.checked) {
store.dispatch(removeQAPPAutoAuth(false))
} else {
store.dispatch(allowQAPPAutoAuth(true))
}
}
async downloadBackup() {
let backupname = ''
checkForLists(e) {
if (e.target.checked) {
store.dispatch(removeQAPPAutoLists(false))
} else {
store.dispatch(allowQAPPAutoLists(true))
}
}
this.backupErrorMessage = ''
checkForFriends(e) {
if (e.target.checked) {
store.dispatch(removeQAPPAutoFriendsList(false))
} else {
store.dispatch(allowQAPPAutoFriendsList(true))
}
}
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' })
checkForDownload() {
const checkPass = this.shadowRoot.getElementById('downloadBackupPassword').value
const rePass = this.shadowRoot.getElementById('rePassword').value
backupname = 'qortal_backup_' + state.app.selectedAddress.address + '.json'
if (checkPass === '') {
this.backupErrorMessage = get("login.pleaseenter")
} else if (checkPass.length < 5) {
this.backupErrorMessage = get("login.lessthen8-2")
} else if (checkPass != rePass) {
this.backupErrorMessage = get("login.notmatch")
} else {
this.downloadBackup()
}
}
await this.saveFileToDisk(blob, backupname)
}
openDevDialog() {
this.closeSettings()
store.dispatch(setIsOpenDevDialog(true))
}
async saveFileToDisk(blob, fileName) {
try {
const fileHandle = await self.showSaveFilePicker({
suggestedName: fileName,
types: [{
description: "File"
}]
})
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"
await this.saveFileToDisk(blob, backupname)
}
const writeFile = async (fileHandle, contents) => {
const writable = await fileHandle.createWritable()
async saveFileToDisk(blob, fileName) {
try {
const fileHandle = await self.showSaveFilePicker({
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("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 = ''
}
}
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 = ''
}
}
}
window.customElements.define('security-view', SecurityView)
window.customElements.define('security-view', SecurityView)

View File

@ -1,309 +1,128 @@
import {css, 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 { html, LitElement } from 'lit'
import { connect } from 'pwa-helpers'
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() {
return {
loggedIn: { type: Boolean },
pages: { type: Array },
selectedView: { type: Object },
theme: { type: String, reflect: true }
}
}
static get properties() {
return {
loggedIn: { type: Boolean },
pages: { type: Array },
selectedView: { type: Object },
theme: { type: String, reflect: true }
}
}
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;
}
static get styles() {
return [userSettingsStyles]
}
.decline {
--mdc-theme-primary: var(--mdc-theme-error)
}
constructor() {
super()
this.selectedView = { id: 'info', name: 'General Account Info' }
this.theme = localStorage.getItem('qortalTheme') ? localStorage.getItem('qortalTheme') : 'light'
}
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;
}
render() {
return html`
<paper-dialog id="userSettingsDialog" class="userSettings" modal>
<div class="actions">
<h2></h2>
<mwc-icon class="close-icon" @click=${() => this.closeSettings()} title="Close Settings" >highlight_off</mwc-icon>
</div>
<div class="container">
<div class="wrapper">
<div class="leftBar" style="display: table; width: 100%;">
<div class="slug">Qortal UI ${translate("settings.settings")}</div>
<ul>
<li @click=${() => this.setSettingsView('info')} ><a class=${this.selectedView.id === 'info' ? 'active' : ''} href="javascript:void(0)">${translate("settings.account")}</a></li>
<li @click=${() => this.setSettingsView('security')} ><a class=${this.selectedView.id === 'security' ? 'active' : ''} href="javascript:void(0)">${translate("settings.security")}</a></li>
<li @click=${() => this.setSettingsView('export')} ><a class=${this.selectedView.id === 'export' ? 'active' : ''} href="javascript:void(0)">${translate("settings.exp1")}</a></li>
<li @click=${() => this.setSettingsView('qr-login')} ><a class=${this.selectedView.id === 'qr-login' ? 'active' : ''} href="javascript:void(0)">${translate("settings.qr_login_menu_item")}</a></li>
<li @click=${() => this.setSettingsView('notification')} ><a class=${this.selectedView.id === 'notification' ? 'active' : ''} href="javascript:void(0)">${translate("settings.notifications")}</a></li>
</ul>
</div>
<div class="mainPage">
<h1>${this.renderHeaderViews()}</h1>
<hr>
${html`${this.renderSettingViews(this.selectedView)}`}
</div>
</div>
</div>
</paper-dialog>
`
}
.actions {
display:flex;
justify-content: space-between;
padding: 0 4em;
margin: 15px 0 -2px 0;
}
stateChanged(state) {
this.loggedIn = state.app.loggedIn
}
.close-icon {
font-size: 36px;
}
renderSettingViews(selectedView) {
if (selectedView.id === 'info') {
return html`<account-view></account-view>`
} else if (selectedView.id === 'security') {
return html`<security-view .closeSettings=${() => this.closeSettings()}></security-view>`
} else if (selectedView.id === 'export') {
return html`<export-keys></export-keys>`
} else if (selectedView.id === 'notification') {
return html`<notifications-view></notifications-view>`
} else if (selectedView.id === 'qr-login') {
return html`<qr-login-view></qr-login-view>`
}
}
.close-icon:hover {
cursor: pointer;
opacity: .6;
}
renderHeaderViews() {
if (this.selectedView.id === 'info') {
return html`${translate("settings.generalinfo")}`
} else if (this.selectedView.id === 'security') {
return html`${translate("settings.accountsecurity")}`
} else if (this.selectedView.id === 'export') {
return html`${translate("settings.exp1")}`
} else if (this.selectedView.id === 'notification') {
return html`UI ${translate("settings.notifications")}`
} else if (this.selectedView.id === 'qr-login') {
return html`${translate("settings.qr_login_menu_item")}`
}
}
.buttons {
text-align:right;
}
setSettingsView(pageId) {
if (pageId === 'info') {
return this.selectedView = { id: 'info', name: 'General Account Info' }
} else if (pageId === 'security') {
return this.selectedView = { id: 'security', name: 'Account Security' }
} else if (pageId === 'export') {
return this.selectedView = { id: 'export', name: 'Export Master Keys' }
} else if (pageId === 'notification') {
return this.selectedView = { id: 'notification', name: 'UI Notifications' }
} else if (pageId === 'qr-login') {
return this.selectedView = { id: 'qr-login', name: 'QR Login' }
}
}
.container {
max-width: 90vw;
margin-left: auto;
margin-right: auto;
margin-top: 20px;
padding: .6em;
}
openSettings() {
if (this.loggedIn) {
this.shadowRoot.getElementById('userSettingsDialog').open()
}
}
ul {
list-style: none;
padding: 0;
margin-bottom: 0;
}
closeSettings() {
this.shadowRoot.getElementById('userSettingsDialog').close()
this.cleanUp()
}
.leftBar {
background-color: var(--white);
color: var(--black);
border: 1px solid var(--border);
padding: 20px 0 0 0;
border-radius: 5px;
}
.leftBar img {
margin: 0 auto;
width: 75%;
height: 75%;
text-align: center;
}
.leftBar .slug {
text-align: center;
margin-top: 20px;
color: var(--black);
font-size: 16px;
font-weight: 600;
margin-bottom: 7px;
}
.leftBar ul li {
border-bottom: 1px solid var(--border);
}
.leftBar ul li:last-child {
border-bottom: none;
}
.leftBar ul li a {
color: var(--black);
font-size: 16px;
font-weight: 400;
text-decoration: none;
padding: .9em;
display: block;
}
.leftBar ul li a i {
margin-right: 8px;
font-size: 16px;
}
.leftBar ul li a:hover {
background-color: var(--menuhover);
color: #515151;
}
.leftBar ul li:active {
border-bottom: none;
}
.leftBar ul li a.active {
color: #515151;
background-color: var(--menuactive);
border-left: 2px solid #515151;
margin-left: -2px;
}
.mainPage {
background-color: var(--white);
color: var(--black);
border: 1px solid var(--border);
padding: 20px 0 10px 0;
border-radius: 5px;
font-size: 16px;
text-align: center;
min-height: 460px;
height: auto;
overflow: auto;
}
@media(max-width:700px) {
.mainPage {
margin-top: 30px;
}
}
@media(min-width:765px) {
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
.actions {
display:flex;
justify-content: space-between;
padding: 0 4em;
margin: 15px 0 -25px 0;
}
.container {
padding: 2em;
}
.wrapper {
display: grid;
grid-template-columns: 1fr 3fr;
grid-gap: 30px;
}
.wrapper > .mainPage {
padding: 2em;
}
.leftBar {
text-align: left;
max-height: 403px;
max-width: 400px;
font-size: 16px;
}
.mainPage {
font-size: 16px;
}
}
`
}
constructor() {
super()
this.selectedView = { id: 'info', name: 'General Account Info' }
this.theme = localStorage.getItem('qortalTheme') ? localStorage.getItem('qortalTheme') : 'light'
}
render() {
return html`
<paper-dialog id="userSettingsDialog" class="userSettings" modal>
<div class="actions">
<h2></h2>
<mwc-icon class="close-icon" @click=${ () => this.closeSettings()} title="Close Settings" >highlight_off</mwc-icon>
</div>
<div class="container">
<div class="wrapper">
<div class="leftBar" style="display: table; width: 100%;">
<div class="slug">Qortal UI ${translate("settings.settings")}</div>
<ul>
<li @click=${ () => this.setSettingsView('info')} ><a class=${this.selectedView.id === 'info' ? 'active' : ''} href="javascript:void(0)">${translate("settings.account")}</a></li>
<li @click=${ () => this.setSettingsView('security')} ><a class=${this.selectedView.id === 'security' ? 'active' : ''} href="javascript:void(0)">${translate("settings.security")}</a></li>
<li @click=${ () => this.setSettingsView('export')} ><a class=${this.selectedView.id === 'export' ? 'active' : ''} href="javascript:void(0)">${translate("settings.exp1") }</a></li>
<li @click=${ () => this.setSettingsView('qr-login')} ><a class=${this.selectedView.id === 'qr-login' ? 'active' : ''} href="javascript:void(0)">${translate("settings.qr_login_menu_item") }</a></li>
<li @click=${ () => this.setSettingsView('notification')} ><a class=${this.selectedView.id === 'notification' ? 'active' : ''} href="javascript:void(0)">${translate("settings.notifications")}</a></li>
</ul>
</div>
<div class="mainPage">
<h1>${this.renderHeaderViews()}</h1>
<hr>
${html`${this.renderSettingViews(this.selectedView)}`}
</div>
</div>
</div>
</paper-dialog>
`
}
stateChanged(state) {
this.loggedIn = state.app.loggedIn
}
renderSettingViews(selectedView) {
if (selectedView.id === 'info') {
return html`<account-view></account-view>`
} else if (selectedView.id === 'security') {
return html`<security-view .closeSettings=${()=> this.closeSettings()}></security-view>`
} else if (selectedView.id === 'export') {
return html`<export-keys></export-keys>`
} else if (selectedView.id === 'notification') {
return html`<notifications-view></notifications-view>`
} else if (selectedView.id === 'qr-login') {
return html`<qr-login-view></qr-login-view>`
}
}
renderHeaderViews() {
if (this.selectedView.id === 'info') {
return html`${translate("settings.generalinfo")}`
} else if (this.selectedView.id === 'security') {
return html`${translate("settings.accountsecurity")}`
} else if (this.selectedView.id === 'export') {
return html`${translate("settings.exp1")}`
} else if (this.selectedView.id === 'notification') {
return html`UI ${translate("settings.notifications")}`
} else if (this.selectedView.id === 'qr-login') {
return html`${translate("settings.qr_login_menu_item")}`
}
}
setSettingsView(pageId) {
if (pageId === 'info') {
return this.selectedView = { id: 'info', name: 'General Account Info' }
} else if (pageId === 'security') {
return this.selectedView = { id: 'security', name: 'Account Security' }
} else if (pageId === 'export') {
return this.selectedView = { id: 'export', name: 'Export Master Keys' }
} else if (pageId === 'notification') {
return this.selectedView = { id: 'notification', name: 'UI Notifications' }
} else if (pageId === 'qr-login') {
return this.selectedView = { id: 'qr-login', name: 'QR Login' }
}
}
openSettings() {
if (this.loggedIn) {
this.shadowRoot.getElementById('userSettingsDialog').open()
}
}
closeSettings() {
this.shadowRoot.getElementById('userSettingsDialog').close()
this.cleanUp()
}
cleanUp() {
this.selectedView = { id: 'info', name: 'General Account Info' }
}
cleanUp() {
this.selectedView = { id: 'info', name: 'General Account Info' }
}
}
window.customElements.define('user-settings', UserSettings)
window.customElements.define('user-settings', UserSettings)

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
async changeStatus(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,
});
};
try {
if (
findMintingAccountFromOtherUser &&
findMintingAccountFromOtherUser.publicKey &&
findMintingAccountFromOtherUser.publicKey[0]
) {
await removeMintingAccount(
findMintingAccountFromOtherUser.publicKey[0]
);
}
} catch (error) {
this.errorMsg = this.renderErrorMsg2();
return;
body: sponsorshipKeyValue
})
}
try {
await addMintingAccount(this.privateRewardShareKey);
await routes.showSnackBar({
data: translate('becomeMinterPage.bchange19'),
});
this.status = 5;
await this.getMintingAcccounts();
if (findMintingAccountFromOtherUser && findMintingAccountFromOtherUser.publicKey && findMintingAccountFromOtherUser.publicKey[0]) {
await removeMintingAccount(
findMintingAccountFromOtherUser.publicKey[0]
)
}
} catch (error) {
this.errorMsg = this.renderErrorMsg3();
this.errorMsg = this.renderErrorMsg2()
return
}
try {
await addMintingAccount(this.privateRewardShareKey)
await routes.showSnackBar({
data: translate('becomeMinterPage.bchange19')
})
this.status = 5
await this.getMintingAcccounts()
} catch (error) {
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,170 +236,150 @@ 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){
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">
<div class="dialog-header" >
<div class="dialog-header" >
<div class="row">
<h1>In progress</h1>
<div class=${`smallLoading marginLoader ${this.status > 3 && 'hide'}`}></div>
<h1>In progress</h1>
<div class=${`smallLoading marginLoader ${this.status > 3 && 'hide'}`}></div>
</div>
<hr />
</div>
<hr />
</div>
<div class="dialog-container">
<ul>
<li class="row between">
<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>
<div class=${`smallLoading marginLoader ${this.status !== 2 && 'hide'}`}></div>
</li>
<li class=${`row between ${this.status < 3 && 'inactiveText'}`}>
<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>
</li>
<li class=${`row between ${this.status < 4 && 'inactiveText'}`}>
<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>
</li>
</ul>
<div class="warning column">
<p>
Warning: do not close the Qortal UI until completion!
</p>
<p class="message-error">${this.errorMsg}</p>
<div class="dialog-container">
<ul>
<li class="row between">
<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>
<div class=${`smallLoading marginLoader ${this.status !== 2 && 'hide'}`}></div>
</li>
<li class=${`row between ${this.status < 3 && 'inactiveText'}`}>
<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>
</li>
<li class=${`row between ${this.status < 4 && 'inactiveText'}`}>
<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>
</li>
</ul>
<div class="warning column">
<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">
${translate("general.close")}
</mwc-button>
` : ''}
</div>
</div>
<div class="modalFooter">
${this.errorMsg || this.status === 5 ? html`
<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,124 +1,84 @@
import {css, html, LitElement} from 'lit'
import {connect} from 'pwa-helpers'
import {store} from '../store.js'
import {translate} from '../../translate'
import { html, LitElement } from 'lit'
import { connect } from 'pwa-helpers'
import { store } from '../store'
import { walletProfileStyles } from '../styles/core-css'
// Multi language support
import { translate } from '../../translate'
class WalletProfile extends connect(store)(LitElement) {
static get properties() {
return {
wallet: { type: Object },
nodeConfig: { type: Object },
accountInfo: { type: Object },
theme: { type: String, reflect: true }
}
}
static get properties() {
return {
wallet: { type: Object },
nodeConfig: { type: Object },
accountInfo: { type: Object },
theme: { type: String, reflect: true }
}
}
static get styles() {
return css`
#profileInMenu {
padding: 12px;
border-top: var(--border);
background: var(--sidetopbar);
color: var(--black);
}
static get styles() {
return [walletProfileStyles]
}
#accountName {
margin: 0;
font-size: 18px;
font-weight: 500;
width: 100%;
padding-bottom: 8px;
display: flex;
}
constructor() {
super()
this.wallet = {}
this.nodeConfig = {}
this.accountInfo = {
names: [],
addressInfo: {}
}
this.theme = localStorage.getItem('qortalTheme') ? localStorage.getItem('qortalTheme') : 'light'
}
#blocksMinted {
margin:0;
margin-top: 0;
font-size: 12px;
color: #03a9f4;
}
render() {
return html`
<div id="profileInMenu">
<div style="padding: 8px 0;">
<div id="accountName">
<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>
<p id="blocksMinted">${translate("walletprofile.blocksminted")} - ${this.accountInfo.addressInfo.blocksMinted + this.accountInfo.addressInfo.blocksMintedAdjustment}</p>
</div>
</div>
<p id="address">${this.wallet.addresses[0].address}</p>
</div>
</div>
`
}
#address {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
margin:0;
margin-top: 8px;
font-size: 11px;
}
firstUpdated() {
// ...
}
.round-fullinfo {
position: relative;
width: 68px;
height: 68px;
border-radius: 50%;
}
getAvatar() {
if (this.accountInfo.names.length === 0) {
return html`<img class="round-fullinfo" src="/img/incognito.png">`
} 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`
.full-info-logo {
width: 68px;
height: 68px;
border-radius: 50%;
}
return html`<img class="round-fullinfo" src="${url}" onerror="this.src='/img/incognito.png';" />`
}
}
.inline-block-child {
flex: 1;
}
`
}
constructor() {
super()
this.wallet = {}
this.nodeConfig = {}
this.accountInfo = {
names: [],
addressInfo: {}
}
this.theme = localStorage.getItem('qortalTheme') ? localStorage.getItem('qortalTheme') : 'light'
}
render() {
return html`
<div id="profileInMenu">
<div style="padding: 8px 0;">
<div id="accountName">
<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>
<p id="blocksMinted">${translate("walletprofile.blocksminted")} - ${this.accountInfo.addressInfo.blocksMinted + this.accountInfo.addressInfo.blocksMintedAdjustment}</p>
</div>
</div>
<p id="address">${this.wallet.addresses[0].address}</p>
</div>
</div>
`
}
firstUpdated() {}
getAvatar() {
if (this.accountInfo.names.length === 0) {
return html`<img class="round-fullinfo" src="/img/incognito.png">`
} 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()}`
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
this.accountInfo = state.app.accountInfo
}
stateChanged(state) {
this.wallet = state.app.wallet
this.nodeConfig = state.app.nodeConfig
this.accountInfo = state.app.accountInfo
}
}
window.customElements.define('wallet-profile', WalletProfile)
window.customElements.define('wallet-profile', WalletProfile)

View File

@ -1,55 +1,62 @@
'use strict'
const utils = {
int32ToBytes (word) {
var byteArray = []
for (var b = 0; b < 32; b += 8) {
byteArray.push((word >>> (24 - b % 32)) & 0xFF)
}
return byteArray
},
int32ToBytes(word) {
var byteArray = []
for (var b = 0; b < 32; b += 8) {
byteArray.push((word >>> (24 - b % 32)) & 0xFF)
}
return byteArray
},
stringtoUTF8Array (message) {
if (typeof message === 'string') {
var s = unescape(encodeURIComponent(message)) // UTF-8
message = new Uint8Array(s.length)
for (var i = 0; i < s.length; i++) {
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
},
stringtoUTF8Array(message) {
if (typeof message === 'string') {
var s = unescape(encodeURIComponent(message)) // UTF-8
message = new Uint8Array(s.length)
for (var i = 0; i < s.length; i++) {
message[i] = s.charCodeAt(i) & 0xff
}
}
int64ToBytes (int64) {
// we want to represent the input as a 8-bytes array
var byteArray = [0, 0, 0, 0, 0, 0, 0, 0]
return message
},
for (var index = 0; index < byteArray.length; index++) {
var byte = int64 & 0xff
byteArray[byteArray.length - index - 1] = byte
int64 = (int64 - byte) / 256
}
// ...buffers then buffers.foreach and append to buffer1
appendBuffer(buffer1, buffer2) {
buffer1 = new Uint8Array(buffer1)
buffer2 = new Uint8Array(buffer2)
return byteArray
},
const tmp = new Uint8Array(buffer1.byteLength + buffer2.byteLength)
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
}
tmp.set(buffer1, 0)
tmp.set(buffer2, buffer1.byteLength)
return tmp
},
int64ToBytes(int64) {
// we want to represent the input as a 8-bytes array
var byteArray = [0, 0, 0, 0, 0, 0, 0, 0]
for (var index = 0; index < byteArray.length; index++) {
var byte = int64 & 0xff
byteArray[byteArray.length - index - 1] = byte
int64 = (int64 - byte) / 256
}
return byteArray
},
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
}
}
export default utils
export default utils

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

@ -15,4 +15,4 @@ Epml.registerPlugin(EpmlStreamPlugin)
Epml.registerPlugin(EpmlProxyPlugin)
Epml.allowProxying = true
export { Epml, EpmlStream }
export { Epml, EpmlStream }

View File

@ -1,106 +1,83 @@
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 { css, html, LitElement } from 'lit'
import { connect } from 'pwa-helpers'
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() {
return {
txInfo: { type: Object },
theme: { type: String, reflect: true }
}
}
static get properties() {
return {
txInfo: { type: Object },
theme: { type: String, reflect: true }
}
}
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);
}
static get styles() {
return [confirmTransactionDialogStyles]
}
.decline {
--mdc-theme-primary: var(--mdc-theme-error)
}
constructor() {
super()
this.transaction = {
template: html`Awaiting transaction info`
}
this.txInfo = html``
listenForRequest(args => this.requestTransaction(args))
this.theme = localStorage.getItem('qortalTheme') ? localStorage.getItem('qortalTheme') : 'light'
}
#txInfo {
text-align: left;
max-width: 520px;
color: var(--black);
}
render() {
return html`
<paper-dialog style="background: var(--white);" id="confirmDialog" modal>
<h2 style="color: var(--black);">${translate("transpage.tchange1")}</h2>
<div id="txInfo">
${this.txInfo}
</div>
<div class="buttons">
<mwc-button class='decline' @click=${e => this.decline(e)} dialog-dismiss>${translate("transpage.tchange2")}</mwc-button>
<mwc-button class='confirm' @click=${e => this.confirm(e)} dialog-confirm autofocus>${translate("transpage.tchange3")}</mwc-button>
</div>
</paper-dialog>
`
}
.buttons {
text-align:right;
}
firstUpdated() {
// ...
}
table td, th{
padding:4px;
text-align:left;
font-size:14px;
color: var(--black);
}
`
}
requestTransaction(transaction) {
this.shadowRoot.getElementById('confirmDialog').open()
this.transaction = transaction
this.txInfo = transaction.render(html)
constructor() {
super()
this.transaction = {
template: html`Awaiting transaction info`
}
this.txInfo = html``
listenForRequest(args => this.requestTransaction(args))
this.theme = localStorage.getItem('qortalTheme') ? localStorage.getItem('qortalTheme') : 'light';
}
return new Promise((resolve, reject) => {
this._resolve = resolve
this._reject = reject
})
}
render() {
return html`
<paper-dialog style="background: var(--white);" id="confirmDialog" modal>
<h2 style="color: var(--black);">${translate("transpage.tchange1")}</h2>
<div id="txInfo">
${this.txInfo}
</div>
<div class="buttons">
<mwc-button class='decline' @click=${e => this.decline(e)} dialog-dismiss>${translate("transpage.tchange2")}</mwc-button>
<mwc-button class='confirm' @click=${e => this.confirm(e)} dialog-confirm autofocus>${translate("transpage.tchange3")}</mwc-button>
</div>
</paper-dialog>
`
}
confirm(e) {
this._resolve({
success: true
})
}
stateChanged(state) {
this.loggedIn = state.app.loggedIn
}
decline(e) {
const rejecterror = get("transactions.declined")
this._reject(new Error(rejecterror))
}
requestTransaction(transaction) {
this.shadowRoot.getElementById('confirmDialog').open()
this.transaction = transaction
this.txInfo = transaction.render(html)
return new Promise((resolve, reject) => {
this._resolve = resolve
this._reject = reject
})
}
confirm(e) {
this._resolve({
success: true
})
}
decline(e) {
const rejecterror = get("transactions.declined")
this._reject(new Error(rejecterror))
}
stateChanged(state) {
this.loggedIn = state.app.loggedIn
}
}
window.customElements.define('confirm-transaction-dialog', ConfirmTransactionDialog)
const txDialog = document.createElement('confirm-transaction-dialog')
export const requestTransactionDialog = document.body.appendChild(txDialog)
export const requestTransactionDialog = document.body.appendChild(txDialog)

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,188 +1,118 @@
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']
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
}
}
}
static get properties() {
return {
welcomeMessage: { type: String, attribute: 'welcome-message', reflectToAttribute: true },
loadingMessage: { type: String, attribute: 'loading-message', reflectToAttribute: true}
}
}
constructor () {
super()
this.welcomeMessage = ''
this.loadingMessage = ''
}
static get styles() {
return [loadingRippleStyles]
}
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);
}
constructor() {
super()
this.welcomeMessage = ''
this.loadingMessage = ''
}
#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">
<div id="ripple">
<div id="rippleShader"></div>
<div id="rippleContentWrapper">
<div id="rippleContent">
<h1 style="color: var(--black);">${this.welcomeMessage}</h1>
<paper-spinner-lite active></paper-spinner-lite>
<p style="color: var(--black);">${this.loadingMessage}</p>
</div>
</div>
</div>
</div>
`
}
`
}
firstUpdated() {
this._rippleWrapper = this.shadowRoot.getElementById('rippleWrapper')
this._ripple = this.shadowRoot.getElementById('ripple')
this._rippleContentWrapper = this.shadowRoot.getElementById('rippleContentWrapper')
}
render () {
return html`
<div id="rippleWrapper">
<div id="ripple">
<div id="rippleShader"></div>
<div id="rippleContentWrapper">
<div id="rippleContent">
<h1 style="color: var(--black);">${this.welcomeMessage}</h1>
<paper-spinner-lite active></paper-spinner-lite>
<p style="color: var(--black);">${this.loadingMessage}</p>
</div>
</div>
</div>
</div>
`
}
open(origin) {
this._rippleWrapper.style.top = origin.y + 'px'
this._rippleWrapper.style.left = origin.x + 'px'
this._rippleContentWrapper.style.marginTop = -origin.y + 'px'
this._rippleContentWrapper.style.marginLeft = -origin.x + 'px'
firstUpdated () {
this._rippleWrapper = this.shadowRoot.getElementById('rippleWrapper')
this._ripple = this.shadowRoot.getElementById('ripple')
this._rippleContentWrapper = this.shadowRoot.getElementById('rippleContentWrapper')
}
return new Promise((resolve, reject) => {
this._ripple.classList.add('activating')
// duh
open (origin) {
this._rippleWrapper.style.top = origin.y + 'px'
this._rippleWrapper.style.left = origin.x + 'px'
this._rippleContentWrapper.style.marginTop = -origin.y + 'px'
this._rippleContentWrapper.style.marginLeft = -origin.x + 'px'
let isOpened = false
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))
})
}
const doneOpeningEvent = () => {
if (isOpened) return
// 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()
})
}
// Clear events
TRANSITION_EVENT_NAMES.forEach(name => this._ripple.removeEventListener(name, doneOpeningEvent))
// 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))
})
}
this._ripple.classList.add('activating-done')
stateChanged (state) {
// this.loggedIn = state.app.loggedIn
}
isOpened = true
resolve()
}
TRANSITION_EVENT_NAMES.forEach(name => this._ripple.addEventListener(name, doneOpeningEvent))
})
}
fade() {
return new Promise((resolve, reject) => {
this._ripple.classList.remove('activating')
this._ripple.classList.remove('activating-done')
this._ripple.classList.remove('disabling')
resolve()
})
}
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) {
// ...
}
}
window.customElements.define('loading-ripple', LoadingRipple)
@ -192,10 +122,9 @@ rippleNode.id = 'ripple-node'
rippleNode.loadingMessage = ''
rippleElement = document.body.appendChild(rippleNode)
setTimeout(() => {
const ripple = document.getElementById('ripple-node')
const mainApp = document.getElementById('main-app')
const shadow = mainApp.shadowRoot
// console.log(shadow)
rippleElement = shadow.appendChild(ripple)
const ripple = document.getElementById('ripple-node')
const mainApp = document.getElementById('main-app')
const shadow = mainApp.shadowRoot
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
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 = {
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;
static get properties() {
return {
onClick: { type: Function },
isLoading: { type: Boolean },
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,122 +1,105 @@
import {css, 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 { html, LitElement } from 'lit'
import { connect } from 'pwa-helpers'
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
class MykeyPage extends connect(store)(LitElement) {
static get properties() {
return {
nodeConfig: { type: Object },
theme: { type: String, reflect: true }
}
}
static get properties() {
return {
nodeConfig: { type: Object },
theme: { type: String, reflect: true }
}
}
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);
}
static get styles() {
return [mykeyPageStyles]
}
.red {
--mdc-theme-primary: red;
}
`
}
constructor() {
super()
this.nodeConfig = {}
this.theme = localStorage.getItem('qortalTheme') ? localStorage.getItem('qortalTheme') : 'light'
}
constructor() {
super()
this.nodeConfig = {}
this.theme = localStorage.getItem('qortalTheme') ? localStorage.getItem('qortalTheme') : 'light';
}
render() {
return html`
<mwc-dialog id="mykeyDialog" heading="${translate("apipage.achange1")}" opened=false>
<div style="min-height:200px; min-width: 300px; box-sizing: border-box; position: relative;">
<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">
${translate("apipage.achange4")}
</mwc-button>
<mwc-button slot="primaryAction" @click="${this.addMykey}">
${translate("apipage.achange5")}
</mwc-button>
</mwc-dialog>
`
}
render() {
return html`
<mwc-dialog id="mykeyDialog" heading="${translate("apipage.achange1")}" opened=false>
<div style="min-height:200px; min-width: 300px; box-sizing: border-box; position: relative;">
<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"
>
${translate("apipage.achange4")}
</mwc-button>
<mwc-button
slot="primaryAction"
@click="${this.addMykey}"
>
${translate("apipage.achange5")}
</mwc-button>
</mwc-dialog>
`
}
firstUpdated() {
// ...
}
stateChanged(state) {
this.config = state.config
this.nodeConfig = state.app.nodeConfig
}
show() {
this.shadowRoot.getElementById('mykeyDialog').show()
}
show() {
this.shadowRoot.getElementById('mykeyDialog').show()
}
async addMykey() {
const mykeyInput = this.shadowRoot.getElementById('mykeyInput').value
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));
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()
}
}
if (testResult === true) {
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
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 { html, LitElement } from 'lit'
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}
`
this.templateEntropy = Math.floor(Math.log(entropy) / Math.log(8))
return final.join(' ')
}
}
customElements.define('random-sentence-generator', RandomSentenceGenerator)
window.customElements.define('random-sentence-generator', RandomSentenceGenerator)
export default RandomSentenceGenerator
export default RandomSentenceGenerator

View File

@ -1,19 +1,21 @@
import {css, html, LitElement} from 'lit'
import {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 { html, LitElement } from 'lit'
import { connect } from 'pwa-helpers'
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,68 +69,65 @@ 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
`
<div>
<span class="name">${this.selectedItem.name}</span>
<span>${this.selectedItem.protocol + '://' + this.selectedItem.domain + ':' + this.selectedItem.port}</span>
</div>
` : html`${translate('settings.selectnode')}`
}
</div>
${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')}
`
}
</div>
<mwc-icon>expand_more</mwc-icon>
</div>
<ul class="${this.dropdownOpen ? 'open' : ''}">
${this.nodeConfig.knownNodes.map(
(n, index) => html`
<li @click="${(e) => this.handleSelection(e, n, index)}">
<div class="list-parent">
<div>
<span class="name">${n.name}</span>
<span>${n.protocol + '://' + n.domain + ':' + n.port}</span>
</div>
<div>
<mwc-button
outlined
@click="${(e) => {
e.stopPropagation();
const currentValues = this.nodeConfig.knownNodes[index]
const dialog = this.shadowRoot.querySelector('#addNodeDialog')
// Set the value for mwc-textfield elements
dialog.querySelector('#nameInput').value = currentValues.name
dialog.querySelector('#domainInput').value = currentValues.domain
dialog.querySelector('#portInput').value = currentValues.port
// Set the selected value for mwc-select
const protocolList = dialog.querySelector('#protocolList')
protocolList.value = currentValues.protocol
this.isBeingEdited = true
this.isBeingEditedIndex = index
this.shadowRoot.querySelector('#addNodeDialog').show()
}}"
>
<mwc-icon class="buttonBlue">edit</mwc-icon>
</mwc-button>
<mwc-button outlined @click="${(e) => this.removeNode(e, index)}">
<mwc-icon class="buttonred">remove</mwc-icon>
</mwc-button>
</div>
${this.nodeConfig.knownNodes.map((n, index) => html`
<li @click="${(e) => this.handleSelection(e, n, index)}">
<div class="list-parent">
<div>
<span class="name">${n.name}</span>
<span>${n.protocol + '://' + n.domain + ':' + n.port}</span>
</div>
</li>
`
)}
<div>
<mwc-button
outlined
@click="${(e) => {
e.stopPropagation()
const currentValues = this.nodeConfig.knownNodes[index]
const dialog = this.shadowRoot.querySelector('#addNodeDialog')
// Set the value for mwc-textfield elements
dialog.querySelector('#nameInput').value = currentValues.name
dialog.querySelector('#domainInput').value = currentValues.domain
dialog.querySelector('#portInput').value = currentValues.port
// Set the selected value for mwc-select
const protocolList = dialog.querySelector('#protocolList')
protocolList.value = currentValues.protocol
this.isBeingEdited = true
this.isBeingEditedIndex = index
this.shadowRoot.querySelector('#addNodeDialog').show()
}}"
>
<mwc-icon class="buttonBlue">edit</mwc-icon>
</mwc-button>
<mwc-button outlined @click="${(e) => this.removeNode(e, index)}">
<mwc-icon class="buttonred">remove</mwc-icon>
</mwc-button>
</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,13 +252,36 @@ 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}`
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')
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')
@ -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
@ -426,9 +340,9 @@ class SettingsPage extends connect(store)(LitElement) {
nodeSelected(selectedNodeIndex) {
const selectedNode = this.nodeConfig.knownNodes[selectedNodeIndex]
const selectedName = `${selectedNode.name}`
const selectedNodeUrl = `${selectedNode.protocol + '://' + selectedNode.domain +':' + selectedNode.port}`
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
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,213 +1,221 @@
import {css, html, LitElement} from 'lit'
import {ifDefined} from 'lit/directives/if-defined.js'
import {sideMenuItemStyle} from './side-menu-item-style.js'
import { html, LitElement } from 'lit'
import { ifDefined } from 'lit/directives/if-defined.js'
import { sideMenuItemStyles } from '../styles/core-css'
import '@vaadin/icon'
import '@vaadin/icons'
import '@polymer/paper-tooltip'
export class SideMenuItem extends LitElement {
static get properties() {
return {
selected: { type: Boolean, reflect: true },
label: { type: String, reflect: true },
expanded: { type: Boolean, reflect: true },
compact: { type: Boolean, reflect: true },
href: { type: String, reflect: true },
target: { type: String, reflect: true },
hide: { type: Boolean }
}
}
static get properties() {
return {
selected: { type: Boolean, reflect: true },
label: { type: String, reflect: true },
expanded: { type: Boolean, reflect: true },
compact: { type: Boolean, reflect: true },
href: { type: String, reflect: true },
target: { type: String, reflect: true },
hide: { type: Boolean }
}
}
static get styles() {
return css`
${sideMenuItemStyle}
`
}
static get styles() {
return [sideMenuItemStyles]
}
constructor() {
super()
this.selected = false
this.expanded = false
this.hide = false
}
constructor() {
super()
this.selected = false
this.expanded = false
this.hide = false
}
render() {
return html`
${this._itemLinkTemplate()} ${this._tooltipTemplate()}
${this._childrenTemplate()}
`
}
render() {
return html`
${this._itemLinkTemplate()} ${this._tooltipTemplate()}
${this._childrenTemplate()}
`
}
firstUpdated(changedProperties) {
if (!this.hasChildren()) {
return
}
this.collapseExpandIcon = document.createElement("vaadin-icon")
this.collapseExpandIcon.id = "collapse-button"
this.shadowRoot.getElementById("content").appendChild(this.collapseExpandIcon)
this._boundOutsideClickListener = this._outsideClickListener.bind(this)
}
firstUpdated(changedProperties) {
if (!this.hasChildren()) {
return
}
_itemLinkTemplate() {
return html`
<a id="itemLink"
level=${this._getLevel}
href=${this.href || '#!'}
@click="${(e) => this._onClick(e)}"
target=${ifDefined(this.target)}
class=${this.hide ? 'hideItem' : ''}
>
<slot class="icon" name="icon"></slot>
<div id ="content">
<span>${this.label}</span>
</div>
</a>
`
}
this.collapseExpandIcon = document.createElement("vaadin-icon")
this.collapseExpandIcon.id = "collapse-button"
this.shadowRoot.getElementById("content").appendChild(this.collapseExpandIcon)
this._boundOutsideClickListener = this._outsideClickListener.bind(this)
}
_tooltipTemplate() {
return html`
${this._getLevel === 0 && this.compact ? html`
<paper-tooltip for="itemLink" position="right" animation-delay="0">
${this.label}
</paper-tooltip>
`
: undefined}
`
}
_itemLinkTemplate() {
return html`
<a id="itemLink"
level=${this._getLevel}
href=${this.href || '#!'}
@click="${(e) => this._onClick(e)}"
target=${ifDefined(this.target)}
class=${this.hide ? 'hideItem' : ''}
>
<slot class="icon" name="icon"></slot>
<div id ="content">
<span>${this.label}</span>
</div>
</a>
`
}
_childrenTemplate() {
return html`
${this.expanded ? html`
${this.compact ? html`
<div id="overlay"><slot></slot></div>
`
: html`
<slot></slot>
`}
`
: undefined}
`
}
_tooltipTemplate() {
return html`
${this._getLevel === 0 && this.compact ?
html`
<paper-tooltip for="itemLink" position="right" animation-delay="0">
${this.label}
</paper-tooltip>
` : undefined
}
`
}
updated(changedProperties) {
changedProperties.forEach((oldValue, propName) => {
if (propName === "compact") {
this._onCompactChanged()
}
_childrenTemplate() {
return html`
${this.expanded ?
html`
${this.compact ?
html`
<div id="overlay"><slot></slot></div>
` : html`
<slot></slot>
`
}
` : undefined
}
`
}
if (propName === "expanded") {
this._onExpandedChanged()
}
updated(changedProperties) {
changedProperties.forEach((oldValue, propName) => {
if (propName === "compact") {
this._onCompactChanged()
}
if (propName === "selected"){
if (oldValue === this.selected){
return
}
if (propName === "expanded") {
this._onExpandedChanged()
}
if (this.selected) {
this._changeSelectedState(true)
this._markParentWithSelectedChild()
}
}
});
}
if (propName === "selected") {
if (oldValue === this.selected) {
return
}
_onCompactChanged() {
this.expanded = false;
if (this.selected) {
this._changeSelectedState(true)
this._markParentWithSelectedChild()
}
}
})
}
if (this.collapseExpandIcon == null) {
return;
}
_onCompactChanged() {
this.expanded = false
if (!this.compact) {
this.collapseExpandIcon["icon"] = "vaadin:chevron-down-small"
} else {
this.collapseExpandIcon["icon"] = "vaadin:chevron-right-small"
}
}
if (this.collapseExpandIcon == null) {
return
}
_onExpandedChanged() {
if (this.collapseExpandIcon == null) {
return;
}
if (!this.compact) {
this.collapseExpandIcon["icon"] = "vaadin:chevron-down-small"
} else {
this.collapseExpandIcon["icon"] = "vaadin:chevron-right-small"
}
}
if (this.expanded) {
this._onHandleExpanded();
} else {
this._onHandleCollapsed();
}
}
_onExpandedChanged() {
if (this.collapseExpandIcon == null) {
return
}
_onHandleExpanded() {
if (!this.compact) {
this.collapseExpandIcon["icon"] = "vaadin:chevron-up-small"
} else {
this.collapseExpandIcon["icon"] = "vaadin:chevron-left-small"
document.addEventListener("click", this._boundOutsideClickListener, true)
}
}
if (this.expanded) {
this._onHandleExpanded()
} else {
this._onHandleCollapsed()
}
}
_onHandleCollapsed() {
if (!this.compact) {
this.collapseExpandIcon["icon"] = "vaadin:chevron-down-small"
} else {
this.collapseExpandIcon["icon"] = "vaadin:chevron-right-small"
document.removeEventListener(
"click",
this._boundOutsideClickListener,
true
)
}
}
_onHandleExpanded() {
if (!this.compact) {
this.collapseExpandIcon["icon"] = "vaadin:chevron-up-small"
} else {
this.collapseExpandIcon["icon"] = "vaadin:chevron-left-small"
document.addEventListener("click", this._boundOutsideClickListener, true)
}
}
_onClick(e) {
if (!this.hasChildren()) {
this.selected = true
} else {
this.expanded = !this.expanded
e.preventDefault()
}
}
_onHandleCollapsed() {
if (!this.compact) {
this.collapseExpandIcon["icon"] = "vaadin:chevron-down-small"
} else {
this.collapseExpandIcon["icon"] = "vaadin:chevron-right-small"
document.removeEventListener(
"click",
this._boundOutsideClickListener,
true
)
}
}
_outsideClickListener(event) {
const eventPath = event.composedPath()
if (eventPath.indexOf(this) < 0) {
this.expanded = false
}
}
_onClick(e) {
if (!this.hasChildren()) {
this.selected = true
} else {
this.expanded = !this.expanded
_changeSelectedState(selected, sourceEvent) {
this.selected = selected
let evt = new CustomEvent("side-menu-item-select", {
bubbles: true,
cancelable: true,
detail: { sourceEvent: sourceEvent }
});
this.dispatchEvent(evt)
}
e.preventDefault()
}
}
hasChildren() {
return !!this.querySelector("side-menu-item")
}
_outsideClickListener(event) {
const eventPath = event.composedPath()
_markParentWithSelectedChild() {
let element = this.parentElement;
while (element instanceof SideMenuItem) {
element.setAttribute('hasSelectedChild', true)
element = element.parentElement;
}
}
if (eventPath.indexOf(this) < 0) {
this.expanded = false
}
}
get _getLevel() {
let level = 0
let element = this.parentElement
while (element instanceof SideMenuItem) {
level++;
element = element.parentElement
}
return level
}
_changeSelectedState(selected, sourceEvent) {
this.selected = selected
let evt = new CustomEvent("side-menu-item-select", {
bubbles: true,
cancelable: true,
detail: { sourceEvent: sourceEvent }
})
this.dispatchEvent(evt)
}
hasChildren() {
return !!this.querySelector("side-menu-item")
}
_markParentWithSelectedChild() {
let element = this.parentElement;
while (element instanceof SideMenuItem) {
element.setAttribute('hasSelectedChild', true)
element = element.parentElement
}
}
get _getLevel() {
let level = 0
let element = this.parentElement
while (element instanceof SideMenuItem) {
level++
element = element.parentElement
}
return level
}
}
window.customElements.define("side-menu-item", SideMenuItem);
window.customElements.define("side-menu-item", SideMenuItem)

View File

@ -1,78 +1,68 @@
import {css, html, LitElement} from 'lit'
import { html, LitElement } from 'lit'
import { sideMenuStyles } from '../styles/core-css'
class SideMenu extends LitElement {
static get properties() {
return {
items: {type: Array},
selectedValue: {type: String, reflect: true},
compact: {type: Boolean, reflect: true}
}
}
static get properties() {
return {
items: { type: Array },
selectedValue: { type: String, reflect: true },
compact: { type: Boolean, reflect: true }
}
}
static get styles() {
return css`
nav {
padding: 0;
}
static get styles() {
return [sideMenuStyles]
}
:host {
list-style: none;
width: 100%;
position: relative;
}
constructor() {
super()
this.compact = false
}
:host([compact]) {
width: auto;
}
`
}
render() {
return html`
<nav @side-menu-item-select=${this._handleSelect}>
<slot></slot>
</nav>
`
}
constructor() {
super()
this.compact = false
}
firstUpdated(_changedProperties) {
this.items = [...this.querySelectorAll("side-menu-item")]
}
render() {
return html`
<nav @side-menu-item-select=${this._handleSelect}>
<slot></slot>
</nav>
`
}
_handleSelect(event) {
let targetItem = event.target
this._deselectAllItems()
targetItem.selected = true
this.selectedValue = targetItem.label
}
firstUpdated(_changedProperties) {
this.items = [...this.querySelectorAll("side-menu-item")]
}
_deselectAllItems() {
this.items.forEach(element => {
if (this.compact) {
element.expanded = false
}
_handleSelect(event) {
let targetItem = event.target
this._deselectAllItems()
targetItem.selected = true
this.selectedValue = targetItem.label
}
element.selected = false
element.hasChildren() ? element.removeAttribute('hasSelectedChild') : undefined
})
}
_deselectAllItems() {
this.items.forEach(element => {
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))
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)
}
})
}
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,78 +1,66 @@
import {css, html, LitElement} from 'lit'
import { html, LitElement } from 'lit'
import '@material/mwc-snackbar'
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
},
_labelText: { type: String },
_stacked: { type: Boolean },
_leading: { type: Boolean },
_closeOnEscape: { type: Boolean },
_timeoutMs: { type: Number },
action: {},
_dismiss: {},
_dismissIcon: { type: String }
}
}
static get properties() {
return {
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 },
_dismiss: {},
_dismissIcon: { type: String },
action: {}
}
}
static get styles() {
return css``
}
constructor() {
super()
this._queue = []
this.busy = false
this._timeoutMs = 5000
}
constructor() {
super()
this._queue = []
this.busy = false
this._timeoutMs = 5000
}
render() {
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`
<mwc-icon-button icon="${this._dismissIcon}" slot="dismiss"></mwc-icon-button>
` : ''
}
</mwc-snackbar>
`
}
render() {
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`
<mwc-icon-button icon="${this._dismissIcon}" slot="dismiss"></mwc-icon-button>
` : ''}
</mwc-snackbar>
`
}
firstUpdated() {
this._snackbar = this.shadowRoot.getElementById('snack')
}
firstUpdated() {
this._snackbar = this.shadowRoot.getElementById('snack')
}
_shift() {
if (this.busy || this._queue.length === 0) return
const item = this._queue.shift()
this._labelText = item.labelText || ''
this._action = item.action || ''
this._dismiss = item.dismiss || false
this._dismissIcon = item.dismissIcon || 'close'
this._leading = !!item.leading
this._closeOnEscape = (item.closeOnEscape && item.closeOnEscape !== false) // JSON.parse maybe needs to be compared to 'false'...in which case no need for complex expression
this._timeoutMs = (item.timeoutMs >= 4000 && item.timeoutMs <= 10000) ? item.timeoutMs : 5000
this._snackbar.show()
}
_shift() {
if (this.busy || this._queue.length === 0) return
const item = this._queue.shift()
this._labelText = item.labelText || ''
this._action = item.action || ''
this._dismiss = item.dismiss || false
this._dismissIcon = item.dismissIcon || 'close'
this._leading = !!item.leading
this._closeOnEscape = (item.closeOnEscape && item.closeOnEscape !== false) // JSON.parse maybe needs to be compared to 'false'...in which case no need for complex expression
this._timeoutMs = (item.timeoutMs >= 4000 && item.timeoutMs <= 10000) ? item.timeoutMs : 5000
this._snackbar.show()
}
add(item) {
this._queue.push(item)
this._shift()
}
add(item) {
this._queue.push(item)
this._shift()
}
}
window.customElements.define('snack-queue', SnackQueue)
@ -82,9 +70,9 @@ queueNode.id = 'queue-node'
queueNode.loadingMessage = ''
queueElement = document.body.appendChild(queueNode)
setTimeout(() => {
queueElement = document.getElementById('queue-node')
const mainApp = document.getElementById('main-app')
const shadow = mainApp.shadowRoot
queueElement = shadow.appendChild(queueElement)
queueElement = document.getElementById('queue-node')
const mainApp = document.getElementById('main-app')
const shadow = mainApp.shadowRoot
queueElement = shadow.appendChild(queueElement)
}, 500)
export default queueElement

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,41 +1,39 @@
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 {initApi} from 'qortal-ui-crypto'
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)
const workerInitChecker = () => {
const state = store.getState()
const state = store.getState()
if (store.getState().app.nodeConfig.knownNodes.length === 0) {
store.dispatch(doLoadNodeConfig())
}
if (store.getState().app.nodeConfig.knownNodes.length === 0) {
store.dispatch(doLoadNodeConfig())
}
if (state.config.loaded) {
store.dispatch(doLoadNodeConfig())
if (state.config.loaded) {
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))
}
}
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) {
store.dispatch(doLoadConfigFromAPI())
store.dispatch(doLoadNodeConfig())
store.dispatch(doLoadConfigFromAPI())
store.dispatch(doLoadNodeConfig())
}
if (!store.getState().user.loaded) {
store.dispatch(doLoadNotificationConfig())
}
store.dispatch(doLoadNotificationConfig())
}

View File

@ -1,19 +1,19 @@
export const loadStateFromLocalStorage = (key) => {
try {
const config = localStorage.getItem(key)
if (config === null) return void 0
return JSON.parse(config)
} catch (e) {
// Could add error handling in case there's a weird one...don't want to overwrite config if it's malfunctioned
return void 0
}
try {
const config = localStorage.getItem(key)
if (config === null) return void 0
return JSON.parse(config)
} catch (e) {
// Could add error handling in case there's a weird one...don't want to overwrite config if it's malfunctioned
return void 0
}
}
export const saveStateToLocalStorage = (key, state) => {
try {
const stateJSON = JSON.stringify(state)
localStorage.setItem(key, stateJSON)
} catch (e) {
console.error(e, 'e')
}
}
try {
const stateJSON = JSON.stringify(state)
localStorage.setItem(key, stateJSON)
} catch (e) {
console.error(e, 'e')
}
}

View File

@ -1,11 +1,13 @@
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) {
return null
}
}
const bytes = CryptoJS.AES.decrypt(ciphertext, salt)
try {
return JSON.parse(bytes.toString(CryptoJS.enc.Utf8))
} catch (err) {
return null
}
}

View File

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

View File

@ -1,12 +1,12 @@
const config = {
default: {
title: 'Qortal',
body: 'Qortal Notifications',
icon: '/img/favicon/favicon-96x96.png'
},
newMessageAudio: '/sound/newmessage.ogg',
messageAlert: '/sound/messageAlert.ogg',
blockAlert: '/sound/blockAlert.ogg'
default: {
title: 'Qortal',
body: 'Qortal Notifications',
icon: '/img/favicon/favicon-96x96.png'
},
newMessageAudio: '/sound/newmessage.ogg',
messageAlert: '/sound/messageAlert.ogg',
blockAlert: '/sound/blockAlert.ogg'
}
export default config
export default config

View File

@ -1,39 +1,40 @@
import { dispatcher } from './dispatcher'
import { NEW_MESSAGE, NEW_MESSAGE_NOTIFICATION_QAPP, NEW_MESSAGE_NOTIFICATION_QAPP_LOCAL } from './types'
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 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
})
}
})
} else {
if ([1, 3, 5, 7, 9, 11, 13, 15].includes(initial)) {
snackbar.add({
labelText: 'Notification is disabled in this browser, Enable it to recieve notifications.',
dismiss: true
})
}
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 = ''
initial = initial + 1
}
return true
} else {
initial = initial + 1
snackbar.add({
labelText: 'Notification is disabled, Enable it to recieve notifications.',
dismiss: true
})
}
})
} else {
if ([1, 3, 5, 7, 9, 11, 13, 15].includes(initial)) {
snackbar.add({
labelText: 'Notification is disabled in this browser, Enable it to recieve notifications.',
dismiss: true
})
}
initial = initial + 1
}
}
/**
@ -41,52 +42,53 @@ 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 = () => {
const newMessage = () => {
let data
let data
if (req.type && req.type === 'qapp') {
data = req
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}`
} 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 }
}
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'
const notificationState = { type: NEW_MESSAGE, data: data }
const notificationStateQapp = { type: NEW_MESSAGE_NOTIFICATION_QAPP, data: data }
const canI = notificationCheck()
data = { title, sound: config.messageAlert, options: { body, icon: config.default.icon, badge: config.default.icon }, req }
}
if (canI === true) {
if (req.type && req.type === 'qapp') {
dispatcher(notificationStateQapp)
} else {
dispatcher(notificationState)
}
const notificationState = { type: NEW_MESSAGE, data: data }
const notificationStateQapp = { type: NEW_MESSAGE_NOTIFICATION_QAPP, data: data }
const canI = notificationCheck()
} 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 })
if (canI === true) {
if (req.type && req.type === 'qapp') {
dispatcher(notificationStateQapp)
} else {
dispatcher(notificationState)
}
} else {
_state = notificationState
}
}
} catch (error) {
console.log('error', error)
}
}else if (!document.hasFocus()) {
newMessage()
} else {
if (page.includes(req.url) === false) {
newMessage()
}
}
}
const page = window.top.location.href
if (req.type && req.type === 'qapp-local-notification') {
try {
dispatcher({ type: NEW_MESSAGE_NOTIFICATION_QAPP_LOCAL, data: req })
} catch (error) {
console.log('error', error)
}
} else if (!document.hasFocus()) {
newMessage()
} else {
if (page.includes(req.url) === false) {
newMessage()
}
}
}

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)
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:
}
}
case NEW_MESSAGE_NOTIFICATION_QAPP:
return newMessageNotificationQapp(notificationState.data)
case NEW_MESSAGE_NOTIFICATION_QAPP_LOCAL:
return newMessageNotificationQappLocal(notificationState.data)
default:
}
}

View File

@ -1 +1 @@
export { newMessage, newMessageNotificationQapp, newMessageNotificationQappLocal } from './new-message'
export { newMessage, newMessageNotificationQapp, newMessageNotificationQappLocal } from './new-message'

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