diff --git a/index.template.html b/index.template.html index 83438a757..bb9f6d053 100644 --- a/index.template.html +++ b/index.template.html @@ -519,19 +519,8 @@

-
-
Activity
-
-
-
-
- -
-
-
+
+
@@ -861,18 +850,7 @@

-
Reward History (-)
-
-
-
-
- -
-
-
+
@@ -1257,6 +1235,6 @@

- +
diff --git a/locale/de/translation.js b/locale/de/translation.js index 9aab184a1..2a30e1635 100644 --- a/locale/de/translation.js +++ b/locale/de/translation.js @@ -228,7 +228,7 @@ export const de_translation = { description: 'Beschreibung', //Description activityBlockReward: 'Block-Erlös', //Block Reward activitySentTo: 'Gesendet an', //Sent to - activitySentToSelf: 'Umbuchung', //Sent to self + activitySentToSelf: 'Umbuchung', //Self activityShieldedAddress: 'Geschützte Adresse', //Shielded address activityReceivedShield: 'Erhalten von geschützter Adresse', //Received from Shielded address activityReceivedFrom: 'Erhalten von', //Received from diff --git a/locale/en/translation.js b/locale/en/translation.js index 7cf743cb1..a9817aa2c 100644 --- a/locale/en/translation.js +++ b/locale/en/translation.js @@ -224,12 +224,11 @@ export const en_translation = { time: 'Time', description: 'Description', activityBlockReward: 'Block Reward', - activitySentTo: 'Sent to', - activitySentToSelf: 'Sent to self', + activitySentTo: 'Sent to {r}', + activitySelf: 'self', activityShieldedAddress: 'Shielded address', - activityReceivedShield: 'Received from Shielded address', - activityReceivedFrom: 'Received from', - activityDelegatedTo: 'Delegated to', + activityReceivedFrom: 'Received from {s}', + activityDelegatedTo: 'Delegated to {r}', activityUndelegated: 'Undelegated', activityUnknown: 'Unknown Tx', password: 'Password', diff --git a/locale/es-mx/translation.js b/locale/es-mx/translation.js index 8de684b83..0e5d9439c 100644 --- a/locale/es-mx/translation.js +++ b/locale/es-mx/translation.js @@ -232,12 +232,11 @@ export const es_mx_translation = { time: 'Hora', //Time description: 'Descripción', //Description activityBlockReward: 'Recompensa del Bloque', //Block Reward - activitySentTo: 'Enviar a', //Sent to - activitySentToSelf: 'Enviar a ti mismo', //Sent to self + activitySentTo: 'Enviar a {r}', //Sent to + activitySelf: 'ti mismo', //Self activityShieldedAddress: 'Dirección Shielded', //Shielded address - activityReceivedShield: 'Recibido desde dirección Shielded', //Received from Shielded address - activityReceivedFrom: 'Recibido de', //Received from - activityDelegatedTo: 'Delegado a', //Delegated to + activityReceivedFrom: 'Recibido de {s}', //Received from + activityDelegatedTo: 'Delegado a {r}', //Delegated to activityUndelegated: 'No delegado', //Undelegated activityUnknown: 'Tx desconocido', //Unknown Tx password: 'Contraseña', //Password diff --git a/locale/fr/translation.js b/locale/fr/translation.js index ade492811..571c49aea 100644 --- a/locale/fr/translation.js +++ b/locale/fr/translation.js @@ -232,12 +232,11 @@ export const fr_translation = { time: 'Heure', //Time description: 'Description', //Description activityBlockReward: 'Récompense en bloc', //Block Reward - activitySentTo: 'Envoyé à', //Sent to - activitySentToSelf: 'Envoyé à soi-même', //Sent to self + activitySentTo: 'Envoyé à {r}', //Sent to + activitySelf: 'soi-même', //Self activityShieldedAddress: 'Adresse protégée', //Shielded address - activityReceivedShield: "Reçu d'une adresse protégée", //Received from Shielded address - activityReceivedFrom: 'Reçu de', //Received from - activityDelegatedTo: 'Délégué à', //Delegated to + activityReceivedFrom: 'Reçu de {s}', //Received from + activityDelegatedTo: 'Délégué à {r}', //Delegated to activityUndelegated: 'Non délégué', //Undelegated activityUnknown: 'Transaction inconnu', //Unknown Tx password: 'Mot de passe', //Password diff --git a/locale/it/translation.js b/locale/it/translation.js index e87375d8e..3abdb5a21 100644 --- a/locale/it/translation.js +++ b/locale/it/translation.js @@ -214,12 +214,11 @@ export const it_translation = { time: 'Tempo', //Time description: 'Descrizione', //Description activityBlockReward: 'Ricompensa del blocco', //Block Reward - activitySentTo: 'Invia a ', //Sent to - activitySentToSelf: 'Invia a te stesso', //Sent to self + activitySentTo: 'Inviato a {r}', //Sent to + activitySelf: 'te stesso', //Self activityShieldedAddress: 'Indirizzo protetto', //Shielded address - activityReceivedShield: 'Ricevi tramite indirizzo protetto', //Received from Shielded address - activityReceivedFrom: 'Ricevuto da ', //Received from - activityDelegatedTo: 'Delegato a ', //Delegated to + activityReceivedFrom: 'Ricevuto da {s}', //Received from + activityDelegatedTo: 'Delegato a {r}', //Delegated to activityUndelegated: 'Non delegato', //Undelegated activityUnknown: 'Tx sconosciuta', //Unknown Tx password: 'Password', //Password diff --git a/locale/ph/translation.js b/locale/ph/translation.js index a7a261956..35851ea3e 100644 --- a/locale/ph/translation.js +++ b/locale/ph/translation.js @@ -235,12 +235,11 @@ export const ph_translation = { time: 'Oras', //Time description: 'Description', //Description activityBlockReward: 'Block Reward', //Block Reward - activitySentTo: 'Naipadala sa ', //Sent to - activitySentToSelf: 'Naipadala sa sarili', //Sent to self + activitySentTo: 'Naipadala sa {r}', //Sent to + activitySelf: 'sarili', //Self activityShieldedAddress: 'Shielded address', //Shielded address - activityReceivedShield: 'Natanggap mula sa Shielded address', //Received from Shielded address - activityReceivedFrom: 'Natanggap mula sa', //Received from - activityDelegatedTo: 'Delegated to', //Delegated to + activityReceivedFrom: 'Natanggap mula sa {s}', //Received from + activityDelegatedTo: 'Delegated to {r}', //Delegated to activityUndelegated: 'Undeligated', //Undelegated activityUnknown: 'Unknown Tx', //Unknown Tx password: 'Password', //Password diff --git a/locale/pt-br/translation.js b/locale/pt-br/translation.js index 8d8733d14..2de7b6a8a 100644 --- a/locale/pt-br/translation.js +++ b/locale/pt-br/translation.js @@ -232,12 +232,11 @@ export const pt_br_translation = { time: 'Tempo', //Time description: 'Descrição', //Description activityBlockReward: 'Bloco Recompensa', //Block Reward - activitySentTo: 'Enviado para', //Sent to - activitySentToSelf: 'Enviado para si mesmo', //Sent to self + activitySentTo: 'Enviado para {r}', //Sent to + activitySelf: 'si mesmo', //Self activityShieldedAddress: 'Endereço Protegido', //Shielded address - activityReceivedShield: 'Recebido de Endereço Protegido', //Received from Shielded address - activityReceivedFrom: 'Recebido de', //Received from - activityDelegatedTo: 'Delegado a', //Delegated to + activityReceivedFrom: 'Recebido de {s}', //Received from + activityDelegatedTo: 'Delegado a {r}', //Delegated to activityUndelegated: 'Não é Delegado', //Undelegated activityUnknown: 'Tx desconhecido', //Unknown Tx password: 'Senha', //Password diff --git a/locale/pt-pt/translation.js b/locale/pt-pt/translation.js index 098ba0b87..06c1f2d13 100644 --- a/locale/pt-pt/translation.js +++ b/locale/pt-pt/translation.js @@ -232,12 +232,11 @@ export const pt_pt_translation = { time: 'Tempo', //Time description: 'Descrição', //Description activityBlockReward: 'Bloco Recompensa', //Block Reward - activitySentTo: 'Enviado para', //Sent to - activitySentToSelf: 'Enviado para si mesmo', //Sent to self + activitySentTo: 'Enviado para {r}', //Sent to + activitySelf: 'si mesmo', //Self activityShieldedAddress: 'Endereço Protegido', //Shielded address - activityReceivedShield: 'Recebido de Endereço Protegido', //Received from Shielded address - activityReceivedFrom: 'Recebido de', //Received from - activityDelegatedTo: 'Delegado a', //Delegated to + activityReceivedFrom: 'Recebido de {s}', //Received from + activityDelegatedTo: 'Delegado a {r}', //Delegated to activityUndelegated: 'Não é Delegado', //Undelegated activityUnknown: 'Tx desconhecido', //Unknown Tx password: 'Senha', //Password diff --git a/locale/template/translation.js b/locale/template/translation.js index 418a38db7..7fd634504 100644 --- a/locale/template/translation.js +++ b/locale/template/translation.js @@ -225,12 +225,11 @@ export const translation_template = { time: '', //Time description: '', //Description activityBlockReward: '', //Block Reward - activitySentTo: '', //Sent to - activitySentToSelf: '', //Sent to self + activitySentTo: '', //Sent to {} + activitySelf: '', // self activityShieldedAddress: '', //Shielded address - activityReceivedShield: '', //Received from Shielded address - activityReceivedFrom: '', //Received from - activityDelegatedTo: '', //Delegated to + activityReceivedFrom: '', //Received from {} + activityDelegatedTo: '', // Delegated to {} activityUndelegated: '', //Undelegated activityUnknown: '', //Unknown Tx password: '', //Password diff --git a/locale/uwu/translation.js b/locale/uwu/translation.js index be76369c4..0803a7fd5 100644 --- a/locale/uwu/translation.js +++ b/locale/uwu/translation.js @@ -228,12 +228,11 @@ export const uwu_translation = { time: 'Time', //Time description: 'Descwiption', //Description activityBlockReward: 'Bwock Rewawrd', //Block Reward - activitySentTo: 'Sentu to', //Sent to - activitySentToSelf: 'Sentu to selfu', //Sent to self - activityShieldedAddress: 'Shielded addwess', //Shielded address - activityReceivedShield: 'Recewived fwom Shielded addwess', //Received from Shielded address - activityReceivedFrom: 'Recewived fwom', //Received from - activityDelegatedTo: 'Delegwated to', //Delegated to + activitySentTo: 'Sentu to {r}', //Sent to + activitySelf: 'selfu', //Self + activityReceivedShield: 'Shielded addwess', //Received from Shielded address + activityReceivedFrom: 'Recewived fwom {s}', //Received from + activityDelegatedTo: 'Delegwated to {r}', //Delegated to activityUndelegated: 'Undelegwated', //Undelegated activityUnknown: 'Unknown Tx', //Unknown Tx password: 'Password', //Password diff --git a/package-lock.json b/package-lock.json index 7e09d1954..7671cbd4d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -34,7 +34,9 @@ "lodash-es": "^4.17.21", "pivx-promos": "^0.2.0", "qr-scanner": "^1.4.2", - "qrcode-generator": "^1.4.4" + "qrcode-generator": "^1.4.4", + "vue": "^3.3.4", + "vue-router": "^4.2.4" }, "devDependencies": { "@types/lodash-es": "^4.17.6", @@ -52,6 +54,7 @@ "node-polyfill-webpack-plugin": "^2.0.1", "prettier": "^2.8.1", "resource-loader": "^4.0.0-rc4", + "vue-loader": "^17.2.2", "webpack": "^5.75.0", "webpack-cli": "^5.0.1", "webpack-dev-server": "^4.11.1", @@ -67,6 +70,17 @@ "node": ">=0.10.0" } }, + "node_modules/@babel/parser": { + "version": "7.22.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.7.tgz", + "integrity": "sha512-7NF8pOkHP5o2vpmGgNGcfAeCvOYhGLyA3Z4eBQkT1RJlWu47n63bCs93QfJ2hIAFCil7L5P2IWhs1oToVgrL0Q==", + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/@discoveryjs/json-ext": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", @@ -833,6 +847,113 @@ "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@vue/compiler-core": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.3.4.tgz", + "integrity": "sha512-cquyDNvZ6jTbf/+x+AgM2Arrp6G4Dzbb0R64jiG804HRMfRiFXWI6kqUVqZ6ZR0bQhIoQjB4+2bhNtVwndW15g==", + "dependencies": { + "@babel/parser": "^7.21.3", + "@vue/shared": "3.3.4", + "estree-walker": "^2.0.2", + "source-map-js": "^1.0.2" + } + }, + "node_modules/@vue/compiler-dom": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.3.4.tgz", + "integrity": "sha512-wyM+OjOVpuUukIq6p5+nwHYtj9cFroz9cwkfmP9O1nzH68BenTTv0u7/ndggT8cIQlnBeOo6sUT/gvHcIkLA5w==", + "dependencies": { + "@vue/compiler-core": "3.3.4", + "@vue/shared": "3.3.4" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.3.4.tgz", + "integrity": "sha512-6y/d8uw+5TkCuzBkgLS0v3lSM3hJDntFEiUORM11pQ/hKvkhSKZrXW6i69UyXlJQisJxuUEJKAWEqWbWsLeNKQ==", + "dependencies": { + "@babel/parser": "^7.20.15", + "@vue/compiler-core": "3.3.4", + "@vue/compiler-dom": "3.3.4", + "@vue/compiler-ssr": "3.3.4", + "@vue/reactivity-transform": "3.3.4", + "@vue/shared": "3.3.4", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.0", + "postcss": "^8.1.10", + "source-map-js": "^1.0.2" + } + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.3.4.tgz", + "integrity": "sha512-m0v6oKpup2nMSehwA6Uuu+j+wEwcy7QmwMkVNVfrV9P2qE5KshC6RwOCq8fjGS/Eak/uNb8AaWekfiXxbBB6gQ==", + "dependencies": { + "@vue/compiler-dom": "3.3.4", + "@vue/shared": "3.3.4" + } + }, + "node_modules/@vue/devtools-api": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.5.0.tgz", + "integrity": "sha512-o9KfBeaBmCKl10usN4crU53fYtC1r7jJwdGKjPT24t348rHxgfpZ0xL3Xm/gLUYnc0oTp8LAmrxOeLyu6tbk2Q==" + }, + "node_modules/@vue/reactivity": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.3.4.tgz", + "integrity": "sha512-kLTDLwd0B1jG08NBF3R5rqULtv/f8x3rOFByTDz4J53ttIQEDmALqKqXY0J+XQeN0aV2FBxY8nJDf88yvOPAqQ==", + "dependencies": { + "@vue/shared": "3.3.4" + } + }, + "node_modules/@vue/reactivity-transform": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@vue/reactivity-transform/-/reactivity-transform-3.3.4.tgz", + "integrity": "sha512-MXgwjako4nu5WFLAjpBnCj/ieqcjE2aJBINUNQzkZQfzIZA4xn+0fV1tIYBJvvva3N3OvKGofRLvQIwEQPpaXw==", + "dependencies": { + "@babel/parser": "^7.20.15", + "@vue/compiler-core": "3.3.4", + "@vue/shared": "3.3.4", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.0" + } + }, + "node_modules/@vue/runtime-core": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.3.4.tgz", + "integrity": "sha512-R+bqxMN6pWO7zGI4OMlmvePOdP2c93GsHFM/siJI7O2nxFRzj55pLwkpCedEY+bTMgp5miZ8CxfIZo3S+gFqvA==", + "dependencies": { + "@vue/reactivity": "3.3.4", + "@vue/shared": "3.3.4" + } + }, + "node_modules/@vue/runtime-dom": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.3.4.tgz", + "integrity": "sha512-Aj5bTJ3u5sFsUckRghsNjVTtxZQ1OyMWCr5dZRAPijF/0Vy4xEoRCwLyHXcj4D0UFbJ4lbx3gPTgg06K/GnPnQ==", + "dependencies": { + "@vue/runtime-core": "3.3.4", + "@vue/shared": "3.3.4", + "csstype": "^3.1.1" + } + }, + "node_modules/@vue/server-renderer": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.3.4.tgz", + "integrity": "sha512-Q6jDDzR23ViIb67v+vM1Dqntu+HUexQcsWKhhQa4ARVzxOY2HbC7QRW/ggkDBd5BU+uM1sV6XOAP0b216o34JQ==", + "dependencies": { + "@vue/compiler-ssr": "3.3.4", + "@vue/shared": "3.3.4" + }, + "peerDependencies": { + "vue": "3.3.4" + } + }, + "node_modules/@vue/shared": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.3.4.tgz", + "integrity": "sha512-7OjdcV8vQ74eiz1TZLzZP4JwqM5fA94K6yntPS5Z25r9HDuGNzaGdgvwKYq6S+MxwF0TFRwe50fIR/MYnakdkQ==" + }, "node_modules/@webassemblyjs/ast": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz", @@ -2626,6 +2747,11 @@ "node": ">=8.0.0" } }, + "node_modules/csstype": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz", + "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==" + }, "node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -3201,6 +3327,11 @@ "node": ">=4.0" } }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==" + }, "node_modules/esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", @@ -3914,6 +4045,12 @@ "node": ">=4" } }, + "node_modules/hash-sum": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/hash-sum/-/hash-sum-2.0.0.tgz", + "integrity": "sha512-WdZTbAByD+pHfl/g9QSsBIIwy8IT+EsPiKDs0KNX+zSHhdDLFKdZu0BQHljvO+0QI/BasbMSUa8wYNCZTvhslg==", + "dev": true + }, "node_modules/hash.js": { "version": "1.1.7", "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", @@ -4868,6 +5005,22 @@ "node": ">=10" } }, + "node_modules/magic-string": { + "version": "0.30.1", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.1.tgz", + "integrity": "sha512-mbVKXPmS0z0G4XqFDCTllmDQ6coZzn94aMlb0o/A4HEHJCKcanlDZwYJgwnkmgD3jyWhUgj9VsPrfd972yPffA==", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.15" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/magic-string/node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==" + }, "node_modules/make-dir": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", @@ -5168,7 +5321,6 @@ "version": "3.3.4", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==", - "dev": true, "bin": { "nanoid": "bin/nanoid.cjs" }, @@ -5701,8 +5853,7 @@ "node_modules/picocolors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "dev": true + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" }, "node_modules/picomatch": { "version": "2.3.1", @@ -5830,7 +5981,6 @@ "version": "8.4.21", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.21.tgz", "integrity": "sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==", - "dev": true, "funding": [ { "type": "opencollective", @@ -7130,7 +7280,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -7727,6 +7876,54 @@ "integrity": "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==", "dev": true }, + "node_modules/vue": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.3.4.tgz", + "integrity": "sha512-VTyEYn3yvIeY1Py0WaYGZsXnz3y5UnGi62GjVEqvEGPl6nxbOrCXbVOTQWBEJUqAyTUk2uJ5JLVnYJ6ZzGbrSw==", + "dependencies": { + "@vue/compiler-dom": "3.3.4", + "@vue/compiler-sfc": "3.3.4", + "@vue/runtime-dom": "3.3.4", + "@vue/server-renderer": "3.3.4", + "@vue/shared": "3.3.4" + } + }, + "node_modules/vue-loader": { + "version": "17.2.2", + "resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-17.2.2.tgz", + "integrity": "sha512-aqNvKJvnz2A/6VWeJZodAo8XLoAlVwBv+2Z6dama+LHsAF+P/xijQ+OfWrxIs0wcGSJduvdzvTuATzXbNKkpiw==", + "dev": true, + "dependencies": { + "chalk": "^4.1.0", + "hash-sum": "^2.0.0", + "watchpack": "^2.4.0" + }, + "peerDependencies": { + "webpack": "^4.1.0 || ^5.0.0-0" + }, + "peerDependenciesMeta": { + "@vue/compiler-sfc": { + "optional": true + }, + "vue": { + "optional": true + } + } + }, + "node_modules/vue-router": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.2.4.tgz", + "integrity": "sha512-9PISkmaCO02OzPVOMq2w82ilty6+xJmQrarYZDkjZBfl4RvYAlt4PKnEX21oW4KTtWfa9OuO/b3qk1Od3AEdCQ==", + "dependencies": { + "@vue/devtools-api": "^6.5.0" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "vue": "^3.2.0" + } + }, "node_modules/watchpack": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", diff --git a/package.json b/package.json index 3737cfa6d..fdb0c1cd8 100644 --- a/package.json +++ b/package.json @@ -44,6 +44,7 @@ "node-polyfill-webpack-plugin": "^2.0.1", "prettier": "^2.8.1", "resource-loader": "^4.0.0-rc4", + "vue-loader": "^17.2.2", "webpack": "^5.75.0", "webpack-cli": "^5.0.1", "webpack-dev-server": "^4.11.1", @@ -75,7 +76,9 @@ "lodash-es": "^4.17.21", "pivx-promos": "^0.2.0", "qr-scanner": "^1.4.2", - "qrcode-generator": "^1.4.4" + "qrcode-generator": "^1.4.4", + "vue": "^3.3.4", + "vue-router": "^4.2.4" }, "alias": { "@ledgerhq/devices": "@ledgerhq/devices/lib-es" diff --git a/scripts/Activity.vue b/scripts/Activity.vue new file mode 100644 index 000000000..e546cedef --- /dev/null +++ b/scripts/Activity.vue @@ -0,0 +1,358 @@ + + + diff --git a/scripts/global.js b/scripts/global.js index c18a4a8da..881d4a9b2 100644 --- a/scripts/global.js +++ b/scripts/global.js @@ -46,9 +46,10 @@ import { Database } from './database.js'; import bitjs from './bitTrx.js'; import { checkForUpgrades } from './changelog.js'; import { FlipDown } from './flipdown.js'; +import { createApp } from 'vue'; +import Activity from './Activity.vue'; import { cReceiveType, - getNameOrAddress, guiAddContactPrompt, guiCheckRecipientInput, guiToggleReceiveType, @@ -66,11 +67,22 @@ export function isLoaded() { export let doms = {}; +// For now we'll import the component as a vue app by itself. Later, when the +// dashboard is rewritten in vue, we can simply add to the dashboard component template. +export const activityDashboard = createApp(Activity, { + title: 'Activity', + rewards: false, +}).mount('#activityDashboard'); + +export const stakingDashboard = createApp(Activity, { + title: 'Reward History', + rewards: true, +}).mount('#stakeActivity'); + export async function start() { doms = { domNavbarToggler: document.getElementById('navbarToggler'), domDashboard: document.getElementById('dashboard'), - domGuiStaking: document.getElementById('guiStaking'), domGuiWallet: document.getElementById('guiWallet'), domGettingStartedBtn: document.getElementById('gettingStartedBtn'), domGuiBalance: document.getElementById('guiBalance'), @@ -83,7 +95,6 @@ export async function start() { domGuiStakingValueCurrency: document.getElementById( 'guiStakingValueCurrency' ), - domGuiBalanceBox: document.getElementById('guiBalanceBox'), domBalanceReload: document.getElementById('balanceReload'), domBalanceReloadStaking: document.getElementById( 'balanceReloadStaking' @@ -92,13 +103,6 @@ export async function start() { domGuiBalanceStakingTicker: document.getElementById( 'guiBalanceStakingTicker' ), - domGuiStakingLoadMore: document.getElementById('stakingLoadMore'), - domGuiStakingLoadMoreIcon: document.getElementById( - 'stakingLoadMoreIcon' - ), - domGuiBalanceBoxStaking: document.getElementById( - 'guiBalanceBoxStaking' - ), domStakeAmount: document.getElementById('delegateAmount'), domUnstakeAmount: document.getElementById('undelegateAmount'), domStakeTab: document.getElementById('stakeTab'), @@ -126,7 +130,6 @@ export async function start() { ), domUnstakeAmountValue: document.getElementById('unstakeAmountValue'), - domGuiViewKey: document.getElementById('guiViewKey'), domModalQR: document.getElementById('ModalQR'), domModalQrLabel: document.getElementById('ModalQRLabel'), domModalQrReceiveTypeBtn: document.getElementById( @@ -207,7 +210,6 @@ export async function start() { domEncryptPasswordBox: document.getElementById('encryptPassword'), domEncryptPasswordFirst: document.getElementById('newPassword'), domEncryptPasswordSecond: document.getElementById('newPasswordRetype'), - domGuiAddress: document.getElementById('guiAddress'), domGenIt: document.getElementById('genIt'), domReqDesc: document.getElementById('reqDesc'), domReqDisplay: document.getElementById('reqDescDisplay'), @@ -217,12 +219,6 @@ export async function start() { domAvailToDelegate: document.getElementById('availToDelegate'), domAvailToUndelegate: document.getElementById('availToUndelegate'), domAnalyticsDescriptor: document.getElementById('analyticsDescriptor'), - domStakingRewardsList: document.getElementById( - 'staking-rewards-content' - ), - domStakingRewardsTitle: document.getElementById( - 'staking-rewards-title' - ), domMnemonicModalContent: document.getElementById( 'ModalMnemonicContent' ), @@ -319,6 +315,7 @@ export async function start() { domTestnetToggler: document.getElementById('testnetToggler'), domAdvancedModeToggler: document.getElementById('advancedModeToggler'), }; + await i18nStart(); await loadImages(); @@ -579,16 +576,16 @@ export function openTab(evt, tabName) { updateMasternodeTab(); } else if ( tabName === 'StakingTab' && - getNetwork().arrTxHistory.length === 0 + stakingDashboard.getTxCount() === 0 ) { // Refresh the TX list - updateActivityGUI(true, false); + stakingDashboard.update(false); } else if ( tabName === 'keypair' && getNetwork().arrTxHistory.length === 0 ) { // Refresh the TX list - updateActivityGUI(false, false); + activityDashboard.update(false); } } @@ -797,319 +794,6 @@ export async function openSendQRScanner() { ); } -/** - * Generate a DOM-optimised activity list - * @param {Array} arrTXs - The TX array to compute the list from - * @param {boolean} fRewards - If this list is for Reward transactions - * @returns {Promise} HTML - The Activity List in HTML string form - */ -export async function createActivityListHTML(arrTXs, fRewards = false) { - const cNet = getNetwork(); - const cDB = await Database.getInstance(); - const cAccount = await cDB.getAccount(); - - // Prepare the table HTML - let strList = ` - - - - - - - - - - `; - - // Prepare time formatting - const dateOptions = { - year: '2-digit', - month: '2-digit', - day: '2-digit', - }; - const timeOptions = { - hour: '2-digit', - minute: '2-digit', - hour12: true, - }; - - // And also keep track of our last Tx's timestamp, to re-use a cache, which is much faster than the slow `.toLocaleDateString` - let prevDateString = ''; - let prevTimestamp = 0; - - // Generate the TX list - for (const cTx of arrTXs) { - // If no account is loaded, we render nothing! - if (!wallet.isLoaded()) break; - - const dateTime = new Date(cTx.time * 1000); - - // If this Tx is older than 24h, then hit the `Date` cache logic, otherwise, use a `Time` and skip it - let strDate = - Date.now() / 1000 - cTx.time > 86400 - ? '' - : dateTime.toLocaleTimeString(undefined, timeOptions); - if (!strDate) { - if ( - prevDateString && - prevTimestamp - cTx.time * 1000 < 12 * 60 * 60 * 1000 - ) { - // Use our date cache - strDate = prevDateString; - } else { - // Create a new date, this Tx is too old to use the cache - prevDateString = dateTime.toLocaleDateString( - undefined, - dateOptions - ); - strDate = prevDateString; - } - } - - // Update the time cache - prevTimestamp = cTx.time * 1000; - - // Coinbase Transactions (rewards) require 100 confs - const fConfirmed = - cNet.cachedBlockCount - cTx.blockHeight >= fRewards ? 100 : 6; - - // Choose the correct icon and colour for the Tx type, or a question mark if the type is unknown - // Defaults: Reward Activity - let icon = 'fa-gift'; - let colour = 'white'; - - // Choose the content type, for the Dashboard; use a generative description, otherwise, a TX-ID - let txContent = fRewards ? cTx.id : translation.activityBlockReward; - - // Format the amount to reduce text size - let formattedAmt = ''; - if (cTx.amount < 0.01) { - formattedAmt = '<0.01'; - } else if (cTx.amount >= 100) { - formattedAmt = Math.round(cTx.amount).toString(); - } else { - formattedAmt = cTx.amount.toFixed(2); - } - - // For 'Send' or 'Receive' TXs: Check if this is a send-to-self transaction - let fSendToSelf = true; - if ( - cTx.type === HistoricalTxType.SENT || - cTx.type === HistoricalTxType.RECEIVED - ) { - // Check all addresses to find our own, caching them for performance - for (const strAddr of cTx.receivers.concat(cTx.senders)) { - // If a previous Tx checked this address, skip it, otherwise, check it against our own address(es) - if (!(await wallet.isOwnAddress(strAddr))) { - // External address, this is not a self-only Tx - fSendToSelf = false; - } - } - } - - // Generate an icon, colour and description for the Tx - if (!fRewards) { - switch (cTx.type) { - case HistoricalTxType.STAKE: - icon = 'fa-gift'; - break; - case HistoricalTxType.SENT: - icon = 'fa-minus'; - colour = '#f93c3c'; - // Figure out WHO this was sent to, and focus on them contextually - if (fSendToSelf) { - txContent = translation.activitySentToSelf; - } else { - // Otherwise, anything to us is likely change, so filter it away - const arrExternalAddresses = ( - await Promise.all( - cTx.receivers.map(async (addr) => [ - await wallet.isOwnAddress(addr), - addr, - ]) - ) - ) - .filter(([isOwnAddress, _]) => { - return !isOwnAddress; - }) - .map(([_, addr]) => - getNameOrAddress(cAccount, addr) - ); - txContent = - translation.activitySentTo + - ' ' + - (cTx.shieldedOutputs - ? translation.activityShieldedAddress - : [ - ...new Set( - arrExternalAddresses.map((addr) => - addr.length >= 32 - ? addr.substring(0, 6) - : addr - ) - ), - ].join(', ') + '...'); - } - break; - case HistoricalTxType.RECEIVED: { - icon = 'fa-plus'; - colour = '#5cff5c'; - // Figure out WHO this was sent from, and focus on them contextually - // Filter away any of our own addresses - const arrExternalAddresses = ( - await Promise.all( - cTx.senders.map(async (addr) => [ - await wallet.isOwnAddress(addr), - addr, - ]) - ) - ) - .filter(([isOwnAddress, _]) => { - return !isOwnAddress; - }) - .map(([_, addr]) => getNameOrAddress(cAccount, addr)); - - if (cTx.shieldedOutputs) { - txContent = translation.activityReceivedShield; - } else { - txContent = - translation.activityReceivedFrom + - ' ' + - [ - ...new Set( - arrExternalAddresses.map((addr) => - addr?.length >= 32 - ? addr.substring(0, 6) - : addr - ) - ), - ].join(', ') + - '...'; - } - break; - } - case HistoricalTxType.DELEGATION: - icon = 'fa-snowflake'; - txContent = - translation.activityDelegatedTo + - ' ' + - cTx.receivers[0].substring(0, 6) + - '...'; - break; - case HistoricalTxType.UNDELEGATION: - icon = 'fa-fire'; - txContent = translation.activityUndelegated; - break; - default: - icon = 'fa-question'; - txContent = translation.activityUnknown; - } - } - - // Render the list element from Tx data - strList += ` - - - - - - `; - } - - // End the table - strList += `
${translation.time}${ - fRewards ? translation.ID : translation.description - }${translation.amount}
- ${strDate} - - - ${sanitizeHTML( - txContent - )} - - - ${formattedAmt} ${ - cChainParams.current.TICKER - } - - ${ - fConfirmed - ? '' - : `` - } -
`; - - // Return the HTML string - return strList; -} - -/** - * Refreshes the specified activity table, charts and related information - */ -export async function updateActivityGUI(fStaking = false, fNewOnly = false) { - const cNet = getNetwork(); - - // Prevent the user from spamming refreshes - if (cNet.historySyncing) return; - - // Remember how much history we had previously - const nPrevHistory = cNet.arrTxHistory.length; - - // Choose the Dashboard or Staking UI accordingly - let domLoadMore = doms.domActivityLoadMore; - let domLoadMoreIcon = doms.domActivityLoadMoreIcon; - if (fStaking) { - domLoadMore = doms.domGuiStakingLoadMore; - domLoadMoreIcon = doms.domGuiStakingLoadMoreIcon; - } - - // Load rewards from the network, displaying the sync spin icon until finished - domLoadMoreIcon.classList.add('fa-spin'); - const arrTXs = await cNet.syncTxHistoryChunk(fNewOnly); - domLoadMoreIcon.classList.remove('fa-spin'); - - // If there's no change in history size post-sync, then we can cancel here, there's nothing new to render - if (nPrevHistory === cNet.arrTxHistory.length) return; - - // Check if all transactions are loaded - if (cNet.isHistorySynced) { - // Hide the load more button - domLoadMore.style.display = 'none'; - } - - // Render the new Activity lists - renderActivityGUI(arrTXs); -} - -/** - * Renders the Activity GUIs (without syncing or refreshing) - * @param {Array} arrTXs - */ -export async function renderActivityGUI(arrTXs) { - // For Staking: Filter the list for only Stakes, display total rewards from known history - const cNet = getNetwork(); - const arrStakes = arrTXs.filter((a) => a.type === HistoricalTxType.STAKE); - const nRewards = arrStakes.reduce((a, b) => a + b.amount, 0); - doms.domStakingRewardsTitle.innerHTML = `${ - cNet.isHistorySynced ? '' : '≥' - }${sanitizeHTML(nRewards)} ${cChainParams.current.TICKER}`; - - // Create and render the Dashboard Activity - doms.domActivityList.innerHTML = await createActivityListHTML( - arrTXs, - false - ); - // Create and render the Staking History - doms.domStakingRewardsList.innerHTML = await createActivityListHTML( - arrStakes, - true - ); -} - /** * Open the Explorer in a new tab for the current wallet, or a specific address * @param {string?} strAddress - Optional address to open, if void, the master key is used @@ -2891,7 +2575,7 @@ export function refreshChainData() { // Fetch block count + UTXOs, update the UI for new transactions cNet.getBlockCount().then((_) => { // Fetch latest Activity - updateActivityGUI(false, true); + activityDashboard.update(true); // If it's open: update the Governance Dashboard if (doms.domGovTab.classList.contains('active')) { diff --git a/scripts/i18n.js b/scripts/i18n.js index fcb65ee08..8b9ab636b 100644 --- a/scripts/i18n.js +++ b/scripts/i18n.js @@ -10,10 +10,11 @@ import { it_translation } from '../locale/it/translation.js'; import { de_translation } from '../locale/de/translation.js'; import { Database } from './database.js'; import { fillAnalyticSelect, setTranslation } from './settings.js'; -import { renderActivityGUI, updateEncryptionGUI } from './global.js'; +import { updateEncryptionGUI } from './global.js'; import { wallet } from './wallet.js'; import { getNetwork } from './network.js'; import { cReceiveType, guiToggleReceiveType } from './contacts-book.js'; +import { reactive } from 'vue'; /** * @type {translation_template} @@ -23,7 +24,7 @@ export const ALERTS = {}; /** * @type {translation_template} */ -export let translation = {}; +export const translation = reactive({}); // TRANSLATION //Create an object of objects filled with all the translations @@ -65,7 +66,6 @@ export function switchTranslation(langName) { const cNet = getNetwork(); if (wallet.isLoaded() && cNet) { updateEncryptionGUI(); - renderActivityGUI(cNet.arrTxHistory); } loadAlerts(); fillAnalyticSelect(); @@ -79,7 +79,7 @@ export function switchTranslation(langName) { langName + ") is not supported yet, if you'd like to contribute translations (for rewards!) contact us on GitHub or Discord!" ); - translation = translatableLanguages.en_translation; + switchTranslation('en'); return false; } } diff --git a/scripts/index.js b/scripts/index.js index 9673cdc59..1ac79b206 100644 --- a/scripts/index.js +++ b/scripts/index.js @@ -42,7 +42,6 @@ export { toggleBottomMenu, createProposal, switchSettings, - updateActivityGUI, govVote, } from './global.js'; export { wallet, generateWallet, importWallet } from './wallet.js'; diff --git a/scripts/network.js b/scripts/network.js index c7fd4d8dc..59e28553f 100644 --- a/scripts/network.js +++ b/scripts/network.js @@ -368,8 +368,9 @@ export class ExplorerNetwork extends Network { async syncTxHistoryChunk(fNewOnly = false) { // Do not allow multiple calls at once if (this.historySyncing) { - return false; + return this.arrTxHistory; } + try { if (!this.enabled || !this.masterKey) return this.arrTxHistory; this.historySyncing = true; diff --git a/scripts/settings.js b/scripts/settings.js index 08a5db7ce..04a39c5be 100644 --- a/scripts/settings.js +++ b/scripts/settings.js @@ -5,11 +5,11 @@ import { guiUpdateImportInput, mempool, refreshChainData, - renderActivityGUI, setDisplayForAllWalletOptions, - updateActivityGUI, updateEncryptionGUI, updateGovernanceTab, + activityDashboard, + stakingDashboard, } from './global.js'; import { wallet, hasEncryptedWallet, importWallet } from './wallet.js'; import { cChainParams } from './chain_params.js'; @@ -276,6 +276,9 @@ export async function setExplorer(explorer, fSilent = false) { const network = new ExplorerNetwork(cExplorer.url, wallet.getMasterKey()); setNetwork(network); + activityDashboard.reset(); + stakingDashboard.reset(); + // Update the selector UI doms.domExplorerSelect.value = cExplorer.url; @@ -540,17 +543,15 @@ export async function toggleTestnet() { doms.domImportWallet.style.display = 'none'; } - // Re-render the Activity UI as empty - await renderActivityGUI([]); - mempool.UTXOs = []; getBalance(true); getStakingBalance(true); await updateEncryptionGUI(!!wallet.getMasterKey()); await fillExplorerSelect(); await fillNodeSelect(); - await updateActivityGUI(); await updateGovernanceTab(); + activityDashboard.reset(); + stakingDashboard.reset(); } export function toggleDebug() { diff --git a/webpack.common.js b/webpack.common.js index f71cc7830..63a5142bb 100644 --- a/webpack.common.js +++ b/webpack.common.js @@ -7,6 +7,7 @@ const HtmlWebpackPlugin = require('html-webpack-plugin'); const NodePolyfillPlugin = require('node-polyfill-webpack-plugin'); const MiniCssExtractPlugin = require('mini-css-extract-plugin'); const CopyPlugin = require('copy-webpack-plugin'); +const { VueLoaderPlugin } = require('vue-loader'); const { readFileSync } = require('fs'); // Inject the Changelog and Version to the app @@ -35,6 +36,17 @@ module.exports = { test: /\.(jpe?g|png|gif|svg|mp3|svg)$/i, type: 'asset/resource', }, + { + test: /\.vue/i, + use: { + loader: 'vue-loader', + options: { + compilerOptions: { + isCustomElement: (tag) => tag === 'center', + }, + }, + }, + }, ], }, resolve: { @@ -55,6 +67,7 @@ module.exports = { 'width=device-width, initial-scale=1, shrink-to-fit=no', }, }), + new VueLoaderPlugin(), // Polyfill for non web libraries new NodePolyfillPlugin(), // Prevents non styled flashing on load diff --git a/webpack.dev.js b/webpack.dev.js index 84301c1f4..dd55f125e 100644 --- a/webpack.dev.js +++ b/webpack.dev.js @@ -4,6 +4,7 @@ const path = require('path'); const { merge } = require('webpack-merge'); const common = require('./webpack.common.js'); +const webpack = require('webpack'); module.exports = merge(common, { mode: 'development', @@ -18,4 +19,10 @@ module.exports = merge(common, { overlay: false, }, }, + plugins: [ + new webpack.DefinePlugin({ + __VUE_OPTIONS_API__: false, + __VUE_PROD_DEVTOOLS__: true, + }), + ], }); diff --git a/webpack.prod.js b/webpack.prod.js index 52c9b416f..00d9840ab 100644 --- a/webpack.prod.js +++ b/webpack.prod.js @@ -4,6 +4,7 @@ const { merge } = require('webpack-merge'); const common = require('./webpack.common.js'); const CssMinimizerPlugin = require('css-minimizer-webpack-plugin'); +const webpack = require('webpack'); module.exports = merge(common, { mode: 'production', @@ -11,4 +12,10 @@ module.exports = merge(common, { // Inject a CSS minimizer alongside the default JS minimizer (the '...' is the inclusion of the default webpack JS minimizer!) minimizer: [new CssMinimizerPlugin(), '...'], }, + plugins: [ + new webpack.DefinePlugin({ + __VUE_OPTIONS_API__: false, + __VUE_PROD_DEVTOOLS__: false, + }), + ], });