forked from Qortal/qortal-ui
Compare commits
7 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
1f37de8f12 | ||
|
fb6b5d54fa | ||
|
f367544498 | ||
|
c3b1875672 | ||
|
6f59c7c583 | ||
|
b66f5d1e1f | ||
|
d596df746d |
11
.gitignore
vendored
11
.gitignore
vendored
@ -1,13 +1,10 @@
|
||||
# npm
|
||||
package-lock.json
|
||||
# Yarn
|
||||
yarn.lock
|
||||
|
||||
# Derived js files
|
||||
plugins/plugins/core/**/*.js
|
||||
qortal-ui-plugins/plugins/core/**/*.js
|
||||
!*.src.js
|
||||
!plugins/plugins/core/**/*charts.js
|
||||
!plugins/plugins/core/**/*css.js
|
||||
core/src/redux/app/version.js
|
||||
!plugins/plugins/core/components/**/*.js
|
||||
qortal-ui-core/src/redux/app/version.js
|
||||
|
||||
# Node modules
|
||||
node_modules/
|
||||
|
71
.travis.yml
Normal file
71
.travis.yml
Normal file
@ -0,0 +1,71 @@
|
||||
|
||||
branches:
|
||||
except:
|
||||
- master
|
||||
|
||||
language: node_js
|
||||
node_js: "14.17.0"
|
||||
|
||||
cache:
|
||||
yarn: true
|
||||
directories:
|
||||
- node_modules
|
||||
# - qortal-ui-core/node_modules
|
||||
# - qortal-ui-plugins/node_modules
|
||||
# - qortal-ui-crypto/node_modules
|
||||
- $HOME/.cache/electron
|
||||
- $HOME/.cache/electron-builder
|
||||
|
||||
install:
|
||||
- sh install-dependencies.sh
|
||||
|
||||
env:
|
||||
global:
|
||||
- ELECTRON_CACHE=$HOME/.cache/electron
|
||||
- ELECTRON_BUILDER_CACHE=$HOME/.cache/electron-builder
|
||||
- ELECTRON_ENABLE_LOGGING=true
|
||||
|
||||
jobs:
|
||||
include:
|
||||
- stage: Deploy Linux
|
||||
if: tag IS present
|
||||
os: linux
|
||||
dist: bionic
|
||||
sudo: required
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
# default Electron dependencies
|
||||
- build-essential
|
||||
- gconf2
|
||||
- gconf-service
|
||||
- libgtk-3-0
|
||||
- libnotify4
|
||||
- libxss-dev
|
||||
- libxss1
|
||||
- xdg-utils
|
||||
- libatspi2.0-0
|
||||
- libappindicator1
|
||||
- libxext-dev
|
||||
- libxtst6
|
||||
- libxtst-dev
|
||||
- libnss3
|
||||
script:
|
||||
- newVersion=$(git describe --abbrev=0)
|
||||
- yarn version --new-version $newVersion
|
||||
- sh set-up-snap.sh
|
||||
- yarn run release
|
||||
- stage: Deploy Windows & Mac
|
||||
if: tag IS present
|
||||
os: osx
|
||||
osx_image: xcode12.5.1
|
||||
script:
|
||||
- newVersion=$(git describe --abbrev=0)
|
||||
- yarn version --new-version $newVersion
|
||||
- yarn run release -- --mac --win
|
||||
before_cache:
|
||||
- rm -rf $HOME/.cache/electron-builder/wine
|
||||
# - stage: Update GitHub with Builds
|
||||
# if: tag IS present
|
||||
# script:
|
||||
# - sh push-updates-with-travis-build.sh
|
@ -5,4 +5,4 @@
|
||||
3) Push changes to your repository
|
||||
4) Push changes to parent repository (make a pull request)
|
||||
|
||||
As long as the code change is good we will merge it.
|
||||
As long the code change is good we will merge it.
|
||||
|
41
README.md
41
README.md
@ -1,9 +1,7 @@
|
||||
# Qortal Project UI
|
||||
|
||||
![GitHub tag (latest by date)](https://img.shields.io/github/v/tag/Qortal/qortal-ui?label=latest%20version)
|
||||
[![GitHub Releases](https://img.shields.io/github/downloads/Qortal/qortal-ui/latest/total)](https://github.com/Qortal/qortal-ui/releases/latest)
|
||||
[![License](https://img.shields.io/badge/license-GPL--3.0-blue)](https://opensource.org/licenses/GPL-3.0)
|
||||
[![Qortal Discord Invite](https://img.shields.io/discord/745037351163527189?color=%237289DA&label=Chat&logo=discord&logoColor=white)](https://discord.com/invite/54UyhB7)
|
||||
[![Qortal Discord Invite](https://img.shields.io/discord/745037351163527189?color=%237289DA&label=chat&logo=discord&logoColor=white)](https://discord.com/invite/54UyhB7)
|
||||
|
||||
Decentralizing The World
|
||||
|
||||
@ -17,50 +15,63 @@ Installation
|
||||
Packages required:
|
||||
- Node.js
|
||||
- npm
|
||||
- yarn
|
||||
|
||||
Easiest way to install the lastest required packages on Linux is via nvm.
|
||||
|
||||
``` sudo apt update && sudo apt install curl -y ``` <br/>
|
||||
``` sudo rm -rf ~/.nvm ``` (Only for update node version)<br/>
|
||||
``` curl https://raw.githubusercontent.com/creationix/nvm/master/install.sh | bash ``` <br/>
|
||||
``` 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.20.3 ``` (Latest LTS: Hydrogen supported by Electron V31) <br/>
|
||||
``` npm --location=global install npm@10.8.1 ``` <br/>
|
||||
``` nvm install v14.17.0 ``` (Latest LTS: Fermium as of writing) <br/>
|
||||
``` npm -g install yarn ``` <br/>
|
||||
|
||||
On BSD do a ``` pkg_add node followed by npm install -g yarn ```
|
||||
|
||||
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.
|
||||
|
||||
Verify your installation with ``` node --version ``` <br/>
|
||||
- If you have an older installation of npm, please do not forget to update that with ``` npm update -g ```
|
||||
Verify your installtion with node --version <br/>
|
||||
- ``` If you have an older installation of npm, please do not forget to update that with npm update -g. ```
|
||||
|
||||
Clone the main UI repo
|
||||
- ``` git clone https://github.com/Qortal/qortal-ui.git ```
|
||||
|
||||
Installation
|
||||
Installation and linking
|
||||
------------------------
|
||||
In `qortal-ui` directory, run:
|
||||
In `qortal-ui-core/`, `qortal-ui-plugins/`, `qortal-ui-crypto/` directories, run:
|
||||
```
|
||||
npm install
|
||||
yarn install
|
||||
yarn link
|
||||
```
|
||||
|
||||
Finally, in the `qortal-ui` directory, run:
|
||||
```
|
||||
yarn link qortal-ui-core
|
||||
yarn link qortal-ui-plugins
|
||||
yarn link qortal-ui-crypto
|
||||
```
|
||||
|
||||
|
||||
|
||||
Build UI server and files
|
||||
-------------------------
|
||||
In `qortal-ui` directory, run:
|
||||
```
|
||||
npm run build
|
||||
yarn run build
|
||||
```
|
||||
|
||||
Start UI Server ( preferred way )
|
||||
---------------
|
||||
```
|
||||
npm run server &
|
||||
yarn run server &
|
||||
```
|
||||
The "&" at the end puts the UI server in the background.
|
||||
|
||||
Run UI using electron
|
||||
---------------------
|
||||
```
|
||||
npm run start-electron
|
||||
yarn run start-electron
|
||||
```
|
||||
|
||||
Build script (unix-like systems only)
|
||||
@ -69,7 +80,7 @@ To automate the above process, run ./build.sh, optionally specifying the followi
|
||||
|
||||
`-s`: run UI server after completing the build<br />
|
||||
`-e`: run electron server after completing the build<br />
|
||||
`-w`: use 'npm run watch' instead of 'npm run build', to enable hot swapping<br />
|
||||
`-f`: force relink and reinstall dependencies<br />
|
||||
`-h`: show help<br />
|
||||
|
||||
Example command to build and run the UI server:
|
||||
|
31
build.js
31
build.js
@ -1,23 +1,28 @@
|
||||
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 uiCore = require('qortal-ui-core')
|
||||
|
||||
const generateBuildConfig = uiCore('generate_build_config')
|
||||
const build = uiCore('build')
|
||||
|
||||
const config = require('./config/config.js')
|
||||
|
||||
const pluginsController = require('qortal-ui-plugins')
|
||||
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()
|
||||
})
|
||||
|
53
build.sh
53
build.sh
@ -1,7 +1,11 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
declare -a YARN_PACKAGE_DEPS=("qortal-ui-core" "qortal-ui-plugins" "qortal-ui-crypto")
|
||||
YARN_LINK_DIR="${HOME}/.config/yarn/link"
|
||||
|
||||
SHOW_HELP=0
|
||||
NPM_WATCH=0
|
||||
FORCE_LINK=0
|
||||
YARN_WATCH=0
|
||||
RUN_SERVER=0
|
||||
RUN_ELECTRON=0
|
||||
|
||||
@ -12,17 +16,22 @@ while [ -n "$*" ]; do
|
||||
SHOW_HELP=1
|
||||
;;
|
||||
|
||||
-w)
|
||||
# Use "npm run watch" instead of "npm run build", to enable hot swapping
|
||||
NPM_WATCH=1
|
||||
-f)
|
||||
# Force relink and reinstall dependencies
|
||||
FORCE_LINK=1
|
||||
;;
|
||||
|
||||
-s)
|
||||
-w)
|
||||
# Use "yarn watch" instead of "yarn build", to enable hot swapping
|
||||
YARN_WATCH=1
|
||||
;;
|
||||
|
||||
-s)
|
||||
# Run server after building
|
||||
RUN_SERVER=1
|
||||
;;
|
||||
|
||||
-e)
|
||||
-e)
|
||||
# Run electron after building
|
||||
RUN_ELECTRON=1
|
||||
;;
|
||||
@ -33,16 +42,34 @@ done
|
||||
if [ "${SHOW_HELP}" -eq 1 ]; then
|
||||
echo
|
||||
echo "Usage:"
|
||||
echo "build.sh [-h] [-w] [-s] [-e]"
|
||||
echo "build.sh [-h] [-f] [-s] [-e]"
|
||||
echo
|
||||
echo "-h: show help"
|
||||
echo "-w: use 'npm run watch' instead of 'npm run build', to enable hot swapping"
|
||||
echo "-f: force relink and reinstall dependencies"
|
||||
echo "-w: use 'yarn watch' instead of 'yarn build', to enable hot swapping"
|
||||
echo "-s: run UI server after completing the build"
|
||||
echo "-e: run electron server after completing the build"
|
||||
echo
|
||||
exit
|
||||
fi
|
||||
|
||||
echo "Checking dependencies..."
|
||||
for PACKAGE in "${YARN_PACKAGE_DEPS[@]}"; do
|
||||
if [ "${FORCE_LINK}" -eq 1 ]; then
|
||||
echo "Unlinking ${PACKAGE}..."
|
||||
yarn --cwd "${PACKAGE}" unlink "${PACKAGE}"
|
||||
yarn --cwd "${PACKAGE}" unlink
|
||||
fi
|
||||
if [ ! -d "${YARN_LINK_DIR}/${PACKAGE}" ]; then
|
||||
echo "Installing and linking ${PACKAGE}..."
|
||||
yarn --cwd "${PACKAGE}" install
|
||||
yarn --cwd "${PACKAGE}" link
|
||||
yarn link "${PACKAGE}"
|
||||
else
|
||||
echo "${PACKAGE} is already linked."
|
||||
fi
|
||||
done
|
||||
|
||||
WATCH_PID=$(cat "watch.pid" || echo "")
|
||||
if [ ! -z "${WATCH_PID}" ]; then
|
||||
echo "Stopping existing watch process..."
|
||||
@ -50,22 +77,22 @@ if [ ! -z "${WATCH_PID}" ]; then
|
||||
rm -f "watch.pid"
|
||||
fi
|
||||
|
||||
if [ "${NPM_WATCH}" -eq 1 ]; then
|
||||
if [ "${YARN_WATCH}" -eq 1 ]; then
|
||||
echo "Building qortal-ui in watch mode..."
|
||||
npm run watch &
|
||||
yarn run watch &
|
||||
echo "$!" > "watch.pid";
|
||||
else
|
||||
npm run build
|
||||
yarn run build
|
||||
fi
|
||||
|
||||
if [ "${RUN_SERVER}" -eq 1 ]; then
|
||||
echo "Running UI server..."
|
||||
trap : INT
|
||||
npm run server
|
||||
yarn run server
|
||||
elif [ "${RUN_ELECTRON}" -eq 1 ]; then
|
||||
echo "Starting electron..."
|
||||
trap : INT
|
||||
npm run start-electron
|
||||
yarn run start-electron
|
||||
fi
|
||||
|
||||
WATCH_PID=$(cat "watch.pid" || echo "")
|
||||
|
@ -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, '../node_modules/qortal-ui-crypto/api.js')
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = build
|
||||
module.exports = build
|
||||
|
@ -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'
|
||||
}
|
||||
|
@ -1,33 +1,27 @@
|
||||
let config = require('./default.config.js')
|
||||
|
||||
let userConfig = {}
|
||||
|
||||
try {
|
||||
userConfig = require('./customConfig.js')
|
||||
userConfig = require('./customConfig.js')
|
||||
} catch (e) {
|
||||
console.warn(e)
|
||||
console.warn('Error loading user config')
|
||||
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
|
||||
}
|
||||
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(userConfig)
|
||||
module.exports = getConfig(userConfig)
|
||||
|
@ -1,3 +1,3 @@
|
||||
const defaultConfig = require('./default.config.js')
|
||||
|
||||
module.exports = {}
|
||||
module.exports = {}
|
||||
|
@ -4,4 +4,10 @@ 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
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
const uiCore = require('../core/ui-core.js')
|
||||
const uiCore = require('qortal-ui-core')
|
||||
const defaultConfig = uiCore('default_config')
|
||||
|
||||
module.exports = defaultConfig
|
||||
|
||||
module.exports = defaultConfig
|
||||
|
@ -1 +1 @@
|
||||
module.exports = {}
|
||||
module.exports = {}
|
||||
|
@ -1,11 +1,10 @@
|
||||
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
|
||||
},
|
||||
},
|
||||
}
|
||||
|
@ -1,135 +0,0 @@
|
||||
const path = require('path')
|
||||
|
||||
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')
|
||||
}
|
||||
|
||||
const aliases = {
|
||||
'qortal-ui-crypto': '../../crypto/api.js'
|
||||
}
|
||||
|
||||
const apiComponents = {
|
||||
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'
|
||||
}
|
||||
}
|
||||
|
||||
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'
|
||||
}
|
||||
]
|
||||
|
||||
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'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
makeSourceAbsolute(path.join(__dirname, srcDir), elementComponents)
|
||||
makeSourceAbsolute(path.join(__dirname, srcDir), functionalComponents)
|
||||
|
||||
module.exports = {
|
||||
options,
|
||||
elementComponents,
|
||||
functionalComponents,
|
||||
inlineComponents,
|
||||
apiComponents,
|
||||
aliases
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
const coin = {
|
||||
name: 'Qortal',
|
||||
symbol: 'QORT',
|
||||
addressCount: 1,
|
||||
addressVersion: 58,
|
||||
decimals: 100000000,
|
||||
logo: '/img/QORT_LOGO.png',
|
||||
icon: '/img/QORT_LOGO.png'
|
||||
}
|
||||
|
||||
module.exports = coin
|
@ -1,11 +0,0 @@
|
||||
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`
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = crypto
|
@ -1,41 +0,0 @@
|
||||
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. */
|
||||
|
||||
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. */
|
||||
|
||||
warning: '#FFA000',
|
||||
error: '#F44336'
|
||||
},
|
||||
|
||||
addressColors: [
|
||||
'#256480',
|
||||
'#002530',
|
||||
'#02564e',
|
||||
'#d32f2f',
|
||||
'#795548',
|
||||
'#004d40',
|
||||
'#006064',
|
||||
'#9c27b0',
|
||||
'#2196f3',
|
||||
'#d81b60'
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = styles
|
@ -1,43 +0,0 @@
|
||||
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'
|
||||
},
|
||||
|
||||
// Notification Settings (All defaults to true)
|
||||
notifications: {
|
||||
q_chat: {
|
||||
playSound: true,
|
||||
showNotification: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = user
|
@ -1,22 +0,0 @@
|
||||
let config = require('./config.js')
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
module.exports = getConfig
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,110 +0,0 @@
|
||||
|
||||
|
||||
@font-face {
|
||||
font-family: 'Material Icons';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: url(flUhRq6tzZclQEJ-Vdg-IuiaDsNc.woff2) format('woff2');
|
||||
}
|
||||
|
||||
.material-icons {
|
||||
font-family: 'Material Icons';
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
font-size: 24px;
|
||||
line-height: 1;
|
||||
letter-spacing: normal;
|
||||
text-transform: none;
|
||||
display: inline-block;
|
||||
white-space: nowrap;
|
||||
word-wrap: normal;
|
||||
direction: ltr;
|
||||
-moz-font-feature-settings: 'liga';
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Material Icons Outlined';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: url(gok-H7zzDkdnRel8-DQ6KAXJ69wP1tGnf4ZGhUce.woff2) format('woff2');
|
||||
}
|
||||
|
||||
.material-icons-outlined {
|
||||
font-family: 'Material Icons Outlined';
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
font-size: 24px;
|
||||
line-height: 1;
|
||||
letter-spacing: normal;
|
||||
text-transform: none;
|
||||
display: inline-block;
|
||||
white-space: nowrap;
|
||||
word-wrap: normal;
|
||||
direction: ltr;
|
||||
-moz-font-feature-settings: 'liga';
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'magistralbold';
|
||||
src: url('magistral_bold-webfont.woff2') format('woff2'),
|
||||
url('magistral_bold-webfont.woff') format('woff');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Montserrat';
|
||||
src: local('Montserrat'),
|
||||
local('Montserrat'),
|
||||
url(Montserrat.ttf) format('truetype');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'MavenPro';
|
||||
src: local('MavenPro'),
|
||||
local('MavenPro'),
|
||||
url(Montserrat.ttf) format('truetype');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'WorkSans';
|
||||
src: local('WorkSans'),
|
||||
local('WorkSans'),
|
||||
url(WorkSans.ttf) format('truetype');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Raleway';
|
||||
src: local('Raleway'),
|
||||
local('Raleway'),
|
||||
url(Raleway.ttf) format('truetype');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'KoHo';
|
||||
src: local('KoHo'),
|
||||
local('KoHo'),
|
||||
url(KoHo.ttf) format('truetype');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Livvic';
|
||||
src: local('Livvic'),
|
||||
local('Livvic'),
|
||||
url(Livvic.ttf) format('truetype');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Paytone One', sans-serif;
|
||||
src: local('PaytoneOne'),
|
||||
local('PaytoneOne'),
|
||||
url(PaytoneOne.ttf) format('truetype');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Twemoji Country Flags';
|
||||
src: url('TwemojiCountryFlags.woff2') format('woff2');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
@ -1,155 +0,0 @@
|
||||
html {
|
||||
--white: #ffffff;
|
||||
--black: #080808;
|
||||
--gray: #c8c8c8;
|
||||
--graylight: #bbbbbb;
|
||||
--plugback: #ffffff;
|
||||
--border: #d0d6de;
|
||||
--border2: #dde2e8;
|
||||
--border3: #080808;
|
||||
--copybutton: #707584;
|
||||
--chat-group: #080808;
|
||||
--chat-bubble: #9f9f9f0a;
|
||||
--chat-bubble-bg: #e6e6e6;
|
||||
--chat-bubble-myBg: #d1ddf2;
|
||||
--chat-bubble-msg-color: #080808;
|
||||
--reaction-bubble-outline: #6b6969;
|
||||
--chat-menu-bg: #ffffff;
|
||||
--chat-menu-outline: #dad9d9;
|
||||
--chat-menu-icon: #3b3b3c;
|
||||
--chat-menu-icon-hover: #dad9d9;
|
||||
--block-user-bg-hover: #dad9d9;
|
||||
--paperclip-icon: #494949;
|
||||
--sectxt: #576374;
|
||||
--vdicon: #707b8a;
|
||||
--tradehead: #6a6c75;
|
||||
--tradeborder: #666666;
|
||||
--tradehave: #555555;
|
||||
--txtfieldborder: #666666;
|
||||
--txtfieldhoverborder: #00000;
|
||||
--relaynodetxt: #646464;
|
||||
--menuhover: #eeeeee;
|
||||
--menuactive: #ebebeb;
|
||||
--menuactivergb: 235, 235, 235;
|
||||
--mainmenutext: #080808;
|
||||
--mainmenutexthover: #080808;
|
||||
--switchbackground: #666666;
|
||||
--switchborder: #333333;
|
||||
--sidetopbar: #ffffff;
|
||||
--nav-selected-color: #dddddd;
|
||||
--nav-selected-color-text: #333333;
|
||||
--nav-color-active: #d1d1d1;
|
||||
--nav-color-hover: #eeeeee;
|
||||
--nav-text-color: #080808;
|
||||
--nav-icon-color: #080808;
|
||||
--nav-border-color: #eeeeee;
|
||||
--nav-border-selected-color: #03a9f4;
|
||||
--error: #d50000;
|
||||
--background: url("/img/qortal_background_light_.jpg");
|
||||
--chatHeadBg: #ebebeb;
|
||||
--chatHeadBgActive: #ebebeb;
|
||||
--chatHeadText: #080808;
|
||||
--chatHeadTextActive: #080808;
|
||||
--lightChatHeadHover: #1e1f201a;
|
||||
--group-header: #929292;
|
||||
--group-drop-shadow: rgb(17 17 26 / 10%) 0px 1px 0px;
|
||||
--reactions-tooltip-bg: #ffffff;
|
||||
--gifs-drop-shadow: #32326926 0px 2px 5px 0px, #0000000d 0px 1px 1px 0px;
|
||||
--gif-tooltip-bg: #dad7ef;
|
||||
--gif-search-icon-bs: rgb(17 17 26 / 10%) 0px 4px 16px, rgb(17 17 26 / 5%) 0px 8px 32px;
|
||||
--gif-search-icon: #ffffff;
|
||||
--gif-button-row-bg: #eaeaef;
|
||||
--gif-button-row-color: #464040;
|
||||
--gif-collection-hover-bg: #eaeaefa3;
|
||||
--app-background-1: #045de9;
|
||||
--app-background-2: #09c6f9;
|
||||
--app-icon: #ffffff;
|
||||
--app-hr: rgba(0, 0, 0, .3);
|
||||
--code-block-text-color: #008fd5;
|
||||
--noavatar: url("/img/noavatar_light.png");
|
||||
--login-border: rgba(0, 167, 245);
|
||||
--login-border-50pct: rgba(0, 167, 245, 0.5);
|
||||
--login-border-10pct: rgba(0, 167, 245, 0.1);
|
||||
--login-button: rgb(3, 169, 244);
|
||||
--general-color-blue: #03a9f4;
|
||||
--qchat-name: #03a9f4;
|
||||
--qchat-my-name: #05be0e;
|
||||
}
|
||||
|
||||
html[theme="dark"] {
|
||||
--white: #0f1a2e;
|
||||
--black: #c9d2d9;
|
||||
--gray: #d8d8d8;
|
||||
--graylight: #0b305e;
|
||||
--plugback: #0f1a2e;
|
||||
--border: #0b305e;
|
||||
--border2: #0b305e;
|
||||
--border3: #767676;
|
||||
--copybutton: #d0d6de;
|
||||
--chat-group: #ffffff;
|
||||
--chat-bubble: #9694941a;
|
||||
--chat-bubble-bg: #2d3749;
|
||||
--chat-bubble-myBg: #40444d;
|
||||
--chat-bubble-msg-color: #ffffff;
|
||||
--reaction-bubble-outline: #ffffff;
|
||||
--chat-menu-bg: #32394c;
|
||||
--chat-menu-outline: #32394c;
|
||||
--chat-menu-icon: #ffffff;
|
||||
--chat-menu-icon-hover: #a49a9a36;
|
||||
--block-user-bg-hover: #121a2f;
|
||||
--paperclip-icon: #d0c9c9;
|
||||
--sectxt: #bbc3cd;
|
||||
--vdicon: #d0d6de;
|
||||
--tradehead: #008fd5;
|
||||
--tradeborder: #0b305e;
|
||||
--tradehave: #dddddd;
|
||||
--txtfieldborder: #0b305e;
|
||||
--txtfieldhoverborder: #ffffff;
|
||||
--relaynodetxt: #d4d4d4;
|
||||
--menuhover: #008fd5;
|
||||
--menuactive: #008fd5;
|
||||
--menuactivergb: 0, 143, 213;
|
||||
--mainmenutext: #008fd5;
|
||||
--mainmenutexthover: #0f1a2e;
|
||||
--switchbackground: #eeeeee;
|
||||
--switchborder: #03a9f4;
|
||||
--sidetopbar: #070d19;
|
||||
--nav-selected-color: #0f1a2e;
|
||||
--nav-selected-color-text: #76c8f5;
|
||||
--nav-color-active: #d1d1d1;
|
||||
--nav-color-hover: #444444;
|
||||
--nav-text-color: #008fd5;
|
||||
--nav-icon-color: #008fd5;
|
||||
--nav-border-color: #0b305e;
|
||||
--nav-border-selected-color: #76c8f5;
|
||||
--error: #d50000;
|
||||
--background: url("/img/qortal_background_dark_.jpg");
|
||||
--chatHeadBg: #008fd5;
|
||||
--chatHeadBgActive: #0f1a2e;
|
||||
--chatHeadText: #ffffff;
|
||||
--chatHeadTextActive: #ffffff;
|
||||
--lightChatHeadHover: #e0e1e31a;
|
||||
--group-header: #c8c8c8;
|
||||
--group-drop-shadow: rgb(191 191 191 / 32%) 0px 1px 0px;
|
||||
--reactions-tooltip-bg: #161515;
|
||||
--gifs-drop-shadow: 0px 2px 2px 0px hsla(0, 0%, 0%, 0.14), 0px 3px 1px -2px hsla(0, 0%, 0%, 0.12), 0px 1px 5px 0px hsla(0, 0%, 0%, 0.2);
|
||||
--gif-tooltip-bg: #586b8d;
|
||||
--gif-search-icon-bs: 0px 8px 10px 1px hsla(0, 0%, 0%, 0.14), 0px 3px 14px 2px hsla(0, 0%, 0%, 0.12), 0px 5px 5px -3px hsla(0, 0%, 0%, 0.2);
|
||||
--gif-search-icon: #586b8d;
|
||||
--gif-button-row-bg: #82899c;
|
||||
--gif-button-row-color: #151212;
|
||||
--gif-collection-hover-bg: #ffffff26;
|
||||
--app-background-1: #7f5a83;
|
||||
--app-background-2: #0d324d;
|
||||
--app-icon: #03a9f4;
|
||||
--app-hr: rgba(255, 255, 255, .3);
|
||||
--code-block-text-color: #008fd5;
|
||||
--noavatar: url("/img/noavatar_dark.png");
|
||||
--login-border: rgba(0, 167, 245);
|
||||
--login-border-50pct: rgba(0, 167, 245, 0.5);
|
||||
--login-border-10pct: rgba(0, 167, 245, 0.1);
|
||||
--login-button: rgb(3, 169, 244);
|
||||
--general-color-blue: #03a9f4;
|
||||
--qchat-name: #03a9f4;
|
||||
--qchat-my-name: #05be0e;
|
||||
}
|
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
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
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
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
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
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
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
@ -1,121 +0,0 @@
|
||||
<!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>
|
||||
</html>
|
@ -1,35 +0,0 @@
|
||||
const Path = require('path')
|
||||
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.startServer = async () => {
|
||||
try {
|
||||
await this.server.register([Inert])
|
||||
|
||||
this.server.route(routes)
|
||||
|
||||
await this.server.start()
|
||||
|
||||
delete this.startServer
|
||||
|
||||
return this.server
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
throw e
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = serverFactory
|
@ -1,106 +0,0 @@
|
||||
const path = require('path')
|
||||
|
||||
const routesOptions = {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
delete response.config.user.tls
|
||||
delete response.config.build
|
||||
|
||||
return JSON.stringify(response)
|
||||
},
|
||||
options: routesOptions
|
||||
}
|
||||
]
|
||||
|
||||
module.exports = createRoutes
|
@ -1,140 +0,0 @@
|
||||
const path = require('path')
|
||||
const createCommonRoutes = require('./createCommonRoutes.js')
|
||||
|
||||
const createPrimaryRoutes = (config, plugins) => {
|
||||
const routes = createCommonRoutes(config)
|
||||
|
||||
let myPlugins = plugins
|
||||
|
||||
const pluginFolders = {}
|
||||
|
||||
const routesOptions = {
|
||||
security: {
|
||||
hsts: {
|
||||
maxAge: 15768000,
|
||||
includeSubDomains: true,
|
||||
preload: true
|
||||
},
|
||||
xframe: 'sameorigin'
|
||||
}
|
||||
}
|
||||
|
||||
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) => {
|
||||
|
||||
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(file, {
|
||||
confine: false
|
||||
})
|
||||
response.header('Access-Control-Allow-Origin', request.info.host)
|
||||
return response
|
||||
},
|
||||
options: routesOptions
|
||||
}
|
||||
|
||||
)
|
||||
|
||||
return routes
|
||||
}
|
||||
|
||||
module.exports = createPrimaryRoutes
|
@ -1,22 +0,0 @@
|
||||
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
|
||||
}
|
||||
|
||||
const serverExports = {
|
||||
createServer
|
||||
}
|
||||
|
||||
module.exports = serverExports
|
@ -1,55 +0,0 @@
|
||||
import * as api from 'qortal-ui-crypto'
|
||||
import mykey from './functional-components/mykey-page'
|
||||
|
||||
export const checkApiKey = async (nodeConfig) => {
|
||||
let selectedNode = nodeConfig.knownNodes[nodeConfig.node]
|
||||
let apiKey = selectedNode.apiKey
|
||||
|
||||
// Attempt to generate an API key
|
||||
const generateUrl = '/admin/apikey/generate'
|
||||
|
||||
let generateRes = await api.request(generateUrl, {
|
||||
method: 'POST'
|
||||
})
|
||||
|
||||
if (generateRes != null && generateRes.error == null && generateRes.length >= 8) {
|
||||
console.log('Generated API key')
|
||||
|
||||
apiKey = generateRes
|
||||
|
||||
// Store the generated API key
|
||||
selectedNode.apiKey = apiKey
|
||||
nodeConfig.knownNodes[nodeConfig.node] = selectedNode
|
||||
localStorage.setItem('myQortalNodes', JSON.stringify(nodeConfig.knownNodes))
|
||||
} else {
|
||||
console.log("Unable to generate API key")
|
||||
}
|
||||
|
||||
// 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
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export const testApiKey = async (apiKey) => {
|
||||
const testUrl = '/admin/apikey/test?apiKey=' + apiKey
|
||||
|
||||
let testRes = await api.request(testUrl, {
|
||||
method: 'GET'
|
||||
})
|
||||
|
||||
return testRes === true
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
import WebWorker from 'web-worker:./computePowWorkerFile.js'
|
||||
|
||||
export default WebWorker
|
@ -1,107 +0,0 @@
|
||||
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 styles() {
|
||||
return [appInfoStyles]
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
this.nodeInfo = []
|
||||
this.coreInfo = []
|
||||
this.nodeConfig = {}
|
||||
this.theme = localStorage.getItem('qortalTheme') ? localStorage.getItem('qortalTheme') : 'light'
|
||||
}
|
||||
|
||||
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>
|
||||
`
|
||||
}
|
||||
|
||||
firstUpdated() {
|
||||
this.getNodeInfo()
|
||||
this.getCoreInfo()
|
||||
|
||||
setInterval(() => {
|
||||
this.getNodeInfo()
|
||||
this.getCoreInfo()
|
||||
}, 60000)
|
||||
}
|
||||
|
||||
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`
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
window.customElements.define('app-info', AppInfo)
|
File diff suppressed because it is too large
Load Diff
@ -1,11 +0,0 @@
|
||||
import { html, LitElement } from 'lit'
|
||||
import { connect } from 'pwa-helpers'
|
||||
import { store } from '../store'
|
||||
|
||||
class MyElement extends connect(store)(LitElement) {
|
||||
render () {
|
||||
return html`<style></style>`
|
||||
}
|
||||
}
|
||||
|
||||
window.customElements.define('my-element', MyElement)
|
@ -1,208 +0,0 @@
|
||||
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 {
|
||||
blocksBehind: { type: Number },
|
||||
nodeUrl: { type: String },
|
||||
address: { type: String },
|
||||
isBehind: { type: Boolean },
|
||||
isSynchronizing: { type: Boolean },
|
||||
hasCoreRunning: { type: Boolean }
|
||||
}
|
||||
}
|
||||
|
||||
static get styles() {
|
||||
return [syncIndicator2Styles]
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
this.blocksBehind = 0
|
||||
this.nodeUrl = ''
|
||||
this.address = ''
|
||||
this.isBehind = false
|
||||
this.isSynchronizing = false
|
||||
this.hasCoreRunning = true
|
||||
this.interval = null
|
||||
this.seenWelcomeSync = false
|
||||
this.numberOfTries = 0
|
||||
this.hasOpened = false
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
${!this.hasCoreRunning ? html`
|
||||
<div class="parent">
|
||||
<span>
|
||||
<mwc-icon id="notification-general-icon" style="color: red; cursor:pointer;user-select:none">
|
||||
priority_high
|
||||
</mwc-icon>
|
||||
</span>
|
||||
<p>
|
||||
${translate("tour.tour17")}
|
||||
</p>
|
||||
</div>
|
||||
` : (this.blocksBehind > 1050 && this.isSynchronizing) ? html`
|
||||
<div class="parent">
|
||||
<div class="column">
|
||||
<div class="row">
|
||||
<span>
|
||||
<img src="/img/syncing.png" style="height: 24px; width: 24px;" />
|
||||
</span>
|
||||
<p>
|
||||
${this.blocksBehind} ${translate("tour.tour20")}
|
||||
</p>
|
||||
</div>
|
||||
<div class="row" style="justify-content: center">
|
||||
<button class="bootstrap-button" @click="${() => {this.bootstrap()}}">
|
||||
${translate("tour.tour18")}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
` : this.isSynchronizing ? html`
|
||||
<div class="parent">
|
||||
<span>
|
||||
<img src="/img/syncing.png" style="height: 24px; width: 24px;" />
|
||||
</span>
|
||||
<p>
|
||||
${translate("tour.tour19")} ${this.blocksBehind ? this.blocksBehind : ""} ${this.blocksBehind ? translate("tour.tour21"): ""}
|
||||
</p>
|
||||
</div>
|
||||
` : "" }
|
||||
`
|
||||
}
|
||||
|
||||
firstUpdated() {
|
||||
this.getNodeUrl()
|
||||
this.address = store.getState().app.selectedAddress.address
|
||||
|
||||
this.seenWelcomeSync = JSON.parse(
|
||||
localStorage.getItem(`welcome-sync-${this.address}`) || 'false'
|
||||
)
|
||||
|
||||
setInterval(() => {
|
||||
this.getNodeUrl()
|
||||
}, 60000)
|
||||
}
|
||||
|
||||
getNodeUrl() {
|
||||
const syncInfoNode = store.getState().app.nodeConfig.knownNodes[store.getState().app.nodeConfig.node]
|
||||
const syncInfoUrl = syncInfoNode.protocol + '://' + syncInfoNode.domain + ':' + syncInfoNode.port
|
||||
this.nodeUrl = syncInfoUrl
|
||||
}
|
||||
|
||||
async getDaySummary() {
|
||||
try {
|
||||
this.fetchingSummary = true
|
||||
|
||||
const endpointLastBlock = `${this.nodeUrl}/blocks/last`
|
||||
const resLastBlock = await fetch(endpointLastBlock)
|
||||
const dataLastBlock = await resLastBlock.json()
|
||||
const timestampNow = Date.now()
|
||||
const currentBlockTimestamp = dataLastBlock.timestamp
|
||||
|
||||
if (currentBlockTimestamp < timestampNow) {
|
||||
const diff = timestampNow - currentBlockTimestamp
|
||||
const inSeconds = diff / 1000
|
||||
const inBlocks = inSeconds / 70
|
||||
this.blocksBehind = parseInt(inBlocks)
|
||||
if (inBlocks >= 100) {
|
||||
this.isBehind = true
|
||||
} else {
|
||||
this.isBehind = false
|
||||
this.blocksBehind = 0
|
||||
}
|
||||
} else {
|
||||
this.blocksBehind = 0
|
||||
this.isBehind = false
|
||||
}
|
||||
} catch (error) {} finally {
|
||||
this.fetchingSummary = false
|
||||
}
|
||||
}
|
||||
|
||||
async checkHowManyBlocksBehind() {
|
||||
try {
|
||||
await this.getDaySummary()
|
||||
this.interval = setInterval(() => {
|
||||
if(this.fetchingSummary) return
|
||||
if (this.isBehind === false) {
|
||||
this.isBehind = null
|
||||
clearInterval(this.interval)
|
||||
}
|
||||
this.getDaySummary()
|
||||
}, 20000)
|
||||
} catch (error) {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
|
||||
async bootstrap() {
|
||||
try {
|
||||
const endpoint = `${this.nodeUrl}/admin/bootstrap/?apiKey=${this.getApiKey()}`
|
||||
const res = await fetch(endpoint)
|
||||
const data = await res.json()
|
||||
if (data === true) {
|
||||
parentEpml.request('showSnackBar', get('tour.tour22'))
|
||||
}
|
||||
} catch (error) {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
|
||||
getApiKey() {
|
||||
const apiNode = store.getState().app.nodeConfig.knownNodes[store.getState().app.nodeConfig.node]
|
||||
return apiNode.apiKey
|
||||
}
|
||||
|
||||
stateChanged(state) {
|
||||
this.address = store.getState().app.selectedAddress.address
|
||||
|
||||
if (!this.seenWelcomeSync && state.app.nodeStatus && state.app.nodeStatus.syncPercent === 100 && this.hasOpened === false) {
|
||||
this.hasOpened = true
|
||||
this.dispatchEvent(
|
||||
new CustomEvent('open-welcome-modal-sync', {
|
||||
bubbles: true,
|
||||
composed: true
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
if (state.app.nodeStatus && Object.keys(state.app.nodeStatus).length === 0) {
|
||||
if (this.numberOfTries > 5) {
|
||||
this.hasCoreRunning = false
|
||||
} else {
|
||||
this.numberOfTries = this.numberOfTries + 1
|
||||
}
|
||||
} else if (state.app.nodeStatus && state.app.nodeStatus.syncPercent === 100 && state.app.nodeStatus.syncPercent !== this.syncPercentage) {
|
||||
this.syncPercentage = state.app.nodeStatus.syncPercent
|
||||
this.isSynchronizing = false
|
||||
} else if (state.app.nodeStatus) {
|
||||
this.hasCoreRunning = true
|
||||
this.numberOfTries = 0
|
||||
this.syncPercentage = state.app.nodeStatus.syncPercent
|
||||
|
||||
if (state.app.nodeStatus.syncPercent !== 100) {
|
||||
this.isSynchronizing = true
|
||||
}
|
||||
|
||||
if (!this.interval && this.isBehind === null && state.app.nodeStatus.isSynchronizing && state.app.nodeStatus.syncPercent !== 100) {
|
||||
this.checkHowManyBlocksBehind()
|
||||
}
|
||||
} else {
|
||||
this.hasCoreRunning = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
window.customElements.define('sync-indicator', SyncIndicator)
|
@ -1,302 +0,0 @@
|
||||
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() {
|
||||
return {
|
||||
getElements: { attribute: false },
|
||||
dialogOpenedCongrats: { type: Boolean },
|
||||
hasViewedTour: { 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.address = ''
|
||||
this._disableTour = this._disableTour.bind(this)
|
||||
this.disableTour = 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>
|
||||
` : ''}
|
||||
`
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
if (name) {
|
||||
this.hasName = true
|
||||
}
|
||||
|
||||
this.hasViewedTour = hasViewedTour
|
||||
|
||||
if (!hasViewedTour) {
|
||||
try {
|
||||
if (name) {
|
||||
this.hasViewedTour = true
|
||||
this.hasName = true
|
||||
localStorage.setItem(`hasViewedTour-${this.address}`, JSON.stringify(true))
|
||||
}
|
||||
} catch (error) {
|
||||
console.log({ error })
|
||||
}
|
||||
}
|
||||
|
||||
await new Promise((res) => {
|
||||
setTimeout(() => {
|
||||
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>
|
||||
`
|
||||
}
|
||||
}]
|
||||
|
||||
const step2 = elements['core-sync-status-id']
|
||||
const step3 = elements['tab']
|
||||
const step4 = elements['checklist']
|
||||
|
||||
if (step2) {
|
||||
steps.push({
|
||||
element: step2,
|
||||
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>
|
||||
|
||||
`
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
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")}})
|
||||
this.hasViewedTour
|
||||
}
|
||||
|
||||
let currentStepIndex = 0
|
||||
|
||||
const driverObj = driver({
|
||||
popoverClass: 'driverjs-theme',
|
||||
showProgress: true,
|
||||
showButtons: ['next', 'previous'],
|
||||
steps: steps,
|
||||
allowClose: false,
|
||||
onDestroyed: () => {
|
||||
localStorage.setItem(`hasViewedTour-${this.address}`, JSON.stringify(true))
|
||||
this.hasViewedTour = true
|
||||
this.openWelcomeModal()
|
||||
}
|
||||
})
|
||||
|
||||
driverObj.drive()
|
||||
} else {
|
||||
this.dispatchEvent(
|
||||
new CustomEvent('send-tour-finished', {
|
||||
bubbles: 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`
|
||||
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-Tube',
|
||||
menus: [],
|
||||
parent: false
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
onClose() {
|
||||
localStorage.setItem(`welcome-sync-${this.address}`, JSON.stringify(true))
|
||||
this.dialogOpenedCongrats = false
|
||||
}
|
||||
}
|
||||
|
||||
window.customElements.define('tour-component', TourComponent)
|
@ -1,77 +0,0 @@
|
||||
.driver-popover.driverjs-theme {
|
||||
background-color: var(--white);
|
||||
color: var(--black);
|
||||
max-width: 500px;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.driver-popover.driverjs-theme .driver-popover-title {
|
||||
font-size: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.driver-popover.driverjs-theme .driver-popover-title,
|
||||
.driver-popover.driverjs-theme .driver-popover-description,
|
||||
.driver-popover.driverjs-theme .driver-popover-progress-text {
|
||||
color: var(--black);
|
||||
font-family: Roboto, sans-serif;
|
||||
}
|
||||
.driver-popover.driverjs-theme .driver-popover-description {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.driver-popover.driverjs-theme button {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
background-color: #000;
|
||||
color: #ffffff;
|
||||
border: 2px solid #000;
|
||||
text-shadow: none;
|
||||
font-size: 14px;
|
||||
padding: 5px 8px;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.driver-popover.driverjs-theme .test-span {
|
||||
color: green;
|
||||
}
|
||||
|
||||
.driver-popover.driverjs-theme button:hover {
|
||||
background-color: #000;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.driver-popover.driverjs-theme .driver-popover-navigation-btns {
|
||||
justify-content: space-between;
|
||||
gap: 3px;
|
||||
}
|
||||
|
||||
.driver-popover.driverjs-theme .driver-popover-close-btn {
|
||||
color: #9b9b9b;
|
||||
}
|
||||
|
||||
.driver-popover.driverjs-theme .driver-popover-close-btn:hover {
|
||||
color: #000;
|
||||
}
|
||||
.driver-popover.driverjs-theme .driver-popover-footer {
|
||||
gap: 20px;
|
||||
}
|
||||
.driver-popover.driverjs-theme .driver-popover-footer button {
|
||||
background-color: #000 !important;
|
||||
}
|
||||
|
||||
.driver-popover.driverjs-theme .driver-popover-arrow-side-left.driver-popover-arrow {
|
||||
border-left-color: #fde047;
|
||||
}
|
||||
|
||||
.driver-popover.driverjs-theme .driver-popover-arrow-side-right.driver-popover-arrow {
|
||||
border-right-color: #fde047;
|
||||
}
|
||||
|
||||
.driver-popover.driverjs-theme .driver-popover-arrow-side-top.driver-popover-arrow {
|
||||
border-top-color: #fde047;
|
||||
}
|
||||
|
||||
.driver-popover.driverjs-theme .driver-popover-arrow-side-bottom.driver-popover-arrow {
|
||||
border-bottom-color: #fde047;
|
||||
}
|
@ -1,48 +0,0 @@
|
||||
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 {
|
||||
theme: { type: String, reflect: true }
|
||||
}
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
this.theme = localStorage.getItem('qortalTheme') ? localStorage.getItem('qortalTheme') : 'light'
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
${this.renderUpdateButton()}
|
||||
`
|
||||
}
|
||||
|
||||
firstUpdated() {
|
||||
// ...
|
||||
}
|
||||
|
||||
renderUpdateButton() {
|
||||
if (!isElectron()) {
|
||||
return html``
|
||||
} else {
|
||||
return html`
|
||||
<div style="display: inline;">
|
||||
<paper-icon-button icon="icons:get-app" @click=${() => this.checkupdate()} title="${translate("appspage.schange38")} UI"></paper-icon-button>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
}
|
||||
|
||||
checkupdate() {
|
||||
window.electronAPI.checkForUpdate()
|
||||
}
|
||||
}
|
||||
|
||||
window.customElements.define('check-for-update', CheckForUpdate)
|
@ -1,58 +0,0 @@
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
|
||||
self.addEventListener('message', async e => {
|
||||
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
|
||||
}
|
@ -1,68 +0,0 @@
|
||||
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
|
||||
}
|
||||
|
||||
self.addEventListener('message', async e => {
|
||||
const response = await computePow(e.data.convertedBytes, e.data.path)
|
||||
postMessage(response)
|
||||
|
||||
})
|
||||
|
||||
const memory = new WebAssembly.Memory({ initial: 256, maximum: 256 })
|
||||
const heap = new Uint8Array(memory.buffer)
|
||||
|
||||
const computePow = async (convertedBytes, path) => {
|
||||
let response = null
|
||||
await new Promise((resolve, reject) => {
|
||||
const _convertedBytesArray = Object.keys(convertedBytes).map(
|
||||
function (key) {
|
||||
return convertedBytes[key]
|
||||
}
|
||||
)
|
||||
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
|
||||
}
|
@ -1,303 +0,0 @@
|
||||
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 }
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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]
|
||||
}
|
||||
|
||||
async updateQortWalletBalance() {
|
||||
let qortAddress = store.getState().app.selectedAddress.address
|
||||
|
||||
await parentEpml.request('apiCall', {
|
||||
url: `/addresses/balance/${qortAddress}?apiKey=${this.myNode.apiKey}`,
|
||||
}).then((res) => {
|
||||
this.qortWalletBalance = res
|
||||
store.dispatch(
|
||||
setCoinBalances({
|
||||
type: 'qort',
|
||||
fullValue: Number(res)
|
||||
})
|
||||
)
|
||||
}).catch(() => {
|
||||
console.log('error')
|
||||
})
|
||||
}
|
||||
|
||||
async updateBtcWalletBalance() {
|
||||
let _url = `/crosschain/btc/walletbalance?apiKey=${this.myNode.apiKey}`
|
||||
let _body = store.getState().app.selectedAddress.btcWallet.derivedMasterPublicKey
|
||||
|
||||
await parentEpml.request('apiCall', {
|
||||
url: _url,
|
||||
method: 'POST',
|
||||
body: _body
|
||||
}).then((res) => {
|
||||
if (isNaN(Number(res))) {
|
||||
//...
|
||||
} else {
|
||||
this.btcWalletBalance = (Number(res) / 1e8).toFixed(8)
|
||||
store.dispatch(
|
||||
setCoinBalances({
|
||||
type: 'btc',
|
||||
fullValue: Number(res)
|
||||
})
|
||||
)
|
||||
}
|
||||
}).catch(() => {
|
||||
console.log('error')
|
||||
})
|
||||
}
|
||||
|
||||
async updateLtcWalletBalance() {
|
||||
let _url = `/crosschain/ltc/walletbalance?apiKey=${this.myNode.apiKey}`
|
||||
let _body = store.getState().app.selectedAddress.ltcWallet.derivedMasterPublicKey
|
||||
|
||||
await parentEpml.request('apiCall', {
|
||||
url: _url,
|
||||
method: 'POST',
|
||||
body: _body
|
||||
}).then((res) => {
|
||||
if (isNaN(Number(res))) {
|
||||
//...
|
||||
} else {
|
||||
this.ltcWalletBalance = (Number(res) / 1e8).toFixed(8)
|
||||
store.dispatch(
|
||||
setCoinBalances({
|
||||
type: 'ltc',
|
||||
fullValue: Number(res)
|
||||
})
|
||||
)
|
||||
|
||||
}
|
||||
}).catch(() => {
|
||||
console.log('error')
|
||||
})
|
||||
}
|
||||
|
||||
async updateDogeWalletBalance() {
|
||||
let _url = `/crosschain/doge/walletbalance?apiKey=${this.myNode.apiKey}`
|
||||
let _body = store.getState().app.selectedAddress.dogeWallet.derivedMasterPublicKey
|
||||
|
||||
await parentEpml.request('apiCall', {
|
||||
url: _url,
|
||||
method: 'POST',
|
||||
body: _body
|
||||
}).then((res) => {
|
||||
if (isNaN(Number(res))) {
|
||||
//...
|
||||
} else {
|
||||
this.dogeWalletBalance = (Number(res) / 1e8).toFixed(8)
|
||||
store.dispatch(
|
||||
setCoinBalances({
|
||||
type: 'doge',
|
||||
fullValue: Number(res)
|
||||
})
|
||||
)
|
||||
}
|
||||
}).catch(() => {
|
||||
console.log('error')
|
||||
})
|
||||
}
|
||||
|
||||
async updateDgbWalletBalance() {
|
||||
let _url = `/crosschain/dgb/walletbalance?apiKey=${this.myNode.apiKey}`
|
||||
let _body = store.getState().app.selectedAddress.dgbWallet.derivedMasterPublicKey
|
||||
|
||||
await parentEpml.request('apiCall', {
|
||||
url: _url,
|
||||
method: 'POST',
|
||||
body: _body
|
||||
}).then((res) => {
|
||||
if (isNaN(Number(res))) {
|
||||
//...
|
||||
} else {
|
||||
this.dgbWalletBalance = (Number(res) / 1e8).toFixed(8)
|
||||
store.dispatch(
|
||||
setCoinBalances({
|
||||
type: 'dgb',
|
||||
fullValue: Number(res)
|
||||
})
|
||||
)
|
||||
}
|
||||
}).catch(() => {
|
||||
console.log('error')
|
||||
})
|
||||
}
|
||||
|
||||
async updateRvnWalletBalance() {
|
||||
let _url = `/crosschain/rvn/walletbalance?apiKey=${this.myNode.apiKey}`
|
||||
let _body = store.getState().app.selectedAddress.rvnWallet.derivedMasterPublicKey
|
||||
|
||||
await parentEpml.request('apiCall', {
|
||||
url: _url,
|
||||
method: 'POST',
|
||||
body: _body
|
||||
}).then((res) => {
|
||||
if (isNaN(Number(res))) {
|
||||
//...
|
||||
} else {
|
||||
this.rvnWalletBalance = (Number(res) / 1e8).toFixed(8)
|
||||
store.dispatch(
|
||||
setCoinBalances({
|
||||
type: 'rvn',
|
||||
fullValue: Number(res)
|
||||
})
|
||||
)
|
||||
}
|
||||
}).catch(() => {
|
||||
console.log('error')
|
||||
})
|
||||
}
|
||||
|
||||
async updateArrrWalletBalance() {
|
||||
let _url = `/crosschain/arrr/walletbalance?apiKey=${this.myNode.apiKey}`
|
||||
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() {
|
||||
if (this.intervalID) { clearInterval(this.intervalID) }
|
||||
window.removeEventListener('ping-coin-controller-with-coin', this._updateCoinList)
|
||||
super.disconnectedCallback()
|
||||
}
|
||||
}
|
||||
|
||||
window.customElements.define('coin-balances-controller', CoinBalancesController)
|
@ -1,204 +0,0 @@
|
||||
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 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 [chatSideNavHeadsStyles]
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
this.selectedAddress = {}
|
||||
this.config = {
|
||||
user: {
|
||||
node: {
|
||||
}
|
||||
}
|
||||
}
|
||||
this.chatInfo = {}
|
||||
this.iconName = ''
|
||||
this.activeChatHeadUrl = ''
|
||||
this.isImageLoaded = false
|
||||
this.imageFetches = 0
|
||||
}
|
||||
|
||||
render() {
|
||||
let avatarImg = ''
|
||||
|
||||
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() {
|
||||
// ...
|
||||
}
|
||||
|
||||
createImage(imageUrl) {
|
||||
const imageHTMLRes = new Image()
|
||||
imageHTMLRes.src = imageUrl
|
||||
imageHTMLRes.style = "width:30px; height:30px; float: left; border-radius:50%; font-size:14px"
|
||||
|
||||
imageHTMLRes.onclick = () => {
|
||||
this.openDialogImage = true
|
||||
}
|
||||
|
||||
imageHTMLRes.onload = () => {
|
||||
this.isImageLoaded = true
|
||||
}
|
||||
|
||||
imageHTMLRes.onerror = () => {
|
||||
if (this.imageFetches < 4) {
|
||||
setTimeout(() => {
|
||||
this.imageFetches = this.imageFetches + 1
|
||||
imageHTMLRes.src = imageUrl
|
||||
}, 500)
|
||||
} else {
|
||||
this.isImageLoaded = false
|
||||
}
|
||||
}
|
||||
|
||||
return imageHTMLRes
|
||||
}
|
||||
|
||||
shouldUpdate(changedProperties) {
|
||||
if (changedProperties.has('activeChatHeadUrl')) {
|
||||
return true
|
||||
}
|
||||
|
||||
if (changedProperties.has('chatInfo')) {
|
||||
return true
|
||||
}
|
||||
|
||||
return !!changedProperties.has('isImageLoaded')
|
||||
}
|
||||
|
||||
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)
|
@ -1,295 +0,0 @@
|
||||
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) {
|
||||
static get properties() {
|
||||
return {
|
||||
isOpen: { type: Boolean },
|
||||
setIsOpen: { attribute: false },
|
||||
isLoading: { type: Boolean },
|
||||
userSelected: { type: Object },
|
||||
alias: { type: String },
|
||||
willFollow: { type: Boolean },
|
||||
notes: { type: String },
|
||||
onSubmit: { attribute: false },
|
||||
editContent: { type: Object },
|
||||
onClose: { attribute: false },
|
||||
mySelectedFeeds: { type: Array },
|
||||
availableFeeedSchemas: { type: Array },
|
||||
isLoadingSchemas: { type: Boolean }
|
||||
}
|
||||
}
|
||||
|
||||
static get styles() {
|
||||
return [addFriendsModalStyles]
|
||||
}
|
||||
|
||||
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[store.getState().app.nodeConfig.node]
|
||||
return myNode.protocol + '://' + myNode.domain + ':' + myNode.port
|
||||
}
|
||||
|
||||
getMyNode() {
|
||||
return store.getState().app.nodeConfig.knownNodes[store.getState().app.nodeConfig.node]
|
||||
}
|
||||
|
||||
clearFields() {
|
||||
this.alias = ''
|
||||
this.willFollow = true
|
||||
this.notes = ''
|
||||
}
|
||||
|
||||
addFriend() {
|
||||
this.onSubmit({
|
||||
name: this.userSelected.name,
|
||||
alias: this.alias,
|
||||
notes: this.notes,
|
||||
willFollow: this.willFollow,
|
||||
mySelectedFeeds: this.mySelectedFeeds
|
||||
})
|
||||
|
||||
this.clearFields()
|
||||
this.onClose()
|
||||
}
|
||||
|
||||
removeFriend() {
|
||||
this.onSubmit(
|
||||
{
|
||||
name: this.userSelected.name,
|
||||
alias: this.alias,
|
||||
notes: this.notes,
|
||||
willFollow: this.willFollow,
|
||||
mySelectedFeeds: this.mySelectedFeeds
|
||||
},
|
||||
true
|
||||
)
|
||||
|
||||
this.clearFields()
|
||||
this.onClose()
|
||||
}
|
||||
|
||||
async updated(changedProperties) {
|
||||
if (changedProperties && changedProperties.has('editContent') && this.editContent) {
|
||||
this.userSelected = { name: this.editContent.name ?? '' }
|
||||
this.notes = this.editContent.notes ?? ''
|
||||
this.willFollow = this.editContent.willFollow ?? true
|
||||
this.alias = this.editContent.alias ?? ''
|
||||
this.requestUpdate()
|
||||
}
|
||||
|
||||
if (changedProperties && changedProperties.has('isOpen') && this.isOpen) {
|
||||
await this.getAvailableFeedSchemas()
|
||||
}
|
||||
}
|
||||
|
||||
async getAvailableFeedSchemas() {
|
||||
try {
|
||||
this.isLoadingSchemas = true
|
||||
const url = `${this.nodeUrl}/arbitrary/resources/search?service=DOCUMENT&identifier=ui_schema_feed&prefix=true`
|
||||
const res = await fetch(url)
|
||||
const data = await res.json()
|
||||
|
||||
if (data.error === 401) {
|
||||
this.availableFeeedSchemas = []
|
||||
} else {
|
||||
this.availableFeeedSchemas = data.filter((item) => item.identifier === 'ui_schema_feed')
|
||||
}
|
||||
|
||||
this.userFoundModalOpen = true
|
||||
} catch (error) {
|
||||
} finally {
|
||||
this.isLoadingSchemas = false
|
||||
}
|
||||
}
|
||||
|
||||
// Standard functions
|
||||
getApiKey() {
|
||||
const coreNode = store.getState().app.nodeConfig.knownNodes[store.getState().app.nodeConfig.node]
|
||||
return coreNode.apiKey
|
||||
}
|
||||
|
||||
isEmptyArray(arr) {
|
||||
if (!arr) { return true }
|
||||
return arr.length === 0
|
||||
}
|
||||
|
||||
round(number) {
|
||||
return (Math.round(parseFloat(number) * 1e8) / 1e8).toFixed(8)
|
||||
}
|
||||
}
|
||||
|
||||
window.customElements.define('add-friends-modal', AddFriendsModal)
|
@ -1,228 +0,0 @@
|
||||
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)
|
||||
|
||||
export class AvatarComponent extends connect(store)(LitElement) {
|
||||
static get properties() {
|
||||
return {
|
||||
resource: { type: Object },
|
||||
isReady: { type: Boolean },
|
||||
status: { type: Object },
|
||||
name: { type: String }
|
||||
}
|
||||
}
|
||||
|
||||
static get styles() {
|
||||
return [avatarComponentStyles]
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
this.resource = {
|
||||
identifier: '',
|
||||
name: '',
|
||||
service: ''
|
||||
}
|
||||
this.status = {
|
||||
status: ''
|
||||
}
|
||||
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>
|
||||
` : ''
|
||||
}
|
||||
</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)
|
||||
}
|
||||
}
|
||||
|
||||
window.customElements.define('avatar-component', AvatarComponent)
|
@ -1,214 +0,0 @@
|
||||
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 get properties() {
|
||||
return {
|
||||
notifications: { type: Array },
|
||||
showChecklist: { type: Boolean },
|
||||
isSynced: { type: Boolean },
|
||||
hasName: { type: Boolean },
|
||||
hasTourFinished: { type: Boolean },
|
||||
theme: { type: String, reflect: true }
|
||||
}
|
||||
}
|
||||
|
||||
static get styles() {
|
||||
return [beginnerChecklistStyles]
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
this.showChecklist = false
|
||||
this.initialFetch = false
|
||||
this.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')
|
||||
}
|
||||
|
||||
_controlTourFinished() {
|
||||
this.hasTourFinished = true
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback()
|
||||
window.addEventListener('send-tour-finished', this._controlTourFinished)
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
window.removeEventListener('send-tour-finished', this._controlTourFinished)
|
||||
super.disconnectedCallback()
|
||||
}
|
||||
|
||||
getNodeUrl() {
|
||||
const myNode = store.getState().app.nodeConfig.knownNodes[store.getState().app.nodeConfig.node]
|
||||
return myNode.protocol + '://' + myNode.domain + ':' + myNode.port
|
||||
}
|
||||
|
||||
getMyNode() {
|
||||
return store.getState().app.nodeConfig.knownNodes[store.getState().app.nodeConfig.node]
|
||||
}
|
||||
|
||||
async getName(recipient) {
|
||||
try {
|
||||
if (!recipient) return ''
|
||||
|
||||
const endpoint = `${this.nodeUrl}/names/address/${recipient}`
|
||||
const res = await fetch(endpoint)
|
||||
const getNames = await res.json()
|
||||
|
||||
this.hasName = Array.isArray(getNames) && getNames.length > 0
|
||||
} catch (error) {
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
stateChanged(state) {
|
||||
if (state.app.nodeStatus && state.app.nodeStatus.syncPercent !== this.syncPercentage) {
|
||||
this.syncPercentage = state.app.nodeStatus.syncPercent
|
||||
|
||||
if (!this.hasAttempted && state.app.selectedAddress && state.app.nodeStatus.syncPercent === 100) {
|
||||
this.hasAttempted = true
|
||||
this.getName(state.app.selectedAddress.address)
|
||||
}
|
||||
}
|
||||
|
||||
if (state.app.accountInfo &&
|
||||
state.app.accountInfo.names.length && state.app.nodeStatus && state.app.nodeStatus.syncPercent === 100 &&
|
||||
this.hasName === false && this.hasAttempted && state.app.accountInfo && state.app.accountInfo.names &&
|
||||
state.app.accountInfo.names.length > 0
|
||||
) {
|
||||
this.hasName = true
|
||||
}
|
||||
}
|
||||
|
||||
handleBlur() {
|
||||
setTimeout(() => {
|
||||
if (!this.shadowRoot.contains(document.activeElement)) {
|
||||
this.showChecklist = false
|
||||
}
|
||||
}, 0)
|
||||
}
|
||||
|
||||
_toggleChecklist() {
|
||||
this.showChecklist = !this.showChecklist
|
||||
|
||||
if (this.showChecklist) {
|
||||
requestAnimationFrame(() => {
|
||||
this.shadowRoot.getElementById('checklist-panel').focus()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 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('beginner-checklist', BeginnerChecklist)
|
@ -1,67 +0,0 @@
|
||||
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
|
||||
}
|
||||
|
||||
self.addEventListener('message', async e => {
|
||||
const response = await computePow(e.data.convertedBytes, e.data.path)
|
||||
postMessage(response)
|
||||
})
|
||||
|
||||
const memory = new WebAssembly.Memory({ initial: 256, maximum: 256 })
|
||||
const heap = new Uint8Array(memory.buffer)
|
||||
|
||||
const computePow = async (convertedBytes, path) => {
|
||||
let response = null
|
||||
await new Promise((resolve, reject) => {
|
||||
const _convertedBytesArray = Object.keys(convertedBytes).map(
|
||||
function (key) {
|
||||
return convertedBytes[key]
|
||||
}
|
||||
)
|
||||
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
|
||||
}
|
@ -1,182 +0,0 @@
|
||||
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() {
|
||||
return {
|
||||
nodeInfos: { type: Array },
|
||||
coreInfos: { type: Array },
|
||||
theme: { type: String, reflect: true }
|
||||
}
|
||||
}
|
||||
|
||||
static get styles() {
|
||||
return [coreSyncStatusStyles]
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
this.nodeInfos = []
|
||||
this.coreInfos = []
|
||||
this.theme = localStorage.getItem('qortalTheme') ? localStorage.getItem('qortalTheme') : 'light'
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<div id="core-sync-status-id">
|
||||
${this.renderSyncStatusIcon()}
|
||||
</div>
|
||||
`
|
||||
}
|
||||
|
||||
firstUpdated() {
|
||||
this.getNodeInfos()
|
||||
this.getCoreInfos()
|
||||
|
||||
setInterval(() => {
|
||||
this.getNodeInfos()
|
||||
this.getCoreInfos()
|
||||
}, 30000)
|
||||
}
|
||||
|
||||
|
||||
async getNodeInfos() {
|
||||
const appInfoNode = store.getState().app.nodeConfig.knownNodes[store.getState().app.nodeConfig.node]
|
||||
const appInfoUrl = appInfoNode.protocol + '://' + appInfoNode.domain + ':' + appInfoNode.port
|
||||
const nodeInfoUrl = `${appInfoUrl}/admin/status`
|
||||
|
||||
await fetch(nodeInfoUrl).then(response => {
|
||||
return response.json()
|
||||
}).then(data => {
|
||||
this.nodeInfos = data
|
||||
}).catch(err => {
|
||||
console.error('Request failed', err)
|
||||
})
|
||||
}
|
||||
|
||||
async getCoreInfos() {
|
||||
const appCoreNode = store.getState().app.nodeConfig.knownNodes[store.getState().app.nodeConfig.node]
|
||||
const appCoreUrl = appCoreNode.protocol + '://' + appCoreNode.domain + ':' + appCoreNode.port
|
||||
const coreInfoUrl = `${appCoreUrl}/admin/info`
|
||||
|
||||
await fetch(coreInfoUrl).then(response => {
|
||||
return response.json()
|
||||
}).then(data => {
|
||||
this.coreInfos = data
|
||||
}).catch(err => {
|
||||
console.error('Request failed', err)
|
||||
})
|
||||
}
|
||||
|
||||
renderSyncStatusIcon() {
|
||||
if (this.nodeInfos.isSynchronizing === true && this.nodeInfos.syncPercent === 99) {
|
||||
return html`
|
||||
<div class="tooltip" style="display: inline;">
|
||||
<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.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>
|
||||
<i></i>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
} else if (this.nodeInfos.isSynchronizing === true && this.nodeInfos.isMintingPossible === false && this.nodeInfos.syncPercent === 100) {
|
||||
return html`
|
||||
<div class="tooltip" style="display: inline;">
|
||||
<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("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>
|
||||
<i></i>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
} else if (this.nodeInfos.isSynchronizing === false && this.nodeInfos.isMintingPossible === false && this.nodeInfos.syncPercent === 100) {
|
||||
return html`
|
||||
<div class="tooltip" style="display: inline;">
|
||||
<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("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>
|
||||
<i></i>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
} else if (this.nodeInfos.isSynchronizing === true && this.nodeInfos.isMintingPossible === true && this.nodeInfos.syncPercent === 100) {
|
||||
return html`
|
||||
<div class="tooltip" style="display: inline;">
|
||||
<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("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>
|
||||
<i></i>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
} else if (this.nodeInfos.isSynchronizing === false && this.nodeInfos.isMintingPossible === true && this.nodeInfos.syncPercent === 100) {
|
||||
return html`
|
||||
<div class="tooltip" style="display: inline;">
|
||||
<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("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>
|
||||
<i></i>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
} else {
|
||||
return html`
|
||||
<div class="tooltip" style="display: inline;">
|
||||
<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.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>
|
||||
<i></i>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
}
|
||||
|
||||
stateChanged(state) {
|
||||
// ...
|
||||
}
|
||||
|
||||
// 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('core-sync-status', CoreSyncStatus)
|
@ -1,360 +0,0 @@
|
||||
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 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)
|
||||
|
||||
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 [feedItemStyles]
|
||||
}
|
||||
|
||||
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>
|
||||
`
|
||||
}
|
||||
|
||||
firstUpdated() {
|
||||
this.observer.observe(this)
|
||||
}
|
||||
|
||||
getNodeUrl() {
|
||||
const myNode = store.getState().app.nodeConfig.knownNodes[store.getState().app.nodeConfig.node]
|
||||
return myNode.protocol + '://' + myNode.domain + ':' + myNode.port
|
||||
}
|
||||
|
||||
getMyNode() {
|
||||
return 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') {
|
||||
const rawData = await this.getRawData()
|
||||
|
||||
const object = {
|
||||
...this.resource.schema.display
|
||||
}
|
||||
|
||||
this.updateDisplayWithPlaceholders(object, {}, rawData.data)
|
||||
this.feedItem = object
|
||||
this.status = response.data
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
const intervalId = setInterval(async () => {
|
||||
if (isCalling) return
|
||||
isCalling = true
|
||||
|
||||
const data = await requestQueue.enqueue(() => {
|
||||
return axios.get(`${this.nodeUrl}/arbitrary/resource/status/${this.resource.service}/${this.resource.name}/${this.resource.identifier}?apiKey=${this.myNode.apiKey}`)
|
||||
})
|
||||
|
||||
const res = data.data
|
||||
|
||||
isCalling = false
|
||||
if (res.localChunkCount) {
|
||||
if (res.percentLoaded) {
|
||||
if (res.percentLoaded === percentLoaded && res.percentLoaded !== 100) {
|
||||
timer = timer - 5
|
||||
} else {
|
||||
timer = 24
|
||||
}
|
||||
if (timer < 0) {
|
||||
timer = 24
|
||||
isCalling = true
|
||||
this.status = {
|
||||
...res,
|
||||
status: 'REFETCHING'
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
isCalling = false
|
||||
this.fetchResource()
|
||||
}, 25000)
|
||||
|
||||
return
|
||||
}
|
||||
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') {
|
||||
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 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({
|
||||
url: `qdn/browser/index.html${query}`,
|
||||
id: this.uid.rnd(),
|
||||
myPlugObj: {
|
||||
"url": "myapp",
|
||||
"domain": "core",
|
||||
"page": `qdn/browser/index.html${query}`,
|
||||
"title": name,
|
||||
"icon": 'vaadin:external-browser',
|
||||
"mwcicon": 'open_in_browser',
|
||||
"menus": [],
|
||||
"parent": false
|
||||
},
|
||||
openExisting: true
|
||||
}))
|
||||
} catch (error) {
|
||||
console.log({ error })
|
||||
}
|
||||
}
|
||||
|
||||
async extractComponents(url) {
|
||||
if (!url.startsWith("qortal://")) {
|
||||
return null
|
||||
}
|
||||
|
||||
url = url.replace(/^(qortal\:\/\/)/, "")
|
||||
|
||||
if (url.includes("/")) {
|
||||
let parts = url.split("/")
|
||||
const service = parts[0].toUpperCase()
|
||||
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}}`
|
||||
|
||||
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 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)
|
||||
}
|
||||
}
|
||||
|
||||
window.customElements.define('feed-item', FeedItem)
|
@ -1,206 +0,0 @@
|
||||
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 get properties() {
|
||||
return {
|
||||
for: { type: String, reflect: true },
|
||||
message: { type: String },
|
||||
openEditFriend: { attribute: false },
|
||||
name: { type: String },
|
||||
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)
|
||||
}
|
||||
|
||||
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>
|
||||
<div class="action-parent">
|
||||
<div
|
||||
class="send-message-button"
|
||||
@click="${() => {
|
||||
this.openEditFriend();
|
||||
this.closePopover();
|
||||
}}"
|
||||
>
|
||||
<mwc-icon style="color: var(--black)">edit</mwc-icon>
|
||||
${translate('friends.friend10')}
|
||||
</div>
|
||||
<div
|
||||
class="send-message-button"
|
||||
@click="${async () => {
|
||||
const address = await this.getUserAddress();
|
||||
if (!address) return;
|
||||
store.dispatch(
|
||||
setNewTab({
|
||||
url: `q-chat`,
|
||||
id: this.uid.rnd(),
|
||||
myPlugObj: {
|
||||
url: 'q-chat',
|
||||
domain: 'core',
|
||||
page: 'q-chat/index.html',
|
||||
title: 'Q-Chat',
|
||||
icon: 'vaadin:chat',
|
||||
mwcicon: 'forum',
|
||||
pluginNumber: 'plugin-qhsyOnpRhT',
|
||||
menus: [],
|
||||
parent: false
|
||||
},
|
||||
openExisting: true
|
||||
})
|
||||
);
|
||||
store.dispatch(
|
||||
setSideEffectAction({
|
||||
type: 'openPrivateChat',
|
||||
data: {
|
||||
address,
|
||||
name: this.name
|
||||
}
|
||||
})
|
||||
);
|
||||
this.closePopover();
|
||||
this.closeSidePanel();
|
||||
}}"
|
||||
>
|
||||
<mwc-icon style="color: var(--black)">send</mwc-icon>
|
||||
${translate('friends.friend8')}
|
||||
</div>
|
||||
<div
|
||||
class="send-message-button"
|
||||
@click="${() => {
|
||||
const query = `?service=APP&name=Q-Mail/to/${this.name}`;
|
||||
store.dispatch(
|
||||
setNewTab({
|
||||
url: `qdn/browser/index.html${query}`,
|
||||
id: this.uid.rnd(),
|
||||
myPlugObj: {
|
||||
url: 'myapp',
|
||||
domain: 'core',
|
||||
page: `qdn/browser/index.html${query}`,
|
||||
title: 'Q-Mail',
|
||||
icon: 'vaadin:mailbox',
|
||||
mwcicon: 'mail_outline',
|
||||
menus: [],
|
||||
parent: false
|
||||
},
|
||||
openExisting: true
|
||||
})
|
||||
);
|
||||
this.closePopover();
|
||||
this.closeSidePanel();
|
||||
}}"
|
||||
>
|
||||
<mwc-icon style="color: var(--black)">mail</mwc-icon>
|
||||
${translate('friends.friend9')}
|
||||
</div>
|
||||
<div
|
||||
class="send-message-button"
|
||||
@click="${() => {
|
||||
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>
|
||||
${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)
|
||||
}
|
||||
}
|
||||
|
||||
window.customElements.define('friend-item-actions', FriendItemActions)
|
@ -1,461 +0,0 @@
|
||||
import { html, LitElement } from 'lit'
|
||||
import { store } from '../../store'
|
||||
import { connect } from 'pwa-helpers'
|
||||
import { translate } from '../../../translate'
|
||||
import { friendsViewStyles } from '../../styles/core-css'
|
||||
import './feed-item'
|
||||
import './friends-view'
|
||||
import '@material/mwc-icon'
|
||||
import '@polymer/paper-spinner/paper-spinner-lite.js'
|
||||
|
||||
const perEndpointCount = 20
|
||||
const totalDesiredCount = 100
|
||||
const maxResultsInMemory = 300
|
||||
|
||||
class FriendsFeed extends connect(store)(LitElement) {
|
||||
static get properties() {
|
||||
return {
|
||||
feed: { type: Array },
|
||||
setHasNewFeed: { attribute: false },
|
||||
isLoading: { type: Boolean },
|
||||
hasFetched: { type: Boolean },
|
||||
mySelectedFeeds: { type: Array }
|
||||
}
|
||||
}
|
||||
|
||||
static get styles() {
|
||||
return [friendsViewStyles]
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
this.feed = []
|
||||
this.feedToRender = []
|
||||
this.nodeUrl = this.getNodeUrl()
|
||||
this.myNode = this.getMyNode()
|
||||
this.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[store.getState().app.nodeConfig.node]
|
||||
return myNode.protocol + '://' + myNode.domain + ':' + myNode.port
|
||||
}
|
||||
|
||||
getMyNode() {
|
||||
return store.getState().app.nodeConfig.knownNodes[store.getState().app.nodeConfig.node]
|
||||
}
|
||||
|
||||
_updateFeeds(event) {
|
||||
this.mySelectedFeeds = event.detail
|
||||
this.reFetchFeedData()
|
||||
this.requestUpdate()
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback()
|
||||
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)
|
||||
}
|
||||
|
||||
getFeedOnInterval() {
|
||||
let interval = null
|
||||
let stop = false
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// identify an element to observe
|
||||
const elementToObserve = this.downObserverElement
|
||||
|
||||
// passing it a callback function
|
||||
const observer = new IntersectionObserver(
|
||||
this.observerHandler,
|
||||
options
|
||||
)
|
||||
|
||||
// call `observe()` on that MutationObserver instance,
|
||||
// passing it the element to observe, and the options object
|
||||
observer.observe(elementToObserve)
|
||||
}
|
||||
|
||||
observerHandler(entries) {
|
||||
if (!entries[0].isIntersecting) {
|
||||
} else {
|
||||
if (this.feedToRender.length < 20) {
|
||||
return
|
||||
}
|
||||
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 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
|
||||
}
|
||||
|
||||
const remainingCount = totalDesiredCount - totalFetched
|
||||
|
||||
// If we've already reached the desired count, break
|
||||
if (remainingCount <= 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
let fetchCount = Math.min(perEndpointCount, remainingCount)
|
||||
let data = await this.fetchDataFromEndpoint(i, fetchCount)
|
||||
|
||||
// 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 < fetchCount) {
|
||||
exhaustedEndpoints.add(i)
|
||||
}
|
||||
|
||||
results = results.concat(data)
|
||||
totalFetched += data.length
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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 }
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
let newResource = {
|
||||
identifier: newItem.identifier,
|
||||
service: newItem.service,
|
||||
name: newItem.name
|
||||
}
|
||||
|
||||
if (newItem.schema) {
|
||||
const resource = newItem
|
||||
|
||||
let clickValue1 = newItem.schema.click;
|
||||
|
||||
newItem.link = replacePlaceholders(clickValue1, resource, newItem.schema.customParams)
|
||||
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()
|
||||
|
||||
// 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)
|
||||
|
||||
// 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')
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
})
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
})
|
||||
}
|
@ -1,79 +0,0 @@
|
||||
import { html, LitElement } from 'lit'
|
||||
import { connect } from 'pwa-helpers'
|
||||
import { store } from '../../store'
|
||||
import { translate } from '../../../translate'
|
||||
import { friendsSidePanelParentStyles } from '../../styles/core-css'
|
||||
import './friends-side-panel'
|
||||
import '@material/mwc-icon'
|
||||
import '@vaadin/tooltip'
|
||||
|
||||
class FriendsSidePanelParent extends connect(store)(LitElement) {
|
||||
static get properties() {
|
||||
return {
|
||||
isOpen: { type: Boolean },
|
||||
hasNewFeed: { type: Boolean }
|
||||
}
|
||||
}
|
||||
|
||||
static get styles() {
|
||||
return [friendsSidePanelParentStyles]
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
this.isOpen = false
|
||||
this.hasNewFeed = false
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<mwc-icon
|
||||
id="friends-icon"
|
||||
@click=${() => {
|
||||
this.isOpen = !this.isOpen
|
||||
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"
|
||||
>
|
||||
group
|
||||
</mwc-icon>
|
||||
<vaadin-tooltip
|
||||
for="friends-icon"
|
||||
position="bottom"
|
||||
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>
|
||||
`
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
window.customElements.define('friends-side-panel-parent', FriendsSidePanelParent)
|
@ -1,94 +0,0 @@
|
||||
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 '@material/mwc-icon'
|
||||
|
||||
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 }
|
||||
}
|
||||
}
|
||||
|
||||
static get styles() {
|
||||
return [friendsSidePanelStyles]
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
this.selected = 'friends'
|
||||
this.closeSidePanel = this.closeSidePanel.bind(this)
|
||||
this.openSidePanel = this.openSidePanel.bind(this)
|
||||
}
|
||||
|
||||
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>
|
||||
<div class="${this.selected === 'feed' ? 'active-content' : 'default-content'}">
|
||||
<friends-feed .setHasNewFeed=${(val) => this.setHasNewFeed(val)}></friends-feed>
|
||||
</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)
|
||||
}
|
||||
}
|
||||
|
||||
window.customElements.define('friends-side-panel', FriendsSidePanel)
|
@ -1,409 +0,0 @@
|
||||
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'
|
||||
|
||||
class FriendsView extends connect(store)(LitElement) {
|
||||
static get properties() {
|
||||
return {
|
||||
error: { type: Boolean },
|
||||
toggle: { attribute: false },
|
||||
userName: { type: String },
|
||||
errorMessage: { type: String },
|
||||
successMessage: { type: String },
|
||||
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 }
|
||||
}
|
||||
}
|
||||
|
||||
static get styles() {
|
||||
return [friendsViewStyles]
|
||||
}
|
||||
|
||||
constructor() {
|
||||
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.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[store.getState().app.nodeConfig.node]
|
||||
return myNode.protocol + '://' + myNode.domain + ':' + myNode.port
|
||||
}
|
||||
|
||||
getMyNode() {
|
||||
return store.getState().app.nodeConfig.knownNodes[store.getState().app.nodeConfig.node]
|
||||
}
|
||||
|
||||
getMoreFriends() { }
|
||||
|
||||
_updateFriends(event) {
|
||||
this.friendList = event.detail
|
||||
}
|
||||
|
||||
_updateFeed(event) {
|
||||
this.mySelectedFeeds = event.detail
|
||||
this.requestUpdate()
|
||||
}
|
||||
|
||||
_addFriend(event) {
|
||||
const name = event.detail;
|
||||
const findFriend = this.friendList.find((friend) => friend.name === name)
|
||||
if (findFriend) {
|
||||
this.editContent = { ...findFriend, mySelectedFeeds: this.mySelectedFeeds }
|
||||
this.userSelected = findFriend
|
||||
} else {
|
||||
this.userSelected = {
|
||||
name
|
||||
}
|
||||
}
|
||||
|
||||
this.isOpenAddFriendsModal = true
|
||||
this.openSidePanel()
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback()
|
||||
window.addEventListener('friends-my-friend-list-event', this._updateFriends)
|
||||
window.addEventListener('friends-my-selected-feeds-event', this._updateFeed)
|
||||
window.addEventListener('add-friend', this._addFriend)
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
window.removeEventListener('friends-my-friend-list-event', this._updateFriends)
|
||||
window.removeEventListener('friends-my-selected-feeds-event', this._updateFeed)
|
||||
window.removeEventListener('add-friend', this._addFriend)
|
||||
super.disconnectedCallback()
|
||||
}
|
||||
|
||||
elementObserver() {
|
||||
const options = {
|
||||
root: this.viewElement,
|
||||
rootMargin: '0px',
|
||||
threshold: 1
|
||||
}
|
||||
|
||||
// identify an element to observe
|
||||
const elementToObserve = this.downObserverElement
|
||||
|
||||
// passing it a callback function
|
||||
const observer = new IntersectionObserver(
|
||||
this.observerHandler,
|
||||
options
|
||||
)
|
||||
|
||||
// call `observe()` on that MutationObserver instance,
|
||||
// passing it the element to observe, and the options object
|
||||
observer.observe(elementToObserve)
|
||||
}
|
||||
|
||||
observerHandler(entries) {
|
||||
if (!entries[0].isIntersecting) {
|
||||
|
||||
} else {
|
||||
if (this.friendList.length < 20) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.getMoreFriends()
|
||||
}
|
||||
}
|
||||
|
||||
async userSearch() {
|
||||
const nameValue = this.shadowRoot.getElementById('sendTo').value
|
||||
|
||||
if (!nameValue) {
|
||||
this.userFound = []
|
||||
this.userFoundModalOpen = true
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const url = `${this.nodeUrl}/names/${nameValue}`
|
||||
const res = await fetch(url)
|
||||
const result = await res.json()
|
||||
|
||||
if (result.error === 401) {
|
||||
this.userFound = []
|
||||
} else {
|
||||
this.userFound = [
|
||||
result
|
||||
]
|
||||
}
|
||||
|
||||
this.userFoundModalOpen = true;
|
||||
} catch (error) {
|
||||
// let err4string = get("chatpage.cchange35")
|
||||
// parentEpml.request('showSnackBar', `${err4string}`)
|
||||
}
|
||||
}
|
||||
|
||||
async myFollowName(name) {
|
||||
let items = [
|
||||
name
|
||||
]
|
||||
|
||||
let namesJsonString = JSON.stringify({ "items": items })
|
||||
|
||||
return await parentEpml.request('apiCall', {
|
||||
url: `/lists/followedNames?apiKey=${this.getApiKey()}`,
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: `${namesJsonString}`
|
||||
})
|
||||
}
|
||||
|
||||
async unFollowName(name) {
|
||||
let items = [
|
||||
name
|
||||
]
|
||||
|
||||
let namesJsonString = JSON.stringify({ "items": items })
|
||||
|
||||
return await parentEpml.request('apiCall', {
|
||||
url: `/lists/followedNames?apiKey=${this.getApiKey()}`,
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: `${namesJsonString}`
|
||||
})
|
||||
}
|
||||
|
||||
async addToFriendList(val, isRemove) {
|
||||
const copyVal = { ...val }
|
||||
delete copyVal.mySelectedFeeds
|
||||
|
||||
if (isRemove) {
|
||||
this.friendList = this.friendList.filter((item) => item.name !== copyVal.name)
|
||||
} else if (this.editContent) {
|
||||
const findFriend = this.friendList.findIndex(item => item.name === copyVal.name)
|
||||
if (findFriend !== -1) {
|
||||
const copyList = [...this.friendList]
|
||||
copyList[findFriend] = copyVal
|
||||
this.friendList = copyList
|
||||
|
||||
}
|
||||
} else {
|
||||
this.friendList = [...this.friendList, copyVal]
|
||||
}
|
||||
|
||||
if (!copyVal.willFollow || isRemove) {
|
||||
await this.unFollowName(copyVal.name)
|
||||
} else if (copyVal.willFollow) {
|
||||
await this.myFollowName(copyVal.name)
|
||||
}
|
||||
|
||||
this.setMySelectedFeeds(val.mySelectedFeeds)
|
||||
|
||||
await new Promise((res) => {
|
||||
setTimeout(() => {
|
||||
res()
|
||||
}, 50)
|
||||
})
|
||||
|
||||
this.userSelected = {}
|
||||
this.shadowRoot.getElementById('sendTo').value = ''
|
||||
this.isLoading = false
|
||||
this.isOpenAddFriendsModal = false
|
||||
this.editContent = null
|
||||
this.setMyFriends(this.friendList)
|
||||
|
||||
if (!isRemove && this.friendList.length === 1) {
|
||||
this.refreshFeed()
|
||||
}
|
||||
}
|
||||
|
||||
setMyFriends(friendList) {
|
||||
localStorage.setItem('friends-my-friend-list', JSON.stringify(friendList));
|
||||
const tempSettingsData = JSON.parse(localStorage.getItem('temp-settings-data') || "{}")
|
||||
const newTemp = {
|
||||
...tempSettingsData,
|
||||
userLists: {
|
||||
data: [friendList],
|
||||
timestamp: Date.now()
|
||||
}
|
||||
}
|
||||
|
||||
localStorage.setItem('temp-settings-data', JSON.stringify(newTemp))
|
||||
|
||||
this.dispatchEvent(
|
||||
new CustomEvent('temp-settings-data-event', {
|
||||
bubbles: true,
|
||||
composed: true
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
setMySelectedFeeds(mySelectedFeeds) {
|
||||
this.mySelectedFeeds = mySelectedFeeds
|
||||
const tempSettingsData = JSON.parse(localStorage.getItem('temp-settings-data') || "{}")
|
||||
|
||||
const newTemp = {
|
||||
...tempSettingsData,
|
||||
friendsFeed: {
|
||||
data: mySelectedFeeds,
|
||||
timestamp: Date.now()
|
||||
}
|
||||
}
|
||||
|
||||
localStorage.setItem('temp-settings-data', JSON.stringify(newTemp))
|
||||
localStorage.setItem('friends-my-selected-feeds', JSON.stringify(mySelectedFeeds))
|
||||
}
|
||||
|
||||
openEditFriend(val) {
|
||||
this.isOpenAddFriendsModal = true
|
||||
this.userSelected = val
|
||||
this.editContent = { ...val, mySelectedFeeds: this.mySelectedFeeds }
|
||||
}
|
||||
|
||||
onClose() {
|
||||
this.isLoading = false;
|
||||
this.isOpenAddFriendsModal = false
|
||||
this.editContent = null
|
||||
this.userSelected = {}
|
||||
}
|
||||
|
||||
// Standard functions
|
||||
getApiKey() {
|
||||
const coreNode = store.getState().app.nodeConfig.knownNodes[store.getState().app.nodeConfig.node]
|
||||
return coreNode.apiKey
|
||||
}
|
||||
|
||||
isEmptyArray(arr) {
|
||||
if (!arr) { return true }
|
||||
return arr.length === 0
|
||||
}
|
||||
|
||||
round(number) {
|
||||
return (Math.round(parseFloat(number) * 1e8) / 1e8).toFixed(8)
|
||||
}
|
||||
}
|
||||
|
||||
window.customElements.define('friends-view', FriendsView)
|
@ -1,562 +0,0 @@
|
||||
import { html, LitElement } from 'lit'
|
||||
import { connect } from 'pwa-helpers'
|
||||
import { store } from '../../store'
|
||||
import { parentEpml } from '../show-plugin'
|
||||
import { get, translate } from '../../../translate'
|
||||
import { profileModalUpdateStyles } from '../../styles/core-css'
|
||||
import '@material/mwc-button'
|
||||
import '@material/mwc-checkbox'
|
||||
import '@material/mwc-dialog'
|
||||
import '@material/mwc-icon'
|
||||
import '@polymer/paper-spinner/paper-spinner-lite.js'
|
||||
import '@vaadin/tooltip'
|
||||
|
||||
class ProfileModalUpdate extends connect(store)(LitElement) {
|
||||
static get properties() {
|
||||
return {
|
||||
isOpen: { type: Boolean },
|
||||
setIsOpen: { attribute: false },
|
||||
isLoading: { type: Boolean },
|
||||
onSubmit: { attribute: false },
|
||||
editContent: { type: Object },
|
||||
onClose: { attribute: false },
|
||||
tagline: { type: String },
|
||||
bio: { type: String },
|
||||
wallets: { type: Array },
|
||||
hasFetchedArrr: { type: Boolean },
|
||||
isOpenCustomDataModal: { type: Boolean },
|
||||
customData: { type: Object },
|
||||
newCustomDataField: { type: Object },
|
||||
newFieldName: { type: String },
|
||||
qortalRequestCustomData: { type: Object },
|
||||
newCustomDataKey: { type: String },
|
||||
newCustomDataValue: { type: String },
|
||||
isSaving: { type: Boolean }
|
||||
}
|
||||
}
|
||||
|
||||
static get styles() {
|
||||
return [profileModalUpdateStyles]
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.isOpen = false
|
||||
this.isLoading = false
|
||||
this.nodeUrl = this.getNodeUrl()
|
||||
this.myNode = this.getMyNode()
|
||||
this.tagline = ''
|
||||
this.bio = ''
|
||||
this.walletList = ['btc', 'ltc', 'doge', 'dgb', 'rvn', 'arrr']
|
||||
let wallets = {}
|
||||
this.walletList.forEach((item) => {
|
||||
wallets[item] = ''
|
||||
})
|
||||
this.wallets = wallets
|
||||
this.walletsUi = new Map()
|
||||
let coinProp = {
|
||||
wallet: null
|
||||
}
|
||||
this.walletList.forEach((c, i) => {
|
||||
this.walletsUi.set(c, { ...coinProp })
|
||||
})
|
||||
this.walletsUi.get('btc').wallet = store.getState().app.selectedAddress.btcWallet
|
||||
this.walletsUi.get('ltc').wallet = store.getState().app.selectedAddress.ltcWallet
|
||||
this.walletsUi.get('doge').wallet = store.getState().app.selectedAddress.dogeWallet
|
||||
this.walletsUi.get('dgb').wallet = store.getState().app.selectedAddress.dgbWallet
|
||||
this.walletsUi.get('rvn').wallet = store.getState().app.selectedAddress.rvnWallet
|
||||
this.hasFetchedArrr = false
|
||||
this.isOpenCustomDataModal = false
|
||||
this.customData = {}
|
||||
this.newCustomDataKey = ""
|
||||
this.newCustomDataValue = ""
|
||||
this.newCustomDataField = {}
|
||||
this.newFieldName = ''
|
||||
this.isSaving = false
|
||||
this.addPrivate = this.addPrivate.bind(this)
|
||||
this.checkForPrivate = this.checkForPrivate.bind(this)
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<div class="modal-overlay ${this.isOpen ? '' : 'hidden'}">
|
||||
<div class="modal-content">
|
||||
<div class="inner-content">
|
||||
<div style="height:15px"></div>
|
||||
<div style="display: flex;flex-direction: column;">
|
||||
<label
|
||||
for="tagline"
|
||||
id="taglineLabel"
|
||||
style="color: var(--black);"
|
||||
>
|
||||
${translate('profile.profile4')}
|
||||
</label>
|
||||
<textarea
|
||||
class="input"
|
||||
@change=${(e) => {
|
||||
this.tagline = e.target.value;
|
||||
}}
|
||||
.value=${this.tagline}
|
||||
?disabled=${this.isLoading}
|
||||
id="tagline"
|
||||
placeholder="${translate('profile.profile4')}"
|
||||
rows="3"
|
||||
></textarea>
|
||||
</div>
|
||||
<div style="height:15px"></div>
|
||||
<div style="display: flex;flex-direction: column;">
|
||||
<label
|
||||
for="bio"
|
||||
id="bioLabel"
|
||||
style="color: var(--black);"
|
||||
>
|
||||
${translate('profile.profile5')}
|
||||
</label>
|
||||
<textarea
|
||||
class="input"
|
||||
@change=${(e) => {
|
||||
this.bio = e.target.value;
|
||||
}}
|
||||
.value=${this.bio}
|
||||
?disabled=${this.isLoading}
|
||||
id="bio"
|
||||
placeholder="${translate('profile.profile5')}"
|
||||
rows="3"
|
||||
></textarea>
|
||||
</div>
|
||||
<div style="height:15px"></div>
|
||||
<p>${translate('profile.profile6')}</p>
|
||||
<div style="display: flex;flex-direction: column;">
|
||||
${Object.keys(this.wallets).map((key) => {
|
||||
return html`
|
||||
<div style="display:flex;justify-content:center;flex-direction:column">
|
||||
<label
|
||||
for=${key}
|
||||
id="taglineLabel"
|
||||
style="color: var(--black);font-size:16px"
|
||||
>
|
||||
${key}
|
||||
</label>
|
||||
<div style="display:flex;gap:15px;align-items:center">
|
||||
<input
|
||||
id=${key}
|
||||
placeholder=${key + ' ' + get('settings.address')}
|
||||
class="input"
|
||||
.value=${this.wallets[key]}
|
||||
@change=${(e) => {
|
||||
this.wallets = {
|
||||
...this.wallets,
|
||||
[key]: e.target.value,
|
||||
};
|
||||
}}
|
||||
/>
|
||||
<mwc-icon
|
||||
id=${`${key}-upload`}
|
||||
@click=${() =>
|
||||
this.fillAddress(key)}
|
||||
style="color:var(--black);cursor:pointer"
|
||||
>
|
||||
upload_2
|
||||
</mwc-icon>
|
||||
<vaadin-tooltip
|
||||
for=${`${key}-upload`}
|
||||
position="bottom"
|
||||
hover-delay=${200}
|
||||
hide-delay=${1}
|
||||
text=${translate('profile.profile21')}
|
||||
></vaadin-tooltip>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
})}
|
||||
</div>
|
||||
<div style="display: flex;flex-direction: column;">
|
||||
${Object.keys(this.customData).map((key) => {
|
||||
return html`
|
||||
<div style="display:flex;justify-content:center;flex-direction:column;gap:25px">
|
||||
<div style="display:flex;gap:15px;align-items:center">
|
||||
<p style="color: var(--black);font-size:16px">${key}</p>
|
||||
<mwc-icon
|
||||
@click=${() => this.updateCustomData(key, this.customData[key])}
|
||||
style="color:var(--black);cursor:pointer"
|
||||
>
|
||||
edit
|
||||
</mwc-icon>
|
||||
<mwc-icon
|
||||
@click=${() => this.removeCustomData(key)}
|
||||
style="color:var(--black);cursor:pointer"
|
||||
>
|
||||
remove
|
||||
</mwc-icon>
|
||||
</div>
|
||||
</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>
|
||||
<div style="display:flex;gap:10px;align-items:center">
|
||||
<button
|
||||
?disabled="${this.isLoading}"
|
||||
class="modal-button"
|
||||
@click=${() => {
|
||||
this.isOpenCustomDataModal = true;
|
||||
}}
|
||||
>
|
||||
${translate('profile.profile8')}
|
||||
</button>
|
||||
<button
|
||||
?disabled="${this.isLoading}"
|
||||
class="modal-button"
|
||||
@click=${() => {
|
||||
if (this.isSaving) return
|
||||
this.saveProfile();
|
||||
}}
|
||||
>
|
||||
${this.isSaving ? html`
|
||||
<paper-spinner-lite active></paper-spinner-lite>
|
||||
` : ''}
|
||||
${this.isSaving ? '' : translate('profile.profile3')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- add custom vars -->
|
||||
<div
|
||||
class="modal-overlay ${this.isOpenCustomDataModal
|
||||
? ''
|
||||
: 'hidden'}"
|
||||
style="z-index:1001"
|
||||
>
|
||||
<div class="modal-content" style="max-width: 950px">
|
||||
<div class="inner-content">
|
||||
<div style="display:flex; justify-content:flex-end">
|
||||
<div class="checkbox-row" style="font-size:16px">
|
||||
<label for="isPrivate" style="color: var(--black);">${get('profile.profile23')}</label>
|
||||
<mwc-checkbox id="isPrivate" @change=${(e) => this.addPrivate(e)} ?checked=${this.checkForPrivate()}></mwc-checkbox>
|
||||
</div>
|
||||
</div>
|
||||
<div style="height:15px"></div>
|
||||
<div style="display:flex;justify-content:center;flex-direction:column">
|
||||
<label
|
||||
for="key-name"
|
||||
id="taglineLabel"
|
||||
style="color: var(--black);font-size:16px"
|
||||
>
|
||||
${translate('profile.profile9')}
|
||||
</label>
|
||||
<div style="display:flex;gap:15px;align-items:center">
|
||||
<input
|
||||
id="key-name"
|
||||
placeholder=${translate(
|
||||
'profile.profile9'
|
||||
)}
|
||||
?disabled=${!!this.qortalRequestCustomData}
|
||||
class="input"
|
||||
.value=${this.newCustomDataKey}
|
||||
@change=${(e) => {
|
||||
this.newCustomDataKey = e.target.value
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<p style=${`${Object.keys(this.newCustomDataField).length ? "margin: 10px 0 10px 0;" : "margin: 10px 0 0 0;"}`}>
|
||||
${translate('profile.profile10')}
|
||||
</p>
|
||||
<div style="display: flex;flex-direction: column;">
|
||||
${Object.keys(this.newCustomDataField).map((key) => {
|
||||
return html`
|
||||
<div style="display:flex;justify-content:center;flex-direction:column">
|
||||
<label for=${key} id="taglineLabel" style="color: var(--black);font-size:16px">${key}</label>
|
||||
<div style="display:flex;gap:15px;align-items:center">
|
||||
<input
|
||||
id=${key}
|
||||
placeholder=${translate('profile.profile13')}
|
||||
class="input"
|
||||
.value=${this.newCustomDataField[key]}
|
||||
@change=${(e) => {
|
||||
this.newCustomDataField = {
|
||||
...this.newCustomDataField,
|
||||
[key]: e.target.value,
|
||||
};
|
||||
}}
|
||||
/>
|
||||
<mwc-icon
|
||||
@click=${() => this.removeField(key)}
|
||||
style="color:var(--black);cursor:pointer"
|
||||
>
|
||||
remove
|
||||
</mwc-icon>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
})}
|
||||
</div>
|
||||
<div style=${`display: flex; flex-direction: row; align-items: center;justify-content:space-between; ${Object.keys(this.newCustomDataField).length ? "margin-top: 10px" : ""}`}>
|
||||
<div style="width:100%;display:flex; flex-direction: column; align-items: flex-start;justify-content:center;gap:10px;">
|
||||
<input
|
||||
placeholder=${translate('profile.profile12')}
|
||||
class="input"
|
||||
.value=${this.newFieldName}
|
||||
@change=${(e) => {
|
||||
this.newFieldName = e.target.value
|
||||
}}
|
||||
/>
|
||||
<input
|
||||
id="value-name"
|
||||
placeholder=${translate('profile.profile13')}
|
||||
class="input"
|
||||
.value=${this.newCustomDataValue}
|
||||
@change=${(e) => {
|
||||
this.newCustomDataValue = e.target.value;
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
class="modal-button"
|
||||
style="margin-top: 25px; width: 100px; min-height: 80px;"
|
||||
@click=${() => {
|
||||
this.addField();
|
||||
}}
|
||||
>
|
||||
${translate('profile.profile11')}
|
||||
</button>
|
||||
</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.isOpenCustomDataModal = false
|
||||
this.newCustomDataKey = ""
|
||||
this.newCustomDataField = {};
|
||||
}}"
|
||||
>
|
||||
${translate('general.close')}
|
||||
</button>
|
||||
<button
|
||||
?disabled="${this.isSaving}"
|
||||
class="modal-button"
|
||||
@click=${() => {
|
||||
this.addCustomData();
|
||||
}}
|
||||
>
|
||||
${translate('profile.profile8')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
|
||||
async firstUpdated() {
|
||||
try {
|
||||
await this.fetchWalletAddress('arrr')
|
||||
} catch (error) {
|
||||
console.log({ error })
|
||||
} finally {
|
||||
}
|
||||
}
|
||||
|
||||
async updated(changedProperties) {
|
||||
if (changedProperties && changedProperties.has('editContent') && this.editContent) {
|
||||
const { bio, tagline, wallets, customData } = this.editContent
|
||||
this.bio = bio ?? ''
|
||||
this.tagline = tagline ?? ''
|
||||
let formWallets = { ...this.wallets }
|
||||
if (wallets && Object.keys(wallets).length) {
|
||||
Object.keys(formWallets).forEach((key) => {
|
||||
if (wallets[key]) {
|
||||
formWallets[key] = wallets[key]
|
||||
}
|
||||
})
|
||||
}
|
||||
this.wallets = formWallets
|
||||
|
||||
this.customData = { ...customData }
|
||||
this.requestUpdate();
|
||||
}
|
||||
if (changedProperties && changedProperties.has('qortalRequestCustomData') && this.qortalRequestCustomData) {
|
||||
this.isOpenCustomDataModal = true
|
||||
this.newCustomDataField = { ...this.qortalRequestCustomData.payload.customData }
|
||||
this.newCustomDataKey = this.qortalRequestCustomData.property
|
||||
this.requestUpdate()
|
||||
}
|
||||
}
|
||||
|
||||
async fetchWalletAddress(coin) {
|
||||
switch (coin) {
|
||||
case 'arrr':
|
||||
const arrrWalletName = `${coin}Wallet`;
|
||||
|
||||
let res = await parentEpml.request('apiCall', {
|
||||
url: `/crosschain/${coin}/walletaddress?apiKey=${this.myNode.apiKey}`,
|
||||
method: 'POST',
|
||||
body: `${store.getState().app.selectedAddress[arrrWalletName].seed58}`
|
||||
})
|
||||
if (res != null && res.error != 1201 && res.length === 78) {
|
||||
this.arrrWalletAddress = res
|
||||
this.hasFetchedArrr = true
|
||||
}
|
||||
break
|
||||
|
||||
default:
|
||||
// Not used for other coins yet
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
async getSelectedWalletAddress(wallet) {
|
||||
switch (wallet) {
|
||||
case 'arrr':
|
||||
if (!this.arrrWalletAddress) {
|
||||
try {
|
||||
await this.fetchWalletAddress('arrr')
|
||||
} catch (error) {
|
||||
console.log({ error })
|
||||
}
|
||||
}
|
||||
// Use address returned by core API
|
||||
return this.arrrWalletAddress
|
||||
|
||||
default:
|
||||
// Use locally derived address
|
||||
return this.walletsUi.get(wallet).wallet.address
|
||||
}
|
||||
}
|
||||
|
||||
getNodeUrl() {
|
||||
const myNode = store.getState().app.nodeConfig.knownNodes[store.getState().app.nodeConfig.node]
|
||||
return myNode.protocol + '://' + myNode.domain + ':' + myNode.port
|
||||
}
|
||||
|
||||
getMyNode() {
|
||||
return store.getState().app.nodeConfig.knownNodes[store.getState().app.nodeConfig.node]
|
||||
}
|
||||
|
||||
clearFields() {
|
||||
this.bio = ''
|
||||
this.tagline = ''
|
||||
}
|
||||
|
||||
async fillAddress(coin) {
|
||||
const address = await this.getSelectedWalletAddress(coin)
|
||||
|
||||
if (address) {
|
||||
this.wallets = {
|
||||
...this.wallets,
|
||||
[coin]: address
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async saveProfile() {
|
||||
try {
|
||||
const data = {
|
||||
version: 1,
|
||||
tagline: this.tagline,
|
||||
bio: this.bio,
|
||||
wallets: this.wallets,
|
||||
customData: this.customData
|
||||
}
|
||||
this.isSaving = true
|
||||
await this.onSubmit(data)
|
||||
this.setIsOpen(false)
|
||||
this.clearFields()
|
||||
this.onClose('success')
|
||||
} catch (error) { } finally {
|
||||
this.isSaving = false
|
||||
}
|
||||
}
|
||||
|
||||
removeField(key) {
|
||||
const copyObj = { ...this.newCustomDataField }
|
||||
delete copyObj[key]
|
||||
this.newCustomDataField = copyObj
|
||||
}
|
||||
|
||||
addField() {
|
||||
if (!this.newFieldName || !this.newCustomDataValue) {
|
||||
let snack5string = get("profile.profile24")
|
||||
parentEpml.request('showSnackBar', `${snack5string}`)
|
||||
return
|
||||
}
|
||||
const copyObj = { ...this.newCustomDataField }
|
||||
copyObj[this.newFieldName] = this.newCustomDataValue
|
||||
this.newCustomDataField = copyObj
|
||||
this.newFieldName = ''
|
||||
this.newCustomDataValue = ''
|
||||
}
|
||||
|
||||
addCustomData() {
|
||||
const copyObj = { ...this.customData }
|
||||
copyObj[this.newCustomDataKey] = this.newCustomDataField
|
||||
this.customData = copyObj
|
||||
this.newCustomDataKey = ''
|
||||
this.newCustomDataField = {}
|
||||
this.newFieldName = ''
|
||||
this.newCustomDataValue = ''
|
||||
this.isOpenCustomDataModal = false
|
||||
}
|
||||
|
||||
updateCustomData(key, data) {
|
||||
this.isOpenCustomDataModal = true
|
||||
this.newCustomDataField = data
|
||||
this.newCustomDataKey = key
|
||||
|
||||
}
|
||||
|
||||
removeCustomData(key) {
|
||||
const copyObj = { ...this.customData }
|
||||
delete copyObj[key]
|
||||
this.customData = copyObj
|
||||
}
|
||||
|
||||
checkForPrivate() {
|
||||
let isPrivate = false
|
||||
if (this.newCustomDataKey.includes('-private')) isPrivate = true
|
||||
return isPrivate
|
||||
}
|
||||
|
||||
addPrivate(e) {
|
||||
if (e.target.checked) {
|
||||
if (this.newCustomDataKey.includes('-private')) {
|
||||
|
||||
} else {
|
||||
this.newCustomDataKey = this.newCustomDataKey + '-private'
|
||||
}
|
||||
} else {
|
||||
this.newCustomDataKey = this.newCustomDataKey.replace('-private', '')
|
||||
}
|
||||
}
|
||||
|
||||
// Standard functions
|
||||
getApiKey() {
|
||||
const coreNode = store.getState().app.nodeConfig.knownNodes[store.getState().app.nodeConfig.node]
|
||||
return coreNode.apiKey
|
||||
}
|
||||
|
||||
isEmptyArray(arr) {
|
||||
if (!arr) { return true }
|
||||
return arr.length === 0
|
||||
}
|
||||
|
||||
round(number) {
|
||||
return (Math.round(parseFloat(number) * 1e8) / 1e8).toFixed(8)
|
||||
}
|
||||
}
|
||||
|
||||
window.customElements.define('profile-modal-update', ProfileModalUpdate)
|
@ -1,929 +0,0 @@
|
||||
import { html, LitElement } from 'lit'
|
||||
import { connect } from 'pwa-helpers'
|
||||
import { store } from '../../store'
|
||||
import { parentEpml } from '../show-plugin'
|
||||
import { publishData, modalHelper } from '../../../../plugins/plugins/utils/classes'
|
||||
import { setNewTab, setProfileData, setSideEffectAction } from '../../redux/app/app-actions'
|
||||
import {
|
||||
decryptGroupData,
|
||||
encryptDataGroup,
|
||||
objectToBase64,
|
||||
uint8ArrayToObject
|
||||
} from '../../../../plugins/plugins/core/components/qdn-action-encryption'
|
||||
import { get, translate } from '../../../translate'
|
||||
import { profileQdnStyles } from '../../styles/core-css'
|
||||
import ShortUniqueId from 'short-unique-id'
|
||||
import WebWorker2 from '../WebWorkerFile.js'
|
||||
import './avatar'
|
||||
import './friends-side-panel'
|
||||
import './profile-modal-update'
|
||||
import '../notification-view/popover'
|
||||
import '@material/mwc-icon'
|
||||
import '@polymer/paper-dialog/paper-dialog.js'
|
||||
import '@polymer/paper-spinner/paper-spinner-lite.js'
|
||||
import '@vaadin/tooltip'
|
||||
|
||||
class ProfileQdn extends connect(store)(LitElement) {
|
||||
static get properties() {
|
||||
return {
|
||||
isOpen: { type: Boolean },
|
||||
syncPercentage: { type: Number },
|
||||
settingsRawData: { type: Object },
|
||||
valuesToBeSavedOnQdn: { type: Object },
|
||||
resourceExists: { type: Boolean },
|
||||
isSaving: { type: Boolean },
|
||||
fee: { type: Object },
|
||||
name: { type: String },
|
||||
isOpenProfileModalUpdate: { type: Boolean },
|
||||
editContent: { type: Object },
|
||||
profileData: { type: Object },
|
||||
imageUrl: { type: String },
|
||||
dialogOpenedProfile: { type: Boolean },
|
||||
profileDataVisiting: { type: Object },
|
||||
nameVisiting: { type: String },
|
||||
hasName: { type: Boolean },
|
||||
resourceExistsVisiting: { type: Boolean },
|
||||
error: { type: String },
|
||||
isFriend: { type: Boolean }
|
||||
}
|
||||
}
|
||||
|
||||
static get styles() {
|
||||
return [profileQdnStyles]
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
this.isOpen = false
|
||||
this.getProfile = this.getProfile.bind(this)
|
||||
this._handleQortalRequestSetData = this._handleQortalRequestSetData.bind(this)
|
||||
this._handleOpenVisiting = this._handleOpenVisiting.bind(this)
|
||||
this.setValues = this.setValues.bind(this)
|
||||
this.saveToQdn = this.saveToQdn.bind(this)
|
||||
this.syncPercentage = 0
|
||||
this.hasRetrievedResource = false
|
||||
this.hasAttemptedToFetchResource = false
|
||||
this.resourceExists = undefined
|
||||
this.settingsRawData = null
|
||||
this.nodeUrl = this.getNodeUrl()
|
||||
this.myNode = this.getMyNode()
|
||||
this.valuesToBeSavedOnQdn = {}
|
||||
this.isSaving = false
|
||||
this.fee = null
|
||||
this.name = undefined
|
||||
this.uid = new ShortUniqueId()
|
||||
this.isOpenProfileModalUpdate = false
|
||||
this.editContent = null
|
||||
this.profileData = null
|
||||
this.qortalRequestCustomData = null
|
||||
this.imageUrl = ''
|
||||
this.dialogOpenedProfile = false
|
||||
this.profileDataVisiting = null
|
||||
this.nameVisiting = ''
|
||||
this.hasName = false
|
||||
this.resourceExistsVisiting = undefined
|
||||
this.error = ''
|
||||
this.getUserAddress = this.getUserAddress.bind(this)
|
||||
this.checkIfInFriendsList = this.checkIfInFriendsList.bind(this)
|
||||
this.isFriend = undefined
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
${this.isSaving || (!this.error && this.resourceExists === undefined) ?
|
||||
html`
|
||||
<paper-spinner-lite active style="display: block; margin: 0 auto;"></paper-spinner-lite>
|
||||
`
|
||||
: !this.name ? html`
|
||||
<mwc-icon
|
||||
id="profile-icon"
|
||||
class=${'notActive'}
|
||||
@click=${() => {
|
||||
const target = this.shadowRoot.getElementById('popover-notification');
|
||||
const popover = this.shadowRoot.querySelector('popover-component');
|
||||
if (popover) {
|
||||
popover.openPopover(target);
|
||||
}
|
||||
}}
|
||||
style="user-select:none;cursor:pointer"
|
||||
>
|
||||
account_circle
|
||||
</mwc-icon>
|
||||
<vaadin-tooltip
|
||||
for="profile-icon"
|
||||
position="bottom"
|
||||
hover-delay=${300}
|
||||
hide-delay=${1}
|
||||
text=${translate('profile.profile20')}
|
||||
></vaadin-tooltip>
|
||||
<popover-component for="profile-icon" message="">
|
||||
<div style="margin-bottom:20px">
|
||||
<p style="margin:10px 0px; font-size:16px">
|
||||
${translate('profile.profile1')}
|
||||
</p>
|
||||
</div>
|
||||
<div style="display:flex;justify-content:center;gap:10px">
|
||||
<div
|
||||
class="accept-button"
|
||||
@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
|
||||
})
|
||||
);
|
||||
const popover = this.shadowRoot.querySelector('popover-component');
|
||||
if (popover) {
|
||||
popover.closePopover();
|
||||
}
|
||||
}}"
|
||||
>
|
||||
${translate('profile.profile2')}
|
||||
</div>
|
||||
</div>
|
||||
</popover-component>
|
||||
`
|
||||
: this.error ? html`
|
||||
<div style="user-select:none;cursor:pointer;opacity:0.5" id="profile-error">
|
||||
<avatar-component
|
||||
.resource=${{
|
||||
name: this.name,
|
||||
service: 'THUMBNAIL',
|
||||
identifier: 'qortal_avatar'
|
||||
}}
|
||||
name=${this.name}
|
||||
></avatar-component>
|
||||
</div>
|
||||
<vaadin-tooltip
|
||||
for="profile-error"
|
||||
position="bottom"
|
||||
hover-delay=${200}
|
||||
hide-delay=${1}
|
||||
text=${translate('profile.profile19')}
|
||||
></vaadin-tooltip>
|
||||
`
|
||||
: html`
|
||||
<div
|
||||
style="user-select:none;cursor:pointer"
|
||||
@click=${() => {
|
||||
if (this.resourceExists && this.profileData) {
|
||||
this.editContent = this.profileData;
|
||||
} else if (this.resourceExists && !this.profileData) {
|
||||
return;
|
||||
}
|
||||
if (this.profileData) {
|
||||
this.profileDataVisiting = this.profileData;
|
||||
this.nameVisiting = this.name;
|
||||
this.dialogOpenedProfile = true;
|
||||
} else {
|
||||
this.isOpenProfileModalUpdate = !this.isOpenProfileModalUpdate;
|
||||
}
|
||||
}}
|
||||
>
|
||||
<avatar-component
|
||||
.resource=${{
|
||||
name: this.name,
|
||||
service: 'THUMBNAIL',
|
||||
identifier: 'qortal_avatar'
|
||||
}}
|
||||
name=${this.name}
|
||||
></avatar-component>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
<profile-modal-update
|
||||
?isOpen=${this.isOpenProfileModalUpdate}
|
||||
.setIsOpen=${(val) => {
|
||||
this.isOpenProfileModalUpdate = val;
|
||||
}}
|
||||
.onSubmit=${this.saveToQdn}
|
||||
.editContent=${this.editContent}
|
||||
.onClose=${(val) => this.onClose(val)}
|
||||
.qortalRequestCustomData=${this.qortalRequestCustomData}
|
||||
></profile-modal-update>
|
||||
<!-- Profile read-view -->
|
||||
${this.dialogOpenedProfile ?
|
||||
html`
|
||||
<paper-dialog class="full-info-wrapper" ?opened="${this.dialogOpenedProfile}">
|
||||
<div class="full-info-logo">
|
||||
${this.avatarFullImage()}
|
||||
</div>
|
||||
<h3>${this.nameVisiting}</h3>
|
||||
<div style="display:flex;gap:15px;justify-content:center;margin-top:10px">
|
||||
${this.nameVisiting !== this.name ?
|
||||
html`
|
||||
<div
|
||||
class="send-message-button"
|
||||
@click="${async () => {
|
||||
const address = await this.getUserAddress();
|
||||
if (!address) return;
|
||||
store.dispatch(
|
||||
setNewTab({
|
||||
url: `q-chat`,
|
||||
id: this.uid.rnd(),
|
||||
myPlugObj: {
|
||||
url: 'q-chat',
|
||||
domain: 'core',
|
||||
page: 'q-chat/index.html',
|
||||
title: 'Q-Chat',
|
||||
icon: 'vaadin:chat',
|
||||
mwcicon: 'forum',
|
||||
pluginNumber: 'plugin-qhsyOnpRhT',
|
||||
menus: [],
|
||||
parent: false
|
||||
},
|
||||
openExisting: true
|
||||
})
|
||||
);
|
||||
store.dispatch(
|
||||
setSideEffectAction({
|
||||
type: 'openPrivateChat',
|
||||
data: {
|
||||
address,
|
||||
name: this.nameVisiting
|
||||
}
|
||||
})
|
||||
);
|
||||
this.dialogOpenedProfile = false;
|
||||
}}"
|
||||
>
|
||||
<mwc-icon id="send-chat-icon" style="color: var(--black)">send</mwc-icon>
|
||||
<vaadin-tooltip
|
||||
for="send-chat-icon"
|
||||
position="bottom"
|
||||
hover-delay=${200}
|
||||
hide-delay=${1}
|
||||
text=${translate('friends.friend8')}
|
||||
></vaadin-tooltip>
|
||||
</div>
|
||||
`
|
||||
: ''
|
||||
}
|
||||
${this.nameVisiting !== this.name ?
|
||||
html`
|
||||
<div
|
||||
class="send-message-button"
|
||||
@click="${() => {
|
||||
const query = `?service=APP&name=Q-Mail/to/${this.nameVisiting}`;
|
||||
store.dispatch(
|
||||
setNewTab({
|
||||
url: `qdn/browser/index.html${query}`,
|
||||
id: this.uid.rnd(),
|
||||
myPlugObj: {
|
||||
url: 'myapp',
|
||||
domain: 'core',
|
||||
page: `qdn/browser/index.html${query}`,
|
||||
title: 'Q-Mail',
|
||||
icon: 'vaadin:mailbox',
|
||||
mwcicon: 'mail_outline',
|
||||
menus: [],
|
||||
parent: false
|
||||
},
|
||||
openExisting: true
|
||||
})
|
||||
);
|
||||
this.dialogOpenedProfile = false;
|
||||
}}"
|
||||
>
|
||||
<mwc-icon id="send-mail-icon" style="color: var(--black)">mail</mwc-icon>
|
||||
<vaadin-tooltip
|
||||
for="send-mail-icon"
|
||||
position="bottom"
|
||||
hover-delay=${200}
|
||||
hide-delay=${1}
|
||||
text=${translate('friends.friend9')}
|
||||
></vaadin-tooltip>
|
||||
</div>
|
||||
`
|
||||
: ''
|
||||
}
|
||||
${this.nameVisiting !== this.name && this.isFriend === false ?
|
||||
html`
|
||||
<div
|
||||
class="send-message-button"
|
||||
@click="${() => {
|
||||
this.dispatchEvent(new CustomEvent('add-friend', {bubbles: true, composed: true, detail: this.nameVisiting}));
|
||||
this.dialogOpenedProfile = false;
|
||||
}}"
|
||||
>
|
||||
<mwc-icon id="add-friend-icon" style="color: var(--black)">person_add</mwc-icon>
|
||||
<vaadin-tooltip
|
||||
for="add-friend-icon"
|
||||
position="bottom"
|
||||
hover-delay=${200}
|
||||
hide-delay=${1}
|
||||
text=${translate('profile.profile26')}
|
||||
></vaadin-tooltip>
|
||||
</div>
|
||||
`
|
||||
: ''
|
||||
}
|
||||
${this.nameVisiting !== this.name && this.isFriend === true ?
|
||||
html`
|
||||
<div
|
||||
class="send-message-button"
|
||||
@click="${() => {
|
||||
this.dispatchEvent(new CustomEvent('add-friend', {bubbles: true, composed: true, detail: this.nameVisiting}));
|
||||
this.dialogOpenedProfile = false;
|
||||
}}"
|
||||
>
|
||||
<mwc-icon id="add-friend-icon" style="color: green">person</mwc-icon>
|
||||
<vaadin-tooltip
|
||||
for="add-friend-icon"
|
||||
position="bottom"
|
||||
hover-delay=${200}
|
||||
hide-delay=${1}
|
||||
text=${translate('profile.profile25')}
|
||||
></vaadin-tooltip>
|
||||
</div>
|
||||
`
|
||||
: ''
|
||||
}
|
||||
</div>
|
||||
<div class="data-info">
|
||||
${this.isLoadingVisitingProfile ?
|
||||
html`
|
||||
<div style="width:100%;display:flex;justify-content:center">
|
||||
<paper-spinner-lite active style="display: block; margin: 0 auto;"></paper-spinner-lite>
|
||||
</div>
|
||||
`
|
||||
: this.resourceExistsVisiting === false ?
|
||||
html`
|
||||
<div style="width:100%;display:flex;justify-content:center">
|
||||
<p>${translate('profile.profile16')}</p>
|
||||
</div>
|
||||
`
|
||||
: this.profileDataVisiting === null ?
|
||||
html`
|
||||
<div style="width:100%;display:flex;justify-content:center">
|
||||
<p>${translate('profile.profile17')}</p>
|
||||
</div>
|
||||
`
|
||||
: html`
|
||||
<p style="font-weight:bold;color:#03a9f4;font-size:17px">
|
||||
${translate('profile.profile4')}
|
||||
</p>
|
||||
<p style="font-size:15px">
|
||||
${this.profileDataVisiting.tagline || translate('profile.profile15')}
|
||||
</p>
|
||||
<p style="font-weight:bold;color:#03a9f4;font-size:17px">
|
||||
${translate('profile.profile5')}
|
||||
</p>
|
||||
<p>
|
||||
${this.profileDataVisiting.bio || translate('profile.profile15')}
|
||||
</p>
|
||||
<p style="font-weight:bold;color:#03a9f4;font-size:17px">
|
||||
${translate('profile.profile6')}
|
||||
</p>
|
||||
${Object.keys(this.profileDataVisiting.wallets).map((key, i) => {
|
||||
return html`
|
||||
<p>
|
||||
<span style="font-weight:bold;text-transform: uppercase">${key}</span>:
|
||||
<span>${this.profileDataVisiting.wallets[key] || translate('profile.profile15')}</span>
|
||||
</p>
|
||||
`
|
||||
})}
|
||||
`
|
||||
}
|
||||
</div>
|
||||
<div>
|
||||
<span class="paybutton">
|
||||
<mwc-button class="green" @click=${() => this.openUserInfo()}>${translate('profile.profile14')}</mwc-button>
|
||||
</span>
|
||||
<span class="buttons">
|
||||
${this.nameVisiting === this.name ?
|
||||
html`
|
||||
<mwc-button @click=${() => this.openEdit()}>${translate('profile.profile3')}</mwc-button>
|
||||
` : ''
|
||||
}
|
||||
<mwc-button class="decline" @click=${() => {this.dialogOpenedProfile = false;}}>${translate('general.close')}</mwc-button>
|
||||
</span>
|
||||
</div>
|
||||
</paper-dialog>
|
||||
`: ''
|
||||
}
|
||||
`
|
||||
}
|
||||
|
||||
firstUpdated() {
|
||||
// ...
|
||||
}
|
||||
|
||||
getNodeUrl() {
|
||||
const myNode = store.getState().app.nodeConfig.knownNodes[store.getState().app.nodeConfig.node]
|
||||
return myNode.protocol + '://' + myNode.domain + ':' + myNode.port
|
||||
}
|
||||
|
||||
getMyNode() {
|
||||
return store.getState().app.nodeConfig.knownNodes[store.getState().app.nodeConfig.node]
|
||||
}
|
||||
|
||||
async getRawData(dataItem) {
|
||||
const url = `${this.nodeUrl}/arbitrary/${dataItem.service}/${dataItem.name}/${dataItem.identifier}`
|
||||
const res = await fetch(url)
|
||||
const data = await res.json()
|
||||
if (data.error) throw new Error('Cannot retrieve your data from qdn')
|
||||
return data
|
||||
}
|
||||
|
||||
async getMyFollowedNames() {
|
||||
let myFollowedNames = []
|
||||
|
||||
try {
|
||||
myFollowedNames = await parentEpml.request('apiCall', {
|
||||
url: `/lists/followedNames?apiKey=${this.myNode.apiKey}`
|
||||
})
|
||||
} catch (error) { }
|
||||
|
||||
return myFollowedNames
|
||||
}
|
||||
|
||||
async followNames(names) {
|
||||
let namesJsonString = JSON.stringify({ items: names })
|
||||
|
||||
return await parentEpml.request('apiCall', {
|
||||
url: `/lists/followedNames?apiKey=${this.myNode.apiKey}`,
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: `${namesJsonString}`
|
||||
})
|
||||
}
|
||||
|
||||
async setValues(response, resource) {
|
||||
if (response) {
|
||||
let data = { ...response }
|
||||
let customData = {}
|
||||
|
||||
for (const key of Object.keys(data.customData || {})) {
|
||||
if (key.includes('-private')) {
|
||||
try {
|
||||
const decryptedData = decryptGroupData(data.customData[key])
|
||||
|
||||
if (decryptedData && !decryptedData.error) {
|
||||
const decryptedDataToBase64 =
|
||||
uint8ArrayToObject(decryptedData)
|
||||
if (decryptedDataToBase64 && !decryptedDataToBase64.error) {
|
||||
customData[key] = decryptedDataToBase64
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log({ error })
|
||||
}
|
||||
} else {
|
||||
customData[key] = data.customData[key]
|
||||
}
|
||||
}
|
||||
|
||||
this.profileData = {
|
||||
...response,
|
||||
customData
|
||||
}
|
||||
|
||||
store.dispatch(setProfileData(this.profileData))
|
||||
}
|
||||
}
|
||||
|
||||
async getVisitingProfile(name) {
|
||||
try {
|
||||
this.isLoadingVisitingProfile = true
|
||||
this.nameVisiting = name
|
||||
this.checkIfInFriendsList(this.nameVisiting)
|
||||
const url = `${this.nodeUrl}/arbitrary/resources/search?service=DOCUMENT&identifier=qortal_profile&mode=ALL&name=${name}&prefix=true&exactmatchnames=true&excludeblocked=true&limit=20`
|
||||
const res = await fetch(url)
|
||||
let data = ''
|
||||
|
||||
try {
|
||||
data = await res.json();
|
||||
if (Array.isArray(data)) {
|
||||
data = data.filter(
|
||||
(item) => item.identifier === 'qortal_profile'
|
||||
)
|
||||
|
||||
if (data.length > 0) {
|
||||
this.resourceExistsVisiting = true
|
||||
const dataItem = data[0]
|
||||
|
||||
try {
|
||||
const response = await this.getRawData(dataItem)
|
||||
|
||||
if (response.wallets) {
|
||||
this.profileDataVisiting = response
|
||||
} else {
|
||||
// this.error = 'Cannot get saved user settings'
|
||||
}
|
||||
} catch (error) {
|
||||
console.log({ error })
|
||||
}
|
||||
} else {
|
||||
this.resourceExistsVisiting = false
|
||||
}
|
||||
} else {
|
||||
// this.error = 'Unable to perform query'
|
||||
}
|
||||
} catch (error) {
|
||||
console.log({ error })
|
||||
data = {
|
||||
error: 'No resource found'
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log({ error })
|
||||
} finally {
|
||||
this.isLoadingVisitingProfile = false
|
||||
}
|
||||
}
|
||||
|
||||
async getProfile() {
|
||||
try {
|
||||
this.error = ''
|
||||
this.fee = await this.getArbitraryFee()
|
||||
this.hasAttemptedToFetchResource = true
|
||||
let resource
|
||||
let nameObject
|
||||
|
||||
try {
|
||||
nameObject = store.getState().app.accountInfo.names[0]
|
||||
} catch (error) { }
|
||||
|
||||
if (!nameObject) {
|
||||
this.name = null
|
||||
this.error = 'no name'
|
||||
throw new Error('no name')
|
||||
}
|
||||
|
||||
this.hasName = true
|
||||
const name = nameObject.name
|
||||
this.name = name
|
||||
const url = `${this.nodeUrl}/arbitrary/resources/search?service=DOCUMENT&mode=ALL&identifier=qortal_profile&name=${name}&prefix=true&exactmatchnames=true&excludeblocked=true&limit=20`
|
||||
const res = await fetch(url)
|
||||
let data = ''
|
||||
|
||||
try {
|
||||
data = await res.json()
|
||||
|
||||
if (Array.isArray(data)) {
|
||||
data = data.filter(
|
||||
(item) => item.identifier === 'qortal_profile'
|
||||
)
|
||||
|
||||
if (data.length > 0) {
|
||||
this.resourceExists = true
|
||||
const dataItem = data[0]
|
||||
|
||||
try {
|
||||
const response = await this.getRawData(dataItem)
|
||||
|
||||
if (response.wallets) {
|
||||
await this.setValues(response, dataItem)
|
||||
} else {
|
||||
this.error = 'Cannot get saved user settings'
|
||||
}
|
||||
} catch (error) {
|
||||
this.error = 'Cannot get saved user settings'
|
||||
}
|
||||
} else {
|
||||
this.resourceExists = false
|
||||
}
|
||||
} else {
|
||||
this.error = 'Unable to perform query'
|
||||
}
|
||||
} catch (error) {
|
||||
data = {
|
||||
error: 'No resource found'
|
||||
}
|
||||
}
|
||||
|
||||
if (resource) {
|
||||
this.hasRetrievedResource = true
|
||||
}
|
||||
} catch (error) {
|
||||
console.log({ error })
|
||||
}
|
||||
}
|
||||
|
||||
stateChanged(state) {
|
||||
if (state.app.nodeStatus && state.app.nodeStatus.syncPercent !== this.syncPercentage) {
|
||||
this.syncPercentage = state.app.nodeStatus.syncPercent
|
||||
|
||||
if (!this.hasAttemptedToFetchResource && state.app.nodeStatus.syncPercent === 100) {
|
||||
this.getProfile()
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
state.app.accountInfo && state.app.accountInfo.names.length &&
|
||||
state.app.nodeStatus && state.app.nodeStatus.syncPercent === 100 &&
|
||||
this.hasName === false && this.hasAttemptedToFetchResource &&
|
||||
state.app.accountInfo && state.app.accountInfo.names &&
|
||||
state.app.accountInfo.names.length > 0
|
||||
) {
|
||||
this.getProfile()
|
||||
}
|
||||
}
|
||||
|
||||
async getArbitraryFee() {
|
||||
const timestamp = Date.now()
|
||||
const url = `${this.nodeUrl}/transactions/unitfee?txType=ARBITRARY×tamp=${timestamp}`
|
||||
const response = await fetch(url)
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Error when fetching arbitrary fee')
|
||||
}
|
||||
|
||||
const data = await response.json()
|
||||
const arbitraryFee = (Number(data) / 1e8).toFixed(8)
|
||||
|
||||
return {
|
||||
timestamp,
|
||||
fee: Number(data),
|
||||
feeToShow: arbitraryFee
|
||||
}
|
||||
}
|
||||
|
||||
async saveToQdn(data) {
|
||||
try {
|
||||
this.isSaving = true
|
||||
|
||||
if (this.resourceExists === true && this.error) throw new Error('Unable to save')
|
||||
|
||||
const nameObject = store.getState().app.accountInfo.names[0]
|
||||
|
||||
if (!nameObject) throw new Error('no name')
|
||||
|
||||
const arbitraryFeeData = await modalHelper.getArbitraryFee()
|
||||
const res = await modalHelper.showModalAndWaitPublish({
|
||||
feeAmount: arbitraryFeeData.feeToShow,
|
||||
})
|
||||
|
||||
if (res.action !== 'accept') throw new Error('User declined publish')
|
||||
|
||||
const name = nameObject.name
|
||||
const identifer = 'qortal_profile'
|
||||
const filename = 'qortal_profile.json'
|
||||
const selectedAddress = store.getState().app.selectedAddress
|
||||
const getArbitraryFee = await this.getArbitraryFee()
|
||||
const feeAmount = getArbitraryFee.fee
|
||||
|
||||
let newObject = structuredClone(data)
|
||||
|
||||
for (const key of Object.keys(newObject.customData || {})) {
|
||||
if (key.includes('-private')) {
|
||||
const dataKey = newObject.customData[key]
|
||||
let isBase64 = false
|
||||
|
||||
try {
|
||||
const decodedString = atob(dataKey)
|
||||
isBase64 = decodedString.includes('qortalGroupEncryptedData')
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
}
|
||||
|
||||
if (isBase64) {
|
||||
newObject['customData'][key] = newObject.customData[key]
|
||||
} else {
|
||||
const toBase64 = await objectToBase64(newObject.customData[key])
|
||||
|
||||
newObject['customData'][key] = encryptDataGroup({
|
||||
data64: toBase64,
|
||||
publicKeys: []
|
||||
})
|
||||
}
|
||||
} else {
|
||||
newObject['customData'][key] = newObject.customData[key]
|
||||
}
|
||||
}
|
||||
|
||||
const newObjectToBase64 = await objectToBase64(newObject)
|
||||
const worker = new WebWorker2()
|
||||
|
||||
try {
|
||||
const resPublish = await publishData({
|
||||
registeredName: encodeURIComponent(name),
|
||||
file: newObjectToBase64,
|
||||
service: 'DOCUMENT',
|
||||
identifier: encodeURIComponent(identifer),
|
||||
parentEpml: parentEpml,
|
||||
uploadType: 'file',
|
||||
selectedAddress: selectedAddress,
|
||||
worker: worker,
|
||||
isBase64: true,
|
||||
filename: filename,
|
||||
apiVersion: 2,
|
||||
withFee: true,
|
||||
feeAmount: feeAmount
|
||||
})
|
||||
|
||||
this.resourceExists = true
|
||||
this.profileData = data
|
||||
store.dispatch(setProfileData(data))
|
||||
parentEpml.request('showSnackBar', get('profile.profile22'))
|
||||
|
||||
worker.terminate()
|
||||
} catch (error) {
|
||||
worker.terminate()
|
||||
}
|
||||
} catch (error) {
|
||||
console.log({ error })
|
||||
throw new Error(error.message)
|
||||
} finally {
|
||||
this.isSaving = false
|
||||
}
|
||||
}
|
||||
|
||||
sendBackEvent(detail) {
|
||||
let iframes
|
||||
|
||||
const mainApp = document.getElementById('main-app')
|
||||
|
||||
if (mainApp && mainApp.shadowRoot) {
|
||||
const appView = mainApp.shadowRoot.querySelector('app-view')
|
||||
if (appView && appView.shadowRoot) {
|
||||
const showPlugin = appView.shadowRoot.querySelector('show-plugin')
|
||||
if (showPlugin && showPlugin.shadowRoot) {
|
||||
iframes = showPlugin.shadowRoot.querySelectorAll('iframe')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
iframes.forEach((iframe) => {
|
||||
const iframeWindow = iframe.contentWindow;
|
||||
const customEvent = new CustomEvent('qortal-request-set-profile-data-response', { detail: detail })
|
||||
|
||||
iframeWindow.dispatchEvent(customEvent)
|
||||
})
|
||||
}
|
||||
|
||||
async _handleQortalRequestSetData(event) {
|
||||
const detail = event.detail
|
||||
|
||||
try {
|
||||
if (!detail.property || !detail.payload) throw new Error('not saved')
|
||||
if (!this.profileData && (this.resourceExists || this.resourceExists === undefined)) throw new Error('unable to fetch the users profile data')
|
||||
|
||||
this.isOpenProfileModalUpdate = true
|
||||
|
||||
this.editContent = {...(this.profileData || {}),}
|
||||
|
||||
if (detail.payload.customData) {
|
||||
this.qortalRequestCustomData = detail
|
||||
}
|
||||
|
||||
// Wait for response event
|
||||
const response = await new Promise((resolve, reject) => {
|
||||
function handleResponseEvent(event) {
|
||||
// Handle the data from the event, if any
|
||||
const responseData = event.detail
|
||||
|
||||
// Clean up by removing the event listener once we've received the response
|
||||
window.removeEventListener('send-back-event', handleResponseEvent)
|
||||
|
||||
if (responseData.response === 'saved') {
|
||||
resolve(responseData)
|
||||
} else {
|
||||
reject(new Error(responseData.error))
|
||||
}
|
||||
}
|
||||
|
||||
// Set up an event listener to wait for the response
|
||||
window.addEventListener('send-back-event', handleResponseEvent)
|
||||
})
|
||||
|
||||
this.sendBackEvent({
|
||||
response: response.response,
|
||||
uniqueId: detail.uniqueId
|
||||
})
|
||||
} catch (error) {
|
||||
this.sendBackEvent({
|
||||
response: 'error',
|
||||
uniqueId: detail.uniqueId
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
_handleOpenVisiting(event) {
|
||||
try {
|
||||
const name = event.detail
|
||||
this.getVisitingProfile(name)
|
||||
this.dialogOpenedProfile = true
|
||||
} catch (error) { }
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback()
|
||||
window.addEventListener('qortal-request-set-profile-data', this._handleQortalRequestSetData)
|
||||
window.addEventListener('open-visiting-profile', this._handleOpenVisiting)
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
window.removeEventListener('qortal-request-set-profile-data', this._handleQortalRequestSetData)
|
||||
window.removeEventListener('open-visiting-profile', this._handleOpenVisiting)
|
||||
super.disconnectedCallback()
|
||||
}
|
||||
|
||||
onClose(isSuccess) {
|
||||
this.isOpenProfileModalUpdate = false
|
||||
this.editContent = null
|
||||
|
||||
if (this.qortalRequestCustomData) {
|
||||
// Create and dispatch custom event
|
||||
const customEvent = new CustomEvent('send-back-event', {
|
||||
detail: {
|
||||
response: isSuccess ? 'saved' : 'not saved'
|
||||
}
|
||||
})
|
||||
|
||||
window.dispatchEvent(customEvent)
|
||||
|
||||
this.qortalRequestCustomData = null
|
||||
}
|
||||
}
|
||||
|
||||
avatarFullImage() {
|
||||
this.imageUrl = `${this.nodeUrl}/arbitrary/THUMBNAIL/${this.nameVisiting}/qortal_avatar?async=true&apiKey=${this.myNode.apiKey}`
|
||||
|
||||
return html`<img class="round-fullinfo" src="${this.imageUrl}" onerror="this.src='/img/incognito.png';" />`
|
||||
}
|
||||
|
||||
openUserInfo() {
|
||||
const infoDialog = document
|
||||
.getElementById('main-app')
|
||||
.shadowRoot.querySelector('app-view')
|
||||
.shadowRoot.querySelector('user-info-view')
|
||||
infoDialog.openUserInfo(this.nameVisiting)
|
||||
}
|
||||
|
||||
openEdit() {
|
||||
this.isOpenProfileModalUpdate = !this.isOpenProfileModalUpdate
|
||||
}
|
||||
|
||||
onCloseVisitingProfile() {
|
||||
this.profileDataVisiting = null
|
||||
this.nameVisiting = ''
|
||||
this.imageUrl = ''
|
||||
this.resourceExistsVisiting = undefined
|
||||
this.isFriend = undefined
|
||||
}
|
||||
|
||||
updated(changedProperties) {
|
||||
if (changedProperties && changedProperties.has('dialogOpenedProfile') && this.dialogOpenedProfile === false) {
|
||||
const prevVal = changedProperties.get('dialogOpenedProfile')
|
||||
if (prevVal === true) this.onCloseVisitingProfile()
|
||||
}
|
||||
}
|
||||
|
||||
async getUserAddress() {
|
||||
try {
|
||||
const url = `${this.nodeUrl}/names/${this.nameVisiting}`
|
||||
const res = await fetch(url)
|
||||
const result = await res.json()
|
||||
|
||||
if (result.error === 401) {
|
||||
return ''
|
||||
} else {
|
||||
return result.owner
|
||||
}
|
||||
} catch (error) {
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
checkIfInFriendsList(name) {
|
||||
try {
|
||||
this.isFriend = undefined
|
||||
const friendList = JSON.parse(localStorage.getItem('friends-my-friend-list') || '[]')
|
||||
const findIndex = friendList.findIndex((friend) => friend.name === name)
|
||||
this.isFriend = findIndex !== -1
|
||||
} catch (error) { }
|
||||
}
|
||||
|
||||
// Standard functions
|
||||
getApiKey() {
|
||||
const coreNode = store.getState().app.nodeConfig.knownNodes[store.getState().app.nodeConfig.node]
|
||||
return coreNode.apiKey
|
||||
}
|
||||
|
||||
isEmptyArray(arr) {
|
||||
if (!arr) { return true }
|
||||
return arr.length === 0
|
||||
}
|
||||
|
||||
round(number) {
|
||||
return (Math.round(parseFloat(number) * 1e8) / 1e8).toFixed(8)
|
||||
}
|
||||
}
|
||||
|
||||
window.customElements.define('profile-qdn', ProfileQdn)
|
@ -1,560 +0,0 @@
|
||||
import { html, LitElement } from 'lit'
|
||||
import { connect } from 'pwa-helpers'
|
||||
import { store } from '../../store'
|
||||
import { parentEpml } from '../show-plugin'
|
||||
import { setNewTab } from '../../redux/app/app-actions'
|
||||
import { publishData } from '../../../../plugins/plugins/utils/classes'
|
||||
import {
|
||||
decryptGroupData,
|
||||
encryptDataGroup,
|
||||
objectToBase64,
|
||||
uint8ArrayToObject
|
||||
} from '../../../../plugins/plugins/core/components/qdn-action-encryption'
|
||||
import { translate } from '../../../translate'
|
||||
import { saveSettingsQdnStyles } from '../../styles/core-css'
|
||||
import ShortUniqueId from 'short-unique-id'
|
||||
import WebWorker from '../WebWorkerFile.js'
|
||||
import './friends-side-panel'
|
||||
import '../notification-view/popover'
|
||||
import '@material/mwc-icon'
|
||||
import '@polymer/paper-spinner/paper-spinner-lite.js'
|
||||
import '@vaadin/tooltip'
|
||||
|
||||
class SaveSettingsQdn extends connect(store)(LitElement) {
|
||||
static get properties() {
|
||||
return {
|
||||
isOpen: { type: Boolean },
|
||||
syncPercentage: { type: Number },
|
||||
settingsRawData: { type: Object },
|
||||
valuesToBeSavedOnQdn: { type: Object },
|
||||
resourceExists: { type: Boolean },
|
||||
isSaving: { type: Boolean },
|
||||
fee: { type: Object },
|
||||
hasName: { type: Boolean },
|
||||
error: { type: String },
|
||||
name: { type: String }
|
||||
}
|
||||
}
|
||||
|
||||
static get styles() {
|
||||
return [saveSettingsQdnStyles]
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
this.isOpen = false
|
||||
this.getGeneralSettingsQdn = this.getGeneralSettingsQdn.bind(this)
|
||||
this._updateTempSettingsData = this._updateTempSettingsData.bind(this)
|
||||
this.setValues = this.setValues.bind(this)
|
||||
this.saveToQdn = this.saveToQdn.bind(this)
|
||||
this.syncPercentage = 0
|
||||
this.hasRetrievedResource = false
|
||||
this.hasAttemptedToFetchResource = false
|
||||
this.resourceExists = undefined
|
||||
this.settingsRawData = null
|
||||
this.nodeUrl = this.getNodeUrl()
|
||||
this.myNode = this.getMyNode()
|
||||
this.valuesToBeSavedOnQdn = {}
|
||||
this.isSaving = false
|
||||
this.fee = null
|
||||
this.hasName = false
|
||||
this.error = ''
|
||||
this.uid = new ShortUniqueId()
|
||||
this.name = undefined
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
${this.isSaving || (!this.error && this.resourceExists === undefined) ?
|
||||
html`
|
||||
<paper-spinner-lite active style="display: block; margin: 0 auto;"></paper-spinner-lite>
|
||||
` : !this.name ? html`
|
||||
<mwc-icon
|
||||
id="profile-icon"
|
||||
class=${'notActive'}
|
||||
@click=${() => {
|
||||
const target = this.shadowRoot.getElementById('popover-notification');
|
||||
const popover = this.shadowRoot.querySelector('popover-component');
|
||||
if (popover) {
|
||||
popover.openPopover(target);
|
||||
}
|
||||
}}
|
||||
style="user-select:none;cursor:pointer"
|
||||
>
|
||||
save
|
||||
</mwc-icon>
|
||||
<vaadin-tooltip
|
||||
for="profile-icon"
|
||||
position="bottom"
|
||||
hover-delay=${300}
|
||||
hide-delay=${1}
|
||||
text=${translate('profile.profile20')}
|
||||
>
|
||||
</vaadin-tooltip>
|
||||
<popover-component for="profile-icon" message="">
|
||||
<div style="margin-bottom:20px">
|
||||
<p style="margin:10px 0px; font-size:16px">
|
||||
${translate('profile.profile1')}
|
||||
</p>
|
||||
</div>
|
||||
<div style="display:flex;justify-content:center;gap:10px">
|
||||
<div
|
||||
class="accept-button"
|
||||
@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: fals
|
||||
},
|
||||
openExisting: true
|
||||
})
|
||||
);
|
||||
const popover = this.shadowRoot.querySelector('popover-component');
|
||||
if (popover) {
|
||||
popover.closePopover();
|
||||
}
|
||||
}}"
|
||||
>
|
||||
${translate('profile.profile2')}
|
||||
</div>
|
||||
</div>
|
||||
</popover-component>
|
||||
` : html`
|
||||
<mwc-icon
|
||||
id="save-icon"
|
||||
class=${Object.values(this.valuesToBeSavedOnQdn).length > 0 || this.resourceExists === false? 'active' : 'notActive'}
|
||||
@click=${() => {
|
||||
if (Object.values(this.valuesToBeSavedOnQdn).length > 0 || this.resourceExists === false) {
|
||||
if (!this.fee) return;
|
||||
const target = this.shadowRoot.getElementById('popover-notification');
|
||||
const popover = this.shadowRoot.querySelector('popover-component');
|
||||
if (popover) {
|
||||
popover.openPopover(target);
|
||||
}
|
||||
}
|
||||
}}
|
||||
style="user-select:none"
|
||||
>
|
||||
save
|
||||
</mwc-icon>
|
||||
<vaadin-tooltip
|
||||
for="save-icon"
|
||||
position="bottom"
|
||||
hover-delay=${300}
|
||||
hide-delay=${1}
|
||||
text=${
|
||||
this.error ? translate('save.saving1')
|
||||
: Object.values(this.valuesToBeSavedOnQdn).length > 0 || this.resourceExists === false
|
||||
? translate('save.saving3') : translate('save.saving2')
|
||||
}
|
||||
></vaadin-tooltip>
|
||||
<popover-component for="save-icon" message="">
|
||||
<div style="margin-bottom:20px">
|
||||
<p style="margin:10px 0px; font-size:16px">
|
||||
${translate('walletpage.wchange12')}: ${this.fee ? this.fee.feeToShow : ''}
|
||||
</p>
|
||||
</div>
|
||||
<div style="display:flex;justify-content:space-between;gap:10px">
|
||||
<div
|
||||
class="undo-button"
|
||||
@click="${() => {
|
||||
localStorage.setItem('temp-settings-data', JSON.stringify({}));
|
||||
this.valuesToBeSavedOnQdn = {};
|
||||
const popover = this.shadowRoot.querySelector('popover-component');
|
||||
if (popover) {
|
||||
popover.closePopover();
|
||||
}
|
||||
this.getGeneralSettingsQdn();
|
||||
}}"
|
||||
>
|
||||
${translate('save.saving4')}
|
||||
</div>
|
||||
<div
|
||||
class="accept-button"
|
||||
@click="${() => {
|
||||
this.saveToQdn();
|
||||
const popover = this.shadowRoot.querySelector('popover-component');
|
||||
if (popover) {
|
||||
popover.closePopover();
|
||||
}
|
||||
}}"
|
||||
>
|
||||
${translate('browserpage.bchange28')}
|
||||
</div>
|
||||
</div>
|
||||
</popover-component>
|
||||
`
|
||||
}
|
||||
`
|
||||
}
|
||||
|
||||
firstUpdated() {
|
||||
// ...
|
||||
}
|
||||
|
||||
getNodeUrl() {
|
||||
const myNode = store.getState().app.nodeConfig.knownNodes[store.getState().app.nodeConfig.node]
|
||||
return myNode.protocol + '://' + myNode.domain + ':' + myNode.port
|
||||
}
|
||||
|
||||
getMyNode() {
|
||||
return store.getState().app.nodeConfig.knownNodes[store.getState().app.nodeConfig.node]
|
||||
}
|
||||
|
||||
async getRawData(dataItem) {
|
||||
const url = `${this.nodeUrl}/arbitrary/${dataItem.service}/${dataItem.name}/${dataItem.identifier}?encoding=base64`
|
||||
const res = await fetch(url)
|
||||
const data = await res.text()
|
||||
if (data.error) throw new Error('Cannot retrieve your data from qdn')
|
||||
const decryptedData = decryptGroupData(data)
|
||||
return uint8ArrayToObject(decryptedData)
|
||||
}
|
||||
|
||||
async getMyFollowedNames() {
|
||||
let myFollowedNames = []
|
||||
|
||||
try {
|
||||
myFollowedNames = await parentEpml.request('apiCall', {
|
||||
url: `/lists/followedNames?apiKey=${this.myNode.apiKey}`
|
||||
})
|
||||
} catch (error) { }
|
||||
|
||||
return myFollowedNames
|
||||
}
|
||||
|
||||
async followNames(names) {
|
||||
let namesJsonString = JSON.stringify({ "items": names })
|
||||
|
||||
return await parentEpml.request('apiCall', {
|
||||
url: `/lists/followedNames?apiKey=${this.myNode.apiKey}`,
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: `${namesJsonString}`
|
||||
})
|
||||
}
|
||||
|
||||
async setValues(response, resource) {
|
||||
this.settingsRawData = response
|
||||
const rawDataTimestamp = resource.updated
|
||||
|
||||
const tempSettingsData = JSON.parse(
|
||||
localStorage.getItem('temp-settings-data') || '{}'
|
||||
)
|
||||
|
||||
const userLists = response.userLists || []
|
||||
const friendsFeed = response.friendsFeed
|
||||
const myMenuPlugs = response.myMenuPlugs
|
||||
|
||||
this.valuesToBeSavedOnQdn = {}
|
||||
|
||||
if (userLists.length > 0 && (!tempSettingsData.userLists || (tempSettingsData.userLists && tempSettingsData.userLists.timestamp < rawDataTimestamp))) {
|
||||
const friendList = userLists[0]
|
||||
const copyPayload = [...friendList]
|
||||
const onlyNames = copyPayload.map((item) => item.name)
|
||||
const followedList = await this.getMyFollowedNames()
|
||||
const namesNotInFollowedList = onlyNames.filter(name => !followedList.includes(name))
|
||||
|
||||
if (namesNotInFollowedList.length > 0) {
|
||||
await this.followNames(namesNotInFollowedList)
|
||||
}
|
||||
|
||||
localStorage.setItem('friends-my-friend-list', JSON.stringify(friendList))
|
||||
|
||||
this.dispatchEvent(
|
||||
new CustomEvent('friends-my-friend-list-event', {
|
||||
bubbles: true,
|
||||
composed: true,
|
||||
detail: copyPayload
|
||||
})
|
||||
)
|
||||
} else if (tempSettingsData.userLists && tempSettingsData.userLists.timestamp > rawDataTimestamp) {
|
||||
this.valuesToBeSavedOnQdn = {
|
||||
...this.valuesToBeSavedOnQdn,
|
||||
userLists: {
|
||||
data: tempSettingsData.userLists.data
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (friendsFeed && (!tempSettingsData.friendsFeed || (tempSettingsData.friendsFeed && tempSettingsData.friendsFeed.timestamp < rawDataTimestamp))) {
|
||||
const copyPayload = [...friendsFeed]
|
||||
|
||||
localStorage.setItem('friends-my-selected-feeds', JSON.stringify(friendsFeed))
|
||||
|
||||
this.dispatchEvent(
|
||||
new CustomEvent('friends-my-selected-feeds-event', {
|
||||
bubbles: true,
|
||||
composed: true,
|
||||
detail: copyPayload
|
||||
})
|
||||
)
|
||||
} else if (tempSettingsData.friendsFeed && tempSettingsData.friendsFeed.timestamp > rawDataTimestamp) {
|
||||
this.valuesToBeSavedOnQdn = {
|
||||
...this.valuesToBeSavedOnQdn,
|
||||
friendsFeed: {
|
||||
data: tempSettingsData.friendsFeed.data
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (myMenuPlugs && (!tempSettingsData.myMenuPlugs || (tempSettingsData.myMenuPlugs && tempSettingsData.myMenuPlugs.timestamp < rawDataTimestamp))) {
|
||||
if (Array.isArray(myMenuPlugs)) {
|
||||
const copyPayload = [...myMenuPlugs]
|
||||
|
||||
localStorage.setItem('myMenuPlugs', JSON.stringify(myMenuPlugs))
|
||||
|
||||
this.dispatchEvent(
|
||||
new CustomEvent('myMenuPlugs-event', {
|
||||
bubbles: true,
|
||||
composed: true,
|
||||
detail: copyPayload
|
||||
})
|
||||
)
|
||||
}
|
||||
} else if (tempSettingsData.myMenuPlugs && tempSettingsData.myMenuPlugs.timestamp > rawDataTimestamp) {
|
||||
this.valuesToBeSavedOnQdn = {
|
||||
...this.valuesToBeSavedOnQdn,
|
||||
myMenuPlugs: {
|
||||
data: tempSettingsData.myMenuPlugs.data
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async getGeneralSettingsQdn() {
|
||||
try {
|
||||
this.error = ""
|
||||
this.fee = await this.getArbitraryFee()
|
||||
this.hasAttemptedToFetchResource = true
|
||||
let resource
|
||||
let nameObject
|
||||
|
||||
try {
|
||||
nameObject = store.getState().app.accountInfo.names[0]
|
||||
} catch (error) { }
|
||||
|
||||
if (!nameObject) {
|
||||
this.name = null
|
||||
this.error = 'no name'
|
||||
throw new Error('no name')
|
||||
}
|
||||
|
||||
const name = nameObject.name
|
||||
this.name = name
|
||||
this.hasName = true
|
||||
this.error = ''
|
||||
const url = `${this.nodeUrl}/arbitrary/resources/search?service=DOCUMENT_PRIVATE&mode=ALL&identifier=qortal_general_settings&name=${name}&prefix=true&exactmatchnames=true&excludeblocked=true&limit=20`
|
||||
const res = await fetch(url)
|
||||
let data = ''
|
||||
|
||||
try {
|
||||
data = await res.json()
|
||||
|
||||
if (Array.isArray(data)) {
|
||||
data = data.filter(
|
||||
(item) => item.identifier === 'qortal_general_settings'
|
||||
)
|
||||
|
||||
if (data.length > 0) {
|
||||
this.resourceExists = true
|
||||
const dataItem = data[0]
|
||||
|
||||
try {
|
||||
const response = await this.getRawData(dataItem)
|
||||
|
||||
if (response.version) {
|
||||
await this.setValues(response, dataItem)
|
||||
} else {
|
||||
this.error = 'Cannot get saved user settings'
|
||||
}
|
||||
} catch (error) {
|
||||
console.log({ error })
|
||||
this.error = 'Cannot get saved user settings'
|
||||
}
|
||||
} else {
|
||||
this.resourceExists = false
|
||||
}
|
||||
} else {
|
||||
this.error = 'Unable to perform query'
|
||||
}
|
||||
} catch (error) {
|
||||
data = {error: 'No resource found'}
|
||||
}
|
||||
|
||||
if (resource) {
|
||||
this.hasRetrievedResource = true
|
||||
}
|
||||
} catch (error) {
|
||||
console.log({ error })
|
||||
}
|
||||
}
|
||||
|
||||
stateChanged(state) {
|
||||
if (state.app.nodeStatus && state.app.nodeStatus.syncPercent !== this.syncPercentage) {
|
||||
this.syncPercentage = state.app.nodeStatus.syncPercent
|
||||
|
||||
if (!this.hasAttemptedToFetchResource && state.app.nodeStatus.syncPercent === 100) {
|
||||
this.getGeneralSettingsQdn()
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
state.app.accountInfo && state.app.accountInfo.names.length && state.app.nodeStatus &&
|
||||
state.app.nodeStatus.syncPercent === 100 && this.hasName === false && this.hasAttemptedToFetchResource &&
|
||||
state.app.accountInfo && state.app.accountInfo.names && state.app.accountInfo.names.length > 0
|
||||
) {
|
||||
this.getGeneralSettingsQdn()
|
||||
}
|
||||
}
|
||||
|
||||
async getArbitraryFee() {
|
||||
const timestamp = Date.now()
|
||||
const url = `${this.nodeUrl}/transactions/unitfee?txType=ARBITRARY×tamp=${timestamp}`
|
||||
const response = await fetch(url)
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Error when fetching arbitrary fee')
|
||||
}
|
||||
|
||||
const data = await response.json()
|
||||
const arbitraryFee = (Number(data) / 1e8).toFixed(8)
|
||||
|
||||
return {
|
||||
timestamp,
|
||||
fee: Number(data),
|
||||
feeToShow: arbitraryFee
|
||||
}
|
||||
}
|
||||
|
||||
async saveToQdn() {
|
||||
try {
|
||||
this.isSaving = true
|
||||
|
||||
if (this.resourceExists === true && this.error) throw new Error('Unable to save')
|
||||
|
||||
const nameObject = store.getState().app.accountInfo.names[0]
|
||||
|
||||
if (!nameObject) throw new Error('no name')
|
||||
|
||||
const name = nameObject.name
|
||||
const identifer = 'qortal_general_settings'
|
||||
const filename = 'qortal_general_settings.json'
|
||||
const selectedAddress = store.getState().app.selectedAddress
|
||||
const getArbitraryFee = await this.getArbitraryFee()
|
||||
const feeAmount = getArbitraryFee.fee
|
||||
const friendsList = JSON.parse(localStorage.getItem('friends-my-friend-list') || '[]')
|
||||
const friendsFeed = JSON.parse(localStorage.getItem('friends-my-selected-feeds') || '[]')
|
||||
const myMenuPlugs = JSON.parse(localStorage.getItem('myMenuPlugs') || '[]')
|
||||
|
||||
let newObject
|
||||
|
||||
if (this.resourceExists === false) {
|
||||
newObject = {
|
||||
version: 1,
|
||||
userLists: [friendsList],
|
||||
friendsFeed,
|
||||
myMenuPlugs
|
||||
}
|
||||
} else if (this.settingsRawData) {
|
||||
const tempSettingsData = JSON.parse(localStorage.getItem('temp-settings-data') || '{}')
|
||||
|
||||
newObject = {...this.settingsRawData}
|
||||
|
||||
for (const key in tempSettingsData) {
|
||||
if (tempSettingsData[key].hasOwnProperty('data')) {
|
||||
if (key === 'userLists' && !Array.isArray(tempSettingsData[key].data)) {
|
||||
continue
|
||||
}
|
||||
|
||||
if (key === 'friendsFeed' && !Array.isArray(tempSettingsData[key].data)) {
|
||||
continue
|
||||
}
|
||||
|
||||
if (key === 'myMenuPlugs' && !Array.isArray(tempSettingsData[key].data)) {
|
||||
continue
|
||||
}
|
||||
|
||||
newObject[key] = tempSettingsData[key].data
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const newObjectToBase64 = await objectToBase64(newObject)
|
||||
const encryptedData = encryptDataGroup({data64: newObjectToBase64, publicKeys: []})
|
||||
const worker = new WebWorker()
|
||||
|
||||
try {
|
||||
const resPublish = await publishData({
|
||||
registeredName: encodeURIComponent(name),
|
||||
file: encryptedData,
|
||||
service: 'DOCUMENT_PRIVATE',
|
||||
identifier: encodeURIComponent(identifer),
|
||||
parentEpml: parentEpml,
|
||||
uploadType: 'file',
|
||||
selectedAddress: selectedAddress,
|
||||
worker: worker,
|
||||
isBase64: true,
|
||||
filename: filename,
|
||||
apiVersion: 2,
|
||||
withFee: true,
|
||||
feeAmount: feeAmount
|
||||
})
|
||||
|
||||
this.resourceExists = true
|
||||
await this.setValues(newObject, { updated: Date.now() })
|
||||
localStorage.setItem('temp-settings-data', JSON.stringify({}))
|
||||
this.valuesToBeSavedOnQdn = {}
|
||||
worker.terminate()
|
||||
} catch (error) {
|
||||
worker.terminate()
|
||||
}
|
||||
} catch (error) {
|
||||
console.log({ error })
|
||||
} finally {
|
||||
this.isSaving = false
|
||||
}
|
||||
}
|
||||
|
||||
_updateTempSettingsData() {
|
||||
this.valuesToBeSavedOnQdn = JSON.parse(localStorage.getItem('temp-settings-data') || '{}')
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback()
|
||||
window.addEventListener('temp-settings-data-event', this._updateTempSettingsData)
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
window.removeEventListener('temp-settings-data-event', this._updateTempSettingsData)
|
||||
super.disconnectedCallback()
|
||||
}
|
||||
|
||||
// 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('save-settings-qdn', SaveSettingsQdn)
|
@ -1,88 +0,0 @@
|
||||
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())
|
||||
})
|
||||
|
||||
const checkLanguage = localStorage.getItem('qortalLanguage')
|
||||
|
||||
if (checkLanguage === null || checkLanguage.length === 0) {
|
||||
localStorage.setItem('qortalLanguage', 'us')
|
||||
use('us')
|
||||
} else {
|
||||
use(checkLanguage)
|
||||
}
|
||||
|
||||
class LanguageSelector extends LitElement {
|
||||
static get properties() {
|
||||
return {
|
||||
theme: { type: String, reflect: true }
|
||||
}
|
||||
}
|
||||
|
||||
static get styles() {
|
||||
return [languageSelectorStyles]
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
this.theme = localStorage.getItem('qortalTheme') ? localStorage.getItem('qortalTheme') : 'light'
|
||||
}
|
||||
|
||||
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>
|
||||
`
|
||||
}
|
||||
|
||||
firstUpdated() {
|
||||
const myElement = this.shadowRoot.getElementById('languageSelect')
|
||||
|
||||
myElement.addEventListener('change', () => {
|
||||
this.selectElement()
|
||||
})
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
window.customElements.define('language-selector', LanguageSelector)
|
@ -1,646 +0,0 @@
|
||||
import { html, LitElement } from 'lit'
|
||||
import { connect } from 'pwa-helpers'
|
||||
import { store } from '../../store'
|
||||
import { createWallet } from '../../../../crypto/api/createWallet'
|
||||
import { doLogin, doLogout, doSelectAddress } from '../../redux/app/app-actions'
|
||||
import { doStoreWallet } from '../../redux/user/user-actions'
|
||||
import { checkApiKey } from '../../apiKeyUtils'
|
||||
import { createAccountSectionStyles } from '../../styles/core-css'
|
||||
import FileSaver from 'file-saver'
|
||||
import ripple from '../../functional-components/loading-ripple'
|
||||
import snackbar from '../../functional-components/snackbar'
|
||||
import '../../functional-components/random-sentence-generator'
|
||||
import '@material/mwc-button'
|
||||
import '@material/mwc-checkbox'
|
||||
import '@material/mwc-dialog'
|
||||
import '@material/mwc-formfield'
|
||||
import '@material/mwc-icon'
|
||||
import '@material/mwc-textfield'
|
||||
import '@polymer/iron-pages'
|
||||
import '@polymer/paper-button/paper-button.js'
|
||||
import '@polymer/paper-input/paper-input-container.js'
|
||||
import '@polymer/paper-input/paper-input.js'
|
||||
import '@polymer/paper-tooltip/paper-tooltip.js'
|
||||
import '@vaadin/password-field/vaadin-password-field.js'
|
||||
import '@vaadin/text-field/vaadin-text-field.js'
|
||||
|
||||
// Multi language support
|
||||
import { get, translate } from '../../../translate'
|
||||
|
||||
let lastPassword = ''
|
||||
|
||||
class CreateAccountSection extends connect(store)(LitElement) {
|
||||
static get properties() {
|
||||
return {
|
||||
nextHidden: { type: Boolean, notify: true },
|
||||
nextDisabled: { 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 },
|
||||
selectedPage: { type: String },
|
||||
error: { type: Boolean },
|
||||
errorMessage: { type: String },
|
||||
nextButtonText: { type: String },
|
||||
saveAccount: { type: Boolean },
|
||||
createAccountLoading: { type: Boolean },
|
||||
showSeedphrase: { type: Boolean },
|
||||
_wallet: { type: Object },
|
||||
_pass: { type: String },
|
||||
_name: { type: String },
|
||||
isDownloadedBackup: { type: Boolean },
|
||||
nodeConfig: { type: Object },
|
||||
theme: { type: String, reflect: true }
|
||||
}
|
||||
}
|
||||
|
||||
static get styles() {
|
||||
return [createAccountSectionStyles]
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
this.nextText = this.renderNextText()
|
||||
this.backText = this.renderBackText()
|
||||
this.nextDisabled = false
|
||||
this._pass = ''
|
||||
this._name = ''
|
||||
this.selectedPage = 'info'
|
||||
this.nextButtonText = this.renderNextText()
|
||||
this.saveAccount = true
|
||||
this.showSeedphrase = false
|
||||
this.isDownloadedBackup = true
|
||||
this.createAccountLoading = false
|
||||
const welcomeMessage = this.renderWelcomeText()
|
||||
this.welcomeMessage = welcomeMessage
|
||||
this.theme = localStorage.getItem('qortalTheme') ? localStorage.getItem('qortalTheme') : 'light'
|
||||
|
||||
this.pages = {
|
||||
info: {
|
||||
next: e => {
|
||||
this.error = false
|
||||
this.errorMessage = ''
|
||||
this.nextButtonText = this.renderCreateText()
|
||||
this.selectPage('password')
|
||||
this.updateNext()
|
||||
},
|
||||
back: () => {
|
||||
this.navigate('welcome')
|
||||
}
|
||||
},
|
||||
password: {
|
||||
next: e => {
|
||||
// Create account and login :)
|
||||
this.createAccountLoading = true
|
||||
|
||||
const nameInput = this.shadowRoot.getElementById('nameInput').value
|
||||
const password = this.shadowRoot.getElementById('password').value
|
||||
const rePassword = this.shadowRoot.getElementById('rePassword').value
|
||||
|
||||
if (password === '') {
|
||||
let snackbar1string = get("login.pleaseenter")
|
||||
|
||||
snackbar.add({
|
||||
labelText: `${snackbar1string}`,
|
||||
dismiss: true
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if (password != rePassword) {
|
||||
let snackbar2string = get("login.notmatch")
|
||||
|
||||
snackbar.add({
|
||||
labelText: `${snackbar2string}`,
|
||||
dismiss: true
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if (password.length < 8 && lastPassword !== password) {
|
||||
let snackbar3string = get("login.lessthen8")
|
||||
|
||||
snackbar.add({
|
||||
labelText: `${snackbar3string}`,
|
||||
dismiss: true
|
||||
})
|
||||
|
||||
lastPassword = password
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if (this.saveAccount === true && nameInput === '') {
|
||||
let snackbar4string = get("login.entername")
|
||||
|
||||
snackbar.add({
|
||||
labelText: `${snackbar4string}`,
|
||||
dismiss: true
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
this._name = nameInput
|
||||
this._pass = password
|
||||
|
||||
let seedObj = {}
|
||||
|
||||
const seedPhrase = this.shadowRoot.getElementById('randSentence').parsedString
|
||||
|
||||
seedObj = { seedPhrase: seedPhrase }
|
||||
|
||||
ripple.welcomeMessage = welcomeMessage
|
||||
|
||||
ripple.open({ x: e.clientX, y: e.clientY }).then(() => createWallet('phrase', seedObj, status => {
|
||||
ripple.loadingMessage = status
|
||||
})).then(wallet => {
|
||||
this._wallet = wallet
|
||||
return ripple.fade()
|
||||
}).then(() => {
|
||||
this.selectPage('backup')
|
||||
this.updateNext()
|
||||
}).catch(e => {
|
||||
snackbar.add({
|
||||
labelText: e,
|
||||
dismiss: true
|
||||
})
|
||||
|
||||
console.error('== Error == \n', e)
|
||||
|
||||
store.dispatch(doLogout())
|
||||
|
||||
ripple.close()
|
||||
})
|
||||
},
|
||||
back: () => {
|
||||
this.selectPage('info')
|
||||
this.updateNext()
|
||||
}
|
||||
},
|
||||
backup: {
|
||||
next: e => {
|
||||
if (!this.isDownloadedBackup) {
|
||||
let snackbar5string = get("login.downloaded")
|
||||
|
||||
snackbar.add({
|
||||
labelText: `${snackbar5string}`,
|
||||
dismiss: true
|
||||
})
|
||||
} else {
|
||||
if (this.saveAccount) {
|
||||
ripple.welcomeMessage = this.renderPrepareText()
|
||||
|
||||
ripple.open({ x: e.clientX, y: e.clientY}).then(() => {
|
||||
store.dispatch(doStoreWallet(this._wallet, this._pass, this._name, () => {
|
||||
ripple.loadingMessage = this.renderLoadingText()
|
||||
})).then(() => {
|
||||
store.dispatch(doLogin(this._wallet))
|
||||
store.dispatch(doSelectAddress(this._wallet.addresses[0]))
|
||||
checkApiKey(this.nodeConfig);
|
||||
this.cleanup()
|
||||
return ripple.fade()
|
||||
}).catch(err => {
|
||||
console.error(err)
|
||||
})
|
||||
}).catch(err => {
|
||||
console.error(err)
|
||||
})
|
||||
} else {
|
||||
store.dispatch(doLogin(this._wallet))
|
||||
store.dispatch(doSelectAddress(this._wallet.addresses[0]))
|
||||
|
||||
checkApiKey()
|
||||
|
||||
this.cleanup()
|
||||
}
|
||||
}
|
||||
},
|
||||
back: () => {
|
||||
this.navigate('welcome')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.pageIndexes = {
|
||||
info: 0,
|
||||
password: 1,
|
||||
backup: 2
|
||||
}
|
||||
|
||||
this.nextEnabled = false
|
||||
this.prevEnabled = false
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<style>
|
||||
div[hidden] {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.flex {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.flex.column {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.horizontal-center {
|
||||
margin: 0;
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
-ms-transform: translateX(-50%);
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
#createAccountSection {
|
||||
max-height: calc(var(--window-height) - 56px);
|
||||
max-width: 440px;
|
||||
max-height: calc(100% - 100px);
|
||||
padding: 0 12px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
#createAccountPages {
|
||||
flex-shrink: 1;
|
||||
text-align: left;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
#createAccountPages [page] {
|
||||
flex-shrink: 1;
|
||||
}
|
||||
|
||||
.section-content {
|
||||
padding: 0 24px;
|
||||
padding-bottom: 0;
|
||||
overflow: auto;
|
||||
flex-shrink: 1;
|
||||
max-height: calc(100vh - 296px);
|
||||
}
|
||||
|
||||
#download-area {
|
||||
border: 2px dashed #ccc;
|
||||
font-family: "Roboto", sans-serif;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
#trigger:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
mwc-checkbox::shadow .mdc-checkbox::after,
|
||||
mwc-checkbox::shadow .mdc-checkbox::before {
|
||||
background-color: var(--mdc-theme-primary)
|
||||
}
|
||||
|
||||
@media only screen and (max-width: ${getComputedStyle(document.body).getPropertyValue('--layout-breakpoint-tablet')}) {
|
||||
|
||||
/* Mobile */
|
||||
#createAccountSection {
|
||||
max-width: 100%;
|
||||
height: calc(var(--window-height) - 56px);
|
||||
}
|
||||
|
||||
#infoContent {
|
||||
height: auto;
|
||||
min-height: calc(var(--window-height) - 96px)
|
||||
}
|
||||
|
||||
#nav {
|
||||
flex-shrink: 0;
|
||||
padding-top: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
#infoContent p {
|
||||
text-align: justify;
|
||||
}
|
||||
|
||||
@keyframes fade {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
iron-pages .animated {
|
||||
animation-duration: 0.6s;
|
||||
animation-name: fade;
|
||||
}
|
||||
|
||||
paper-input {
|
||||
--paper-input-container-input-color: var(--mdc-theme-on-surface);
|
||||
}
|
||||
|
||||
paper-icon-button {
|
||||
--paper-icon-button-ink-color: var(--mdc-theme-primary);
|
||||
}
|
||||
|
||||
paper-button {
|
||||
color: var(--mdc-theme-primary);
|
||||
text-transform: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
</style>
|
||||
<div id="createAccountSection" class="flex column">
|
||||
<iron-pages selected="${this.selectedPage}" attr-for-selected="page" id="createAccountPages">
|
||||
<div page="info">
|
||||
<div id="infoContent" class="section-content">
|
||||
<br>
|
||||
<h3 style="text-align: center; margin-top: 0; color: var(--black); font-weight: 100; font-family: 'Roboto Mono', monospace;">${translate("login.createaccount")}</h3>
|
||||
<p style="color: var(--black);">
|
||||
${translate("login.createwelcome")}
|
||||
</p>
|
||||
<p style="color: var(--black); margin-bottom:0;">
|
||||
${translate("login.createa")} ‘
|
||||
<paper-button id="myseedshow" @click=${() => this.shadowRoot.querySelector('#mySeedDialog').show()}>${translate("login.seedphrase")}</paper-button>
|
||||
<paper-tooltip for="myseedshow" position="top" animation-delay="0">${translate("login.click")}</paper-tooltip>
|
||||
’ ${translate("login.willbe")}
|
||||
</p>
|
||||
<p style="color: var(--black); margin-bottom: 0; text-align: center;">
|
||||
${translate("login.clicknext")}
|
||||
</p>
|
||||
<br>
|
||||
</div>
|
||||
<mwc-dialog id="mySeedDialog">
|
||||
<div style="min-height:250px; min-width: 300px; box-sizing: border-box; position: relative;">
|
||||
<div style="text-align: center;">
|
||||
<h2 style="color: var(--black);">${translate("login.createdseed")}</h2>
|
||||
<hr>
|
||||
</div>
|
||||
<br>
|
||||
<div style="border-radius: 4px; padding-top: 8px; background: rgba(3,169,244,0.1); margin-top: 24px;">
|
||||
<div style="display: inline-block; padding:12px; width:calc(100% - 84px);">
|
||||
<random-sentence-generator
|
||||
template="adverb verb noun adjective noun adverb verb noun adjective noun adjective verbed adjective noun"
|
||||
id="randSentence"
|
||||
></random-sentence-generator>
|
||||
</div>
|
||||
<!--
|
||||
--- --- --- --- --- --- --- --- --- --- --- --- --- -
|
||||
Calculations
|
||||
--- --- --- --- --- --- --- --- --- --- --- --- --- -
|
||||
403 adjectives
|
||||
60 interjections
|
||||
243 adverbs
|
||||
2353 nouns
|
||||
3387 verbs
|
||||
--- --- --- --- --- --- --- --- --- --- --- --- --- -
|
||||
sooo 243*3387*403*2353*3387*403*2353*403*2353 ~ 2^92
|
||||
--- --- --- --- --- --- --- --- --- --- --- --- --- -
|
||||
-->
|
||||
</div>
|
||||
<br>
|
||||
<div class="horizontal-center">
|
||||
<mwc-button raised label="${translate("login.saveseed")}" icon="save" @click=${() => this.downloadSeedphrase()}></mwc-button>
|
||||
</div>
|
||||
</div>
|
||||
<mwc-button slot="primaryAction" dialogAction="cancel" class="red">${translate("general.close")}</mwc-button>
|
||||
</mwc-dialog>
|
||||
</div>
|
||||
<div page="password">
|
||||
<div id="saveContent" class="section-content">
|
||||
<h3 style="color: var(--black); text-align: center;">${translate("login.savein")}</h3>
|
||||
<p style="color: var(--black); text-align: justify;">${translate("login.ready")}</p>
|
||||
<div style="display:flex;" ?hidden="${!this.saveAccount}">
|
||||
<mwc-icon style="color: var(--black); padding: 10px; padding-left:0; padding-top: 42px;">perm_identity</mwc-icon>
|
||||
<vaadin-text-field style="width:100%;" label="${translate("login.name")}" id="nameInput"></vaadin-text-field>
|
||||
</div>
|
||||
<div style="display:flex;">
|
||||
<mwc-icon style="color: var(--black); padding: 10px; padding-left:0; padding-top: 42px;">password</mwc-icon>
|
||||
<vaadin-password-field style="width:100%;" label="${translate("login.password")}" id="password" autofocus></vaadin-password-field>
|
||||
</div>
|
||||
<div style="display:flex;">
|
||||
<mwc-icon style="color: var(--black); padding: 10px; padding-left:0; padding-top: 42px;">password</mwc-icon>
|
||||
<vaadin-password-field style="width:100%;" label="${translate("login.confirmpass")}" id="rePassword"></vaadin-password-field>
|
||||
</div>
|
||||
<div style="text-align:right; vertical-align: top; line-height: 40px; margin:0;">
|
||||
<mwc-formfield alignEnd>
|
||||
<label for="saveInBrowserCheckbox" @click=${() => this.shadowRoot.getElementById('saveInBrowserCheckbox').click()}><span style="color: var(--black);">${translate("login.save")}</span></label>
|
||||
<mwc-checkbox style="display: inline; id="saveInBrowserCheckbox" @click=${e => { this.saveAccount = !e.target.checked }} ?checked=${this.saveAccount}></mwc-checkbox>
|
||||
</mwc-formfield>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div page="backup">
|
||||
<div id="downloadBackup" class="section-content">
|
||||
<h3 style="color: var(--black); text-align: center;">${translate("login.savewallet")}</h3>
|
||||
<p style="color: var(--black); text-align: justify;">${translate("login.created1")}${this.saveAccount ? this.renderCreateSaveText() : '.'}</p>
|
||||
<p style="color: var(--black); margin: 0;">${translate("login.backup")}</p>
|
||||
<br>
|
||||
<div id="download-area">
|
||||
<div style="line-height: 40px;">
|
||||
<span style="color: var(--black); padding-top: 6px; margin-right: 10px; text-align: center;">${translate("login.downloadbackup")}</span>
|
||||
<slot id="trigger" name="inputTrigger" @click=${() =>
|
||||
this.downloadBackup(this._wallet)} style="dispay: inline; text-align: center;">
|
||||
<mwc-button>
|
||||
<mwc-icon>cloud_download</mwc-icon>
|
||||
${translate("general.save")}
|
||||
</mwc-button>
|
||||
</slot>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</iron-pages>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
this.shadowRoot.getElementById('randSentence').generate()
|
||||
this.shadowRoot.getElementById('nameInput').value = ''
|
||||
this.shadowRoot.getElementById('password').value = ''
|
||||
this.shadowRoot.getElementById('rePassword').value = ''
|
||||
this.showSeedphrase = false
|
||||
this.selectPage('info')
|
||||
this.error = false
|
||||
this.errorMessage = ''
|
||||
this.nextButtonText = 'Next'
|
||||
this.createAccountLoading = false
|
||||
this.saveAccount = true
|
||||
this.isDownloadedBackup = true
|
||||
this._wallet = ''
|
||||
this._pass = ''
|
||||
this._name = ''
|
||||
}
|
||||
|
||||
renderBackText() {
|
||||
return html`${translate("general.back")}`
|
||||
}
|
||||
|
||||
renderNextText() {
|
||||
return html`${translate("general.next")}`
|
||||
}
|
||||
|
||||
renderCreateText() {
|
||||
return html`${translate("general.create")}`
|
||||
}
|
||||
|
||||
renderContinueText() {
|
||||
return html`${translate("general.continue")}`
|
||||
}
|
||||
|
||||
renderPrepareText() {
|
||||
return html`${translate("login.prepare")}`
|
||||
}
|
||||
|
||||
renderWelcomeText() {
|
||||
return html`${translate("login.welmessage")}`
|
||||
}
|
||||
|
||||
renderLoadingText() {
|
||||
return html`${translate("login.loading")}`
|
||||
}
|
||||
|
||||
renderCreateAccountText() {
|
||||
return html`${translate("login.createaccount")}`
|
||||
}
|
||||
|
||||
renderCreateSaveText() {
|
||||
return html`${translate("login.created2")}`
|
||||
}
|
||||
|
||||
_pageChange(newPage, oldPage) {
|
||||
if (!this.shadowRoot.querySelector('#createAccountPages') || !newPage) {
|
||||
return
|
||||
}
|
||||
|
||||
const pages = this.shadowRoot.querySelector('#createAccountPages').children
|
||||
|
||||
// Run the animation on the newly selected page
|
||||
const newIndex = this.pageIndexes[newPage]
|
||||
|
||||
if (!pages[newIndex].className.includes('animated')) {
|
||||
pages[newIndex].className += ' animated'
|
||||
}
|
||||
|
||||
if (typeof oldPage !== 'undefined') {
|
||||
const oldIndex = this.pageIndexes[oldPage]
|
||||
|
||||
// Stop the animation of hidden pages
|
||||
pages[oldIndex].classList.remove('animated')
|
||||
}
|
||||
}
|
||||
|
||||
selectPage(newPage) {
|
||||
const oldPage = this.selectedPage
|
||||
|
||||
this.selectedPage = newPage
|
||||
this._pageChange(newPage, oldPage)
|
||||
}
|
||||
|
||||
updateNext() {
|
||||
if (this.selectedPage === 'info') {
|
||||
this.nextText = this.renderNextText()
|
||||
this.nextDisabled = false
|
||||
} else if (this.selectedPage === 'password') {
|
||||
this.nextDisabled = false
|
||||
this.nextText = this.renderCreateAccountText()
|
||||
} else if (this.selectedPage === 'backup') {
|
||||
this.downloadBackup(this._wallet)
|
||||
this.nextDisabled = false
|
||||
this.backHidden = true
|
||||
this.nextText = this.renderContinueText()
|
||||
}
|
||||
|
||||
this.updatedProperty()
|
||||
}
|
||||
|
||||
back(e) {
|
||||
this.pages[this.selectedPage].back(e)
|
||||
}
|
||||
|
||||
next(e) {
|
||||
this.pages[this.selectedPage].next(e)
|
||||
}
|
||||
|
||||
updatedProperty() {
|
||||
this.dispatchEvent(new CustomEvent('updatedProperty', {
|
||||
detail: {},
|
||||
bubbles: true,
|
||||
composed: true
|
||||
}))
|
||||
}
|
||||
|
||||
navigate(page) {
|
||||
this.dispatchEvent(new CustomEvent('navigate', {
|
||||
detail: { page },
|
||||
bubbles: true,
|
||||
composed: true
|
||||
}))
|
||||
}
|
||||
|
||||
stateChanged(state) {
|
||||
this.nodeConfig = state.app.nodeConfig
|
||||
}
|
||||
|
||||
async downloadBackup(wallet) {
|
||||
let backupname = ""
|
||||
|
||||
const state = store.getState()
|
||||
const data = await wallet.generateSaveWalletData(this._pass, state.config.crypto.kdfThreads, () => { })
|
||||
const dataString = JSON.stringify(data)
|
||||
const blob = new Blob([dataString], { type: 'text/plain;charset=utf-8' })
|
||||
|
||||
backupname = "qortal_backup_" + wallet.addresses[0].address + ".json"
|
||||
|
||||
await this.saveFileToDisk(blob, backupname)
|
||||
}
|
||||
|
||||
async downloadSeedphrase() {
|
||||
let seedname = ""
|
||||
|
||||
const seed = this.shadowRoot.getElementById('randSentence').parsedString
|
||||
const blob = new Blob([seed], { type: 'text/plain;charset=utf-8' })
|
||||
|
||||
seedname = "qortal_seedphrase.txt"
|
||||
|
||||
await this.saveFileToDisk(blob, seedname)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
window.customElements.define('create-account-section', CreateAccountSection)
|
@ -1,711 +0,0 @@
|
||||
import { html, LitElement } from 'lit'
|
||||
import { connect } from 'pwa-helpers'
|
||||
import { store } from '../../store'
|
||||
import { checkApiKey } from '../../apiKeyUtils'
|
||||
import { doLogin, doSelectAddress } from '../../redux/app/app-actions'
|
||||
import { doRemoveWallet, doStoreWallet } from '../../redux/user/user-actions'
|
||||
import { createWallet } from '../../../../crypto/api/createWallet'
|
||||
import { createAccountSectionStyles } from '../../styles/core-css'
|
||||
import ripple from '../../functional-components/loading-ripple'
|
||||
import snackbar from '../../functional-components/snackbar'
|
||||
import '../../functional-components/frag-file-input'
|
||||
import '@material/mwc-button'
|
||||
import '@material/mwc-checkbox'
|
||||
import '@material/mwc-dialog'
|
||||
import '@material/mwc-formfield'
|
||||
import '@material/mwc-icon'
|
||||
import '@material/mwc-icon-button'
|
||||
import '@material/mwc-textfield'
|
||||
import '@polymer/iron-pages'
|
||||
import '@polymer/paper-input/paper-input-container.js'
|
||||
import '@polymer/paper-input/paper-input.js'
|
||||
import '@polymer/paper-ripple'
|
||||
import '@polymer/iron-collapse'
|
||||
import '@polymer/paper-spinner/paper-spinner-lite.js'
|
||||
import '@vaadin/text-field/vaadin-text-field.js'
|
||||
import '@vaadin/password-field/vaadin-password-field.js'
|
||||
|
||||
// Multi language support
|
||||
import { translate } from '../../../translate'
|
||||
|
||||
class LoginSection extends connect(store)(LitElement) {
|
||||
static get properties() {
|
||||
return {
|
||||
nextHidden: { type: Boolean, notify: true },
|
||||
nextDisabled: { 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 },
|
||||
loginFunction: { type: Object },
|
||||
selectedWallet: { type: Object },
|
||||
selectedPage: { type: String },
|
||||
wallets: { type: Object },
|
||||
loginErrorMessage: { type: String },
|
||||
hasStoredWallets: { type: Boolean },
|
||||
saveInBrowser: { type: Boolean },
|
||||
backedUpWalletJSON: { type: Object },
|
||||
backedUpSeedLoading: { type: Boolean },
|
||||
nodeConfig: { type: Object },
|
||||
names: { type: Array },
|
||||
namesAvatar: { type: String },
|
||||
theme: { type: String, reflect: true }
|
||||
}
|
||||
}
|
||||
|
||||
static get styles() {
|
||||
return [createAccountSectionStyles]
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
this.nextHidden = true
|
||||
this.backText = this.renderBackText()
|
||||
this.backedUpSeedLoading = false
|
||||
this.hasStoredWallets = Object.keys(store.getState().user.storedWallets).length > 0
|
||||
this.selectedPage = this.hasStoredWallets ? 'storedWallet' : 'loginOptions'
|
||||
this.selectedWallet = {}
|
||||
this.loginErrorMessage = ''
|
||||
this.saveInBrowser = false
|
||||
this.names = []
|
||||
this.namesAvatar = ''
|
||||
this.theme = localStorage.getItem('qortalTheme') ? localStorage.getItem('qortalTheme') : 'light'
|
||||
|
||||
this.loginOptions = [
|
||||
{
|
||||
page: 'phrase',
|
||||
linkText: this.renderSeedText(),
|
||||
icon: 'short_text'
|
||||
},
|
||||
{
|
||||
page: 'storedWallet',
|
||||
linkText: this.renderSavedText(),
|
||||
icon: 'save'
|
||||
},
|
||||
{
|
||||
page: 'seed',
|
||||
linkText: this.renderQoraText(),
|
||||
icon: 'clear_all'
|
||||
},
|
||||
{
|
||||
page: 'backedUpSeed',
|
||||
linkText: this.renderBackupText(),
|
||||
icon: 'insert_drive_file'
|
||||
}
|
||||
]
|
||||
|
||||
this.showPasswordCheckboxPages = ['seed', 'phrase', 'V1Seed', 'unlockBackedUpSeed']
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<style>
|
||||
#loginSection {
|
||||
padding: 0;
|
||||
text-align: left;
|
||||
padding-top: 12px;
|
||||
--paper-spinner-color: var(--mdc-theme-primary);
|
||||
--paper-spinner-stroke-width: 2px;
|
||||
}
|
||||
|
||||
#loginPages {
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
#walletsPage {}
|
||||
|
||||
#wallets {
|
||||
max-height: 50vh;
|
||||
border-bottom: 1px solid #eee;
|
||||
border-top: 1px solid #eee;
|
||||
overflow-y: auto;
|
||||
box-shadow: 0 0 15px 0px rgb(0 0 0 / 10%);
|
||||
background: var(--white);
|
||||
margin: 2vh;
|
||||
}
|
||||
|
||||
.wallet {
|
||||
position: relative;
|
||||
padding: 15px 60px 15px 15px;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
border-bottom: solid 1px #dedede;
|
||||
}
|
||||
|
||||
.wallet .wallet-details {
|
||||
float: left;
|
||||
width: auto;
|
||||
height: 60px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.wallet .wallet-details p {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
margin: 0;
|
||||
height: 20px;
|
||||
font-size: 15px;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.wallet .walletAddress {
|
||||
height: 40px !important;
|
||||
line-height: 40px !important;
|
||||
color: var(--black);
|
||||
}
|
||||
|
||||
.wallet .walletName {
|
||||
color: var(--black);
|
||||
}
|
||||
|
||||
.wallet p span {
|
||||
color: var(--black);
|
||||
font-size: 12px;
|
||||
width: 50px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.removeWallet {
|
||||
position: absolute;
|
||||
right: 5px;
|
||||
top: 20px;
|
||||
color: tomato;
|
||||
--mdc-icon-size: 30px;
|
||||
}
|
||||
|
||||
.login-option {
|
||||
max-width: 300px;
|
||||
position: relative;
|
||||
padding: 16px 0 8px 12px;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.loginIcon {
|
||||
padding-right: 12px;
|
||||
margin-top: -2px;
|
||||
color: var(--black);
|
||||
}
|
||||
|
||||
*[hidden] {
|
||||
display: none !important;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
h1 {
|
||||
padding: 24px;
|
||||
padding-top: 0;
|
||||
margin: 0;
|
||||
font-size: 24px;
|
||||
font-weight: 100;
|
||||
}
|
||||
|
||||
.accountIcon {
|
||||
font-size: 42px;
|
||||
padding-top: 8px;
|
||||
}
|
||||
|
||||
#unlockStoredPage {
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
#unlockStoredPage mwc-icon {
|
||||
font-size: 48px;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: ${getComputedStyle(document.body).getPropertyValue('--layout-breakpoint-tablet')}) {
|
||||
|
||||
/* Mobile */
|
||||
#wallets {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
#loginSection {
|
||||
height: calc(var(--window-height) - 56px);
|
||||
}
|
||||
|
||||
.wallet {
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.backButton {
|
||||
padding: 14px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
#pagesContainer {
|
||||
max-height: calc(var(--window-height) - 184px);
|
||||
}
|
||||
|
||||
.checkboxLabel:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div id="loginSection">
|
||||
<div id="pagesContainer">
|
||||
<iron-pages style="padding: 0;" selected="${this.selectedPage}" attr-for-selected="page" id="loginPages">
|
||||
<div page="loginOptions">
|
||||
<h3 style="color: var(--black);">${translate("login.howlogin")}</h3>
|
||||
${this.loginOptions.map(({ page, linkText, icon }) => html`
|
||||
<div class="login-option" @click=${() => { this.selectedPage = page }}>
|
||||
<paper-ripple></paper-ripple>
|
||||
<div><mwc-icon class='loginIcon'>${icon}</mwc-icon></div>
|
||||
<div><span style="color: var(--black)">${linkText}</span></div>
|
||||
</div>
|
||||
`)}
|
||||
</div>
|
||||
<div page="storedWallet" id="walletsPage">
|
||||
<div style="text-align: center; padding-left:0;">
|
||||
<h1 style="padding:0; color: var(--black);">${translate("login.youraccounts")}</h1>
|
||||
<p style="margin:0; padding: 0 0 12px 0; color: var(--black);">${translate("login.clickto")}</p>
|
||||
</div>
|
||||
<div id="wallets">
|
||||
${(Object.entries(this.wallets || {}).length < 1) ? html`
|
||||
<p style="color: var(--black); padding: 0 0 6px 0;">${translate("login.needcreate")}</p>
|
||||
` : ''}
|
||||
${Object.entries(this.wallets || {}).map(wallet => html`
|
||||
<div class="wallet">
|
||||
<div class="selectWallet" @click=${() => this.selectWallet(wallet[1])}>
|
||||
<paper-ripple></paper-ripple>
|
||||
<div class='wallet-details'>
|
||||
<p class='walletName'><span style="color: var(--black);">${translate("login.name")}</span>${wallet[1].name || "No saved name"}</p>
|
||||
<p class="walletAddress"><span style="color: var(--black);">${translate("login.address")}</span>${wallet[1].address0}</p>
|
||||
</div>
|
||||
</div>
|
||||
<mwc-icon-button class="removeWallet" @click="${(e) => this.toDeleteWallet(wallet[1].address0)}" icon="clear"></mwc-icon-button>
|
||||
<mwc-dialog id="deleteWalletDialog">
|
||||
<div style="text-align: center;">
|
||||
<h2>Qortal UI</h2>
|
||||
<hr>
|
||||
</div>
|
||||
<br>
|
||||
<p>${translate("login.areyousure")}</p>
|
||||
<mwc-button slot="primaryAction" @click="${(e) => this.removeWallet(this.myToDeleteWallet)}">
|
||||
${translate("general.yes")}
|
||||
</mwc-button>
|
||||
<mwc-button slot="secondaryAction" dialogAction="cancel" class="red">
|
||||
${translate("general.no")}
|
||||
</mwc-button>
|
||||
</mwc-dialog>
|
||||
</div>
|
||||
`)}
|
||||
</div>
|
||||
</div>
|
||||
<div page="phrase" id="phrasePage">
|
||||
<div style="padding:0;">
|
||||
<div style="display:flex;">
|
||||
<mwc-icon style="padding: 10px; padding-left: 0; padding-top: 42px; color: var(--black);">short_text</mwc-icon>
|
||||
<vaadin-password-field style="width:100%;" label="${translate("login.seed")}" id="existingSeedPhraseInput" @keydown="${e => this.seedListener(e)}" autofocus></vaadin-password-field>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div page="seed" id="seedPage">
|
||||
<div>
|
||||
<div style="display: flex;">
|
||||
<mwc-icon style="padding: 10px; padding-left: 0; padding-top: 42px; color: var(--black);">clear_all</mwc-icon>
|
||||
<vaadin-password-field style="width: 100%;" label="${translate("login.qora")}" id="v1SeedInput" @keydown="${e => this.seedListener(e)}" autofocus></vaadin-password-field>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div page="unlockStored" id="unlockStoredPage">
|
||||
<div style="text-align:center;">
|
||||
${this.namesAvatar}
|
||||
<br>
|
||||
<span style="font-size:14px; font-weight: 100; font-family: 'Roboto Mono', monospace; color: var(--black);">${this.selectedWallet.address0}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div page="backedUpSeed">
|
||||
${!this.backedUpSeedLoading ? html`
|
||||
<h3 style="color: var(--black);">${translate("login.upload")}</h3>
|
||||
<frag-file-input accept=".zip,.json" @file-read-success="${e => this.loadBackup(e.detail.result)}"></frag-file-input>
|
||||
` : html`
|
||||
<paper-spinner-lite active style="display: block; margin: 0 auto;"></paper-spinner-lite>
|
||||
`}
|
||||
</div>
|
||||
<div page="unlockBackedUpSeed">
|
||||
<h3 style="text-align: center; color: var(--black);">${translate("login.decrypt")}</h3>
|
||||
</div>
|
||||
</iron-pages>
|
||||
<iron-collapse style="" ?opened=${this.showName(this.selectedPage)} id="passwordCollapse">
|
||||
<div style="display:flex;">
|
||||
<mwc-icon style="padding: 10px; padding-left: 0; padding-top: 42px; color: var(--black);">perm_identity</mwc-icon>
|
||||
<vaadin-text-field style="width:100%;" label="${translate("login.name")}" id="nameInput"></vaadin-text-field>
|
||||
</div>
|
||||
</iron-collapse>
|
||||
<iron-collapse style="" ?opened=${this.showPassword(this.selectedPage)} id="passwordCollapse">
|
||||
<div style="display:flex;">
|
||||
<mwc-icon style="padding: 10px; padding-left: 0; padding-top: 42px; color: var(--black);">password</mwc-icon>
|
||||
<vaadin-password-field style="width:100%;" label="${translate("login.password")}" id="password" @keyup=${e => this.keyupEnter(e, e => this.emitNext(e))} autofocus></vaadin-password-field>
|
||||
</div>
|
||||
</iron-collapse>
|
||||
<div style="text-align: right; color: var(--mdc-theme-error)">
|
||||
${this.loginErrorMessage}
|
||||
</div>
|
||||
${this.showPasswordCheckboxPages.includes(this.selectedPage) ? html`
|
||||
<div style="text-align: right; vertical-align: top; line-height: 40px; margin:0;">
|
||||
<mwc-formfield alignEnd>
|
||||
<label for="storeCheckbox" class="checkboxLabel" @click=${() => this.shadowRoot.getElementById('storeCheckbox').click()}><span style="color: var(--black);">${translate("login.save")}</span></label>
|
||||
<mwc-checkbox style="display: inline;" id="storeCheckbox" @click=${e => { this.saveInBrowser = !e.target.checked }} ?checked="${this.saveInBrowser}"></mwc-checkbox>
|
||||
</mwc-formfield>
|
||||
</div>
|
||||
` : ''}
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
|
||||
firstUpdated() {
|
||||
this.loadingRipple = ripple
|
||||
|
||||
const pages = this.shadowRoot.querySelector('#loginPages')
|
||||
|
||||
pages.addEventListener('selected-item-changed', () => {
|
||||
if (!pages.selectedItem) {
|
||||
// ...
|
||||
} else {
|
||||
this.updateNext()
|
||||
this.shadowRoot.querySelector('#nameInput').value = ''
|
||||
this.shadowRoot.querySelector('#password').value = ''
|
||||
this.loginErrorMessage = ''
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
renderBackText() {
|
||||
return html`${translate("general.back")}`
|
||||
}
|
||||
|
||||
renderNextText() {
|
||||
return html`${translate("general.next")}`
|
||||
}
|
||||
|
||||
renderLoginText() {
|
||||
return html`${translate("login.login")}`
|
||||
}
|
||||
|
||||
renderSeedText() {
|
||||
return html`${translate("login.seed")}`
|
||||
}
|
||||
|
||||
renderSavedText() {
|
||||
return html`${translate("login.saved")}`
|
||||
}
|
||||
|
||||
renderQoraText() {
|
||||
return html`${translate("login.qora")}`
|
||||
}
|
||||
|
||||
renderBackupText() {
|
||||
return html`${translate("login.backup")}`
|
||||
}
|
||||
|
||||
renderPrepareText() {
|
||||
return html`${translate("login.prepare")}`
|
||||
}
|
||||
|
||||
renderError1Text() {
|
||||
return html`${translate("login.error1")}`
|
||||
}
|
||||
|
||||
renderError2Text() {
|
||||
return html`${translate("login.error2")}`
|
||||
}
|
||||
|
||||
renderError3Text() {
|
||||
return html`${translate("login.lp8")}`
|
||||
}
|
||||
|
||||
renderLoginAvatar(renderAddress) {
|
||||
const avatarNode = store.getState().app.nodeConfig.knownNodes[store.getState().app.nodeConfig.node]
|
||||
const avatarUrl = avatarNode.protocol + '://' + avatarNode.domain + ':' + avatarNode.port
|
||||
const namesUrl = `${avatarUrl}/names/address/${renderAddress}?limit=0&reverse=true`
|
||||
|
||||
this.namesAvatar = ''
|
||||
|
||||
if (this.isEmptyArray(this.names)) {
|
||||
fetch(namesUrl).then(response => {
|
||||
return response.json()
|
||||
}).then(data => {
|
||||
this.names = data
|
||||
if (this.isEmptyArray(this.names)) {
|
||||
this.namesAvatar = html`<mwc-icon id='accountIcon' style="padding-bottom: 24px; color: var(--black);">account_circle</mwc-icon>`
|
||||
} else {
|
||||
const url = `${avatarUrl}/arbitrary/THUMBNAIL/${this.names[0].name}/qortal_avatar?async=true`
|
||||
this.namesAvatar = html`
|
||||
<img style="width: 48px; height: 48px; border-radius: 25%; padding-bottom: 16px;" src="${url}" onerror="this.src='/img/incognito.png';" />
|
||||
<br>
|
||||
<span style="font-size:16px; font-weight: 400; font-family: 'Roboto Mono', monospace; color: var(--black);">${this.names[0].name}</span>
|
||||
<br>
|
||||
`
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
selectWallet(wallet) {
|
||||
this.selectedWallet = wallet
|
||||
this.selectedPage = 'unlockStored'
|
||||
this.names = []
|
||||
this.renderLoginAvatar(this.selectedWallet.address0)
|
||||
}
|
||||
|
||||
toDeleteWallet(deleteAddress) {
|
||||
this.myToDeleteWallet = deleteAddress
|
||||
this.shadowRoot.querySelector('#deleteWalletDialog').show()
|
||||
}
|
||||
|
||||
removeWallet(walletAddress) {
|
||||
delete store.getState().user.storedWallets[walletAddress]
|
||||
|
||||
this.wallets = store.getState().user.storedWallets
|
||||
|
||||
store.dispatch(
|
||||
doRemoveWallet(walletAddress)
|
||||
)
|
||||
|
||||
this.cleanup()
|
||||
}
|
||||
|
||||
stateChanged(state) {
|
||||
this.loggedIn = state.app.loggedIn
|
||||
this.wallets = state.user.storedWallets
|
||||
this.hasStoredWallets = this.wallets.length > 0
|
||||
this.nodeConfig = state.app.nodeConfig
|
||||
}
|
||||
|
||||
seedListener(e) {
|
||||
if (e.key === 'Enter') {
|
||||
this.emitNext(e)
|
||||
}
|
||||
}
|
||||
|
||||
keyupEnter(e, action) {
|
||||
if (e.keyCode === 13) {
|
||||
e.preventDefault()
|
||||
action(e)
|
||||
}
|
||||
}
|
||||
|
||||
emitNext(e) {
|
||||
this.dispatchEvent(new CustomEvent('next', { detail: {} }))
|
||||
}
|
||||
|
||||
loadBackup(file) {
|
||||
let error = ''
|
||||
let pf
|
||||
|
||||
this.selectedPage = 'unlockBackedUpSeed'
|
||||
|
||||
try {
|
||||
pf = JSON.parse(file)
|
||||
} catch (e) {
|
||||
this.loginErrorMessage = this.renderError1Text()
|
||||
}
|
||||
|
||||
try {
|
||||
const requiredFields = ['address0', 'salt', 'iv', 'version', 'encryptedSeed', 'mac', 'kdfThreads']
|
||||
|
||||
for (const field of requiredFields) {
|
||||
if (!(field in pf)) throw new Error(field + ' not found in JSON')
|
||||
}
|
||||
} catch (e) {
|
||||
error = e
|
||||
}
|
||||
|
||||
if (error !== '') {
|
||||
snackbar.add({
|
||||
labelText: error
|
||||
})
|
||||
|
||||
this.selectedPage = 'backedUpSeed'
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
this.backedUpWalletJSON = pf
|
||||
}
|
||||
|
||||
showName(selectedPage) {
|
||||
return (this.saveInBrowser && ['unlockBackedUpSeed', 'seed', 'phrase'].includes(selectedPage)) || ([''].includes(selectedPage))
|
||||
}
|
||||
|
||||
showPassword(selectedPage) {
|
||||
let willBeShown = (this.saveInBrowser && ['unlockBackedUpSeed', 'seed', 'phrase'].includes(selectedPage)) || (['unlockBackedUpSeed', 'unlockStored'].includes(selectedPage))
|
||||
|
||||
if (willBeShown) this.shadowRoot.getElementById('password').focus()
|
||||
|
||||
return willBeShown
|
||||
}
|
||||
|
||||
get walletSources() {
|
||||
return {
|
||||
seed: () => {
|
||||
const seed = this.shadowRoot.querySelector('#v1SeedInput').value
|
||||
const name = this.shadowRoot.getElementById('nameInput').value
|
||||
const password = this.shadowRoot.getElementById('password').value
|
||||
|
||||
return {
|
||||
seed,
|
||||
password,
|
||||
name
|
||||
}
|
||||
},
|
||||
storedWallet: () => {
|
||||
const wallet = this.selectedWallet
|
||||
const password = this.shadowRoot.getElementById('password').value
|
||||
|
||||
return {
|
||||
wallet,
|
||||
password
|
||||
}
|
||||
},
|
||||
phrase: () => {
|
||||
const seedPhrase = this.shadowRoot.querySelector('#existingSeedPhraseInput').value
|
||||
|
||||
if (seedPhrase == '') {
|
||||
throw new Error('Please enter a seedphrase')
|
||||
return
|
||||
}
|
||||
|
||||
const name = this.shadowRoot.getElementById('nameInput').value
|
||||
const password = this.shadowRoot.getElementById('password').value
|
||||
|
||||
return {
|
||||
seedPhrase,
|
||||
name,
|
||||
password
|
||||
}
|
||||
},
|
||||
backedUpSeed: () => {
|
||||
const wallet = this.backedUpWalletJSON
|
||||
const name = this.shadowRoot.getElementById('nameInput').value
|
||||
const password = this.shadowRoot.getElementById('password').value
|
||||
|
||||
return {
|
||||
password,
|
||||
wallet,
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
loginOptionIsSelected(type) {
|
||||
return this.loginOptions.map(op => op.page).includes(type)
|
||||
}
|
||||
|
||||
login(e) {
|
||||
let type = this.selectedPage === 'unlockStored' ? 'storedWallet' : this.selectedPage
|
||||
|
||||
type = type === 'unlockBackedUpSeed' ? 'backedUpSeed' : type
|
||||
|
||||
if (!this.loginOptionIsSelected(type)) {
|
||||
throw new Error(this.renderError2Text())
|
||||
}
|
||||
|
||||
// First decrypt...
|
||||
this.loadingRipple.welcomeMessage = this.renderPrepareText()
|
||||
|
||||
this.loadingRipple.open({
|
||||
x: e.clientX,
|
||||
y: e.clientY
|
||||
}).then(() => {
|
||||
const source = this.walletSources[type]()
|
||||
return createWallet(type, source, status => {
|
||||
this.loadingRipple.loadingMessage = status
|
||||
}).then(wallet => {
|
||||
store.dispatch(doLogin(wallet))
|
||||
store.dispatch(doSelectAddress(wallet.addresses[0]))
|
||||
this.navigate('show-address')
|
||||
const storedWallets = store.getState().user.storedWallets
|
||||
const storedWalletAddress = storedWallets[wallet.addresses[0].address]
|
||||
|
||||
if (!storedWalletAddress) {
|
||||
if (this.saveInBrowser && type !== 'storedWallet') {
|
||||
store.dispatch(doStoreWallet(wallet, source.password, source.name, () => {
|
||||
ripple.loadingMessage = status
|
||||
})).catch(err => console.error(err))
|
||||
}
|
||||
}
|
||||
checkApiKey(this.nodeConfig)
|
||||
this.cleanup()
|
||||
return this.loadingRipple.fade()
|
||||
})
|
||||
}).catch(e => {
|
||||
this.loginErrorMessage = this.renderError3Text()
|
||||
console.error(e)
|
||||
return this.loadingRipple.close()
|
||||
})
|
||||
}
|
||||
|
||||
back() {
|
||||
if (['seed', 'phrase', 'storedWallet', 'backedUpSeed'].includes(this.selectedPage)) {
|
||||
this.selectedPage = 'loginOptions'
|
||||
} else if (this.selectedPage === 'loginOptions') {
|
||||
this.navigate('welcome')
|
||||
} else if (this.selectedPage === 'unlockStored') {
|
||||
this.selectedPage = 'storedWallet'
|
||||
} else if (this.selectedPage === 'unlockBackedUpSeed') {
|
||||
this.selectedPage = 'backedUpSeed'
|
||||
}
|
||||
}
|
||||
|
||||
next(e) {
|
||||
this.login(e)
|
||||
}
|
||||
|
||||
// clicks next for parent
|
||||
clickNext() {
|
||||
}
|
||||
|
||||
updateNext() {
|
||||
if (['phrase', 'seed', 'unlockStored', 'unlockBackedUpSeed'].includes(this.selectedPage)) {
|
||||
this.nextText = this.renderLoginText()
|
||||
this.nextHidden = false
|
||||
// Should enable/disable the next button based on whether or not password are inputted
|
||||
} else if (['storedWallet', 'loginOptions', 'backedUpSeed'].includes(this.selectedPage)) {
|
||||
this.nextHidden = true
|
||||
this.nextText = this.renderNextText()
|
||||
}
|
||||
|
||||
this.updatedProperty()
|
||||
}
|
||||
|
||||
updatedProperty() {
|
||||
this.dispatchEvent(new CustomEvent('updatedProperty', {
|
||||
detail: {},
|
||||
bubbles: true,
|
||||
composed: true
|
||||
}))
|
||||
}
|
||||
|
||||
navigate(page) {
|
||||
this.dispatchEvent(new CustomEvent('navigate', {
|
||||
detail: { page },
|
||||
bubbles: true,
|
||||
composed: true
|
||||
}))
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
this.wallet = {}
|
||||
this.shadowRoot.querySelector('#nameInput').value = ''
|
||||
this.shadowRoot.querySelector('#password').value = ''
|
||||
this.hasStoredWallets = Object.keys(store.getState().user.storedWallets).length > 0
|
||||
this.selectedPage = this.hasStoredWallets ? 'storedWallet' : 'loginOptions'
|
||||
this.shadowRoot.querySelector('#deleteWalletDialog').close()
|
||||
}
|
||||
|
||||
isEmptyArray(arr) {
|
||||
if (!arr) { return true }
|
||||
return arr.length === 0
|
||||
}
|
||||
}
|
||||
|
||||
window.customElements.define('login-section', LoginSection)
|
@ -1,406 +0,0 @@
|
||||
import { html, LitElement } from 'lit'
|
||||
import { connect } from 'pwa-helpers'
|
||||
import { store } from '../../store'
|
||||
import { stateAwait } from '../../stateAwait'
|
||||
import {
|
||||
addAutoLoadImageChat,
|
||||
addChatLastSeen,
|
||||
addTabInfo,
|
||||
allowQAPPAutoAuth,
|
||||
allowQAPPAutoFriendsList,
|
||||
allowQAPPAutoLists,
|
||||
allowShowSyncIndicator,
|
||||
removeAutoLoadImageChat,
|
||||
removeQAPPAutoAuth,
|
||||
removeQAPPAutoFriendsList,
|
||||
removeQAPPAutoLists,
|
||||
removeShowSyncIndicator,
|
||||
setNewNotification,
|
||||
setNewTab,
|
||||
setSideEffectAction,
|
||||
setTabNotifications
|
||||
} 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
|
||||
}
|
||||
|
||||
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 }
|
||||
}
|
||||
}
|
||||
|
||||
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'
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<style>
|
||||
|
||||
canvas {
|
||||
display: block;
|
||||
vertical-align: bottom;
|
||||
}
|
||||
|
||||
.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-container {
|
||||
max-width: 1240px;
|
||||
max-height: var(--window-height);
|
||||
margin-right: auto;
|
||||
margin-left: auto;
|
||||
width: calc(100vw);
|
||||
}
|
||||
|
||||
.qortal-logo {
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
width: 200px;
|
||||
max-width: 40%;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
#loginContainerPages {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
#loginContainerPages [page] {
|
||||
background: none;
|
||||
padding: 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;
|
||||
}
|
||||
|
||||
.login-card p {
|
||||
margin-top: 0;
|
||||
font-size: 1rem;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.login-card h1 {
|
||||
margin-bottom: 12px;
|
||||
font-size: 64px;
|
||||
}
|
||||
|
||||
.login-card h5 {
|
||||
margin-top: -16px;
|
||||
margin-left: 100px;
|
||||
font-size: 14px;
|
||||
color: var(--black);
|
||||
}
|
||||
|
||||
.login-card h6 {
|
||||
font-size: 12px;
|
||||
color: var(--mdc-theme-primary);
|
||||
}
|
||||
|
||||
.login-card iron-pages {
|
||||
height: 100%;
|
||||
margin-top: -16px;
|
||||
}
|
||||
|
||||
.backButton {
|
||||
padding-top: 18px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#login-pages-nav {
|
||||
text-align: left;
|
||||
padding: 12px 0 8px 0;
|
||||
}
|
||||
|
||||
#nav-next {
|
||||
float: right;
|
||||
}
|
||||
|
||||
@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"] {}
|
||||
}
|
||||
|
||||
@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%);
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateX(0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes grow-up {
|
||||
from {
|
||||
overflow: hidden;
|
||||
max-height: 0;
|
||||
}
|
||||
|
||||
to {
|
||||
overflow: hidden;
|
||||
max-height: var(--window-height);
|
||||
}
|
||||
}
|
||||
|
||||
iron-pages .animated,
|
||||
.animated {
|
||||
animation-duration: ${animationDuration}s;
|
||||
animation-name: grow-up;
|
||||
}
|
||||
|
||||
div[page]>paper-icon-button {
|
||||
margin: 12px;
|
||||
}
|
||||
|
||||
.corner-box {
|
||||
border-color: var(--mdc-theme-primary) !important;
|
||||
}
|
||||
|
||||
[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>
|
||||
`
|
||||
}
|
||||
|
||||
firstUpdated() {
|
||||
stateAwait(state => {
|
||||
return 'primary' in state.config.styles.theme.colors
|
||||
}).catch(e => console.error(e))
|
||||
|
||||
const loginContainerPages = this.shadowRoot.querySelector('#loginContainerPages')
|
||||
const loginCard = this.shadowRoot.querySelector('#login-card')
|
||||
|
||||
const navigate = e => {
|
||||
this.selectPage(e.detail.page)
|
||||
}
|
||||
|
||||
const updatedProperty = e => {
|
||||
const selectedPageElement = this.selectedPageElement
|
||||
|
||||
this.selectedPageElement = {}
|
||||
|
||||
setTimeout(() => { this.selectedPageElement = selectedPageElement }, 1)
|
||||
}
|
||||
|
||||
loginContainerPages.addEventListener('selected-item-changed', () => {
|
||||
if (!loginContainerPages.selectedItem) {
|
||||
|
||||
if (this.selectedPageElement.removeEventListener) {
|
||||
this.selectedPageElement.removeEventListener('navigate', navigate)
|
||||
this.selectedPageElement.removeEventListener('updatedProperty', updatedProperty)
|
||||
}
|
||||
|
||||
this.selectedPageElement = {}
|
||||
|
||||
loginCard.classList.remove('animated')
|
||||
loginCard.className += ' animated'
|
||||
} else {
|
||||
setTimeout(() => {
|
||||
this.selectedPageElement = loginContainerPages.selectedItem
|
||||
this.selectedPageElement.addEventListener('navigate', navigate)
|
||||
this.selectedPageElement.addEventListener('updatedProperty', updatedProperty)
|
||||
|
||||
setTimeout(() => loginCard.classList.remove('animated'), animationDuration * 1000)
|
||||
}, 1)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
getPreSelectedPage() {
|
||||
return 'welcome'
|
||||
}
|
||||
|
||||
renderSelectedNodeOnStart() {
|
||||
const selectedNodeIndexOnStart = localStorage.getItem('mySelectedNode')
|
||||
const catchSavedNodes = JSON.parse(localStorage.getItem('myQortalNodes'))
|
||||
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)
|
@ -1,44 +0,0 @@
|
||||
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 {
|
||||
hideNav: { type: Boolean, notify: true },
|
||||
theme: { type: String, reflect: true }
|
||||
}
|
||||
}
|
||||
|
||||
static get styles() {
|
||||
return [welcomePageStyles]
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
this.hideNav = true
|
||||
this.theme = localStorage.getItem('qortalTheme') ? localStorage.getItem('qortalTheme') : 'light'
|
||||
}
|
||||
|
||||
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>
|
||||
`
|
||||
}
|
||||
|
||||
navigate(page) {
|
||||
this.dispatchEvent(new CustomEvent('navigate', {
|
||||
detail: { page },
|
||||
bubbles: true,
|
||||
composed: true
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
window.customElements.define('welcome-page', WelcomePage)
|
@ -1,61 +0,0 @@
|
||||
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 styles() {
|
||||
return [logoutViewStyles]
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
this.theme = localStorage.getItem('qortalTheme') ? localStorage.getItem('qortalTheme') : 'light'
|
||||
}
|
||||
|
||||
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>
|
||||
`
|
||||
}
|
||||
|
||||
openLogout() {
|
||||
this.shadowRoot.getElementById('userLogoutDialog').open()
|
||||
}
|
||||
|
||||
async confirm(e) {
|
||||
store.dispatch(doLogout())
|
||||
e.stopPropagation()
|
||||
}
|
||||
|
||||
decline() {
|
||||
this.shadowRoot.getElementById('userLogoutDialog').close()
|
||||
this.requestUpdate()
|
||||
}
|
||||
}
|
||||
|
||||
window.customElements.define('logout-view', LogoutView)
|
@ -1,66 +0,0 @@
|
||||
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 './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 }
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`${this.renderViews(this.loggedIn)}`
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback()
|
||||
this.initial = 0
|
||||
|
||||
if (!isElectron()) {
|
||||
} else {
|
||||
window.addEventListener('contextmenu', (event) => {
|
||||
event.preventDefault()
|
||||
window.electronAPI.showMyMenu()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dynamic renderViews method to introduce conditional rendering of views based on user's logged in state.
|
||||
* @param {Boolean} isLoggedIn
|
||||
*/
|
||||
renderViews(isLoggedIn) {
|
||||
if (isLoggedIn) {
|
||||
return html`<app-view></app-view>`
|
||||
} else {
|
||||
return html`<login-view></login-view>`
|
||||
}
|
||||
}
|
||||
|
||||
_loadPlugins() {
|
||||
loadPlugins()
|
||||
}
|
||||
|
||||
stateChanged(state) {
|
||||
this.loggedIn = state.app.loggedIn
|
||||
if (this.loggedIn === true && this.initial === 0) {
|
||||
this.initial = this.initial + 1
|
||||
this._loadPlugins()
|
||||
}
|
||||
document.title = state.config.coin.name
|
||||
}
|
||||
}
|
||||
|
||||
window.customElements.define('main-app', MainApp)
|
@ -1,114 +0,0 @@
|
||||
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())
|
||||
})
|
||||
|
||||
const checkLanguage = localStorage.getItem('qortalLanguage')
|
||||
|
||||
if (checkLanguage === null || checkLanguage.length === 0) {
|
||||
localStorage.setItem('qortalLanguage', 'us')
|
||||
use('us')
|
||||
} else {
|
||||
use(checkLanguage)
|
||||
}
|
||||
|
||||
class NewSelector extends LitElement {
|
||||
static get properties() {
|
||||
return {
|
||||
theme: { type: String, reflect: true }
|
||||
}
|
||||
}
|
||||
|
||||
static get styles() {
|
||||
return [newSelectorStyles]
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
this.theme = localStorage.getItem('qortalTheme') ? localStorage.getItem('qortalTheme') : 'light'
|
||||
}
|
||||
|
||||
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>
|
||||
|
||||
`
|
||||
}
|
||||
|
||||
firstUpdated() {
|
||||
const myElement = this.shadowRoot.getElementById('languageNew')
|
||||
|
||||
myElement.addEventListener('change', () => {
|
||||
this.selectElement()
|
||||
})
|
||||
|
||||
myElement.addEventListener('click', () => {
|
||||
const element1 = localStorage.getItem('qortalLanguage')
|
||||
const element2 = this.shadowRoot.getElementById('languageNew').value
|
||||
|
||||
if (element1 === element2) {
|
||||
myElement.style.display = 'none'
|
||||
}
|
||||
})
|
||||
|
||||
this.selectElement()
|
||||
}
|
||||
|
||||
selectElement() {
|
||||
const selectedLanguage = localStorage.getItem('qortalLanguage')
|
||||
|
||||
let element = this.shadowRoot.getElementById('languageNew')
|
||||
|
||||
element.value = selectedLanguage
|
||||
element.style.display = 'none'
|
||||
}
|
||||
|
||||
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'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
window.customElements.define('new-selector', NewSelector)
|
@ -1,263 +0,0 @@
|
||||
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 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.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>
|
||||
`}
|
||||
</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}>
|
||||
<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>
|
||||
` : ''}
|
||||
${repeat(
|
||||
this.notifications,
|
||||
(notification) => notification.reference.signature, // key function
|
||||
(notification) => html`
|
||||
<notification-item-tx
|
||||
.changeStatus=${(val1, val2) =>
|
||||
this.changeStatus(val1, val2)}
|
||||
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() {
|
||||
this.showNotifications = !this.showNotifications;
|
||||
if (this.showNotifications) {
|
||||
requestAnimationFrame(() => {
|
||||
this.shadowRoot.getElementById('notification-panel').focus();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
window.customElements.define('notification-bell-general', NotificationBellGeneral)
|
||||
|
||||
class NotificationItemTx extends connect(store)(LitElement) {
|
||||
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()
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<div class="notification-item" @click=${() => { }}>
|
||||
<div>
|
||||
<p style="margin-bottom:10px; font-weight:bold">
|
||||
${translate('transpage.tchange1')}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p style="margin-bottom:5px">
|
||||
${translate('walletpage.wchange35')}: ${this.type}
|
||||
</p>
|
||||
<p style="margin-bottom:5px">
|
||||
${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>` : ''}
|
||||
</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
|
||||
}
|
||||
}
|
||||
|
||||
window.customElements.define('notification-item-tx', NotificationItemTx)
|
@ -1,262 +0,0 @@
|
||||
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'
|
||||
|
||||
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 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'
|
||||
}
|
||||
|
||||
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>
|
||||
`
|
||||
}
|
||||
|
||||
firstUpdated() {
|
||||
this.getNotifications()
|
||||
|
||||
document.addEventListener('click', (event) => {
|
||||
const path = event.composedPath()
|
||||
if (!path.includes(this)) {
|
||||
this.showNotifications = false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
getApiKey() {
|
||||
const apiNode = store.getState().app.nodeConfig.knownNodes[store.getState().app.nodeConfig.node]
|
||||
return apiNode.apiKey
|
||||
}
|
||||
|
||||
async getNotifications() {
|
||||
const myNode = store.getState().app.nodeConfig.knownNodes[store.getState().app.nodeConfig.node]
|
||||
const nodeUrl = myNode.protocol + '://' + myNode.domain + ':' + myNode.port
|
||||
|
||||
let interval = null
|
||||
let stop = false
|
||||
|
||||
const getNewMail = async () => {
|
||||
const getMail = async (recipientName, recipientAddress) => {
|
||||
const query = `qortal_qmail_${recipientName.slice(
|
||||
0,
|
||||
20
|
||||
)}_${recipientAddress.slice(-6)}_mail_`
|
||||
|
||||
const url = `${nodeUrl}/arbitrary/resources/search?service=MAIL_PRIVATE&query=${query}&limit=10&includemetadata=false&offset=0&reverse=true&excludeblocked=true`
|
||||
|
||||
const response = await fetch(url, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
})
|
||||
|
||||
return await response.json()
|
||||
}
|
||||
|
||||
if (!stop && !this.showNotifications) {
|
||||
stop = true
|
||||
|
||||
try {
|
||||
const address = window.parent.reduxStore.getState().app?.selectedAddress?.address;
|
||||
const name = window.parent.reduxStore.getState().app?.accountInfo?.names[0]?.name
|
||||
|
||||
if (!name || !address) return
|
||||
|
||||
const mailArray = await getMail(name, address)
|
||||
|
||||
let notificationsToShow = []
|
||||
|
||||
if (mailArray.length > 0) {
|
||||
const lastVisited = localStorage.getItem("Q-Mail-last-visited")
|
||||
|
||||
if (lastVisited) {
|
||||
mailArray.forEach((mail) => {
|
||||
if (mail.created > lastVisited) notificationsToShow.push(mail)
|
||||
})
|
||||
} else {
|
||||
notificationsToShow = mailArray
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (!this.initialFetch && notificationsToShow.length > 0) {
|
||||
const mail = notificationsToShow[0]
|
||||
const urlPic = `${nodeUrl}/arbitrary/THUMBNAIL/${mail.name}/qortal_avatar?async=true}`
|
||||
|
||||
await routes.showNotification({
|
||||
data: {
|
||||
title: 'New Q-Mail',
|
||||
type: 'qapp',
|
||||
sound: config.messageAlert,
|
||||
url: '',
|
||||
options: {
|
||||
body: `You have an unread mail from ${mail.name}`,
|
||||
icon: urlPic,
|
||||
badge: urlPic
|
||||
}
|
||||
}
|
||||
})
|
||||
} else if (notificationsToShow.length > 0) {
|
||||
if (notificationsToShow[0].created > (this.notifications[0]?.created || 0)) {
|
||||
const mail = notificationsToShow[0]
|
||||
const urlPic = `${nodeUrl}/arbitrary/THUMBNAIL/${mail.name}/qortal_avatar?async=true}`
|
||||
|
||||
await routes.showNotification({
|
||||
data: {
|
||||
title: 'New Q-Mail',
|
||||
type: 'qapp',
|
||||
sound: config.messageAlert,
|
||||
url: '',
|
||||
options: {
|
||||
body: `You have an unread mail from ${mail.name}`,
|
||||
icon: urlPic,
|
||||
badge: urlPic
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
this.notifications = notificationsToShow
|
||||
|
||||
this.notificationCount = this.notifications.length !== 0
|
||||
|
||||
if (!this.initialFetch) this.initialFetch = true
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
|
||||
stop = false
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
setTimeout(() => {
|
||||
getNewMail()
|
||||
}, 5000)
|
||||
|
||||
interval = setInterval(getNewMail, 60000)
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
|
||||
_toggleNotifications() {
|
||||
if (this.notifications.length === 0) return
|
||||
this.showNotifications = !this.showNotifications
|
||||
}
|
||||
|
||||
_openTabQmail() {
|
||||
const query = `?service=APP&name=Q-Mail`
|
||||
|
||||
store.dispatch(setNewTab({
|
||||
url: `qdn/browser/index.html${query}`,
|
||||
id: 'q-mail-notification',
|
||||
myPlugObj: {
|
||||
"url": "myapp",
|
||||
"domain": "core",
|
||||
"page": `qdn/browser/index.html${query}`,
|
||||
"title": "Q-Mail",
|
||||
"icon": "vaadin:mailbox",
|
||||
"mwcicon": "mail_outline",
|
||||
"menus": [],
|
||||
"parent": false
|
||||
}
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
window.customElements.define('notification-bell', NotificationBell)
|
@ -1,56 +0,0 @@
|
||||
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 get properties() {
|
||||
return {
|
||||
for: { type: String, reflect: true },
|
||||
message: { type: String }
|
||||
}
|
||||
}
|
||||
|
||||
static get styles() {
|
||||
return [popoverComponentStyles]
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
this.message = ''
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<span class="close-icon" @click="${this.closePopover}"><mwc-icon style="color: var(--black)">close</mwc-icon></span>
|
||||
<div><mwc-icon style="color: var(--black)">info</mwc-icon> ${this.message} <slot></slot></div>
|
||||
`
|
||||
}
|
||||
|
||||
attachToTarget(target) {
|
||||
if (!this.popperInstance && target) {
|
||||
this.popperInstance = createPopper(target, this, {
|
||||
placement: 'bottom',
|
||||
strategy: 'fixed'
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
window.customElements.define('popover-component', PopoverComponent)
|
@ -1,62 +0,0 @@
|
||||
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 }
|
||||
}
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
this.theme = localStorage.getItem('qortalTheme') ? localStorage.getItem('qortalTheme') : 'light'
|
||||
}
|
||||
|
||||
static get styles() {
|
||||
return [qortThemeToggleStyles]
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
|
||||
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
|
||||
})
|
||||
)
|
||||
|
||||
window.localStorage.setItem('qortalTheme', this.theme)
|
||||
|
||||
this.initTheme()
|
||||
}
|
||||
|
||||
initTheme() {
|
||||
document.querySelector('html').setAttribute('theme', this.theme)
|
||||
}
|
||||
}
|
||||
|
||||
window.customElements.define('qort-theme-toggle', QortThemeToggle)
|
@ -1,100 +0,0 @@
|
||||
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 {
|
||||
searchContentString: { type: String },
|
||||
theme: { type: String, reflect: true }
|
||||
}
|
||||
}
|
||||
|
||||
static get styles() {
|
||||
return [searchModalStyles]
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
this.searchContentString = ''
|
||||
this.theme = localStorage.getItem('qortalTheme') ? localStorage.getItem('qortalTheme') : 'light'
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<div style="display: inline;">
|
||||
<paper-icon-button icon="icons:search" @click=${() => this.openSearch()} title="${translate("websitespage.schange35")}"></paper-icon-button></a>
|
||||
</div>
|
||||
|
||||
<paper-dialog id="searchSettingsDialog" class="searchSettings">
|
||||
<div style="display: inline;">
|
||||
<div class="search">
|
||||
<vaadin-text-field
|
||||
style="width: 350px"
|
||||
id="searchContent"
|
||||
placeholder="${translate("explorerpage.exp1")}"
|
||||
value="${this.searchContentString}"
|
||||
@keydown="${this.searchKeyListener}"
|
||||
clear-button-visible
|
||||
>
|
||||
</vaadin-text-field>
|
||||
<paper-icon-button icon="icons:search" @click="${() => this.openUserInfo()}" title="${translate("websitespage.schange35")}"></paper-icon-button>
|
||||
<paper-icon-button icon="icons:close" @click="${() => this.closeSearch()}" title="${translate("general.close")}"></paper-icon-button>
|
||||
</div>
|
||||
</div>
|
||||
</paper-dialog>
|
||||
`
|
||||
}
|
||||
|
||||
firstUpdated() {
|
||||
// ...
|
||||
}
|
||||
|
||||
openSearch() {
|
||||
this.shadowRoot.getElementById('searchSettingsDialog').open()
|
||||
}
|
||||
|
||||
closeSearch() {
|
||||
this.shadowRoot.getElementById('searchSettingsDialog').close()
|
||||
}
|
||||
|
||||
searchKeyListener(e) {
|
||||
if (e.key === 'Enter') {
|
||||
this.openUserInfo()
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
} 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)
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user