Compare commits

..

7 Commits

Author SHA1 Message Date
QuickMythril
1f37de8f12 Added BTC, NMC, DASH, & FIRO to Trade Portal
Also fixed the estimated trade fee for RVN, and updated the ACCT for BTC.
2022-05-07 03:30:17 -04:00
QuickMythril
fb6b5d54fa Update DGB/RVN to match master
Added trade fees to new-coins since they were not part of the master branch.
2022-05-07 03:02:34 -04:00
QuickMythril
f367544498 Added NMC, DASH, & FIRO to Wallet
Note that the edits which show removed code are actually still there.  The file difference is being rendered improperly.
2022-05-07 02:45:19 -04:00
QuickMythril
c3b1875672 Update DGB/RVN to match master
Added some changes that were not applied to new-coins during the previous merge from the master branch.
2022-05-07 01:49:13 -04:00
QuickMythril
6f59c7c583 Added support for NMC, DASH, & FIRO
This allows for sending and creating wallets.
2022-05-07 01:24:54 -04:00
QuickMythril
b66f5d1e1f Fixed some typos
Added missing RVN entry, reordered an entry, added missing DGB/RVN entry, and fixed a duplicate entry.
2022-05-07 01:04:51 -04:00
QuickMythril
d596df746d Added new coin icons
For BTC trades and NMC, DASH, & FIRO support.
2022-05-07 00:14:18 -04:00
4144 changed files with 50147 additions and 160022 deletions

11
.gitignore vendored
View File

@ -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
View 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

View File

@ -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.

View File

@ -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:

View File

@ -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()
})

View File

@ -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 "")

View File

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

View File

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

View File

@ -1,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)

View File

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

View File

@ -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
}

View File

@ -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

View File

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

View File

@ -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
},
},
}

View File

@ -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
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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.

View File

@ -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;
}

View File

@ -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

View File

@ -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>

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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
}

View File

@ -1,3 +0,0 @@
import WebWorker from 'web-worker:./computePowWorkerFile.js'
export default WebWorker

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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;
}

View File

@ -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)

View File

@ -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
}

View File

@ -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
}

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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
}

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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
})
}

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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&timestamp=${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)

View File

@ -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&timestamp=${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)

View File

@ -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)

View File

@ -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>
&nbsp; ${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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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