diff --git a/qortal-ui-core/language/de.json b/qortal-ui-core/language/de.json
index 449987b0..518cab5c 100644
--- a/qortal-ui-core/language/de.json
+++ b/qortal-ui-core/language/de.json
@@ -128,7 +128,11 @@
"snack2":"UI mit Knoten verbunden",
"snack3":"Benutzerdefinierter Knoten erfolgreich hinzugefügt und gespeichert",
"snack4":"Knoten erfolgreich gespeichert als",
- "snack5":"Knoten erfolgreich importiert"
+ "snack5":"Knoten erfolgreich importiert",
+ "exp1":"Privaten Hauptschlüssel exportieren",
+ "exp2":"Hauptschlüssel exportieren",
+ "exp3":"Exportieren",
+ "exp4":"Bitte wählen Sie eine Brieftasche aus, um den privaten Hauptschlüssel zu sichern."
},
"appinfo":{
"blockheight":"Blockhöhe",
@@ -155,7 +159,8 @@
"continue":"Fortsetzen",
"save":"Speichern",
"balance":"Guthaben",
- "balances":"IHR WALLET-GUTHABEN"
+ "balances":"IHR WALLET-GUTHABEN",
+ "update":"AKTUALISIERE WALLET-GUTHABEN"
},
"startminting":{
"smchange1":"Prägekonten können nicht abgerufen werden",
@@ -328,7 +333,8 @@
"tchange45":"AUTO KAUFEN MIT",
"tchange46":"AUTOKAUF",
"tchange47":"Verkaufe für diesen Preis",
- "tchange48":"NICHT GENUG"
+ "tchange48":"NICHT GENUG",
+ "tchange49":"Preisdiagramm"
},
"rewardsharepage":{
"rchange1":"Belohnungsanteile",
@@ -606,7 +612,11 @@
"gchange51":"Beitreten",
"gchange52":"Administrator",
"gchange53":"Mitglied",
- "gchange54":"Mitglieder"
+ "gchange54":"Mitglieder",
+ "gchange55":"Private Gruppe suchen",
+ "gchange56":"Zu suchender Gruppenname",
+ "gchange57":"Privater Gruppenname nicht gefunden",
+ "gchange58":"Beachten Sie, dass der Gruppenname genau übereinstimmen muss."
},
"puzzlepage":{
"pchange1":"Rätsel",
@@ -794,6 +804,15 @@
"mg49":"Beim Drücken auf Bestätigen wird die Anfrage zum Abbrechen der Einladung gesendet!",
"mg50":"Kommt bald...",
"mg51":"Minimum 3 Zeichen / Maximum 32 Zeichen",
- "mg52":"Maximal 128 Zeichen"
+ "mg52":"Maximal 128 Zeichen",
+ "mg53":"Ihre offenen Beitrittsanfragen",
+ "mg54":"Keine offenen Beitrittsanfragen",
+ "mg55":"Sind Sie sicher, dass Sie die Beitrittsanfrage von diesem Mitglied annehmen werden?",
+ "mg56":"Beim Drücken von Bestätigen wird die Beitrittsanfrage gesendet!",
+ "mg57":"Beitrittsanfrage erfolgreich angenommen",
+ "mg58":"ETWAS GING FALSCH",
+ "mg59":"Beitrittsanfrage abbrechen erfolgreich akzeptiert",
+ "mg60":"Sind Sie sicher, dass Sie die Beitrittsanfrage dieses Mitglieds abbrechen möchten?",
+ "mg61":"Beim Drücken auf Bestätigen wird die Anfrage zum Abbrechen des Beitritts gesendet!"
}
}
diff --git a/qortal-ui-core/language/es.json b/qortal-ui-core/language/es.json
index 19e7c00f..eab3f6d0 100644
--- a/qortal-ui-core/language/es.json
+++ b/qortal-ui-core/language/es.json
@@ -128,7 +128,11 @@
"snack2":"UI conectada al nodo",
"snack3":"Nodo personalizado agregado y guardado con éxito",
"snack4":"Nodos guardados con éxito como",
- "snack5":"Nodos importados con éxito"
+ "snack5":"Nodos importados con éxito",
+ "exp1":"Exportar clave maestra privada",
+ "exp2":"Exportar clave maestra",
+ "exp3":"Exportar",
+ "exp4":"Elija una billetera para hacer una copia de seguridad de la clave maestra privada."
},
"appinfo":{
"blockheight":"Altura del Bloque",
@@ -155,7 +159,8 @@
"continue":"Continuar",
"save":"Guardar",
"balance":"Saldo",
- "balances":"LOS SALDOS DE TU BILLETERA"
+ "balances":"LOS SALDOS DE TU BILLETERA",
+ "update":"ACTUALIZAR SALDOS DE CARTERA"
},
"startminting":{
"smchange1":"No se pueden obtener cuentas de acuñación",
@@ -328,7 +333,8 @@
"tchange45":"AUTO COMPRAR CON",
"tchange46":"COMPRA AUTOMÁTICA",
"tchange47":"Vender por este precio",
- "tchange48":"NO ES SUFICIENTE"
+ "tchange48":"NO ES SUFICIENTE",
+ "tchange49":"Gráfico de precios"
},
"rewardsharepage":{
"rchange1":"Rewardshares",
@@ -606,7 +612,11 @@
"gchange51":"Unirse",
"gchange52":"Aministrador",
"gchange53":"Miembro",
- "gchange54":"Miembros"
+ "gchange54":"Miembros",
+ "gchange55":"Buscar grupo privado",
+ "gchange56":"Nombre del grupo a buscar",
+ "gchange57":"Nombre de grupo privado no encontrado",
+ "gchange58":"Tenga en cuenta que el nombre del grupo debe coincidir exactamente."
},
"puzzlepage":{
"pchange1":"Rompecabezas",
@@ -794,6 +804,15 @@
"mg49":"¡Al presionar confirmar, se enviará la solicitud de cancelación de invitación!",
"mg50":"Próximamente...",
"mg51":"Mínimo 3 Caracteres / Máximo 32 Caracteres",
- "mg52":"Máximo de 128 caracteres"
+ "mg52":"Máximo de 128 caracteres",
+ "mg53":"Tus solicitudes abiertas de unión",
+ "mg54":"Sin solicitudes de unión abiertas",
+ "mg55":"¿Está seguro de aceptar la solicitud de ingreso de este miembro?",
+ "mg56":"¡Al presionar confirmar, se enviará la solicitud de aceptación de ingreso!",
+ "mg57":"Solicitud de ingreso aceptada con éxito",
+ "mg58":"ALGO SALIO MAL",
+ "mg59":"Solicitud de cancelación de unión aceptada con éxito",
+ "mg60":"¿Está seguro de cancelar la solicitud de ingreso de este miembro?",
+ "mg61":"¡Al presionar confirmar, se enviará la solicitud de cancelación de unión!"
}
}
diff --git a/qortal-ui-core/language/fr.json b/qortal-ui-core/language/fr.json
index 9cc2d17c..f22be6c5 100644
--- a/qortal-ui-core/language/fr.json
+++ b/qortal-ui-core/language/fr.json
@@ -128,7 +128,11 @@
"snack2":"Interface utilisateur connectée au noeud",
"snack3":"Noeud personnalisé ajouté et enregistré avec succès",
"snack4":"Les noeuds ont été enregistrés avec succès sous",
- "snack5":"Les noeuds ont été importés avec succès"
+ "snack5":"Les noeuds ont été importés avec succès",
+ "exp1":"Exporter la clé principale privée",
+ "exp2":"Exporter la clé principale",
+ "exp3":"Exporter",
+ "exp4":"Veuillez choisir un portefeuille pour sauvegarder la clé principale privée."
},
"appinfo":{
"blockheight":"Hauteur de bloc",
@@ -155,7 +159,8 @@
"continue":"Continuer",
"save":"Sauvegarder",
"balance":"Solde",
- "balances":"VOS SOLDES DE PORTEFEUILLE"
+ "balances":"VOS SOLDES DE PORTEFEUILLE",
+ "update":"METTRE À JOUR LES SOLDES DES PORTEFEUILLES"
},
"startminting":{
"smchange1":"Impossible de récupérer les comptes de frappe",
@@ -328,7 +333,8 @@
"tchange45":"ACHAT AUTO AVEC",
"tchange46":"ACHAT AUTOMATIQUE",
"tchange47":"Vendre à ce prix",
- "tchange48":"PAS ASSEZ"
+ "tchange48":"PAS ASSEZ",
+ "tchange49":"Tableau des prix"
},
"rewardsharepage":{
"rchange1":"Récompenses",
@@ -606,7 +612,11 @@
"gchange51":"Rejoindre",
"gchange52":"Admin",
"gchange53":"Membre",
- "gchange54":"Membres"
+ "gchange54":"Membres",
+ "gchange55":"Rechercher un groupe privé",
+ "gchange56":"Nom du groupe à rechercher",
+ "gchange57":"Nom de groupe privé introuvable",
+ "gchange58":"Notez que le nom du groupe doit correspondre exactement."
},
"puzzlepage":{
"pchange1":"Puzzles",
@@ -794,6 +804,15 @@
"mg49":"En appuyant sur confirmer, la demande d'annulation d'invitation sera envoyée !",
"mg50":"Bientôt disponible...",
"mg51":"Minimum 3 caractères / Maximum 32 caractères",
- "mg52":"Maximum 128 caractères"
+ "mg52":"Maximum 128 caractères",
+ "mg53":"Vos demandes d'ouverture de jointure",
+ "mg54":"Aucune demande de jointure ouverte",
+ "mg55":"Êtes-vous sûr d'accepter la demande d'adhésion de ce membre ?",
+ "mg56":"En appuyant sur confirmer, la demande d'adhésion acceptée sera envoyée !",
+ "mg57":"Demande d'adhésion acceptée avec succès",
+ "mg58":"QUELQUE CHOSE S'EST TROMPÉ",
+ "mg59":"Annuler la demande d'adhésion acceptée avec succès",
+ "mg60":"Êtes-vous sûr d'annuler la demande d'adhésion de ce membre ?",
+ "mg61":"En appuyant sur confirmer, la demande d'annulation de l'adhésion sera envoyée !"
}
}
diff --git a/qortal-ui-core/language/hindi.json b/qortal-ui-core/language/hindi.json
index 77e0de5a..617a2b64 100644
--- a/qortal-ui-core/language/hindi.json
+++ b/qortal-ui-core/language/hindi.json
@@ -129,7 +129,11 @@
"snack2":"यूआई नोड से जुड़ा",
"snack3":"कस्टम नोड को सफलतापूर्वक जोड़ा और सहेजा गया",
"snack4":"नोड्स सफलतापूर्वक सहेजे गए",
- "snack5":"नोड्स सफलतापूर्वक आयात किए गए"
+ "snack5":"नोड्स सफलतापूर्वक आयात किए गए",
+ "exp1":"निजी मास्टर कुंजी निर्यात करें",
+ "exp2":"निर्यात मास्टर कुंजी",
+ "exp3":"निर्यात",
+ "exp4":"निजी मास्टर कुंजी का बैकअप लेने के लिए कृपया एक वॉलेट चुनें।"
},
"appinfo":{
"blockheight":"ब्लॉक ऊँचाई",
@@ -156,7 +160,8 @@
"continue":"जारी रखें",
"save":"सहेजें",
"balance":"संतुलन",
- "balances":"आपका वॉलेट बैलेंस"
+ "balances":"आपका वॉलेट बैलेंस",
+ "update":"अपडेट वॉलेट बैलेंस"
},
"startminting":{
"smchange1":"खनन खाते नहीं लाए जा सकते",
@@ -329,7 +334,8 @@
"tchange45":"ऑटो के साथ खरीदें",
"tchange46":"ऑटो खरीदें",
"tchange47":"इस कीमत पर बेचें",
- "tchange48":"पर्याप्त नहीं"
+ "tchange48":"पर्याप्त नहीं",
+ "tchange49":"मूल्य चार्ट"
},
"rewardsharepage":{
"rchange1":"रिवॉर्डशेयर",
@@ -607,7 +613,11 @@
"gchange51":"शामिल हों",
"gchange52":"व्यवस्थापक",
"gchange53":"सदस्य",
- "gchange54":"सदस्यों"
+ "gchange54":"सदस्यों",
+ "gchange55":"निजी समूह खोजें",
+ "gchange56":"खोजने के लिए समूह का नाम",
+ "gchange57":"निजी समूह का नाम नहीं मिला",
+ "gchange58":"ध्यान दें कि समूह का नाम सटीक मेल खाना चाहिए।"
},
"puzzlepage":{
"pchange1":"पहेलि",
@@ -794,7 +804,16 @@
"mg48":"क्या आप वाकई इस सदस्य के लिए आमंत्रण रद्द करना चाहते हैं?",
"mg49":"पुष्टि करें दबाने पर, रद्द आमंत्रण अनुरोध भेजा जाएगा!",
"mg50":"जल्द ही आ रहा है ...",
- "mg51": "न्यूनतम 3 वर्ण / अधिकतम 32 वर्ण",
- "mg52": "अधिकतम 128 वर्ण"
+ "mg51":"न्यूनतम 3 वर्ण / अधिकतम 32 वर्ण",
+ "mg52":"अधिकतम 128 वर्ण",
+ "mg53":"आपका खुला शामिल होने का अनुरोध",
+ "mg54":"नो ओपन जॉइन रिक्वेस्ट",
+ "mg55":"क्या आप निश्चित रूप से इस सदस्य के शामिल होने के अनुरोध को स्वीकार करना चाहते हैं?",
+ "mg56":"पुष्टि करें दबाने पर, स्वीकार करने का अनुरोध भेजा जाएगा!",
+ "mg57":"जुड़ने का अनुरोध सफलतापूर्वक स्वीकार किया गया",
+ "mg58":"कुछ गलत हो गया",
+ "mg59":"रद्द करें शामिल होने का अनुरोध सफलतापूर्वक स्वीकार किया गया",
+ "mg60":"क्या आप निश्चित रूप से इस सदस्य के शामिल होने के अनुरोध को रद्द करना चाहते हैं?",
+ "mg61":"पुष्टि करें दबाने पर, रद्द करने का अनुरोध भेजा जाएगा!"
}
}
diff --git a/qortal-ui-core/language/hr.json b/qortal-ui-core/language/hr.json
index 0c3013c1..3b369348 100644
--- a/qortal-ui-core/language/hr.json
+++ b/qortal-ui-core/language/hr.json
@@ -128,7 +128,11 @@
"snack2":"UI spojen na čvor",
"snack3":"Uspješno dodan i spremljen prilagođeni čvor",
"snack4":"Čvorovi su uspješno spremljeni kao",
- "snack5":"Čvorovi su uspješno uvezeni"
+ "snack5":"Čvorovi su uspješno uvezeni",
+ "exp1":"Izvezi privatni glavni ključ",
+ "exp2":"Glavni ključ izvoza",
+ "exp3":"Izvoz",
+ "exp4":"Odaberite novčanik za sigurnosnu kopiju privatnog glavnog ključa."
},
"appinfo":{
"blockheight":"Visina bloka",
@@ -155,7 +159,8 @@
"continue":"Nastavi",
"save":"Spremi",
"balance":"Kreditna",
- "balances":"VAŠ NOVČANIK JE NA SALJU"
+ "balances":"VAŠ NOVČANIK JE NA SALJU",
+ "update":"AŽURIRAJTE STANJE NOVČANIKA"
},
"startminting":{
"smchange1":"Nije moguće dohvatiti račune za kovanje",
@@ -328,7 +333,8 @@
"tchange45":"AUTO KUPITE SA",
"tchange46":"AUTO OTKUP",
"tchange47":"Prodaj za ovu cijenu",
- "tchange48":"NEDOVOLJNO"
+ "tchange48":"NEDOVOLJNO",
+ "tchange49":"Grafikon cijena"
},
"rewardsharepage":{
"rchange1":"Nagradni udio (Rewardshares)",
@@ -606,7 +612,11 @@
"gchange51":"Pridruži",
"gchange52":"Admin",
"gchange53":"Član",
- "gchange54":"Članovi"
+ "gchange54":"Članovi",
+ "gchange55":"Traži privatnu grupu",
+ "gchange56":"Naziv grupe za pretraživanje",
+ "gchange57":"Ime privatne grupe nije pronađeno",
+ "gchange58":"Imajte na umu da se naziv grupe mora točno podudarati."
},
"puzzlepage":{
"pchange1":"Zagonetke",
@@ -793,7 +803,16 @@
"mg48":"Jeste li sigurni da želite otkazati poziv za ovog člana?",
"mg49":"Pritiskom na potvrdu, zahtjev za pozivnicu za otkazivanje bit će poslan!",
"mg50":"Uskoro...",
- "mg51": "Minimalno 3 znaka / Maksimalno 32 znaka",
- "mg52": "Maksimalno 128 znakova"
+ "mg51":"Minimalno 3 znaka / Maksimalno 32 znaka",
+ "mg52":"Maksimalno 128 znakova",
+ "mg53":"Vaši otvoreni zahtjevi za pridruživanje",
+ "mg54":"Nema otvorenih zahtjeva za pridruživanje",
+ "mg55":"Jeste li sigurni da prihvaćate zahtjev za pridruživanje ovog člana?",
+ "mg56":"Pritiskom na potvrdu, zahtjev za prihvaćanje pridruživanja bit će poslan!",
+ "mg57":"Zahtjev za pridruživanje uspješno prihvaćen",
+ "mg58":"NEŠTO JE POŠLO PO ZLOU",
+ "mg59":"Poništi zahtjev za pridruživanje uspješno prihvaćen",
+ "mg60":"Jeste li sigurni da želite otkazati zahtjev za pridruživanje ovog člana?",
+ "mg61":"Pritiskom na potvrdu, bit će poslan zahtjev za otkazivanje pridruživanja!"
}
}
diff --git a/qortal-ui-core/language/hu.json b/qortal-ui-core/language/hu.json
index 5bebf438..c30d020d 100644
--- a/qortal-ui-core/language/hu.json
+++ b/qortal-ui-core/language/hu.json
@@ -128,7 +128,11 @@
"snack2":"UI csatlakozik a csomóponthoz",
"snack3":"Az egyéni csomópont sikeresen hozzáadva és mentve",
"snack4":"A csomópontok sikeresen mentve másként",
- "snack5":"A csomópontok sikeresen importálva"
+ "snack5":"A csomópontok sikeresen importálva",
+ "exp1":"Privát főkulcs exportálása",
+ "exp2":"Főkulcs exportálása",
+ "exp3":"Exportálás",
+ "exp4":"Kérjük, válasszon egy tárcát a privát főkulcs biztonsági mentéséhez."
},
"appinfo":{
"blockheight":"Blokk Magassága",
@@ -155,7 +159,8 @@
"continue":"Folytatódik/folytatáshoz",
"save":"Mentéshez",
"balance":"Hitel",
- "balances":"A PÉNZTÁRCSA EGYENLEGEK"
+ "balances":"A PÉNZTÁRCSA EGYENLEGEK",
+ "update":"FRISSÍTSE A PÉNZTÁRCSA-EGYENLEGEKET"
},
"startminting":{
"smchange1":"Nem lehet lekérni a pénzverési számlákat",
@@ -328,7 +333,8 @@
"tchange45":"AUTOMATIKUS VÁSÁRLÁS",
"tchange46":"AUTOMATIKUS VÁSÁRLÁS",
"tchange47":"Eladni ezen az áron",
- "tchange48":"NEM ELÉG"
+ "tchange48":"NEM ELÉG",
+ "tchange49":"Árdiagram"
},
"rewardsharepage":{
"rchange1":"Jutalommegosztások",
@@ -606,7 +612,11 @@
"gchange51":"Csatlakoz",
"gchange52":"Kormányozo",
"gchange53":"Tag",
- "gchange54":"Tagok"
+ "gchange54":"Tagok",
+ "gchange55":"Keresés privát csoportban",
+ "gchange56":"A keresendő csoport neve",
+ "gchange57":"A privát csoport neve nem található",
+ "gchange58":"Ne feledje, hogy a csoport nevének pontosan meg kell egyeznie."
},
"puzzlepage":{
"pchange1":"Rejtvények",
@@ -794,6 +804,15 @@
"mg49":"A megerősítés megnyomására a rendszer elküldi a meghívó visszavonási kérelmét!",
"mg50":"Hamarosan...",
"mg51":"Minimum 3 karakter / legfeljebb 32 karakter",
- "mg52":"Legfeljebb 128 karakter"
+ "mg52":"Legfeljebb 128 karakter",
+ "mg53":"Az Ön nyitott csatlakozási kérelmei",
+ "mg54":"Nincsenek nyitott csatlakozási kérelmek",
+ "mg55":"Biztosan elfogadja ennek a tagnak a csatlakozási kérelmét?",
+ "mg56":"A megerősítés megnyomására a rendszer elküldi az elfogadási csatlakozási kérelmet!",
+ "mg57":"Csatlakozási kérelem sikeresen elfogadva",
+ "mg58":"VALAMI RÁMADT",
+ "mg59":"Csatlakozási kérelem visszavonása sikeresen elfogadva",
+ "mg60":"Biztosan visszavonja ennek a tagnak a csatlakozási kérelmét?",
+ "mg61":"A megerősítés megnyomására a csatlakozás megszakítási kérelme elküldésre kerül!"
}
}
diff --git a/qortal-ui-core/language/it.json b/qortal-ui-core/language/it.json
index 1592451a..6efe10d4 100644
--- a/qortal-ui-core/language/it.json
+++ b/qortal-ui-core/language/it.json
@@ -128,7 +128,11 @@
"snack2":"Interfaccia utente collegata al nodo",
"snack3":"Nodo personalizzato aggiunto e salvato con successo",
"snack4":"Nodi salvati con successo come",
- "snack5":"Nodi importati correttamente"
+ "snack5":"Nodi importati correttamente",
+ "exp1":"Esporta chiave master privata",
+ "exp2":"Esporta chiave master",
+ "exp3":"Esporta",
+ "exp4":"Scegli un portafoglio per il backup della chiave master privata."
},
"appinfo":{
"blockheight":"Altezza blocco",
@@ -155,7 +159,8 @@
"continue":"Continua",
"save":"Salva",
"balance":"Saldo",
- "balances":"IL TUO SALDO DEL PORTAFOGLIO"
+ "balances":"I SALDI DEL TUO PORTAFOGLIO",
+ "update":"AGGIORNA I SALDI DEL PORTAFOGLIO"
},
"startminting":{
"smchange1":"Impossibile recuperare i conti di conio",
@@ -328,7 +333,8 @@
"tchange45":"ACQUISTA AUTO CON",
"tchange46":"ACQUISTO AUTO",
"tchange47":"Vendi a questo prezzo",
- "tchange48":"NON ABBASTANZA"
+ "tchange48":"NON ABBASTANZA",
+ "tchange49":"Tabella dei prezzi"
},
"rewardsharepage":{
"rchange1":"Quote di ricompensa",
@@ -606,7 +612,11 @@
"gchange51":"Unisciti",
"gchange52":"Amministratore",
"gchange53":"Membro",
- "gchange54":"Membri"
+ "gchange54":"Membri",
+ "gchange55":"Cerca gruppo privato",
+ "gchange56":"Nome gruppo da cercare",
+ "gchange57":"Nome gruppo privato non trovato",
+ "gchange58":"Nota che il nome del gruppo deve corrispondere esattamente."
},
"puzzlepage":{
"pchange1":"Puzzle",
@@ -794,6 +804,15 @@
"mg49":"Premendo conferma, verrà inviata la richiesta di annullamento dell'invito!",
"mg50":"Prossimamente...",
"mg51":"Minimo 3 caratteri / Massimo 32 caratteri",
- "mg52":"Massimo 128 caratteri"
+ "mg52":"Massimo 128 caratteri",
+ "mg53":"Le tue richieste di partecipazione aperte",
+ "mg54":"Nessuna richiesta di partecipazione aperta",
+ "mg55":"Sei sicuro di accettare la richiesta di adesione di questo membro ?",
+ "mg56":"Premendo conferma, verrà inviata la richiesta di partecipazione accettata!",
+ "mg57":"Richiesta di partecipazione accettata con successo",
+ "mg58":"QUALCOSA È ANDATO storto",
+ "mg59":"Annulla richiesta di partecipazione accettata con successo",
+ "mg60":"Sei sicuro di voler annullare la richiesta di adesione di questo membro ?",
+ "mg61":"Premendo conferma, verrà inviata la richiesta di annullamento partecipazione!"
}
}
diff --git a/qortal-ui-core/language/ko.json b/qortal-ui-core/language/ko.json
index c36bd93b..2dc9b4d6 100644
--- a/qortal-ui-core/language/ko.json
+++ b/qortal-ui-core/language/ko.json
@@ -128,7 +128,11 @@
"snack2":"노드에 연결된 UI",
"snack3":"사용자 정의 노드를 성공적으로 추가하고 저장했습니다.",
"snack4":"노드가 다음으로 성공적으로 저장되었습니다",
- "snack5":"노드를 성공적으로 가져왔습니다."
+ "snack5":"노드를 성공적으로 가져왔습니다.",
+ "exp1":"개인 마스터 키 내보내기",
+ "exp2":"마스터 키 내보내기",
+ "exp3":"내보내기",
+ "exp4":"개인 마스터 키를 백업할 지갑을 선택하세요."
},
"appinfo":{
"blockheight":"블록 높이",
@@ -155,7 +159,8 @@
"continue":"계속하다",
"save":"저장",
"balance":"균형",
- "balances":"지갑 잔액"
+ "balances":"지갑 잔액",
+ "update":"월렛 잔액 업데이트"
},
"startminting":{
"smchange1":"발행 계정을 가져올 수 없습니다",
@@ -328,7 +333,8 @@
"tchange45":"자동 구매",
"tchange46":"자동 구매",
"tchange47":"이 가격에 팔아요",
- "tchange48":"부족한"
+ "tchange48":"부족한",
+ "tchange49":"가격 차트"
},
"rewardsharepage":{
"rchange1":"보상 공유",
@@ -606,7 +612,11 @@
"gchange51":"가입",
"gchange52":"관리자",
"gchange53":"회원",
- "gchange54":"회원들"
+ "gchange54":"회원들",
+ "gchange55":"비공개 그룹 검색",
+ "gchange56":"검색할 그룹 이름",
+ "gchange57":"비공개 그룹 이름을 찾을 수 없음",
+ "gchange58":"그룹 이름이 정확히 일치해야 합니다."
},
"puzzlepage":{
"pchange1":"퍼즐",
@@ -794,6 +804,15 @@
"mg49":"확인을 누르면 초대 취소 요청이 전송됩니다!",
"mg50":"출시 예정...",
"mg51":"최소 3자 / 최대 32자",
- "mg52":"최대 128자"
+ "mg52":"최대 128자",
+ "mg53":"귀하의 오픈 조인 요청",
+ "mg54":"오픈 조인 요청 없음",
+ "mg55":"이 회원의 가입 요청을 수락하시겠습니까?",
+ "mg56":"확인을 누르면 가입 수락 요청이 전송됩니다!",
+ "mg57":"가입 요청이 성공적으로 수락됨",
+ "mg58":"뭔가 잘못되었습니다",
+ "mg59":"가입 요청 취소 성공",
+ "mg60":"이 회원의 가입 요청을 취소하시겠습니까?",
+ "mg61":"확인을 누르면 가입 취소 요청이 전송됩니다!"
}
}
diff --git a/qortal-ui-core/language/no.json b/qortal-ui-core/language/no.json
index 9a16ace5..85054308 100644
--- a/qortal-ui-core/language/no.json
+++ b/qortal-ui-core/language/no.json
@@ -128,7 +128,11 @@
"snack2":"UI koblet til node",
"snack3":"Egendefinert node er lagt til og lagret",
"snack4":"Noder ble lagret som",
- "snack5":"Noder ble importert"
+ "snack5":"Noder ble importert",
+ "exp1":"Eksporter privat hovednøkkel",
+ "exp2":"Eksporter hovednøkkel",
+ "exp3":"Eksporter",
+ "exp4":"Velg en lommebok for å sikkerhetskopiere den private hovednøkkelen."
},
"appinfo":{
"blockheight":"Blokkhøyde",
@@ -155,7 +159,8 @@
"continue":"Fortsett",
"save":"Lagre",
"balance":"Saldo",
- "balances":"DIN WALLET-SALDO"
+ "balances":"DIN WALLET-SALDO",
+ "update":"OPPDATERT WALLET-SALDOER"
},
"startminting":{
"smchange1":"Kan ikke hente myntingkontoer",
@@ -328,7 +333,8 @@
"tchange45":"AUTOKJØP MED",
"tchange46":"AUTOKJØP",
"tchange47":"Selges for denne prisen",
- "tchange48":"IKKE NOK"
+ "tchange48":"IKKE NOK",
+ "tchange49":"Prisdiagram"
},
"rewardsharepage":{
"rchange1":"Belønningsdel",
@@ -606,7 +612,11 @@
"gchange51":"Bli med",
"gchange52":"Admin",
"gchange53":"Medlem",
- "gchange54":"Medlemmer"
+ "gchange54":"Medlemmer",
+ "gchange55":"Søk i privat gruppe",
+ "gchange56":"Gruppenavn å søke",
+ "gchange57":"Privat gruppenavn ikke funnet",
+ "gchange58":"Merk at gruppenavnet må samsvare nøyaktig."
},
"puzzlepage":{
"pchange1":"Puzzles",
@@ -794,6 +804,15 @@
"mg49":"Når du trykker på bekreftelse, vil forespørselen om kansellering av invitasjon bli sendt!",
"mg50":"Kommer snart...",
"mg51":"Minimum 3 tegn / maksimum 32 tegn",
- "mg52": "Maksimalt 128 tegn"
+ "mg52":"Maksimalt 128 tegn",
+ "mg53":"Dine åpne forespørsler om bli med",
+ "mg54":"Ingen åpne forespørsler om medlemskap",
+ "mg55":"Er du sikker på å godta forespørselen fra dette medlemmet?",
+ "mg56":"Når du trykker på bekreft, vil forespørselen om å godta bli sendt bli sendt!",
+ "mg57":"Bli med forespørsel ble godtatt",
+ "mg58":"NOE GIKK FEIL",
+ "mg59":"Avbryt deltakelsesforespørsel ble godtatt",
+ "mg60":"Er du sikker på å avbryte forespørselen om å bli medlem fra dette medlemmet?",
+ "mg61":"Når du trykker på bekreft, vil forespørselen om kansellering bli sendt!"
}
}
diff --git a/qortal-ui-core/language/pl.json b/qortal-ui-core/language/pl.json
index e57f725a..0b3ef019 100644
--- a/qortal-ui-core/language/pl.json
+++ b/qortal-ui-core/language/pl.json
@@ -128,7 +128,11 @@
"snack2":"Interfejs użytkownika połączony z węzłem",
"snack3":"Pomyślnie dodano i zapisano niestandardowy węzeł",
"snack4":"Węzły pomyślnie zapisane jako",
- "snack5":"Węzły pomyślnie zaimportowane"
+ "snack5":"Węzły pomyślnie zaimportowane",
+ "exp1":"Eksportuj prywatny klucz główny",
+ "exp2":"Eksportuj klucz główny",
+ "exp3":"Eksportuj",
+ "exp4":"Wybierz portfel do wykonania kopii zapasowej prywatnego klucza głównego."
},
"appinfo":{
"blockheight":"Wysokość bloku",
@@ -155,7 +159,8 @@
"continue":"Kontynuuj",
"save":"Zapisz",
"balance":"Saldo",
- "balances":"SALDO TWOJEGO PORTFELA"
+ "balances":"SALDO TWOJEGO PORTFELA",
+ "update":"AKTUALIZUJ SALDA W PORTFELU"
},
"startminting":{
"smchange1":"Nie można pobrać kont menniczych",
@@ -328,7 +333,8 @@
"tchange45":"AUTO KUP Z",
"tchange46":"AUTO KUP",
"tchange47":"Sprzedaj za tę cenę",
- "tchange48":"NIEWYSTARCZAJĄCO"
+ "tchange48":"NIEWYSTARCZAJĄCO",
+ "tchange49":"Tabela cen"
},
"rewardsharepage":{
"rchange1":"Podział nagród",
@@ -606,7 +612,11 @@
"gchange51":"Dołącz",
"gchange52":"Administrator",
"gchange53":"Członek",
- "gchange54":"Członkowie"
+ "gchange54":"Członkowie",
+ "gchange55":"Wyszukaj grupę prywatną",
+ "gchange56":"Nazwa grupy do wyszukania",
+ "gchange57":"Nie znaleziono nazwy grupy prywatnej",
+ "gchange58":"Zauważ, że nazwa grupy musi dokładnie pasować."
},
"puzzlepage":{
"pchange1":"Zagadki",
@@ -793,7 +803,16 @@
"mg48":"Czy na pewno chcesz anulować zaproszenie dla tego członka?",
"mg49":"Po naciśnięciu potwierdzenia zostanie wysłana prośba o anulowanie zaproszenia!",
"mg50":"Już wkrótce...",
- "mg51": "Minimum 3 znaki / Maksymalnie 32 znaki",
- "mg52": "Maksymalnie 128 znaków"
+ "mg51":"Minimum 3 znaki / Maksymalnie 32 znaki",
+ "mg52":"Maksymalnie 128 znaków",
+ "mg53":"Twoje otwarte prośby o dołączenie",
+ "mg54":"Brak otwartych próśb o dołączenie",
+ "mg55":"Czy na pewno akceptujesz prośbę o dołączenie od tego członka?",
+ "mg56":"Po naciśnięciu potwierdzenia zostanie wysłana prośba o zaakceptowanie dołączenia!",
+ "mg57":"Prośba o dołączenie pomyślnie zaakceptowana",
+ "mg58":"COŚ POszło nie tak",
+ "mg59":"Anuluj prośbę o dołączenie pomyślnie zaakceptowaną",
+ "mg60":"Czy na pewno chcesz anulować prośbę o dołączenie od tego członka?",
+ "mg61":"Po naciśnięciu potwierdzenia, zostanie wysłana prośba o anulowanie dołączenia!"
}
}
diff --git a/qortal-ui-core/language/pt.json b/qortal-ui-core/language/pt.json
index 5f56fe4c..41e55557 100644
--- a/qortal-ui-core/language/pt.json
+++ b/qortal-ui-core/language/pt.json
@@ -128,7 +128,11 @@
"snack2":"UI conectada ao nó",
"snack3":"Nó personalizado adicionado e salvo com sucesso",
"snack4":"Nós salvos com sucesso como",
- "snack5":"Nós importados com sucesso"
+ "snack5":"Nós importados com sucesso",
+ "exp1":"Exportar Chave Mestra Privada",
+ "exp2":"Exportar Chave Mestra",
+ "exp3":"Exportar",
+ "exp4":"Por favor, escolha uma carteira para fazer backup da chave mestra privada."
},
"appinfo":{
"blockheight":"Altura do Bloco",
@@ -155,7 +159,8 @@
"continue":"Continuar",
"save":"Salvar",
"balance":"Saldo",
- "balances":"SEUS SALDOS DE CARTEIRA"
+ "balances":"SEUS SALDOS DE CARTEIRA",
+ "update":"ATUALIZAR SALDOS DA CARTEIRA"
},
"startminting":{
"smchange1":"Não é possível buscar contas de cunhagem",
@@ -328,7 +333,8 @@
"tchange45":"COMPRA AUTOMÁTICA COM",
"tchange46":"COMPRA AUTOMÁTICA",
"tchange47":"Vendo por este preço",
- "tchange48":"INSUFICIENTE"
+ "tchange48":"INSUFICIENTE",
+ "tchange49":"Tabela de preços"
},
"rewardsharepage":{
"rchange1":"Ações de recompensa",
@@ -606,7 +612,11 @@
"gchange51":"Entrar",
"gchange52":"Administrador",
"gchange53":"Membro",
- "gchange54":"Membros"
+ "gchange54":"Membros",
+ "gchange55":"Pesquisar Grupo Privado",
+ "gchange56":"Nome do grupo para pesquisar",
+ "gchange57":"Nome do grupo privado não encontrado",
+ "gchange58":"Observe que o nome do grupo deve corresponder exatamente."
},
"puzzlepage":{
"pchange1":"Enigmas",
@@ -794,6 +804,15 @@
"mg49":"Ao pressionar confirmar, a solicitação de cancelamento do convite será enviada!",
"mg50":"Em Breve...",
"mg51":"Mínimo de 3 caracteres / Máximo de 32 caracteres",
- "mg52":"Máximo de 128 caracteres"
+ "mg52":"Máximo de 128 caracteres",
+ "mg53":"Suas solicitações de entrada abertas",
+ "mg54":"Nenhuma solicitação de entrada aberta",
+ "mg55":"Tem certeza que aceita a solicitação de entrada deste membro?",
+ "mg56":"Ao pressionar confirmar, o pedido de aceitação de entrada será enviado!",
+ "mg57":"Solicitação de entrada aceita com sucesso",
+ "mg58":"ALGO DEU ERRADO",
+ "mg59":"Cancelar pedido de entrada aceito com sucesso",
+ "mg60":"Tem certeza que deseja cancelar a solicitação de ingresso deste membro ?",
+ "mg61":"Ao pressionar confirmar, o pedido de cancelamento de adesão será enviado!"
}
}
diff --git a/qortal-ui-core/language/ro.json b/qortal-ui-core/language/ro.json
index d65da8f2..29efe5b0 100644
--- a/qortal-ui-core/language/ro.json
+++ b/qortal-ui-core/language/ro.json
@@ -128,7 +128,11 @@
"snack2":"Interfata de utilizare conectata la nod",
"snack3":"Nod personalizat adaugat si salvat cu succes",
"snack4":"Nodurile au fost salvate cu succes ca",
- "snack5":"Nodurile au fost importate cu succes"
+ "snack5":"Nodurile au fost importate cu succes",
+ "exp1":"Exportați cheia principală privată",
+ "exp2":"Exportați cheia principală",
+ "exp3":"Export",
+ "exp4":"Vă rugăm să alegeți un portofel pentru a face backup cheii master private."
},
"appinfo":{
"blockheight":"Dimensiunea blocului",
@@ -155,7 +159,8 @@
"continue":"Continua",
"save":"Salveza",
"balance":"Credit",
- "balances":"SOLDELE PORTOTELULUI DVS"
+ "balances":"SOLDELE PORTOTELULUI DVS",
+ "update":"ACTUALIZAȚI SOLDELE PORTOTELULUI"
},
"startminting":{
"smchange1":"Nu se pot prelua conturile de batere",
@@ -328,7 +333,8 @@
"tchange45":"CUMPARA AUTOMATA CU",
"tchange46":"CUMPARARE AUTOMATA",
"tchange47":"Vinde la acest pret",
- "tchange48":"INSUFICIENT"
+ "tchange48":"INSUFICIENT",
+ "tchange49":"Diagrama prețurilor"
},
"rewardsharepage":{
"rchange1":"Cote de recompensa",
@@ -606,7 +612,11 @@
"gchange51":"Inscriere",
"gchange52":"Admin",
"gchange53":"Membru",
- "gchange54":"Membrii"
+ "gchange54":"Membrii",
+ "gchange55":"Căutați grup privat",
+ "gchange56":"Numele grupului de căutat",
+ "gchange57":"Numele grupului privat nu a fost găsit",
+ "gchange58":"Rețineți că numele grupului trebuie să se potrivească exact."
},
"puzzlepage":{
"pchange1":"Puzzle-uri",
@@ -794,6 +804,15 @@
"mg49":"La apăsarea confirmării, cererea de anulare a invitației va fi trimisă!",
"mg50":"În curând...",
"mg51":"Minim 3 caractere / Maxim 32 de caractere",
- "mg52":"Maximum 128 de caractere"
+ "mg52":"Maximum 128 de caractere",
+ "mg53":"Solicitările dvs. de înscriere deschise",
+ "mg54":"Fără solicitări de aderare deschise",
+ "mg55":"Sunteți sigur că acceptați solicitarea de alăturare de la acest membru?",
+ "mg56":"La apăsarea confirmării, va fi trimisă cererea de acceptare a înscrierii!",
+ "mg57":"Solicitarea de înscriere a fost acceptată cu succes",
+ "mg58":"CEVA A MERAT GREUT",
+ "mg59":"Anularea cererii de aderare a fost acceptată cu succes",
+ "mg60":"Sigur anulați solicitarea de alăturare de la acest membru?",
+ "mg61":"La apăsarea confirmării, cererea de anulare a aderării va fi trimisă!"
}
}
diff --git a/qortal-ui-core/language/rs.json b/qortal-ui-core/language/rs.json
index 1cad90c9..ccd07326 100644
--- a/qortal-ui-core/language/rs.json
+++ b/qortal-ui-core/language/rs.json
@@ -128,7 +128,11 @@
"snack2":"UI je povezan sa čvorom",
"snack3":"Uspešno dodat i sačuvan prilagođeni čvor",
"snack4":"Čvorovi su uspešno sačuvani kao",
- "snack5":"Čvorovi su uspešno uvezeni"
+ "snack5":"Čvorovi su uspešno uvezeni",
+ "exp1":"Izvezi privatni glavni ključ",
+ "exp2":"Izvezi glavni ključ",
+ "exp3":"Izvoz",
+ "exp4":"Molimo izaberite novčanik za rezervnu kopiju privatnog glavnog ključa."
},
"appinfo":{
"blockheight":"Visina Bloka",
@@ -155,7 +159,8 @@
"continue":"Nastavite",
"save":"Sačuvajte",
"balance":"Kredit",
- "balances":"VAŠI STANJE U NOVČANIKU"
+ "balances":"VAŠI STANJE U NOVČANIKU",
+ "update":"AŽURIRAJTE STANJE NOVČANIKA"
},
"startminting":{
"smchange1":"Nije moguće preuzeti naloge za kovanje",
@@ -328,7 +333,8 @@
"tchange45":"AUTO KUPI SA",
"tchange46":"AUTO BUI",
"tchange47":"Prodaj za ovu cenu",
- "tchange48":"NEDOVOLJNO"
+ "tchange48":"NEDOVOLJNO",
+ "tchange49":"Grafik cena"
},
"rewardsharepage":{
"rchange1":"Udeo nagrade",
@@ -606,7 +612,11 @@
"gchange51":"Uđite",
"gchange52":"Administrator",
"gchange53":"Član",
- "gchange54":"Članovi"
+ "gchange54":"Članovi",
+ "gchange55":"Pretraži privatnu grupu",
+ "gchange56":"Ime grupe za pretragu",
+ "gchange57":"Ime privatne grupe nije pronađeno",
+ "gchange58":"Imajte na umu da ime grupe mora potpuno da se podudara."
},
"puzzlepage":{
"pchange1":"Slagalice",
@@ -794,6 +804,15 @@
"mg49":"Pritiskom na potvrdi, zahtev za otkazivanje pozivnice će biti poslat!",
"mg50":"Uskoro...",
"mg51":"Najmanje 3 znaka / maksimalno 32 znaka",
- "mg52":"Maksimalno 128 znakova"
+ "mg52":"Maksimalno 128 znakova",
+ "mg53":"Vaši zahtevi za otvoreno pridruživanje",
+ "mg54":"Nema otvorenih zahteva za pridruživanje",
+ "mg55":"Da li ste sigurni da prihvatate zahtev za pridruživanje ovog člana?",
+ "mg56":"Pritiskom na potvrdi, biće poslat zahtev za prihvatanje pridruživanja!",
+ "mg57":"Zahtev za pridruživanje je uspešno prihvaćen",
+ "mg58":"NEŠTO JE POŠLO NA ZLO",
+ "mg59":"Zahtev za otkazivanje pridruživanja je uspešno prihvaćen",
+ "mg60":"Da li ste sigurni da otkažete zahtev za pridruživanje ovog člana?",
+ "mg61":"Pritiskom na potvrdu, zahtev za otkazivanje pridruživanja će biti poslat!"
}
}
diff --git a/qortal-ui-core/language/ru.json b/qortal-ui-core/language/ru.json
index 1865f095..b7539374 100644
--- a/qortal-ui-core/language/ru.json
+++ b/qortal-ui-core/language/ru.json
@@ -128,7 +128,11 @@
"snack2":"Пользовательский интерфейс, подключенный к узлу",
"snack3":"Пользовательский узел успешно добавлен и сохранен",
"snack4":"Узлы успешно сохранены как",
- "snack5":"Узлы успешно импортированы"
+ "snack5":"Узлы успешно импортированы",
+ "exp1":"Экспорт закрытого мастер-ключа",
+ "exp2":"Экспорт мастер-ключа",
+ "exp3":"Экспорт",
+ "exp4":"Пожалуйста, выберите кошелек для резервного копирования приватного главного ключа."
},
"appinfo":{
"blockheight":"Высота блока",
@@ -155,7 +159,8 @@
"continue":"Продолжить",
"save":"Сохранить",
"balance":"кредит",
- "balances":"БАЛАНС ВАШЕГО КОШЕЛЬКА"
+ "balances":"БАЛАНС ВАШЕГО КОШЕЛЬКА",
+ "update":"ОБНОВИТЬ БАЛАНС КОШЕЛЬКА"
},
"startminting":{
"smchange1":"Не удается получить учетные записи минтинга",
@@ -328,7 +333,8 @@
"tchange45":"АВТО КУПИТЬ С",
"tchange46":"АВТО КУПИТЬ",
"tchange47":"Продать по этой цене",
- "tchange48":"НЕДОСТАТОЧНО"
+ "tchange48":"НЕДОСТАТОЧНО",
+ "tchange49":"График цен"
},
"rewardsharepage":{
"rchange1":"Вознаграждения",
@@ -606,7 +612,11 @@
"gchange51":"Присоединиться",
"gchange52":"Администратор",
"gchange53":"Член",
- "gchange54":"Члены"
+ "gchange54":"Члены",
+ "gchange55":"Поиск в закрытой группе",
+ "gchange56":"Имя группы для поиска",
+ "gchange57":"Имя частной группы не найдено",
+ "gchange58":"Обратите внимание, что название группы должно точно совпадать."
},
"puzzlepage":{
"pchange1":"Головоломки",
@@ -794,6 +804,15 @@
"mg49":"При нажатии подтверждения будет отправлен запрос на отмену приглашения!",
"mg50":"Скоро...",
"mg51":"Минимум 3 символа / максимум 32 символа",
- "mg52":"Максимум 128 символов"
+ "mg52":"Максимум 128 символов",
+ "mg53":"Ваши открытые запросы на вступление",
+ "mg54":"Открытых запросов на присоединение нет",
+ "mg55":"Вы уверены, что принимаете запрос на вступление от этого участника?",
+ "mg56":"При нажатии подтверждения будет отправлен запрос на присоединение!",
+ "mg57":"Запрос на присоединение успешно принят",
+ "mg58":"ЧТО-ТО ПОШЛО НЕ ТАК",
+ "mg59":"Запрос на отмену присоединения успешно принят",
+ "mg60":"Вы уверены, что отмените запрос на вступление от этого участника?",
+ "mg61":"При нажатии кнопки подтверждения будет отправлен запрос на отмену присоединения!"
}
}
diff --git a/qortal-ui-core/language/us.json b/qortal-ui-core/language/us.json
index e6a0ab28..6818dafc 100644
--- a/qortal-ui-core/language/us.json
+++ b/qortal-ui-core/language/us.json
@@ -93,42 +93,46 @@
"selectfile": "Select file",
"dragfile": "Drag and drop backup here"
},
- "settings": {
- "generalinfo": "General Account Info",
- "address": "Address",
- "publickey": "Public Key",
- "settings": "Settings",
- "account": "Account",
- "security": "Security",
- "qr_login_menu_item": "QR Login",
- "qr_login_description_1": "Scan this code to unlock your wallet on other device using the same password which you logged in with.",
- "qr_login_description_2": "Choose a password which you will use to unlock your wallet on other device after scanning the QR code.",
- "qr_login_button_1": "Show login QR code",
- "qr_login_button_2": "Generate login QR code",
- "notifications": "Notifications",
- "accountsecurity": "Account Security",
- "password": "Password",
- "download": "Download Backup File",
- "choose": "Please choose a password to encrypt your backup with. (This can be the same as the one you logged in with, or different)",
- "block": "Block Notifications (Coming Soon...)",
- "playsound": "Play Sound",
- "shownotifications": "Show Notifications",
- "nodeurl": "Node Url",
- "nodehint": "Select a node from the default list of nodes above or add a custom node to the list above by clicking on the button below",
- "addcustomnode": "Add Custom Node",
- "addandsave": "Add And Save",
- "protocol": "Protocol",
- "domain": "Domain",
- "port": "Port",
- "import": "Import Nodes",
- "export": "Export Nodes",
- "deletecustomnode": "Remove All Custom Nodes",
- "warning": "Your existing nodes will be deleted and from backup new created.",
- "snack1": "Successfully deleted and added standard nodes",
- "snack2": "UI conected to node",
- "snack3": "Successfully added and saved custom node",
- "snack4": "Nodes successfully saved as",
- "snack5": "Nodes successfully imported"
+ "settings":{
+ "generalinfo":"General Account Info",
+ "address":"Address",
+ "publickey":"Public Key",
+ "settings":"Settings",
+ "account":"Account",
+ "security":"Security",
+ "qr_login_menu_item":"QR Login",
+ "qr_login_description_1":"Scan this code to unlock your wallet on other device using the same password which you logged in with.",
+ "qr_login_description_2":"Choose a password which you will use to unlock your wallet on other device after scanning the QR code.",
+ "qr_login_button_1":"Show login QR code",
+ "qr_login_button_2":"Generate login QR code",
+ "notifications":"Notifications",
+ "accountsecurity":"Account Security",
+ "password":"Password",
+ "download":"Download Backup File",
+ "choose":"Please choose a password to encrypt your backup with. (This can be the same as the one you logged in with, or different)",
+ "block":"Block Notifications (Coming Soon...)",
+ "playsound":"Play Sound",
+ "shownotifications":"Show Notifications",
+ "nodeurl":"Node Url",
+ "nodehint":"Select a node from the default list of nodes above or add a custom node to the list above by clicking on the button below",
+ "addcustomnode":"Add Custom Node",
+ "addandsave":"Add And Save",
+ "protocol":"Protocol",
+ "domain":"Domain",
+ "port":"Port",
+ "import":"Import Nodes",
+ "export":"Export Nodes",
+ "deletecustomnode":"Remove All Custom Nodes",
+ "warning":"Your existing nodes will be deleted and from backup new created.",
+ "snack1":"Successfully deleted and added standard nodes",
+ "snack2":"UI conected to node",
+ "snack3":"Successfully added and saved custom node",
+ "snack4":"Nodes successfully saved as",
+ "snack5":"Nodes successfully imported",
+ "exp1":"Export Private Master Key",
+ "exp2":"Export Master Key",
+ "exp3":"Export",
+ "exp4":"Please choose a wallet to backup the private master key."
},
"appinfo":{
"blockheight":"Block Height",
@@ -155,7 +159,8 @@
"continue":"Continue",
"save":"Save",
"balance":"Balance",
- "balances":"YOUR WALLET BALANCES"
+ "balances":"YOUR WALLET BALANCES",
+ "update":"UPDATE WALLET BALANCES"
},
"startminting": {
"smchange1": "Cannot fetch minting accounts",
@@ -328,7 +333,8 @@
"tchange45":"AUTO BUY WITH",
"tchange46":"AUTO BUY",
"tchange47":"Sell for this price",
- "tchange48":"NOT ENOUGH"
+ "tchange48":"NOT ENOUGH",
+ "tchange49":"Price Chart"
},
"rewardsharepage": {
"rchange1": "Rewardshares",
@@ -575,61 +581,65 @@
"bcchange16": "Choose Recipient or Search for One Below",
"bcchange17": "FORWARDED"
},
- "grouppage": {
- "gchange1": "Qortal Groups",
- "gchange2": "Create Group",
- "gchange3": "Your Joined Groups",
- "gchange4": "Group Name",
- "gchange5": "Description",
- "gchange6": "Role",
- "gchange7": "Action",
- "gchange8": "Not a member of any group!",
- "gchange9": "Public Groups",
- "gchange10": "Owner",
- "gchange11": "No Open Public Groups available!",
- "gchange12": "Create a New Group",
- "gchange13": "Group Type",
- "gchange14": "This Field is Required",
- "gchange15": "Select an option",
- "gchange16": "Public",
- "gchange17": "Private",
- "gchange18": "Group Approval Threshold (number / percentage of Admins that must approve a transaction):",
- "gchange19": "NONE",
- "gchange20": "ONE",
- "gchange21": "Minimum Block delay for Group Transaction Approvals:",
- "gchange22": "minutes",
- "gchange23": "hour",
- "gchange24": "hours",
- "gchange25": "day",
- "gchange26": "days",
- "gchange27": "Maximum Block delay for Group Transaction Approvals:",
- "gchange28": "Creating Group",
- "gchange29": "Create Group",
- "gchange30": "Join Group Request",
- "gchange31": "Date Created",
- "gchange32": "Date Updated",
- "gchange33": "Joining",
- "gchange34": "Join Group",
- "gchange35": "Leave Group Request",
- "gchange36": "Leaving",
- "gchange37": "Leave Group",
- "gchange38": "Manage Group Owner:",
- "gchange39": "Manage Group Admin:",
- "gchange40": "Manage Group",
- "gchange41": "Group Creation Successful!",
- "gchange42": "Invalid Group Name",
- "gchange43": "Invalid Group Description",
- "gchange44": "Select a Group Typ",
- "gchange45": "Select a Group Approval Threshold",
- "gchange46": "Select a Minimum Block delay for Group Transaction Approvals",
- "gchange47": "Select a Maximum Block delay for Group Transaction Approvals",
- "gchange48": "Join Group Request Sent Successfully!",
- "gchange49": "Leave Group Request Sent Successfully!",
- "gchange50": "Leave",
- "gchange51": "Join",
- "gchange52": "Admin",
- "gchange53": "Member",
- "gchange54": "Members"
+ "grouppage":{
+ "gchange1":"Qortal Groups",
+ "gchange2":"Create Group",
+ "gchange3":"Your Joined Groups",
+ "gchange4":"Group Name",
+ "gchange5":"Description",
+ "gchange6":"Role",
+ "gchange7":"Action",
+ "gchange8":"Not a member of any group!",
+ "gchange9":"Public Groups",
+ "gchange10":"Owner",
+ "gchange11":"No Open Public Groups available!",
+ "gchange12":"Create a New Group",
+ "gchange13":"Group Type",
+ "gchange14":"This Field is Required",
+ "gchange15":"Select an option",
+ "gchange16":"Public",
+ "gchange17":"Private",
+ "gchange18":"Group Approval Threshold (number / percentage of Admins that must approve a transaction):",
+ "gchange19":"NONE",
+ "gchange20":"ONE",
+ "gchange21":"Minimum Block delay for Group Transaction Approvals:",
+ "gchange22":"minutes",
+ "gchange23":"hour",
+ "gchange24":"hours",
+ "gchange25":"day",
+ "gchange26":"days",
+ "gchange27":"Maximum Block delay for Group Transaction Approvals:",
+ "gchange28":"Creating Group",
+ "gchange29":"Create Group",
+ "gchange30":"Join Group Request",
+ "gchange31":"Date Created",
+ "gchange32":"Date Updated",
+ "gchange33":"Joining",
+ "gchange34":"Join Group",
+ "gchange35":"Leave Group Request",
+ "gchange36":"Leaving",
+ "gchange37":"Leave Group",
+ "gchange38":"Manage Group Owner:",
+ "gchange39":"Manage Group Admin:",
+ "gchange40":"Manage Group",
+ "gchange41":"Group Creation Successful!",
+ "gchange42":"Invalid Group Name",
+ "gchange43":"Invalid Group Description",
+ "gchange44":"Select a Group Typ",
+ "gchange45":"Select a Group Approval Threshold",
+ "gchange46":"Select a Minimum Block delay for Group Transaction Approvals",
+ "gchange47":"Select a Maximum Block delay for Group Transaction Approvals",
+ "gchange48":"Join Group Request Sent Successfully!",
+ "gchange49":"Leave Group Request Sent Successfully!",
+ "gchange50":"Leave",
+ "gchange51":"Join",
+ "gchange52":"Admin",
+ "gchange53":"Member",
+ "gchange54":"Members",
+ "gchange55":"Search Private Group",
+ "gchange56":"Group Name To Search",
+ "gchange57":"Private Group Name Not Found",
+ "gchange58":"Note that group name must exact match."
},
"puzzlepage": {
"pchange1": "Puzzles",
@@ -817,6 +827,15 @@
"mg49":"On pressing confirm, the cancel invite request will be sent!",
"mg50":"Coming Soon...",
"mg51":"Minimum 3 Characters / Maximum 32 Characters",
- "mg52":"Maximum 128 Characters"
+ "mg52":"Maximum 128 Characters",
+ "mg53":"Your Open Join Requests",
+ "mg54":"No Open Join Requests",
+ "mg55":"Are you sure to accept the join request from this member ?",
+ "mg56":"On pressing confirm, the accept join request will be sent!",
+ "mg57":"Join Request Successfully Accepted",
+ "mg58":"SOMETHING WENT WRONG",
+ "mg59":"Cancel Join Request Successfully Accepted",
+ "mg60":"Are you sure to cancel the join request from this member ?",
+ "mg61":"On pressing confirm, the cancel join request will be sent!"
}
}
\ No newline at end of file
diff --git a/qortal-ui-core/language/zhc.json b/qortal-ui-core/language/zhc.json
index b8052dfd..b2b1a6b4 100644
--- a/qortal-ui-core/language/zhc.json
+++ b/qortal-ui-core/language/zhc.json
@@ -128,7 +128,11 @@
"snack2":"连接到节点的 UI",
"snack3":"成功添加并保存自定义节点",
"snack4":"节点成功保存为",
- "snack5":"节点成功导入"
+ "snack5":"节点成功导入",
+ "exp1":"导出主密钥",
+ "exp2":"导出主密钥",
+ "exp3":"导出",
+ "exp4":"请选择一个钱包来备份私钥。"
},
"appinfo":{
"blockheight":"区块高度",
@@ -155,7 +159,8 @@
"continue":"继续",
"save":"保存",
"balance":"信用",
- "balances":"您的钱包余额"
+ "balances":"您的钱包余额",
+ "update":"更新钱包余额"
},
"startminting":{
"smchange1":"无法获取铸币帐户",
@@ -328,7 +333,8 @@
"tchange45":"自动购买",
"tchange46":"自动购买",
"tchange47":"以这个价格出售",
- "tchange48":"不够"
+ "tchange48":"不够",
+ "tchange49":"价格图表"
},
"rewardsharepage":{
"rchange1":"铸币密钥",
@@ -606,7 +612,11 @@
"gchange51":"加入",
"gchange52":"管理员",
"gchange53":"成员",
- "gchange54":"成员数量"
+ "gchange54":"成员数量",
+ "gchange55":"搜索私人群组",
+ "gchange56":"要搜索的组名",
+ "gchange57":"未找到私人组名",
+ "gchange58":"注意组名必须完全匹配。"
},
"puzzlepage":{
"pchange1":"益智游戏",
@@ -794,6 +804,15 @@
"mg49":"按下确认后,将发送取消邀请请求!",
"mg50":"即将推出……",
"mg51":"最少 3 个字符 / 最多 32 个字符",
- "mg52":"最多 128 个字符"
+ "mg52":"最多 128 个字符",
+ "mg53":"您的公开加入请求",
+ "mg54":"没有开放的加入请求",
+ "mg55":"您确定接受该会员的加入请求吗?",
+ "mg56":"按下确认后,将发送接受加入请求!",
+ "mg57":"成功接受加入请求",
+ "mg58":"出了点问题",
+ "mg59":"取消加入请求已成功接受",
+ "mg60":"您确定要取消该会员的加入请求吗?",
+ "mg61":"按下确认后,将发送取消加入请求!"
}
}
diff --git a/qortal-ui-core/language/zht.json b/qortal-ui-core/language/zht.json
index 113b8b14..2c5c86f3 100644
--- a/qortal-ui-core/language/zht.json
+++ b/qortal-ui-core/language/zht.json
@@ -128,7 +128,11 @@
"snack2":"連接到節點的 UI",
"snack3":"成功添加並保存自定義節點",
"snack4":"節點成功保存為",
- "snack5":"節點成功導入"
+ "snack5":"節點成功導入",
+ "exp1":"導出主密鑰",
+ "exp2":"導出主密鑰",
+ "exp3":"導出",
+ "exp4":"請選擇一個錢包來備份私鑰。"
},
"appinfo":{
"blockheight":"區塊高度",
@@ -155,7 +159,8 @@
"continue":"繼續",
"save":"保存",
"balance":"信用",
- "balances":"您的錢包餘額"
+ "balances":"您的錢包餘額",
+ "update":"更新錢包餘額"
},
"startminting":{
"smchange1":"無法獲取鑄幣帳戶",
@@ -328,7 +333,8 @@
"tchange45":"自動購買",
"tchange46":"自動購買",
"tchange47":"以這個價格出售",
- "tchange48":"不夠"
+ "tchange48":"不夠",
+ "tchange49":"價格圖表"
},
"rewardsharepage":{
"rchange1":"鑄幣密鑰",
@@ -606,7 +612,11 @@
"gchange51":"加入",
"gchange52":"管理員",
"gchange53":"成員",
- "gchange54":"成員數量"
+ "gchange54":"成員數量",
+ "gchange55":"搜索私人群組",
+ "gchange56":"要搜索的組名",
+ "gchange57":"未找到私人組名",
+ "gchange58":"注意組名必須完全匹配。"
},
"puzzlepage":{
"pchange1":"益智游戲",
@@ -794,6 +804,15 @@
"mg49":"按下確認後,將發送取消邀請請求!",
"mg50":"即將推出……",
"mg51":"最少 3 個字符 / 最多 32 個字符",
- "mg52":"最多 128 個字符"
+ "mg52":"最多 128 個字符",
+ "mg53":"您的公開加入請求",
+ "mg54":"沒有開放的加入請求",
+ "mg55":"您確定接受該會員的加入請求嗎?",
+ "mg56":"按下確認後,將發送接受加入請求!",
+ "mg57":"成功接受加入請求",
+ "mg58":"出了點問題",
+ "mg59":"取消加入請求已成功接受",
+ "mg60":"您確定要取消該會員的加入請求嗎?",
+ "mg61":"按下確認後,將發送取消加入請求!"
}
}
diff --git a/qortal-ui-core/package.json b/qortal-ui-core/package.json
index d3557c13..51034438 100644
--- a/qortal-ui-core/package.json
+++ b/qortal-ui-core/package.json
@@ -22,7 +22,7 @@
"sass": "1.57.1"
},
"devDependencies": {
- "@babel/core": "7.20.7",
+ "@babel/core": "7.20.12",
"@material/mwc-button": "0.27.0",
"@material/mwc-checkbox": "0.27.0",
"@material/mwc-dialog": "0.27.0",
@@ -74,7 +74,7 @@
"random-sentence-generator": "0.0.8",
"redux": "4.2.0",
"redux-thunk": "2.4.2",
- "rollup": "3.9.0",
+ "rollup": "3.9.1",
"rollup-plugin-node-globals": "1.4.0",
"rollup-plugin-progress": "1.1.2",
"rollup-plugin-scss": "3.0.0",
diff --git a/qortal-ui-core/src/components/app-view.js b/qortal-ui-core/src/components/app-view.js
index dbc8c9e0..200c0c8f 100644
--- a/qortal-ui-core/src/components/app-view.js
+++ b/qortal-ui-core/src/components/app-view.js
@@ -6,6 +6,7 @@ import { addTradeBotRoutes } from '../tradebot/addTradeBotRoutes.js'
import { get, translate, translateUnsafeHTML } from 'lit-translate'
import '@polymer/paper-icon-button/paper-icon-button.js'
+import '@polymer/paper-progress/paper-progress.js'
import '@polymer/iron-icons/iron-icons.js'
import '@polymer/app-layout/app-layout.js'
import '@polymer/paper-ripple'
@@ -38,6 +39,7 @@ class AppView extends connect(store)(LitElement) {
theme: { type: String, reflect: true },
addressInfo: { type: Object },
searchContentString: { type: String },
+ getAllBalancesLoading: { type: Boolean },
botQortWallet: { type: String },
botBtcWallet: { type: String },
botLtcWallet: { type: String },
@@ -154,6 +156,10 @@ class AppView extends connect(store)(LitElement) {
border-top: var(--border);
}
+ paper-progress {
+ --paper-progress-active-color: var(--mdc-theme-primary);
+ }
+
.s-menu {
list-style: none;
padding: 0px 0px;
@@ -207,7 +213,7 @@ class AppView extends connect(store)(LitElement) {
.balanceheadertext {
position: absolute;
margin: auto;
- font-size: 16px;
+ font-size: 14px;
font-weight: 400;
width: 250px;
display: inline;
@@ -323,11 +329,12 @@ class AppView extends connect(store)(LitElement) {
constructor() {
super()
+ this.theme = localStorage.getItem('qortalTheme') ? localStorage.getItem('qortalTheme') : 'light'
this.searchContentString = ''
this.urls = [];
this.nodeType = ''
this.addressInfo = {}
- this.theme = localStorage.getItem('qortalTheme') ? localStorage.getItem('qortalTheme') : 'light'
+ this.getAllBalancesLoading = false
this.botQortWallet = ''
this.botBtcWallet = ''
this.botLtcWallet = ''
@@ -414,13 +421,18 @@ class AppView extends connect(store)(LitElement) {
+ ${this.getAllBalancesLoading ? html`` : ''}
${this.balanceTicker}
@@ -1505,6 +1517,7 @@ class AppView extends connect(store)(LitElement) {
}
async getAllBalances() {
+ this.getAllBalancesLoading = true
await this.updateQortWalletBalance()
await this.updateBtcWalletBalance()
await this.updateLtcWalletBalance()
@@ -1513,6 +1526,7 @@ class AppView extends connect(store)(LitElement) {
await this.updateRvnWalletBalance()
await this.fetchArrrWalletAddress()
await this.updateArrrWalletBalance()
+ this.getAllBalancesLoading = false
}
async renderBalances() {
@@ -2132,4 +2146,4 @@ class AppView extends connect(store)(LitElement) {
}
}
-window.customElements.define('app-view', AppView)
+window.customElements.define('app-view', AppView)
\ No newline at end of file
diff --git a/qortal-ui-core/src/components/settings-view/export-keys.js b/qortal-ui-core/src/components/settings-view/export-keys.js
new file mode 100644
index 00000000..1456ce3a
--- /dev/null
+++ b/qortal-ui-core/src/components/settings-view/export-keys.js
@@ -0,0 +1,236 @@
+import { LitElement, html, css } from 'lit'
+import { connect } from 'pwa-helpers'
+import { store } from '../../store.js'
+import { use, get, translate, translateUnsafeHTML, registerTranslateConfig } from 'lit-translate'
+
+import '@material/mwc-dialog'
+import '@material/mwc-button'
+import '@material/mwc-icon'
+import FileSaver from 'file-saver'
+
+class ExportKeys extends connect(store)(LitElement) {
+ static get properties() {
+ return {
+ theme: { type: String, reflect: true },
+ backupErrorMessage: { type: String },
+ btcPMK: { type: String },
+ ltcPMK: { type: String },
+ dogePMK: { type: String },
+ dgbPMK: { type: String },
+ rvnPMK: { type: String },
+ btcWALLET: { type: String },
+ ltcWALLET: { type: String },
+ dogeWALLET: { type: String },
+ dgbWALLET: { type: String },
+ rvnWALLET: { type: String },
+ btcName: { type: String },
+ ltcName: { type: String },
+ dogeName: { type: String },
+ dgbName: { type: String },
+ rvnName: { type: String },
+ btcShort: { type: String },
+ ltcShort: { type: String },
+ dogeShort: { type: String },
+ dgbShort: { type: String },
+ rvnShort: { type: String },
+ dWalletAddress: { type: String },
+ dPrivateKey: { type: String },
+ dCoinName: { type: String },
+ dCoinShort: { type: String }
+ }
+ }
+
+ static get styles() {
+ return css`
+ * {
+ --mdc-theme-primary: rgb(3, 169, 244);
+ --mdc-theme-surface: var(--white);
+ --mdc-dialog-content-ink-color: var(--black);
+ --mdc-dialog-min-width: 500px;
+ --mdc-dialog-max-width: 500px;
+ --lumo-primary-text-color: rgb(0, 167, 245);
+ --lumo-primary-color-50pct: rgba(0, 167, 245, 0.5);
+ --lumo-primary-color-10pct: rgba(0, 167, 245, 0.1);
+ --lumo-primary-color: hsl(199, 100%, 48%);
+ --lumo-base-color: var(--white);
+ --lumo-body-text-color: var(--black);
+ --lumo-secondary-text-color: var(--sectxt);
+ --lumo-contrast-60pct: var(--vdicon);
+ }
+
+ .center-box {
+ position: absolute;
+ width: 100%;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, 0%);
+ text-align: center;
+ }
+
+ .sub-main {
+ position: relative;
+ text-align: center;
+ width: 100%;
+ }
+
+ .content-box {
+ text-align: center;
+ display: inline-block;
+ min-width: 400px;
+ margin-bottom: 10px;
+ margin-left: 10px;
+ margin-top: 20px;
+ }
+
+ .export-button {
+ display: inline-flex;
+ flex-direction: column;
+ justify-content: center;
+ align-content: center;
+ border: none;
+ border-radius: 20px;
+ padding-left: 10px;
+ padding-right: 10px;
+ color: white;
+ background: #03a9f4;
+ width: 75%;
+ font-size: 16px;
+ cursor: pointer;
+ height: 40px;
+ margin-top: 1rem;
+ text-transform: uppercase;
+ text-decoration: none;
+ transition: all .2s;
+ position: relative;
+ }
+
+ .red {
+ --mdc-theme-primary: red;
+ }
+ `
+ }
+
+ constructor() {
+ super()
+ this.theme = localStorage.getItem('qortalTheme') ? localStorage.getItem('qortalTheme') : 'light'
+ this.backupErrorMessage = ''
+ this.btcPMK = store.getState().app.selectedAddress.btcWallet.derivedMasterPrivateKey
+ this.btcWALLET = store.getState().app.selectedAddress.btcWallet.address
+ this.btcName = 'Bitcoin'
+ this.btcShort = 'btc'
+ this.ltcPMK = store.getState().app.selectedAddress.ltcWallet.derivedMasterPrivateKey
+ this.ltcWALLET = store.getState().app.selectedAddress.ltcWallet.address
+ this.ltcName = 'Litecoin'
+ this.ltcShort = 'ltc'
+ this.dogePMK = store.getState().app.selectedAddress.dogeWallet.derivedMasterPrivateKey
+ this.dogeWALLET = store.getState().app.selectedAddress.dogeWallet.address
+ this.dogeName = 'Dogecoin'
+ this.dogeShort = 'doge'
+ this.dgbPMK = store.getState().app.selectedAddress.dgbWallet.derivedMasterPrivateKey
+ this.dgbWALLET = store.getState().app.selectedAddress.dgbWallet.address
+ this.dgbName = 'Digibyte'
+ this.dgbShort = 'dgb'
+ this.rvnPMK = store.getState().app.selectedAddress.rvnWallet.derivedMasterPrivateKey
+ this.rvnWALLET = store.getState().app.selectedAddress.rvnWallet.address
+ this.rvnName = 'Ravencoin'
+ this.rvnShort = 'rvn'
+ this.dWalletAddress = ''
+ this.dPrivateKey = ''
+ this.dCoinName = ''
+ this.dCoinShort = 'btc'
+ }
+
+ render() {
+ return html`
+
+
+
+ ${translate("settings.exp4")}
+
+
+
+
+
+
+
![](/img/btc.png)
${this.btcWALLET}
+
+
this.checkForPmkDownload(this.btcWALLET, this.btcPMK, this.btcName, this.btcShort)} class="export-button"> ${translate("settings.exp2")}
+
+
+
+
![](/img/ltc.png)
${this.ltcWALLET}
+
+
this.checkForPmkDownload(this.ltcWALLET, this.ltcPMK, this.ltcName, this.ltcShort)} class="export-button"> ${translate("settings.exp2")}
+
+
+
+
![](/img/doge.png)
${this.dogeWALLET}
+
+
this.checkForPmkDownload(this.dogeWALLET, this.dogePMK, this.dogeName, this.dogeShort)} class="export-button"> ${translate("settings.exp2")}
+
+
+
+
![](/img/dgb.png)
${this.dgbWALLET}
+
+
this.checkForPmkDownload(this.dgbWALLET, this.dgbPMK, this.dgbName, this.dgbShort)} class="export-button"> ${translate("settings.exp2")}
+
+
+
+
![](/img/rvn.png)
${this.rvnWALLET}
+
+
this.checkForPmkDownload(this.rvnWALLET, this.rvnPMK, this.rvnName, this.rvnShort)} class="export-button"> ${translate("settings.exp2")}
+
+
+
+
+
+ ${this.dCoinName} ${translate("settings.exp2")}
+
+ ${translate("settings.address")}: ${this.dWalletAddress}
+ this.closeSavePkmDialog()}"
+ class="red"
+ >
+ ${translate("general.close")}
+
+ this.exportKey(this.dPrivateKey, this.dCoinName, this.dWalletAddress)}"
+ >
+ ${translate("settings.exp3")}
+
+
+
+ `
+ }
+
+ closeSavePkmDialog() {
+ this.shadowRoot.querySelector('#savePkmDialog').close()
+ }
+
+ checkForPmkDownload(wAddress, wPkm, wName, wShort) {
+ this.dWalletAddress = ''
+ this.dPrivateKey = ''
+ this.dCoinName = ''
+ this.dCoinShort = ''
+ this.dWalletAddress = wAddress
+ this.dPrivateKey = wPkm
+ this.dCoinName = wName
+ this.dCoinShort = wShort
+ this.shadowRoot.querySelector('#savePkmDialog').show()
+ }
+
+ async exportKey(cMasterKey, cName, cAddress) {
+ const myPrivateMasterKey = cMasterKey
+ const myCoinName = cName
+ const myCoinAddress = cAddress
+ const blob = new Blob([`${myPrivateMasterKey}`], { type: 'text/plain;charset=utf-8' })
+ FileSaver.saveAs(blob, `Private_Master_Key_${myCoinName}_${myCoinAddress}.txt`)
+ }
+
+ stateChanged(state) {
+ }
+}
+
+window.customElements.define('export-keys', ExportKeys)
diff --git a/qortal-ui-core/src/components/settings-view/user-settings.js b/qortal-ui-core/src/components/settings-view/user-settings.js
index 4d522f17..66a8c887 100644
--- a/qortal-ui-core/src/components/settings-view/user-settings.js
+++ b/qortal-ui-core/src/components/settings-view/user-settings.js
@@ -10,6 +10,7 @@ import './account-view.js'
import './security-view.js'
import './notifications-view.js'
import './qr-login-view.js'
+import './export-keys.js'
import { doLogout } from '../../redux/app/app-actions.js'
@@ -156,6 +157,7 @@ class UserSettings extends connect(store)(LitElement) {
font-size: 16px;
text-align: center;
min-height: 460px;
+ height: 60vh;
}
@media(max-width:700px) {
@@ -226,6 +228,7 @@ class UserSettings extends connect(store)(LitElement) {
@@ -247,25 +250,29 @@ class UserSettings extends connect(store)(LitElement) {
renderSettingViews(selectedView) {
if (selectedView.id === 'info') {
- return html``;
+ return html``
} else if (selectedView.id === 'security') {
- return html``;
+ return html``
+ } else if (selectedView.id === 'export') {
+ return html``
} else if (selectedView.id === 'notification') {
- return html``;
+ return html``
} else if (selectedView.id === 'qr-login') {
- return html``;
+ return html``
}
}
renderHeaderViews() {
if (this.selectedView.id === 'info') {
- return html`${translate("settings.generalinfo")}`;
+ return html`${translate("settings.generalinfo")}`
} else if (this.selectedView.id === 'security') {
- return html`${translate("settings.accountsecurity")}`;
+ return html`${translate("settings.accountsecurity")}`
+ } else if (this.selectedView.id === 'export') {
+ return html`${translate("settings.exp1")}`
} else if (this.selectedView.id === 'notification') {
- return html`UI ${translate("settings.notifications")}`;
+ return html`UI ${translate("settings.notifications")}`
} else if (this.selectedView.id === 'qr-login') {
- return html`${translate("settings.qr_login_menu_item")}`;
+ return html`${translate("settings.qr_login_menu_item")}`
}
}
@@ -274,6 +281,8 @@ class UserSettings extends connect(store)(LitElement) {
return this.selectedView = { id: 'info', name: 'General Account Info' }
} else if (pageId === 'security') {
return this.selectedView = { id: 'security', name: 'Account Security' }
+ } else if (pageId === 'export') {
+ return this.selectedView = { id: 'export', name: 'Export Master Keys' }
} else if (pageId === 'notification') {
return this.selectedView = { id: 'notification', name: 'UI Notifications' }
} else if (pageId === 'qr-login') {
diff --git a/qortal-ui-core/src/components/user-info-view/user-info-view.js b/qortal-ui-core/src/components/user-info-view/user-info-view.js
index ada00969..453b6ccf 100644
--- a/qortal-ui-core/src/components/user-info-view/user-info-view.js
+++ b/qortal-ui-core/src/components/user-info-view/user-info-view.js
@@ -6,8 +6,6 @@ import { doLogout } from '../../redux/app/app-actions.js'
import { get, translate, translateUnsafeHTML } from 'lit-translate'
import '@polymer/paper-dialog/paper-dialog.js'
-import '@polymer/neon-animation/animations/scale-up-animation.js';
-import '@polymer/neon-animation/animations/fade-out-animation.js';
import '@material/mwc-button'
import '@material/mwc-icon'
import '@vaadin/grid'
@@ -1173,7 +1171,7 @@ class UserInfoView extends connect(store)(LitElement) {
-
+
${this.boughtBTCTemplate()}
@@ -1193,7 +1191,7 @@ class UserInfoView extends connect(store)(LitElement) {
-
+
${this.soldBTCTemplate()}
@@ -1213,7 +1211,7 @@ class UserInfoView extends connect(store)(LitElement) {
-
+
${translate("walletpage.wchange5")}
@@ -1773,7 +1771,7 @@ class UserInfoView extends connect(store)(LitElement) {
openTrades() {
this.shadowRoot.getElementById('userTrades').open()
- this.shadowRoot.getElementById('userInfoDialog').close()
+ this.shadowRoot.getElementById('userFullInfoDialog').close()
}
async openUserBoughtDialog() {
diff --git a/qortal-ui-plugins/package.json b/qortal-ui-plugins/package.json
index 8e35a49a..e8396fc1 100644
--- a/qortal-ui-plugins/package.json
+++ b/qortal-ui-plugins/package.json
@@ -26,8 +26,7 @@
"short-unique-id": "^4.4.4"
},
"devDependencies": {
- "@babel/core": "7.20.7",
- "@github/time-elements": "3.1.2",
+ "@babel/core": "7.20.12",
"@material/mwc-button": "0.27.0",
"@material/mwc-checkbox": "0.27.0",
"@material/mwc-dialog": "0.27.0",
@@ -63,7 +62,7 @@
"html-escaper": "3.0.3",
"lit": "2.5.0",
"lit-translate": "2.0.1",
- "rollup": "3.9.0",
+ "rollup": "3.9.1",
"rollup-plugin-node-globals": "1.4.0",
"rollup-plugin-progress": "1.1.2",
"rollup-plugin-terser": "7.0.2",
@@ -72,4 +71,4 @@
"engines": {
"node": ">=16.17.1"
}
-}
+}
\ No newline at end of file
diff --git a/qortal-ui-plugins/plugins/core/components/TimeAgo.js b/qortal-ui-plugins/plugins/core/components/TimeAgo.js
index c212a736..ca3633b7 100644
--- a/qortal-ui-plugins/plugins/core/components/TimeAgo.js
+++ b/qortal-ui-plugins/plugins/core/components/TimeAgo.js
@@ -1,6 +1,6 @@
import { LitElement, html, css } from 'lit'
-import '@github/time-elements'
+import './time-elements/index.js'
class TimeAgo extends LitElement {
static get properties() {
diff --git a/qortal-ui-plugins/plugins/core/components/time-elements/extended-time-element.js b/qortal-ui-plugins/plugins/core/components/time-elements/extended-time-element.js
new file mode 100644
index 00000000..583dee79
--- /dev/null
+++ b/qortal-ui-plugins/plugins/core/components/time-elements/extended-time-element.js
@@ -0,0 +1,87 @@
+import { makeFormatter } from './utils';
+const datetimes = new WeakMap();
+export default class ExtendedTimeElement extends HTMLElement {
+ static get observedAttributes() {
+ return [
+ 'datetime',
+ 'day',
+ 'format',
+ 'lang',
+ 'hour',
+ 'minute',
+ 'month',
+ 'second',
+ 'title',
+ 'weekday',
+ 'year',
+ 'time-zone-name'
+ ];
+ }
+ connectedCallback() {
+ const title = this.getFormattedTitle();
+ if (title && !this.hasAttribute('title')) {
+ this.setAttribute('title', title);
+ }
+ const text = this.getFormattedDate();
+ if (text) {
+ this.textContent = text;
+ }
+ }
+ attributeChangedCallback(attrName, oldValue, newValue) {
+ const oldTitle = this.getFormattedTitle();
+ if (attrName === 'datetime') {
+ const millis = Date.parse(newValue);
+ if (isNaN(millis)) {
+ datetimes.delete(this);
+ }
+ else {
+ datetimes.set(this, new Date(millis));
+ }
+ }
+ const title = this.getFormattedTitle();
+ const currentTitle = this.getAttribute('title');
+ if (attrName !== 'title' && title && (!currentTitle || currentTitle === oldTitle)) {
+ this.setAttribute('title', title);
+ }
+ const text = this.getFormattedDate();
+ if (text) {
+ this.textContent = text;
+ }
+ }
+ get date() {
+ return datetimes.get(this);
+ }
+ getFormattedTitle() {
+ const date = this.date;
+ if (!date)
+ return;
+ const formatter = titleFormatter();
+ if (formatter) {
+ return formatter.format(date);
+ }
+ else {
+ try {
+ return date.toLocaleString();
+ }
+ catch (e) {
+ if (e instanceof RangeError) {
+ return date.toString();
+ }
+ else {
+ throw e;
+ }
+ }
+ }
+ }
+ getFormattedDate() {
+ return;
+ }
+}
+const titleFormatter = makeFormatter({
+ day: 'numeric',
+ month: 'short',
+ year: 'numeric',
+ hour: 'numeric',
+ minute: '2-digit',
+ timeZoneName: 'short'
+});
diff --git a/qortal-ui-plugins/plugins/core/components/time-elements/index.js b/qortal-ui-plugins/plugins/core/components/time-elements/index.js
new file mode 100644
index 00000000..8b3dbe4c
--- /dev/null
+++ b/qortal-ui-plugins/plugins/core/components/time-elements/index.js
@@ -0,0 +1,705 @@
+const weekdays = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
+const months = [
+ 'January',
+ 'February',
+ 'March',
+ 'April',
+ 'May',
+ 'June',
+ 'July',
+ 'August',
+ 'September',
+ 'October',
+ 'November',
+ 'December'
+];
+function pad(num) {
+ return `0${num}`.slice(-2);
+}
+function strftime(time, formatString) {
+ const day = time.getDay();
+ const date = time.getDate();
+ const month = time.getMonth();
+ const year = time.getFullYear();
+ const hour = time.getHours();
+ const minute = time.getMinutes();
+ const second = time.getSeconds();
+ return formatString.replace(/%([%aAbBcdeHIlmMpPSwyYZz])/g, function (_arg) {
+ let match;
+ const modifier = _arg[1];
+ switch (modifier) {
+ case '%':
+ return '%';
+ case 'a':
+ return weekdays[day].slice(0, 3);
+ case 'A':
+ return weekdays[day];
+ case 'b':
+ return months[month].slice(0, 3);
+ case 'B':
+ return months[month];
+ case 'c':
+ return time.toString();
+ case 'd':
+ return pad(date);
+ case 'e':
+ return String(date);
+ case 'H':
+ return pad(hour);
+ case 'I':
+ return pad(strftime(time, '%l'));
+ case 'l':
+ if (hour === 0 || hour === 12) {
+ return String(12);
+ }
+ else {
+ return String((hour + 12) % 12);
+ }
+ case 'm':
+ return pad(month + 1);
+ case 'M':
+ return pad(minute);
+ case 'p':
+ if (hour > 11) {
+ return 'PM';
+ }
+ else {
+ return 'AM';
+ }
+ case 'P':
+ if (hour > 11) {
+ return 'pm';
+ }
+ else {
+ return 'am';
+ }
+ case 'S':
+ return pad(second);
+ case 'w':
+ return String(day);
+ case 'y':
+ return pad(year % 100);
+ case 'Y':
+ return String(year);
+ case 'Z':
+ match = time.toString().match(/\((\w+)\)$/);
+ return match ? match[1] : '';
+ case 'z':
+ match = time.toString().match(/\w([+-]\d\d\d\d) /);
+ return match ? match[1] : '';
+ }
+ return '';
+ });
+}
+function makeFormatter(options) {
+ let format;
+ return function () {
+ if (format)
+ return format;
+ if ('Intl' in window) {
+ try {
+ format = new Intl.DateTimeFormat(undefined, options);
+ return format;
+ }
+ catch (e) {
+ if (!(e instanceof RangeError)) {
+ throw e;
+ }
+ }
+ }
+ };
+}
+let dayFirst = null;
+const dayFirstFormatter = makeFormatter({ day: 'numeric', month: 'short' });
+function isDayFirst() {
+ if (dayFirst !== null) {
+ return dayFirst;
+ }
+ const formatter = dayFirstFormatter();
+ if (formatter) {
+ const output = formatter.format(new Date(0));
+ dayFirst = !!output.match(/^\d/);
+ return dayFirst;
+ }
+ else {
+ return false;
+ }
+}
+let yearSeparator = null;
+const yearFormatter = makeFormatter({ day: 'numeric', month: 'short', year: 'numeric' });
+function isYearSeparator() {
+ if (yearSeparator !== null) {
+ return yearSeparator;
+ }
+ const formatter = yearFormatter();
+ if (formatter) {
+ const output = formatter.format(new Date(0));
+ yearSeparator = !!output.match(/\d,/);
+ return yearSeparator;
+ }
+ else {
+ return true;
+ }
+}
+function isThisYear(date) {
+ const now = new Date();
+ return now.getUTCFullYear() === date.getUTCFullYear();
+}
+function makeRelativeFormat(locale, options) {
+ if ('Intl' in window && 'RelativeTimeFormat' in window.Intl) {
+ try {
+ return new Intl.RelativeTimeFormat(locale, options);
+ }
+ catch (e) {
+ if (!(e instanceof RangeError)) {
+ throw e;
+ }
+ }
+ }
+}
+function localeFromElement(el) {
+ const container = el.closest('[lang]');
+ if (container instanceof HTMLElement && container.lang) {
+ return container.lang;
+ }
+ return 'default';
+}
+
+const datetimes = new WeakMap();
+class ExtendedTimeElement extends HTMLElement {
+ static get observedAttributes() {
+ return [
+ 'datetime',
+ 'day',
+ 'format',
+ 'lang',
+ 'hour',
+ 'minute',
+ 'month',
+ 'second',
+ 'title',
+ 'weekday',
+ 'year',
+ 'time-zone-name'
+ ];
+ }
+ connectedCallback() {
+ const title = this.getFormattedTitle();
+ if (title && !this.hasAttribute('title')) {
+ this.setAttribute('title', title);
+ }
+ const text = this.getFormattedDate();
+ if (text) {
+ this.textContent = text;
+ }
+ }
+ attributeChangedCallback(attrName, oldValue, newValue) {
+ const oldTitle = this.getFormattedTitle();
+ if (attrName === 'datetime') {
+ const millis = Date.parse(newValue);
+ if (isNaN(millis)) {
+ datetimes.delete(this);
+ }
+ else {
+ datetimes.set(this, new Date(millis));
+ }
+ }
+ const title = this.getFormattedTitle();
+ const currentTitle = this.getAttribute('title');
+ if (attrName !== 'title' && title && (!currentTitle || currentTitle === oldTitle)) {
+ this.setAttribute('title', title);
+ }
+ const text = this.getFormattedDate();
+ if (text) {
+ this.textContent = text;
+ }
+ }
+ get date() {
+ return datetimes.get(this);
+ }
+ getFormattedTitle() {
+ const date = this.date;
+ if (!date)
+ return;
+ const formatter = titleFormatter();
+ if (formatter) {
+ return formatter.format(date);
+ }
+ else {
+ try {
+ return date.toLocaleString();
+ }
+ catch (e) {
+ if (e instanceof RangeError) {
+ return date.toString();
+ }
+ else {
+ throw e;
+ }
+ }
+ }
+ }
+ getFormattedDate() {
+ return;
+ }
+}
+const titleFormatter = makeFormatter({
+ day: 'numeric',
+ month: 'short',
+ year: 'numeric',
+ hour: 'numeric',
+ minute: '2-digit',
+ timeZoneName: 'short'
+});
+
+const formatters = new WeakMap();
+class LocalTimeElement extends ExtendedTimeElement {
+ attributeChangedCallback(attrName, oldValue, newValue) {
+ if (attrName === 'hour' || attrName === 'minute' || attrName === 'second' || attrName === 'time-zone-name') {
+ formatters.delete(this);
+ }
+ super.attributeChangedCallback(attrName, oldValue, newValue);
+ }
+ getFormattedDate() {
+ const d = this.date;
+ if (!d)
+ return;
+ const date = formatDate(this, d) || '';
+ const time = formatTime(this, d) || '';
+ return `${date} ${time}`.trim();
+ }
+}
+function formatDate(el, date) {
+ const props = {
+ weekday: {
+ short: '%a',
+ long: '%A'
+ },
+ day: {
+ numeric: '%e',
+ '2-digit': '%d'
+ },
+ month: {
+ short: '%b',
+ long: '%B'
+ },
+ year: {
+ numeric: '%Y',
+ '2-digit': '%y'
+ }
+ };
+ let format = isDayFirst() ? 'weekday day month year' : 'weekday month day, year';
+ for (const prop in props) {
+ const value = props[prop][el.getAttribute(prop) || ''];
+ format = format.replace(prop, value || '');
+ }
+ format = format.replace(/(\s,)|(,\s$)/, '');
+ return strftime(date, format).replace(/\s+/, ' ').trim();
+}
+function formatTime(el, date) {
+ const options = {};
+ const hour = el.getAttribute('hour');
+ if (hour === 'numeric' || hour === '2-digit')
+ options.hour = hour;
+ const minute = el.getAttribute('minute');
+ if (minute === 'numeric' || minute === '2-digit')
+ options.minute = minute;
+ const second = el.getAttribute('second');
+ if (second === 'numeric' || second === '2-digit')
+ options.second = second;
+ const tz = el.getAttribute('time-zone-name');
+ if (tz === 'short' || tz === 'long')
+ options.timeZoneName = tz;
+ if (Object.keys(options).length === 0) {
+ return;
+ }
+ let factory = formatters.get(el);
+ if (!factory) {
+ factory = makeFormatter(options);
+ formatters.set(el, factory);
+ }
+ const formatter = factory();
+ if (formatter) {
+ return formatter.format(date);
+ }
+ else {
+ const timef = options.second ? '%H:%M:%S' : '%H:%M';
+ return strftime(date, timef);
+ }
+}
+if (!window.customElements.get('local-time')) {
+ window.LocalTimeElement = LocalTimeElement;
+ window.customElements.define('local-time', LocalTimeElement);
+}
+
+class RelativeTime {
+ constructor(date, locale) {
+ this.date = date;
+ this.locale = locale;
+ }
+ toString() {
+ const ago = this.timeElapsed();
+ if (ago) {
+ return ago;
+ }
+ else {
+ const ahead = this.timeAhead();
+ if (ahead) {
+ return ahead;
+ }
+ else {
+ return `on ${this.formatDate()}`;
+ }
+ }
+ }
+ timeElapsed() {
+ const ms = new Date().getTime() - this.date.getTime();
+ const sec = Math.round(ms / 1000);
+ const min = Math.round(sec / 60);
+ const hr = Math.round(min / 60);
+ const day = Math.round(hr / 24);
+ if (ms >= 0 && day < 30) {
+ return this.timeAgoFromMs(ms);
+ }
+ else {
+ return null;
+ }
+ }
+ timeAhead() {
+ const ms = this.date.getTime() - new Date().getTime();
+ const sec = Math.round(ms / 1000);
+ const min = Math.round(sec / 60);
+ const hr = Math.round(min / 60);
+ const day = Math.round(hr / 24);
+ if (ms >= 0 && day < 30) {
+ return this.timeUntil();
+ }
+ else {
+ return null;
+ }
+ }
+ timeAgo() {
+ const ms = new Date().getTime() - this.date.getTime();
+ return this.timeAgoFromMs(ms);
+ }
+ timeAgoFromMs(ms) {
+ const sec = Math.round(ms / 1000);
+ const min = Math.round(sec / 60);
+ const hr = Math.round(min / 60);
+ const day = Math.round(hr / 24);
+ const month = Math.round(day / 30);
+ const year = Math.round(month / 12);
+ if (ms < 0) {
+ return formatRelativeTime(this.locale, 0, 'second');
+ }
+ else if (sec < 10) {
+ return formatRelativeTime(this.locale, 0, 'second');
+ }
+ else if (sec < 45) {
+ return formatRelativeTime(this.locale, -sec, 'second');
+ }
+ else if (sec < 90) {
+ return formatRelativeTime(this.locale, -min, 'minute');
+ }
+ else if (min < 45) {
+ return formatRelativeTime(this.locale, -min, 'minute');
+ }
+ else if (min < 90) {
+ return formatRelativeTime(this.locale, -hr, 'hour');
+ }
+ else if (hr < 24) {
+ return formatRelativeTime(this.locale, -hr, 'hour');
+ }
+ else if (hr < 36) {
+ return formatRelativeTime(this.locale, -day, 'day');
+ }
+ else if (day < 30) {
+ return formatRelativeTime(this.locale, -day, 'day');
+ }
+ else if (month < 18) {
+ return formatRelativeTime(this.locale, -month, 'month');
+ }
+ else {
+ return formatRelativeTime(this.locale, -year, 'year');
+ }
+ }
+ microTimeAgo() {
+ const ms = new Date().getTime() - this.date.getTime();
+ const sec = Math.round(ms / 1000);
+ const min = Math.round(sec / 60);
+ const hr = Math.round(min / 60);
+ const day = Math.round(hr / 24);
+ const month = Math.round(day / 30);
+ const year = Math.round(month / 12);
+ if (min < 1) {
+ return '1m';
+ }
+ else if (min < 60) {
+ return `${min}m`;
+ }
+ else if (hr < 24) {
+ return `${hr}h`;
+ }
+ else if (day < 365) {
+ return `${day}d`;
+ }
+ else {
+ return `${year}y`;
+ }
+ }
+ timeUntil() {
+ const ms = this.date.getTime() - new Date().getTime();
+ return this.timeUntilFromMs(ms);
+ }
+ timeUntilFromMs(ms) {
+ const sec = Math.round(ms / 1000);
+ const min = Math.round(sec / 60);
+ const hr = Math.round(min / 60);
+ const day = Math.round(hr / 24);
+ const month = Math.round(day / 30);
+ const year = Math.round(month / 12);
+ if (month >= 18) {
+ return formatRelativeTime(this.locale, year, 'year');
+ }
+ else if (month >= 12) {
+ return formatRelativeTime(this.locale, year, 'year');
+ }
+ else if (day >= 45) {
+ return formatRelativeTime(this.locale, month, 'month');
+ }
+ else if (day >= 30) {
+ return formatRelativeTime(this.locale, month, 'month');
+ }
+ else if (hr >= 36) {
+ return formatRelativeTime(this.locale, day, 'day');
+ }
+ else if (hr >= 24) {
+ return formatRelativeTime(this.locale, day, 'day');
+ }
+ else if (min >= 90) {
+ return formatRelativeTime(this.locale, hr, 'hour');
+ }
+ else if (min >= 45) {
+ return formatRelativeTime(this.locale, hr, 'hour');
+ }
+ else if (sec >= 90) {
+ return formatRelativeTime(this.locale, min, 'minute');
+ }
+ else if (sec >= 45) {
+ return formatRelativeTime(this.locale, min, 'minute');
+ }
+ else if (sec >= 10) {
+ return formatRelativeTime(this.locale, sec, 'second');
+ }
+ else {
+ return formatRelativeTime(this.locale, 0, 'second');
+ }
+ }
+ microTimeUntil() {
+ const ms = this.date.getTime() - new Date().getTime();
+ const sec = Math.round(ms / 1000);
+ const min = Math.round(sec / 60);
+ const hr = Math.round(min / 60);
+ const day = Math.round(hr / 24);
+ const month = Math.round(day / 30);
+ const year = Math.round(month / 12);
+ if (day >= 365) {
+ return `${year}y`;
+ }
+ else if (hr >= 24) {
+ return `${day}d`;
+ }
+ else if (min >= 60) {
+ return `${hr}h`;
+ }
+ else if (min > 1) {
+ return `${min}m`;
+ }
+ else {
+ return '1m';
+ }
+ }
+ formatDate() {
+ let format = isDayFirst() ? '%e %b' : '%b %e';
+ if (!isThisYear(this.date)) {
+ format += isYearSeparator() ? ', %Y' : ' %Y';
+ }
+ return strftime(this.date, format);
+ }
+ formatTime() {
+ const formatter = timeFormatter();
+ if (formatter) {
+ return formatter.format(this.date);
+ }
+ else {
+ return strftime(this.date, '%l:%M%P');
+ }
+ }
+}
+function formatRelativeTime(locale, value, unit) {
+ const formatter = makeRelativeFormat(locale, { numeric: 'auto' });
+ if (formatter) {
+ return formatter.format(value, unit);
+ }
+ else {
+ return formatEnRelativeTime(value, unit);
+ }
+}
+function formatEnRelativeTime(value, unit) {
+ if (value === 0) {
+ switch (unit) {
+ case 'year':
+ case 'quarter':
+ case 'month':
+ case 'week':
+ return `this ${unit}`;
+ case 'day':
+ return 'today';
+ case 'hour':
+ case 'minute':
+ return `in 0 ${unit}s`;
+ case 'second':
+ return 'now';
+ }
+ }
+ else if (value === 1) {
+ switch (unit) {
+ case 'year':
+ case 'quarter':
+ case 'month':
+ case 'week':
+ return `next ${unit}`;
+ case 'day':
+ return 'tomorrow';
+ case 'hour':
+ case 'minute':
+ case 'second':
+ return `in 1 ${unit}`;
+ }
+ }
+ else if (value === -1) {
+ switch (unit) {
+ case 'year':
+ case 'quarter':
+ case 'month':
+ case 'week':
+ return `last ${unit}`;
+ case 'day':
+ return 'yesterday';
+ case 'hour':
+ case 'minute':
+ case 'second':
+ return `1 ${unit} ago`;
+ }
+ }
+ else if (value > 1) {
+ switch (unit) {
+ case 'year':
+ case 'quarter':
+ case 'month':
+ case 'week':
+ case 'day':
+ case 'hour':
+ case 'minute':
+ case 'second':
+ return `in ${value} ${unit}s`;
+ }
+ }
+ else if (value < -1) {
+ switch (unit) {
+ case 'year':
+ case 'quarter':
+ case 'month':
+ case 'week':
+ case 'day':
+ case 'hour':
+ case 'minute':
+ case 'second':
+ return `${-value} ${unit}s ago`;
+ }
+ }
+ throw new RangeError(`Invalid unit argument for format() '${unit}'`);
+}
+const timeFormatter = makeFormatter({ hour: 'numeric', minute: '2-digit' });
+
+class RelativeTimeElement extends ExtendedTimeElement {
+ getFormattedDate() {
+ const date = this.date;
+ if (!date)
+ return;
+ return new RelativeTime(date, localeFromElement(this)).toString();
+ }
+ connectedCallback() {
+ nowElements.push(this);
+ if (!updateNowElementsId) {
+ updateNowElements();
+ updateNowElementsId = window.setInterval(updateNowElements, 60 * 1000);
+ }
+ super.connectedCallback();
+ }
+ disconnectedCallback() {
+ const ix = nowElements.indexOf(this);
+ if (ix !== -1) {
+ nowElements.splice(ix, 1);
+ }
+ if (!nowElements.length) {
+ if (updateNowElementsId) {
+ clearInterval(updateNowElementsId);
+ updateNowElementsId = null;
+ }
+ }
+ }
+}
+const nowElements = [];
+let updateNowElementsId;
+function updateNowElements() {
+ let time, i, len;
+ for (i = 0, len = nowElements.length; i < len; i++) {
+ time = nowElements[i];
+ time.textContent = time.getFormattedDate() || '';
+ }
+}
+if (!window.customElements.get('relative-time')) {
+ window.RelativeTimeElement = RelativeTimeElement;
+ window.customElements.define('relative-time', RelativeTimeElement);
+}
+
+class TimeAgoElement extends RelativeTimeElement {
+ getFormattedDate() {
+ const format = this.getAttribute('format');
+ const date = this.date;
+ if (!date)
+ return;
+ if (format === 'micro') {
+ return new RelativeTime(date, localeFromElement(this)).microTimeAgo();
+ }
+ else {
+ return new RelativeTime(date, localeFromElement(this)).timeAgo();
+ }
+ }
+}
+if (!window.customElements.get('time-ago')) {
+ window.TimeAgoElement = TimeAgoElement;
+ window.customElements.define('time-ago', TimeAgoElement);
+}
+
+class TimeUntilElement extends RelativeTimeElement {
+ getFormattedDate() {
+ const format = this.getAttribute('format');
+ const date = this.date;
+ if (!date)
+ return;
+ if (format === 'micro') {
+ return new RelativeTime(date, localeFromElement(this)).microTimeUntil();
+ }
+ else {
+ return new RelativeTime(date, localeFromElement(this)).timeUntil();
+ }
+ }
+}
+if (!window.customElements.get('time-until')) {
+ window.TimeUntilElement = TimeUntilElement;
+ window.customElements.define('time-until', TimeUntilElement);
+}
+
+export { LocalTimeElement, RelativeTimeElement, TimeAgoElement, TimeUntilElement };
diff --git a/qortal-ui-plugins/plugins/core/components/time-elements/local-time-element.js b/qortal-ui-plugins/plugins/core/components/time-elements/local-time-element.js
new file mode 100644
index 00000000..3494d218
--- /dev/null
+++ b/qortal-ui-plugins/plugins/core/components/time-elements/local-time-element.js
@@ -0,0 +1,81 @@
+import { strftime, makeFormatter, isDayFirst } from './utils';
+import ExtendedTimeElement from './extended-time-element';
+const formatters = new WeakMap();
+export default class LocalTimeElement extends ExtendedTimeElement {
+ attributeChangedCallback(attrName, oldValue, newValue) {
+ if (attrName === 'hour' || attrName === 'minute' || attrName === 'second' || attrName === 'time-zone-name') {
+ formatters.delete(this);
+ }
+ super.attributeChangedCallback(attrName, oldValue, newValue);
+ }
+ getFormattedDate() {
+ const d = this.date;
+ if (!d)
+ return;
+ const date = formatDate(this, d) || '';
+ const time = formatTime(this, d) || '';
+ return `${date} ${time}`.trim();
+ }
+}
+function formatDate(el, date) {
+ const props = {
+ weekday: {
+ short: '%a',
+ long: '%A'
+ },
+ day: {
+ numeric: '%e',
+ '2-digit': '%d'
+ },
+ month: {
+ short: '%b',
+ long: '%B'
+ },
+ year: {
+ numeric: '%Y',
+ '2-digit': '%y'
+ }
+ };
+ let format = isDayFirst() ? 'weekday day month year' : 'weekday month day, year';
+ for (const prop in props) {
+ const value = props[prop][el.getAttribute(prop) || ''];
+ format = format.replace(prop, value || '');
+ }
+ format = format.replace(/(\s,)|(,\s$)/, '');
+ return strftime(date, format).replace(/\s+/, ' ').trim();
+}
+function formatTime(el, date) {
+ const options = {};
+ const hour = el.getAttribute('hour');
+ if (hour === 'numeric' || hour === '2-digit')
+ options.hour = hour;
+ const minute = el.getAttribute('minute');
+ if (minute === 'numeric' || minute === '2-digit')
+ options.minute = minute;
+ const second = el.getAttribute('second');
+ if (second === 'numeric' || second === '2-digit')
+ options.second = second;
+ const tz = el.getAttribute('time-zone-name');
+ if (tz === 'short' || tz === 'long')
+ options.timeZoneName = tz;
+ if (Object.keys(options).length === 0) {
+ return;
+ }
+ let factory = formatters.get(el);
+ if (!factory) {
+ factory = makeFormatter(options);
+ formatters.set(el, factory);
+ }
+ const formatter = factory();
+ if (formatter) {
+ return formatter.format(date);
+ }
+ else {
+ const timef = options.second ? '%H:%M:%S' : '%H:%M';
+ return strftime(date, timef);
+ }
+}
+if (!window.customElements.get('local-time')) {
+ window.LocalTimeElement = LocalTimeElement;
+ window.customElements.define('local-time', LocalTimeElement);
+}
diff --git a/qortal-ui-plugins/plugins/core/components/time-elements/relative-time-element.js b/qortal-ui-plugins/plugins/core/components/time-elements/relative-time-element.js
new file mode 100644
index 00000000..5654e08d
--- /dev/null
+++ b/qortal-ui-plugins/plugins/core/components/time-elements/relative-time-element.js
@@ -0,0 +1,44 @@
+import RelativeTime from './relative-time';
+import ExtendedTimeElement from './extended-time-element';
+import { localeFromElement } from './utils';
+export default class RelativeTimeElement extends ExtendedTimeElement {
+ getFormattedDate() {
+ const date = this.date;
+ if (!date)
+ return;
+ return new RelativeTime(date, localeFromElement(this)).toString();
+ }
+ connectedCallback() {
+ nowElements.push(this);
+ if (!updateNowElementsId) {
+ updateNowElements();
+ updateNowElementsId = window.setInterval(updateNowElements, 60 * 1000);
+ }
+ super.connectedCallback();
+ }
+ disconnectedCallback() {
+ const ix = nowElements.indexOf(this);
+ if (ix !== -1) {
+ nowElements.splice(ix, 1);
+ }
+ if (!nowElements.length) {
+ if (updateNowElementsId) {
+ clearInterval(updateNowElementsId);
+ updateNowElementsId = null;
+ }
+ }
+ }
+}
+const nowElements = [];
+let updateNowElementsId;
+function updateNowElements() {
+ let time, i, len;
+ for (i = 0, len = nowElements.length; i < len; i++) {
+ time = nowElements[i];
+ time.textContent = time.getFormattedDate() || '';
+ }
+}
+if (!window.customElements.get('relative-time')) {
+ window.RelativeTimeElement = RelativeTimeElement;
+ window.customElements.define('relative-time', RelativeTimeElement);
+}
diff --git a/qortal-ui-plugins/plugins/core/components/time-elements/relative-time.js b/qortal-ui-plugins/plugins/core/components/time-elements/relative-time.js
new file mode 100644
index 00000000..a2b4a6e5
--- /dev/null
+++ b/qortal-ui-plugins/plugins/core/components/time-elements/relative-time.js
@@ -0,0 +1,290 @@
+import { strftime, makeFormatter, makeRelativeFormat, isDayFirst, isThisYear, isYearSeparator } from './utils';
+export default class RelativeTime {
+ constructor(date, locale) {
+ this.date = date;
+ this.locale = locale;
+ }
+ toString() {
+ const ago = this.timeElapsed();
+ if (ago) {
+ return ago;
+ }
+ else {
+ const ahead = this.timeAhead();
+ if (ahead) {
+ return ahead;
+ }
+ else {
+ return `on ${this.formatDate()}`;
+ }
+ }
+ }
+ timeElapsed() {
+ const ms = new Date().getTime() - this.date.getTime();
+ const sec = Math.round(ms / 1000);
+ const min = Math.round(sec / 60);
+ const hr = Math.round(min / 60);
+ const day = Math.round(hr / 24);
+ if (ms >= 0 && day < 30) {
+ return this.timeAgoFromMs(ms);
+ }
+ else {
+ return null;
+ }
+ }
+ timeAhead() {
+ const ms = this.date.getTime() - new Date().getTime();
+ const sec = Math.round(ms / 1000);
+ const min = Math.round(sec / 60);
+ const hr = Math.round(min / 60);
+ const day = Math.round(hr / 24);
+ if (ms >= 0 && day < 30) {
+ return this.timeUntil();
+ }
+ else {
+ return null;
+ }
+ }
+ timeAgo() {
+ const ms = new Date().getTime() - this.date.getTime();
+ return this.timeAgoFromMs(ms);
+ }
+ timeAgoFromMs(ms) {
+ const sec = Math.round(ms / 1000);
+ const min = Math.round(sec / 60);
+ const hr = Math.round(min / 60);
+ const day = Math.round(hr / 24);
+ const month = Math.round(day / 30);
+ const year = Math.round(month / 12);
+ if (ms < 0) {
+ return formatRelativeTime(this.locale, 0, 'second');
+ }
+ else if (sec < 10) {
+ return formatRelativeTime(this.locale, 0, 'second');
+ }
+ else if (sec < 45) {
+ return formatRelativeTime(this.locale, -sec, 'second');
+ }
+ else if (sec < 90) {
+ return formatRelativeTime(this.locale, -min, 'minute');
+ }
+ else if (min < 45) {
+ return formatRelativeTime(this.locale, -min, 'minute');
+ }
+ else if (min < 90) {
+ return formatRelativeTime(this.locale, -hr, 'hour');
+ }
+ else if (hr < 24) {
+ return formatRelativeTime(this.locale, -hr, 'hour');
+ }
+ else if (hr < 36) {
+ return formatRelativeTime(this.locale, -day, 'day');
+ }
+ else if (day < 30) {
+ return formatRelativeTime(this.locale, -day, 'day');
+ }
+ else if (month < 18) {
+ return formatRelativeTime(this.locale, -month, 'month');
+ }
+ else {
+ return formatRelativeTime(this.locale, -year, 'year');
+ }
+ }
+ microTimeAgo() {
+ const ms = new Date().getTime() - this.date.getTime();
+ const sec = Math.round(ms / 1000);
+ const min = Math.round(sec / 60);
+ const hr = Math.round(min / 60);
+ const day = Math.round(hr / 24);
+ const month = Math.round(day / 30);
+ const year = Math.round(month / 12);
+ if (min < 1) {
+ return '1m';
+ }
+ else if (min < 60) {
+ return `${min}m`;
+ }
+ else if (hr < 24) {
+ return `${hr}h`;
+ }
+ else if (day < 365) {
+ return `${day}d`;
+ }
+ else {
+ return `${year}y`;
+ }
+ }
+ timeUntil() {
+ const ms = this.date.getTime() - new Date().getTime();
+ return this.timeUntilFromMs(ms);
+ }
+ timeUntilFromMs(ms) {
+ const sec = Math.round(ms / 1000);
+ const min = Math.round(sec / 60);
+ const hr = Math.round(min / 60);
+ const day = Math.round(hr / 24);
+ const month = Math.round(day / 30);
+ const year = Math.round(month / 12);
+ if (month >= 18) {
+ return formatRelativeTime(this.locale, year, 'year');
+ }
+ else if (month >= 12) {
+ return formatRelativeTime(this.locale, year, 'year');
+ }
+ else if (day >= 45) {
+ return formatRelativeTime(this.locale, month, 'month');
+ }
+ else if (day >= 30) {
+ return formatRelativeTime(this.locale, month, 'month');
+ }
+ else if (hr >= 36) {
+ return formatRelativeTime(this.locale, day, 'day');
+ }
+ else if (hr >= 24) {
+ return formatRelativeTime(this.locale, day, 'day');
+ }
+ else if (min >= 90) {
+ return formatRelativeTime(this.locale, hr, 'hour');
+ }
+ else if (min >= 45) {
+ return formatRelativeTime(this.locale, hr, 'hour');
+ }
+ else if (sec >= 90) {
+ return formatRelativeTime(this.locale, min, 'minute');
+ }
+ else if (sec >= 45) {
+ return formatRelativeTime(this.locale, min, 'minute');
+ }
+ else if (sec >= 10) {
+ return formatRelativeTime(this.locale, sec, 'second');
+ }
+ else {
+ return formatRelativeTime(this.locale, 0, 'second');
+ }
+ }
+ microTimeUntil() {
+ const ms = this.date.getTime() - new Date().getTime();
+ const sec = Math.round(ms / 1000);
+ const min = Math.round(sec / 60);
+ const hr = Math.round(min / 60);
+ const day = Math.round(hr / 24);
+ const month = Math.round(day / 30);
+ const year = Math.round(month / 12);
+ if (day >= 365) {
+ return `${year}y`;
+ }
+ else if (hr >= 24) {
+ return `${day}d`;
+ }
+ else if (min >= 60) {
+ return `${hr}h`;
+ }
+ else if (min > 1) {
+ return `${min}m`;
+ }
+ else {
+ return '1m';
+ }
+ }
+ formatDate() {
+ let format = isDayFirst() ? '%e %b' : '%b %e';
+ if (!isThisYear(this.date)) {
+ format += isYearSeparator() ? ', %Y' : ' %Y';
+ }
+ return strftime(this.date, format);
+ }
+ formatTime() {
+ const formatter = timeFormatter();
+ if (formatter) {
+ return formatter.format(this.date);
+ }
+ else {
+ return strftime(this.date, '%l:%M%P');
+ }
+ }
+}
+function formatRelativeTime(locale, value, unit) {
+ const formatter = makeRelativeFormat(locale, { numeric: 'auto' });
+ if (formatter) {
+ return formatter.format(value, unit);
+ }
+ else {
+ return formatEnRelativeTime(value, unit);
+ }
+}
+function formatEnRelativeTime(value, unit) {
+ if (value === 0) {
+ switch (unit) {
+ case 'year':
+ case 'quarter':
+ case 'month':
+ case 'week':
+ return `this ${unit}`;
+ case 'day':
+ return 'today';
+ case 'hour':
+ case 'minute':
+ return `in 0 ${unit}s`;
+ case 'second':
+ return 'now';
+ }
+ }
+ else if (value === 1) {
+ switch (unit) {
+ case 'year':
+ case 'quarter':
+ case 'month':
+ case 'week':
+ return `next ${unit}`;
+ case 'day':
+ return 'tomorrow';
+ case 'hour':
+ case 'minute':
+ case 'second':
+ return `in 1 ${unit}`;
+ }
+ }
+ else if (value === -1) {
+ switch (unit) {
+ case 'year':
+ case 'quarter':
+ case 'month':
+ case 'week':
+ return `last ${unit}`;
+ case 'day':
+ return 'yesterday';
+ case 'hour':
+ case 'minute':
+ case 'second':
+ return `1 ${unit} ago`;
+ }
+ }
+ else if (value > 1) {
+ switch (unit) {
+ case 'year':
+ case 'quarter':
+ case 'month':
+ case 'week':
+ case 'day':
+ case 'hour':
+ case 'minute':
+ case 'second':
+ return `in ${value} ${unit}s`;
+ }
+ }
+ else if (value < -1) {
+ switch (unit) {
+ case 'year':
+ case 'quarter':
+ case 'month':
+ case 'week':
+ case 'day':
+ case 'hour':
+ case 'minute':
+ case 'second':
+ return `${-value} ${unit}s ago`;
+ }
+ }
+ throw new RangeError(`Invalid unit argument for format() '${unit}'`);
+}
+const timeFormatter = makeFormatter({ hour: 'numeric', minute: '2-digit' });
diff --git a/qortal-ui-plugins/plugins/core/components/time-elements/time-ago-element.js b/qortal-ui-plugins/plugins/core/components/time-elements/time-ago-element.js
new file mode 100644
index 00000000..6ac6b25a
--- /dev/null
+++ b/qortal-ui-plugins/plugins/core/components/time-elements/time-ago-element.js
@@ -0,0 +1,21 @@
+import RelativeTime from './relative-time';
+import RelativeTimeElement from './relative-time-element';
+import { localeFromElement } from './utils';
+export default class TimeAgoElement extends RelativeTimeElement {
+ getFormattedDate() {
+ const format = this.getAttribute('format');
+ const date = this.date;
+ if (!date)
+ return;
+ if (format === 'micro') {
+ return new RelativeTime(date, localeFromElement(this)).microTimeAgo();
+ }
+ else {
+ return new RelativeTime(date, localeFromElement(this)).timeAgo();
+ }
+ }
+}
+if (!window.customElements.get('time-ago')) {
+ window.TimeAgoElement = TimeAgoElement;
+ window.customElements.define('time-ago', TimeAgoElement);
+}
diff --git a/qortal-ui-plugins/plugins/core/components/time-elements/time-until-element.js b/qortal-ui-plugins/plugins/core/components/time-elements/time-until-element.js
new file mode 100644
index 00000000..2b57a845
--- /dev/null
+++ b/qortal-ui-plugins/plugins/core/components/time-elements/time-until-element.js
@@ -0,0 +1,21 @@
+import RelativeTime from './relative-time';
+import RelativeTimeElement from './relative-time-element';
+import { localeFromElement } from './utils';
+export default class TimeUntilElement extends RelativeTimeElement {
+ getFormattedDate() {
+ const format = this.getAttribute('format');
+ const date = this.date;
+ if (!date)
+ return;
+ if (format === 'micro') {
+ return new RelativeTime(date, localeFromElement(this)).microTimeUntil();
+ }
+ else {
+ return new RelativeTime(date, localeFromElement(this)).timeUntil();
+ }
+ }
+}
+if (!window.customElements.get('time-until')) {
+ window.TimeUntilElement = TimeUntilElement;
+ window.customElements.define('time-until', TimeUntilElement);
+}
diff --git a/qortal-ui-plugins/plugins/core/components/time-elements/utils.js b/qortal-ui-plugins/plugins/core/components/time-elements/utils.js
new file mode 100644
index 00000000..7bf4b1e7
--- /dev/null
+++ b/qortal-ui-plugins/plugins/core/components/time-elements/utils.js
@@ -0,0 +1,166 @@
+const weekdays = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
+const months = [
+ 'January',
+ 'February',
+ 'March',
+ 'April',
+ 'May',
+ 'June',
+ 'July',
+ 'August',
+ 'September',
+ 'October',
+ 'November',
+ 'December'
+];
+function pad(num) {
+ return `0${num}`.slice(-2);
+}
+export function strftime(time, formatString) {
+ const day = time.getDay();
+ const date = time.getDate();
+ const month = time.getMonth();
+ const year = time.getFullYear();
+ const hour = time.getHours();
+ const minute = time.getMinutes();
+ const second = time.getSeconds();
+ return formatString.replace(/%([%aAbBcdeHIlmMpPSwyYZz])/g, function (_arg) {
+ let match;
+ const modifier = _arg[1];
+ switch (modifier) {
+ case '%':
+ return '%';
+ case 'a':
+ return weekdays[day].slice(0, 3);
+ case 'A':
+ return weekdays[day];
+ case 'b':
+ return months[month].slice(0, 3);
+ case 'B':
+ return months[month];
+ case 'c':
+ return time.toString();
+ case 'd':
+ return pad(date);
+ case 'e':
+ return String(date);
+ case 'H':
+ return pad(hour);
+ case 'I':
+ return pad(strftime(time, '%l'));
+ case 'l':
+ if (hour === 0 || hour === 12) {
+ return String(12);
+ }
+ else {
+ return String((hour + 12) % 12);
+ }
+ case 'm':
+ return pad(month + 1);
+ case 'M':
+ return pad(minute);
+ case 'p':
+ if (hour > 11) {
+ return 'PM';
+ }
+ else {
+ return 'AM';
+ }
+ case 'P':
+ if (hour > 11) {
+ return 'pm';
+ }
+ else {
+ return 'am';
+ }
+ case 'S':
+ return pad(second);
+ case 'w':
+ return String(day);
+ case 'y':
+ return pad(year % 100);
+ case 'Y':
+ return String(year);
+ case 'Z':
+ match = time.toString().match(/\((\w+)\)$/);
+ return match ? match[1] : '';
+ case 'z':
+ match = time.toString().match(/\w([+-]\d\d\d\d) /);
+ return match ? match[1] : '';
+ }
+ return '';
+ });
+}
+export function makeFormatter(options) {
+ let format;
+ return function () {
+ if (format)
+ return format;
+ if ('Intl' in window) {
+ try {
+ format = new Intl.DateTimeFormat(undefined, options);
+ return format;
+ }
+ catch (e) {
+ if (!(e instanceof RangeError)) {
+ throw e;
+ }
+ }
+ }
+ };
+}
+let dayFirst = null;
+const dayFirstFormatter = makeFormatter({ day: 'numeric', month: 'short' });
+export function isDayFirst() {
+ if (dayFirst !== null) {
+ return dayFirst;
+ }
+ const formatter = dayFirstFormatter();
+ if (formatter) {
+ const output = formatter.format(new Date(0));
+ dayFirst = !!output.match(/^\d/);
+ return dayFirst;
+ }
+ else {
+ return false;
+ }
+}
+let yearSeparator = null;
+const yearFormatter = makeFormatter({ day: 'numeric', month: 'short', year: 'numeric' });
+export function isYearSeparator() {
+ if (yearSeparator !== null) {
+ return yearSeparator;
+ }
+ const formatter = yearFormatter();
+ if (formatter) {
+ const output = formatter.format(new Date(0));
+ yearSeparator = !!output.match(/\d,/);
+ return yearSeparator;
+ }
+ else {
+ return true;
+ }
+}
+export function isThisYear(date) {
+ const now = new Date();
+ return now.getUTCFullYear() === date.getUTCFullYear();
+}
+export function makeRelativeFormat(locale, options) {
+ if ('Intl' in window && 'RelativeTimeFormat' in window.Intl) {
+ try {
+ return new Intl.RelativeTimeFormat(locale, options);
+ }
+ catch (e) {
+ if (!(e instanceof RangeError)) {
+ throw e;
+ }
+ }
+ }
+}
+export function localeFromElement(el) {
+ const container = el.closest('[lang]');
+ if (container instanceof HTMLElement && container.lang) {
+ return container.lang;
+ }
+ return 'default';
+}
diff --git a/qortal-ui-plugins/plugins/core/group-management/group-management.src.js b/qortal-ui-plugins/plugins/core/group-management/group-management.src.js
index 0221abe2..edcfd254 100644
--- a/qortal-ui-plugins/plugins/core/group-management/group-management.src.js
+++ b/qortal-ui-plugins/plugins/core/group-management/group-management.src.js
@@ -7,6 +7,7 @@ registerTranslateConfig({
loader: lang => fetch(`/language/${lang}.json`).then(res => res.json())
})
+import '../components/time-elements/index.js'
import '@material/mwc-button'
import '@material/mwc-dialog'
import '@material/mwc-formfield'
@@ -21,7 +22,6 @@ import '@vaadin/icon'
import '@vaadin/icons'
import '@vaadin/grid'
import '@vaadin/grid/vaadin-grid-filter-column.js'
-import '@github/time-elements'
const parentEpml = new Epml({ type: 'WINDOW', source: window.parent })
@@ -30,12 +30,15 @@ class GroupManagement extends LitElement {
return {
loading: { type: Boolean },
publicGroups: { type: Array },
+ privateGroups: { type: Array },
joinedGroups: { type: Array },
groupInvites: { type: Array },
+ privateGroupSearch: { type: Array },
newMembersList: { type: Array },
newAdminsList: { type: Array },
newBannedList: { type: Array },
newGroupInvitesList: { type: Array },
+ newGroupJoinsList: { type: Array },
recipientPublicKey: { type: String },
selectedAddress: { type: Object },
manageGroupObj: { type: Object },
@@ -77,6 +80,7 @@ class GroupManagement extends LitElement {
toInviteMemberToGroup: { type: String },
toCancelInviteMemberName: { type: String },
toCancelInviteMemberAddress: { type: String },
+ searchGroupName: { type: String },
errorMessage: { type: String },
successMessage: { type: String }
}
@@ -152,6 +156,11 @@ class GroupManagement extends LitElement {
color: red;
}
+ .success-icon {
+ font-size: 48px;
+ color: #198754;
+ }
+
.close-icon {
font-size: 36px;
}
@@ -410,6 +419,13 @@ class GroupManagement extends LitElement {
background: var(--white);
color: var(--black);
}
+
+ #search {
+ width: 100%;
+ display: flex;
+ margin: auto;
+ align-items: center;
+ }
`
}
@@ -418,12 +434,15 @@ class GroupManagement extends LitElement {
this.theme = localStorage.getItem('qortalTheme') ? localStorage.getItem('qortalTheme') : 'light'
this.selectedAddress = {}
this.publicGroups = []
+ this.privateGroups = []
this.joinedGroups = []
this.groupInvites = []
+ this.privateGroupSearch = []
this.newMembersList = []
this.newAdminsList = []
this.newBannedList = []
this.newGroupInvitesList = []
+ this.newGroupJoinsList = []
this.manageGroupObj = {}
this.joinGroupObj = {}
this.leaveGroupObj = {}
@@ -458,6 +477,7 @@ class GroupManagement extends LitElement {
this.toInviteMemberToGroup = ''
this.toCancelInviteMemberName = ''
this.toCancelInviteMemberAddress = ''
+ this.searchGroupName = ''
this.errorMessage = ''
this.successMessage = ''
this.selectedView = { id: 'group-members', name: 'Group Members' }
@@ -823,6 +843,7 @@ class GroupManagement extends LitElement {
groupInviteTemplate() {
return html`
+
${translate("managegroup.mg36")}
${translate("managegroup.mg35")}
` : html``}
+
+ ${translate("managegroup.mg53")}
+
+ {
+ render(html`${this.renderAvatar(data.item)}`, root)
+ }}
+ >
+ {
+ render(html`${data.item.name}`, root)
+ }}
+ >
+ {
+ render(html`${data.item.owner}`, root)
+ }}
+ >
+ {
+ render(html`${this.renderConfirmRequestButton(data.item)}`, root)
+ }}
+ >
+ {
+ render(html`${this.renderDeclineRequestButton(data.item)}`, root)
+ }}
+ >
+
+ ${this.isEmptyArray(this.newGroupJoinsList) ? html`
+ ${translate("managegroup.mg54")}
+ ` : html``}
+
+
this.openInviteMemberToGroupDialog()}>
${translate("managegroup.mg2")}
@@ -1020,6 +1089,72 @@ class GroupManagement extends LitElement {
${translate("general.close")}
+
+
+
+ group_add
+
${translate("managegroup.mg57")}
+ ${translate("walletpage.wchange43")}
+
+
+ this.closeSuccessJoinDialog()}
+ class="red"
+ >
+ ${translate("general.close")}
+
+
+
+
+
+ warning
+
${translate("managegroup.mg58")}
+ ${this.errorMessage}
+ ${translate("walletpage.wchange44")}
+
+
+ this.closeErrorJoinDialog()}
+ class="red"
+ >
+ ${translate("general.close")}
+
+
+
+
+
+ person_remove
+
${translate("managegroup.mg59")}
+ ${translate("walletpage.wchange43")}
+
+
+ this.closeCancelSuccessJoinDialog()}
+ class="red"
+ >
+ ${translate("general.close")}
+
+
+
+
+
+ warning
+
${translate("managegroup.mg58")}
+ ${this.errorMessage}
+ ${translate("walletpage.wchange44")}
+
+
+ this.closeCancelErrorJoinDialog()}
+ class="red"
+ >
+ ${translate("general.close")}
+
+
`
}
@@ -1136,6 +1271,45 @@ class GroupManagement extends LitElement {
this.shadowRoot.querySelector('#createGroupDialog').show()}>add${translate("grouppage.gchange2")}
+
+
${translate("grouppage.gchange55")}
+
+
+
+
+ this.doGroupSearch(e)}">
+
+ ${translate("websitespage.schange35")}
+
+
+
+
+
+ {
+ if (data.item.isOpen === true) {
+ render(html`${translate("managegroup.mg44")}`, root)
+ } else {
+ render(html`${translate("managegroup.mg45")}`, root)
+ }
+ }}>
+
+
+ {
+ render(html` this.joinGroup(data.item)}>queue ${translate("grouppage.gchange51")}`, root)
+ }}>
+
+
+
${translate("grouppage.gchange3")}
@@ -1150,7 +1324,9 @@ class GroupManagement extends LitElement {
}}>
${this.isEmptyArray(this.joinedGroups) ? html`
-
${translate("grouppage.gchange8")}
+
+ ${translate("grouppage.gchange8")}
+
`: ''}
@@ -1175,7 +1351,9 @@ class GroupManagement extends LitElement {
}}>
${this.isEmptyArray(this.groupInvites) ? html`
-
${translate("managegroup.mg35")}
+
+ ${translate("managegroup.mg35")}
+
`: ''}
@@ -1477,6 +1655,22 @@ class GroupManagement extends LitElement {
${translate("general.close")}
+
+
+
+ warning
+
${translate("grouppage.gchange57")}
+ ${translate("grouppage.gchange58")}
+
+
+ this.closePrivateGroupErrorDialog()}
+ class="red"
+ >
+ ${translate("general.close")}
+
+
`
}
@@ -1504,6 +1698,14 @@ class GroupManagement extends LitElement {
return myGs
}
+ const getPrivateGroups = async () => {
+ let privateG = await parentEpml.request('apiCall', {
+ url: `/groups?limit=0&reverse=true`
+ })
+ let myPgs = privateG.filter(myP => myP.isOpen === false)
+ return myPgs
+ }
+
const getJoinedGroups = async () => {
let joinedG = await parentEpml.request('apiCall', {
url: `/groups/member/${this.selectedAddress.address}`
@@ -1563,13 +1765,15 @@ class GroupManagement extends LitElement {
const getOpen_JoinedGroups = async () => {
let _joinedGroups = await getJoinedGroups()
let _publicGroups = await getOpenPublicGroups()
+ let _privateGroups = await getPrivateGroups()
let results = _publicGroups.filter(myOpenGroup => {
let value = _joinedGroups.some(myJoinedGroup => myOpenGroup.groupId === myJoinedGroup.groupId)
return !value
});
this.publicGroups = results
+ this.privateGroups = _privateGroups
this.joinedGroups = _joinedGroups
- setTimeout(getOpen_JoinedGroups, 30000)
+ setTimeout(getOpen_JoinedGroups, 60000)
}
window.addEventListener("contextmenu", (event) => {
@@ -1722,6 +1926,36 @@ class GroupManagement extends LitElement {
}
}
+ searchGroupListener(e) {
+ if (e.key === 'Enter') {
+ this.doGroupSearch(e)
+ }
+ }
+
+ doGroupSearch(e) {
+ this.renderSearchResult()
+ }
+
+ renderSearchResult() {
+ this.privateGroupSearch = []
+ let searchGroupName = this.shadowRoot.getElementById('searchGroupName').value
+ if (searchGroupName.length === 0) {
+ let err1string = get("websitespage.schange34")
+ parentEpml.request('showSnackBar', `${err1string}`)
+ } else {
+ this.privateGroupSearch = this.privateGroups.filter(myS => myS.groupName === searchGroupName)
+ if (this.privateGroupSearch.length === 0) {
+ this.shadowRoot.querySelector('#privateGroupErrorDialog').show()
+ }
+ }
+ }
+
+ closePrivateGroupErrorDialog() {
+ this.shadowRoot.querySelector('#privateGroupErrorDialog').close()
+ this.shadowRoot.getElementById('searchGroupName').value = ''
+ this.privateGroupSearch = []
+ }
+
renderBanButton(groupObj) {
return html` this.openCreateBanMemberDialog(groupObj)}>create ${translate("managegroup.mg6")}`
}
@@ -1874,6 +2108,38 @@ class GroupManagement extends LitElement {
this.errorMessage = ''
}
+ renderConfirmRequestButton(joinObj) {
+ return html` this.createAcceptJoinGroupMember(joinObj)}>add_task ${translate("transpage.tchange3")}`
+ }
+
+ renderDeclineRequestButton(joinObj) {
+ return html` this.kickJoinGroupMember(joinObj)}>cancel ${translate("transpage.tchange2")}`
+ }
+
+ closeSuccessJoinDialog() {
+ this.shadowRoot.querySelector('#successJoinDialog').close()
+ this.successMessage = ''
+ this.errorMessage = ''
+ }
+
+ closeErrorJoinDialog() {
+ this.shadowRoot.querySelector('#errorJoinDialog').close()
+ this.successMessage = ''
+ this.errorMessage = ''
+ }
+
+ closeCancelSuccessJoinDialog() {
+ this.shadowRoot.querySelector('#cancelSuccessJoinDialog').close()
+ this.successMessage = ''
+ this.errorMessage = ''
+ }
+
+ closeCancelErrorJoinDialog() {
+ this.shadowRoot.querySelector('#cancelErrorJoinDialog').close()
+ this.successMessage = ''
+ this.errorMessage = ''
+ }
+
openMemberInfo(inviteGroupId) {
const _inviteMemberInfo = this.shadowRoot.getElementById('toInviteMemberToGroup').value
const _nviteMemberTime = this.shadowRoot.getElementById("inviteMemberTime").value
@@ -1939,6 +2205,7 @@ class GroupManagement extends LitElement {
closeFieldErrorDialog() {
this.shadowRoot.querySelector('#fieldErrorDialog').close()
}
+
async unitCreateFee() {
const myNode = window.parent.reduxStore.getState().app.nodeConfig.knownNodes[window.parent.reduxStore.getState().app.nodeConfig.node]
const nodeUrl = myNode.protocol + '://' + myNode.domain + ':' + myNode.port
@@ -2248,6 +2515,57 @@ class GroupManagement extends LitElement {
}
}
+ async getNewGroupJoinList(theGroup) {
+ let callGroupID = theGroup
+ const myNode = window.parent.reduxStore.getState().app.nodeConfig.knownNodes[window.parent.reduxStore.getState().app.nodeConfig.node]
+ const nodeUrl = myNode.protocol + '://' + myNode.domain + ':' + myNode.port
+
+ let joinObj = []
+ this.groupJoinMembers = []
+
+ await parentEpml.request('apiCall', {
+ url: `/groups/joinrequests/${callGroupID}`
+ }).then(res => {
+ this.groupJoinMembers = res
+ })
+
+ if (this.groupJoinMembers.length === 0) {
+ return
+ } else {
+ this.groupJoinMembers.map(a => {
+ let callTheJoinMember = a.joiner
+ let callSingleJoinMemberUrl = `${nodeUrl}/names/address/${callTheJoinMember}`
+ fetch(callSingleJoinMemberUrl).then(res => {
+ return res.json()
+ }).then(jsonRes => {
+ if (jsonRes.length) {
+ jsonRes.map(b => {
+ const joinObjToAdd = {
+ groupId: a.groupId,
+ name: b.name,
+ owner: b.owner,
+ time: '86400',
+ reason: 'NotAllowed'
+ }
+ joinObj.push(joinObjToAdd)
+ })
+ } else {
+ const noName = 'No registered name'
+ const noNameObj = {
+ groupId: a.groupId,
+ name: noName,
+ owner: a.joiner,
+ time: '86400',
+ reason: 'NotAllowed'
+ }
+ joinObj.push(noNameObj)
+ }
+ this.newGroupJoinsList = joinObj
+ })
+ })
+ }
+ }
+
closeManageGroupOwnerDialog() {
this.resetDefaultSettings()
this.shadowRoot.getElementById('manageGroupOwnerDialog').close()
@@ -2276,6 +2594,7 @@ class GroupManagement extends LitElement {
await this.getNewMemberList(groupObj.groupId)
await this.getNewBannedList(groupObj.groupId)
await this.getNewGroupInvitesList(groupObj.groupId)
+ await this.getNewGroupJoinList(groupObj.groupId)
await manageGroupDelay(1000)
this.shadowRoot.getElementById('manageGroupOwnerDialog').open()
}
@@ -2944,6 +3263,148 @@ class GroupManagement extends LitElement {
validateReceiver()
}
+ async createAcceptJoinGroupMember(joinObj) {
+ const member = joinObj.owner
+ const inviteTime = joinObj.time
+ const inviteGroupMemberFeeInput = this.inviteGroupMemberFee
+ const theGroupId = joinObj.groupId
+ this.isLoading = true
+ this.btnDisable = true
+
+ const getLastRef = async () => {
+ let myRef = await parentEpml.request('apiCall', {
+ type: 'api',
+ url: `/addresses/lastreference/${this.selectedAddress.address}`
+ })
+ return myRef
+ }
+
+ const validateReceiver = async () => {
+ let lastRef = await getLastRef()
+ let myTransaction = await makeTransactionRequest(lastRef)
+ getTxnRequestResponse(myTransaction)
+ }
+
+ const makeTransactionRequest = async (lastRef) => {
+ const myMember = member
+ const myLastRef = lastRef
+ const myGroupId = theGroupId
+ const myFee = inviteGroupMemberFeeInput
+ const myInviteTime = inviteTime
+ const myInviteMemberDialog1 = get("managegroup.mg55")
+ const myInviteMemberDialog2 = get("managegroup.mg56")
+
+ let myTxnrequest = await parentEpml.request('transaction', {
+ type: 29,
+ nonce: this.selectedAddress.nonce,
+ params: {
+ fee: myFee,
+ recipient: myMember,
+ rGroupId: myGroupId,
+ rInviteTime: myInviteTime,
+ lastReference: myLastRef,
+ inviteMemberDialog1: myInviteMemberDialog1,
+ inviteMemberDialog2: myInviteMemberDialog2
+ }
+ })
+ return myTxnrequest
+ }
+
+ const getTxnRequestResponse = (txnResponse) => {
+ if (txnResponse.success === false && txnResponse.message) {
+ this.errorMessage = txnResponse.message
+ this.shadowRoot.querySelector('#errorJoinDialog').show()
+ this.isLoading = false
+ this.btnDisable = false
+ throw new Error(txnResponse)
+ } else if (txnResponse.success === true && !txnResponse.data.error) {
+ this.shadowRoot.querySelector('#successJoinDialog').show()
+ this.errorMessage = ''
+ this.successMessage = this.renderSuccessText()
+ this.isLoading = false
+ this.btnDisable = false
+ } else {
+ this.errorMessage = txnResponse.data.message
+ this.shadowRoot.querySelector('#errorJoinDialog').show()
+ this.isLoading = false
+ this.btnDisable = false
+ throw new Error(txnResponse)
+ }
+ }
+ validateReceiver()
+ }
+
+ async kickJoinGroupMember(joinObj) {
+ const member = joinObj.owner
+ const reason = joinObj.reason
+ const kickGroupMemberFeeInput = this.kickGroupMemberFee
+ const theGroupId = joinObj.groupId
+ this.isLoading = true
+ this.btnDisable = true
+
+ const getLastRef = async () => {
+ let myRef = await parentEpml.request('apiCall', {
+ type: 'api',
+ url: `/addresses/lastreference/${this.selectedAddress.address}`
+ })
+ return myRef
+ }
+
+ const validateReceiver = async () => {
+ let lastRef = await getLastRef()
+ let myTransaction = await makeTransactionRequest(lastRef)
+ getTxnRequestResponse(myTransaction)
+ }
+
+ const makeTransactionRequest = async (lastRef) => {
+ const myMember = member
+ const myLastRef = lastRef
+ const myGroupId = theGroupId
+ const myFee = kickGroupMemberFeeInput
+ const myReason = reason
+ const myKickMemberDialog1 = get("managegroup.mg60")
+ const myKickMemberDialog2 = get("managegroup.mg61")
+
+ let myTxnrequest = await parentEpml.request('transaction', {
+ type: 28,
+ nonce: this.selectedAddress.nonce,
+ params: {
+ fee: myFee,
+ recipient: myMember,
+ rGroupId: myGroupId,
+ rBanReason: myReason,
+ lastReference: myLastRef,
+ kickMemberDialog1: myKickMemberDialog1,
+ kickMemberDialog2: myKickMemberDialog2
+ }
+ })
+ return myTxnrequest
+ }
+
+ const getTxnRequestResponse = (txnResponse) => {
+ if (txnResponse.success === false && txnResponse.message) {
+ this.errorMessage = txnResponse.message
+ this.shadowRoot.querySelector('#cancelErrorJoinDialog').show()
+ this.isLoading = false
+ this.btnDisable = false
+ throw new Error(txnResponse)
+ } else if (txnResponse.success === true && !txnResponse.data.error) {
+ this.shadowRoot.querySelector('#cancelSuccessJoinDialog').show()
+ this.errorMessage = ''
+ this.successMessage = this.renderSuccessText()
+ this.isLoading = false
+ this.btnDisable = false
+ } else {
+ this.errorMessage = txnResponse.data.message
+ this.shadowRoot.querySelector('#cancelErrorJoinDialog').show()
+ this.isLoading = false
+ this.btnDisable = false
+ throw new Error(txnResponse)
+ }
+ }
+ validateReceiver()
+ }
+
async addGroupAdmin(groupId) {
const member = this.shadowRoot.getElementById('memberToAdmin').value
const addGroupAdminFeeInput = this.addGroupAdminFee
@@ -3178,4 +3639,4 @@ class GroupManagement extends LitElement {
}
}
-window.customElements.define('group-management', GroupManagement)
+window.customElements.define('group-management', GroupManagement)
\ No newline at end of file
diff --git a/qortal-ui-plugins/plugins/core/name-registration/name-registration.src.js b/qortal-ui-plugins/plugins/core/name-registration/name-registration.src.js
index 879dda46..05019570 100644
--- a/qortal-ui-plugins/plugins/core/name-registration/name-registration.src.js
+++ b/qortal-ui-plugins/plugins/core/name-registration/name-registration.src.js
@@ -280,7 +280,7 @@ class NameRegistration extends LitElement {
}
async uploadAvatar(nameObj) {
- let name = nameObj.name
+ let name = encodeURIComponent(nameObj.name)
window.location.href = `../qdn/publish/index.html?service=THUMBNAIL&identifier=qortal_avatar&name=${name}&uploadType=file&category=Avatar&showName=false&showService=false&showIdentifier=false`
}
diff --git a/qortal-ui-plugins/plugins/core/trade-portal/charts/arrr-charts.js b/qortal-ui-plugins/plugins/core/trade-portal/charts/arrr-charts.js
new file mode 100644
index 00000000..561cd9df
--- /dev/null
+++ b/qortal-ui-plugins/plugins/core/trade-portal/charts/arrr-charts.js
@@ -0,0 +1,214 @@
+import { LitElement, html, css } from 'lit'
+import { render } from 'lit/html.js'
+import { Epml } from '../../../../epml.js'
+import { use, get, translate, translateUnsafeHTML, registerTranslateConfig } from 'lit-translate'
+
+registerTranslateConfig({
+ loader: lang => fetch(`/language/${lang}.json`).then(res => res.json())
+})
+
+import '@polymer/paper-dialog/paper-dialog.js'
+import * as Highcharts from 'highcharts'
+import Exporting from 'highcharts/modules/exporting'
+Exporting(Highcharts)
+import StockChart from 'highcharts/modules/stock'
+StockChart(Highcharts)
+import 'highcharts/highcharts-more.js'
+import 'highcharts/modules/accessibility.js'
+import 'highcharts/modules/boost.js'
+import 'highcharts/modules/data.js'
+import 'highcharts/modules/export-data.js'
+import 'highcharts/modules/offline-exporting.js'
+
+let arrrChartDialog
+
+const parentEpml = new Epml({ type: 'WINDOW', source: window.parent })
+
+class ArrrCharts extends LitElement {
+ static get properties() {
+ return {
+ isLoadingTradesChart: { type: Boolean },
+ arrrTrades: { type: Array },
+ arrrPrice: { type: Array }
+ }
+ }
+
+ static get styles() {
+ return css`
+ .loadingContainer {
+ height: 100%;
+ width: 100%;
+ }
+
+ .trades-chart {
+ color: var(--black);
+ background: var(--white);
+ border: 1px solid var(--black);
+ border-radius: 25px;
+ padding: 15px;
+ }
+
+ .chart-container {
+ margin: auto;
+ color: var(--black);
+ text-align: center;
+ padding: 15px;
+ height: 30vh;
+ width: 80vw;
+ }
+
+ .chart-info-wrapper {
+ background: transparent;
+ height: 38vh;
+ width: 83vw;
+ overflow: auto;
+ }
+
+ .chart-loading-wrapper {
+ color: var(--black);
+ background: var(--white);
+ border: 1px solid var(--black);
+ border-radius: 15px;
+ }
+ `
+ }
+
+ constructor() {
+ super()
+ this.theme = localStorage.getItem('qortalTheme') ? localStorage.getItem('qortalTheme') : 'light'
+ this.isLoadingTradesChart = false
+ this.arrrTrades = []
+ this.arrrPrice = []
+ }
+
+ render() {
+ return html`
+
+
+ ${translate("login.loading")}
+
+
+
+
+
+ `
+ }
+
+ async firstUpdated() {
+ this.changeTheme()
+ this.changeLanguage()
+
+ window.addEventListener('storage', () => {
+ const checkLanguage = localStorage.getItem('qortalLanguage')
+ const checkTheme = localStorage.getItem('qortalTheme')
+
+ use(checkLanguage)
+
+ this.theme = (checkTheme === 'dark') ? 'dark' : 'light'
+ document.querySelector('html').setAttribute('theme', this.theme)
+ })
+ }
+
+ async loadTradesChart() {
+ this.isLoadingTradesChart = true
+ this.shadowRoot.getElementById('loadChartDialog').open()
+ await this.getArrrTrades()
+ this.isLoadingTradesChart = false
+ this.shadowRoot.getElementById('loadChartDialog').close()
+ this.enableArrrStockPriceChart()
+ }
+
+ async getArrrTrades() {
+ let currentArrrTimestamp = Date.now()
+ const monthBackArrr = currentArrrTimestamp - 31556952000
+ await parentEpml.request("apiCall", { url: `/crosschain/trades?foreignBlockchain=PIRATECHAIN&minimumTimestamp=${monthBackArrr}&limit=0&reverse=false` }).then((res) => {
+ this.arrrTrades = res
+ })
+ this.arrrPrice = this.arrrTrades.map(item => {
+ const arrrSellPrice = this.round(parseFloat(item.foreignAmount) / parseFloat(item.qortAmount))
+ return [item.tradeTimestamp, parseFloat(arrrSellPrice)]
+ }).filter(item => !!item)
+ }
+
+ enableArrrStockPriceChart() {
+ const arrrStockPriceData = this.arrrPrice
+ const header = 'QORT / ARRR ' + get("tradepage.tchange49")
+ Highcharts.stockChart(this.shadowRoot.querySelector('#arrrStockPriceContainer'), {
+ accessibility: {
+ enabled: false
+ },
+ credits: {
+ enabled: false
+ },
+ rangeSelector: {
+ selected: 1,
+ labelStyle: {color: 'var(--black)'},
+ inputStyle: {color: '#03a9f4'}
+ },
+ chart: {
+ backgroundColor: 'transparent'
+ },
+ title: {
+ text: header,
+ style: {color: 'var(--black)'}
+ },
+ xAxis: {
+ labels: {
+ style: {
+ color: '#03a9f4'
+ }
+ }
+ },
+ yAxis: {
+ labels: {
+ style: {
+ color: '#03a9f4'
+ }
+ }
+ },
+ series: [{
+ name: 'QORT / ARRR',
+ data: arrrStockPriceData,
+ tooltip: {
+ valueDecimals: 8
+ }
+ }]
+ })
+ }
+
+ async open() {
+ await this.loadTradesChart()
+ this.shadowRoot.getElementById('arrrChartDialog').open()
+ }
+
+ changeTheme() {
+ const checkTheme = localStorage.getItem('qortalTheme')
+ this.theme = (checkTheme === 'dark') ? 'dark' : 'light'
+ document.querySelector('html').setAttribute('theme', this.theme);
+ }
+
+ changeLanguage() {
+ const checkLanguage = localStorage.getItem('qortalLanguage')
+
+ if (checkLanguage === null || checkLanguage.length === 0) {
+ localStorage.setItem('qortalLanguage', 'us')
+ use('us')
+ } else {
+ use(checkLanguage)
+ }
+ }
+
+ round(number) {
+ let result = (Math.round(parseFloat(number) * 1e8) / 1e8).toFixed(8)
+ return result
+ }
+}
+
+window.customElements.define('arrr-charts', ArrrCharts)
+
+const chartsarrr = document.createElement('arrr-charts')
+arrrChartDialog = document.body.appendChild(chartsarrr)
+
+export default arrrChartDialog
\ No newline at end of file
diff --git a/qortal-ui-plugins/plugins/core/trade-portal/charts/btc-charts.js b/qortal-ui-plugins/plugins/core/trade-portal/charts/btc-charts.js
new file mode 100644
index 00000000..c31d272b
--- /dev/null
+++ b/qortal-ui-plugins/plugins/core/trade-portal/charts/btc-charts.js
@@ -0,0 +1,215 @@
+import { LitElement, html, css } from 'lit'
+import { render } from 'lit/html.js'
+import { Epml } from '../../../../epml.js'
+import { use, get, translate, translateUnsafeHTML, registerTranslateConfig } from 'lit-translate'
+
+registerTranslateConfig({
+ loader: lang => fetch(`/language/${lang}.json`).then(res => res.json())
+})
+
+import '@polymer/paper-dialog/paper-dialog.js'
+import * as Highcharts from 'highcharts'
+import Exporting from 'highcharts/modules/exporting'
+Exporting(Highcharts)
+import StockChart from 'highcharts/modules/stock'
+StockChart(Highcharts)
+import 'highcharts/highcharts-more.js'
+import 'highcharts/modules/accessibility.js'
+import 'highcharts/modules/boost.js'
+import 'highcharts/modules/data.js'
+import 'highcharts/modules/export-data.js'
+import 'highcharts/modules/offline-exporting.js'
+
+let btcChartDialog
+
+const parentEpml = new Epml({ type: 'WINDOW', source: window.parent })
+
+class BtcCharts extends LitElement {
+ static get properties() {
+ return {
+ isLoadingTradesChart: { type: Boolean },
+ btcTrades: { type: Array },
+ btcPrice: { type: Array }
+ }
+ }
+
+ static get styles() {
+ return css`
+ .loadingContainer {
+ height: 100%;
+ width: 100%;
+ }
+
+ .trades-chart {
+ color: var(--black);
+ background: var(--white);
+ border: 1px solid var(--black);
+ border-radius: 25px;
+ padding: 15px;
+ }
+
+ .chart-container {
+ margin: auto;
+ color: var(--black);
+ text-align: center;
+ padding: 15px;
+ height: 30vh;
+ width: 80vw;
+ }
+
+ .chart-info-wrapper {
+ background: transparent;
+ height: 38vh;
+ width: 83vw;
+ overflow: auto;
+ }
+
+ .chart-loading-wrapper {
+ color: var(--black);
+ background: var(--white);
+ border: 1px solid var(--black);
+ border-radius: 15px;
+ }
+ `
+ }
+
+ constructor() {
+ super()
+ this.theme = localStorage.getItem('qortalTheme') ? localStorage.getItem('qortalTheme') : 'light'
+ this.isLoadingTradesChart = false
+ this.btcTrades = []
+ this.btcPrice = []
+ }
+
+ render() {
+ return html`
+
+
+ ${translate("login.loading")}
+
+
+
+
+
+ `
+ }
+
+ async firstUpdated() {
+ this.changeTheme()
+ this.changeLanguage()
+
+ window.addEventListener('storage', () => {
+ const checkLanguage = localStorage.getItem('qortalLanguage')
+ const checkTheme = localStorage.getItem('qortalTheme')
+
+ use(checkLanguage)
+
+ this.theme = (checkTheme === 'dark') ? 'dark' : 'light'
+ document.querySelector('html').setAttribute('theme', this.theme)
+ })
+ }
+
+ async loadTradesChart() {
+ this.isLoadingTradesChart = true
+ this.shadowRoot.getElementById('loadChartDialog').open()
+ await this.getBtcTrades()
+ this.isLoadingTradesChart = false
+ this.shadowRoot.getElementById('loadChartDialog').close()
+ this.enableBtcStockPriceChart()
+ }
+
+ async getBtcTrades() {
+ let currentBtcTimestamp = Date.now()
+ const monthBackBtc = currentBtcTimestamp - 31556952000
+ await parentEpml.request("apiCall", { url: `/crosschain/trades?foreignBlockchain=BITCOIN&minimumTimestamp=${monthBackBtc}&limit=0&reverse=false` }).then((res) => {
+ this.btcTrades = res
+ })
+
+ this.btcPrice = this.btcTrades.map(item => {
+ const btcSellPrice = this.round(parseFloat(item.foreignAmount) / parseFloat(item.qortAmount))
+ return [item.tradeTimestamp, parseFloat(btcSellPrice)]
+ }).filter(item => !!item)
+ }
+
+ enableBtcStockPriceChart() {
+ const btcStockPriceData = this.btcPrice
+ const header = 'QORT / BTC ' + get("tradepage.tchange49")
+ Highcharts.stockChart(this.shadowRoot.querySelector('#btcStockPriceContainer'), {
+ accessibility: {
+ enabled: false
+ },
+ credits: {
+ enabled: false
+ },
+ rangeSelector: {
+ selected: 1,
+ labelStyle: {color: 'var(--black)'},
+ inputStyle: {color: '#03a9f4'}
+ },
+ chart: {
+ backgroundColor: 'transparent'
+ },
+ title: {
+ text: header,
+ style: {color: 'var(--black)'}
+ },
+ xAxis: {
+ labels: {
+ style: {
+ color: '#03a9f4'
+ }
+ }
+ },
+ yAxis: {
+ labels: {
+ style: {
+ color: '#03a9f4'
+ }
+ }
+ },
+ series: [{
+ name: 'QORT / BTC',
+ data: btcStockPriceData,
+ tooltip: {
+ valueDecimals: 8
+ }
+ }]
+ })
+ }
+
+ async open() {
+ await this.loadTradesChart()
+ this.shadowRoot.getElementById('btcChartDialog').open()
+ }
+
+ changeTheme() {
+ const checkTheme = localStorage.getItem('qortalTheme')
+ this.theme = (checkTheme === 'dark') ? 'dark' : 'light'
+ document.querySelector('html').setAttribute('theme', this.theme);
+ }
+
+ changeLanguage() {
+ const checkLanguage = localStorage.getItem('qortalLanguage')
+
+ if (checkLanguage === null || checkLanguage.length === 0) {
+ localStorage.setItem('qortalLanguage', 'us')
+ use('us')
+ } else {
+ use(checkLanguage)
+ }
+ }
+
+ round(number) {
+ let result = (Math.round(parseFloat(number) * 1e8) / 1e8).toFixed(8)
+ return result
+ }
+}
+
+window.customElements.define('btc-charts', BtcCharts)
+
+const chartsbtc = document.createElement('btc-charts')
+btcChartDialog = document.body.appendChild(chartsbtc)
+
+export default btcChartDialog
\ No newline at end of file
diff --git a/qortal-ui-plugins/plugins/core/trade-portal/charts/dgb-charts.js b/qortal-ui-plugins/plugins/core/trade-portal/charts/dgb-charts.js
new file mode 100644
index 00000000..5e57aad7
--- /dev/null
+++ b/qortal-ui-plugins/plugins/core/trade-portal/charts/dgb-charts.js
@@ -0,0 +1,214 @@
+import { LitElement, html, css } from 'lit'
+import { render } from 'lit/html.js'
+import { Epml } from '../../../../epml.js'
+import { use, get, translate, translateUnsafeHTML, registerTranslateConfig } from 'lit-translate'
+
+registerTranslateConfig({
+ loader: lang => fetch(`/language/${lang}.json`).then(res => res.json())
+})
+
+import '@polymer/paper-dialog/paper-dialog.js'
+import * as Highcharts from 'highcharts'
+import Exporting from 'highcharts/modules/exporting'
+Exporting(Highcharts)
+import StockChart from 'highcharts/modules/stock'
+StockChart(Highcharts)
+import 'highcharts/highcharts-more.js'
+import 'highcharts/modules/accessibility.js'
+import 'highcharts/modules/boost.js'
+import 'highcharts/modules/data.js'
+import 'highcharts/modules/export-data.js'
+import 'highcharts/modules/offline-exporting.js'
+
+let dgbChartDialog
+
+const parentEpml = new Epml({ type: 'WINDOW', source: window.parent })
+
+class DgbCharts extends LitElement {
+ static get properties() {
+ return {
+ isLoadingTradesChart: { type: Boolean },
+ dgbTrades: { type: Array },
+ dgbPrice: { type: Array }
+ }
+ }
+
+ static get styles() {
+ return css`
+ .loadingContainer {
+ height: 100%;
+ width: 100%;
+ }
+
+ .trades-chart {
+ color: var(--black);
+ background: var(--white);
+ border: 1px solid var(--black);
+ border-radius: 25px;
+ padding: 15px;
+ }
+
+ .chart-container {
+ margin: auto;
+ color: var(--black);
+ text-align: center;
+ padding: 15px;
+ height: 30vh;
+ width: 80vw;
+ }
+
+ .chart-info-wrapper {
+ background: transparent;
+ height: 38vh;
+ width: 83vw;
+ overflow: auto;
+ }
+
+ .chart-loading-wrapper {
+ color: var(--black);
+ background: var(--white);
+ border: 1px solid var(--black);
+ border-radius: 15px;
+ }
+ `
+ }
+
+ constructor() {
+ super()
+ this.theme = localStorage.getItem('qortalTheme') ? localStorage.getItem('qortalTheme') : 'light'
+ this.isLoadingTradesChart = false
+ this.dgbTrades = []
+ this.dgbPrice = []
+ }
+
+ render() {
+ return html`
+
+
+ ${translate("login.loading")}
+
+
+
+
+
+ `
+ }
+
+ async firstUpdated() {
+ this.changeTheme()
+ this.changeLanguage()
+
+ window.addEventListener('storage', () => {
+ const checkLanguage = localStorage.getItem('qortalLanguage')
+ const checkTheme = localStorage.getItem('qortalTheme')
+
+ use(checkLanguage)
+
+ this.theme = (checkTheme === 'dark') ? 'dark' : 'light'
+ document.querySelector('html').setAttribute('theme', this.theme)
+ })
+ }
+
+ async loadTradesChart() {
+ this.isLoadingTradesChart = true
+ this.shadowRoot.getElementById('loadChartDialog').open()
+ await this.getDgbTrades()
+ this.isLoadingTradesChart = false
+ this.shadowRoot.getElementById('loadChartDialog').close()
+ this.enableDgbStockPriceChart()
+ }
+
+ async getDgbTrades() {
+ let currentDgbTimestamp = Date.now()
+ const monthBackDgb = currentDgbTimestamp - 31556952000
+ await parentEpml.request("apiCall", { url: `/crosschain/trades?foreignBlockchain=DIGIBYTE&minimumTimestamp=${monthBackDgb}&limit=0&reverse=false` }).then((res) => {
+ this.dgbTrades = res
+ })
+ this.dgbPrice = this.dgbTrades.map(item => {
+ const dgbSellPrice = this.round(parseFloat(item.foreignAmount) / parseFloat(item.qortAmount))
+ return [item.tradeTimestamp, parseFloat(dgbSellPrice)]
+ }).filter(item => !!item)
+ }
+
+ enableDgbStockPriceChart() {
+ const dgbStockPriceData = this.dgbPrice
+ const header = 'QORT / DGB ' + get("tradepage.tchange49")
+ Highcharts.stockChart(this.shadowRoot.querySelector('#dgbStockPriceContainer'), {
+ accessibility: {
+ enabled: false
+ },
+ credits: {
+ enabled: false
+ },
+ rangeSelector: {
+ selected: 1,
+ labelStyle: {color: 'var(--black)'},
+ inputStyle: {color: '#03a9f4'}
+ },
+ chart: {
+ backgroundColor: 'transparent'
+ },
+ title: {
+ text: header,
+ style: {color: 'var(--black)'}
+ },
+ xAxis: {
+ labels: {
+ style: {
+ color: '#03a9f4'
+ }
+ }
+ },
+ yAxis: {
+ labels: {
+ style: {
+ color: '#03a9f4'
+ }
+ }
+ },
+ series: [{
+ name: 'QORT / DGB',
+ data: dgbStockPriceData,
+ tooltip: {
+ valueDecimals: 8
+ }
+ }]
+ })
+ }
+
+ async open() {
+ await this.loadTradesChart()
+ this.shadowRoot.getElementById('dgbChartDialog').open()
+ }
+
+ changeTheme() {
+ const checkTheme = localStorage.getItem('qortalTheme')
+ this.theme = (checkTheme === 'dark') ? 'dark' : 'light'
+ document.querySelector('html').setAttribute('theme', this.theme);
+ }
+
+ changeLanguage() {
+ const checkLanguage = localStorage.getItem('qortalLanguage')
+
+ if (checkLanguage === null || checkLanguage.length === 0) {
+ localStorage.setItem('qortalLanguage', 'us')
+ use('us')
+ } else {
+ use(checkLanguage)
+ }
+ }
+
+ round(number) {
+ let result = (Math.round(parseFloat(number) * 1e8) / 1e8).toFixed(8)
+ return result
+ }
+}
+
+window.customElements.define('dgb-charts', DgbCharts)
+
+const chartsdgb = document.createElement('dgb-charts')
+dgbChartDialog = document.body.appendChild(chartsdgb)
+
+export default dgbChartDialog
\ No newline at end of file
diff --git a/qortal-ui-plugins/plugins/core/trade-portal/charts/doge-charts.js b/qortal-ui-plugins/plugins/core/trade-portal/charts/doge-charts.js
new file mode 100644
index 00000000..a4267361
--- /dev/null
+++ b/qortal-ui-plugins/plugins/core/trade-portal/charts/doge-charts.js
@@ -0,0 +1,214 @@
+import { LitElement, html, css } from 'lit'
+import { render } from 'lit/html.js'
+import { Epml } from '../../../../epml.js'
+import { use, get, translate, translateUnsafeHTML, registerTranslateConfig } from 'lit-translate'
+
+registerTranslateConfig({
+ loader: lang => fetch(`/language/${lang}.json`).then(res => res.json())
+})
+
+import '@polymer/paper-dialog/paper-dialog.js'
+import * as Highcharts from 'highcharts'
+import Exporting from 'highcharts/modules/exporting'
+Exporting(Highcharts)
+import StockChart from 'highcharts/modules/stock'
+StockChart(Highcharts)
+import 'highcharts/highcharts-more.js'
+import 'highcharts/modules/accessibility.js'
+import 'highcharts/modules/boost.js'
+import 'highcharts/modules/data.js'
+import 'highcharts/modules/export-data.js'
+import 'highcharts/modules/offline-exporting.js'
+
+let dogeChartDialog
+
+const parentEpml = new Epml({ type: 'WINDOW', source: window.parent })
+
+class DogeCharts extends LitElement {
+ static get properties() {
+ return {
+ isLoadingTradesChart: { type: Boolean },
+ dogeTrades: { type: Array },
+ dogePrice: { type: Array }
+ }
+ }
+
+ static get styles() {
+ return css`
+ .loadingContainer {
+ height: 100%;
+ width: 100%;
+ }
+
+ .trades-chart {
+ color: var(--black);
+ background: var(--white);
+ border: 1px solid var(--black);
+ border-radius: 25px;
+ padding: 15px;
+ }
+
+ .chart-container {
+ margin: auto;
+ color: var(--black);
+ text-align: center;
+ padding: 15px;
+ height: 30vh;
+ width: 80vw;
+ }
+
+ .chart-info-wrapper {
+ background: transparent;
+ height: 38vh;
+ width: 83vw;
+ overflow: auto;
+ }
+
+ .chart-loading-wrapper {
+ color: var(--black);
+ background: var(--white);
+ border: 1px solid var(--black);
+ border-radius: 15px;
+ }
+ `
+ }
+
+ constructor() {
+ super()
+ this.theme = localStorage.getItem('qortalTheme') ? localStorage.getItem('qortalTheme') : 'light'
+ this.isLoadingTradesChart = false
+ this.dogeTrades = []
+ this.dogePrice = []
+ }
+
+ render() {
+ return html`
+
+
+ ${translate("login.loading")}
+
+
+
+
+
+ `
+ }
+
+ async firstUpdated() {
+ this.changeTheme()
+ this.changeLanguage()
+
+ window.addEventListener('storage', () => {
+ const checkLanguage = localStorage.getItem('qortalLanguage')
+ const checkTheme = localStorage.getItem('qortalTheme')
+
+ use(checkLanguage)
+
+ this.theme = (checkTheme === 'dark') ? 'dark' : 'light'
+ document.querySelector('html').setAttribute('theme', this.theme)
+ })
+ }
+
+ async loadTradesChart() {
+ this.isLoadingTradesChart = true
+ this.shadowRoot.getElementById('loadChartDialog').open()
+ await this.getDogeTrades()
+ this.isLoadingTradesChart = false
+ this.shadowRoot.getElementById('loadChartDialog').close()
+ this.enableDogeStockPriceChart()
+ }
+
+ async getDogeTrades() {
+ let currentDogeTimestamp = Date.now()
+ const monthBackDoge = currentDogeTimestamp - 31556952000
+ await parentEpml.request("apiCall", { url: `/crosschain/trades?foreignBlockchain=DOGECOIN&minimumTimestamp=${monthBackDoge}&limit=0&reverse=false` }).then((res) => {
+ this.dogeTrades = res
+ })
+ this.dogePrice = this.dogeTrades.map(item => {
+ const dogeSellPrice = this.round(parseFloat(item.foreignAmount) / parseFloat(item.qortAmount))
+ return [item.tradeTimestamp, parseFloat(dogeSellPrice)]
+ }).filter(item => !!item)
+ }
+
+ enableDogeStockPriceChart() {
+ const dogeStockPriceData = this.dogePrice
+ const header = 'QORT / DOGE ' + get("tradepage.tchange49")
+ Highcharts.stockChart(this.shadowRoot.querySelector('#dogeStockPriceContainer'), {
+ accessibility: {
+ enabled: false
+ },
+ credits: {
+ enabled: false
+ },
+ rangeSelector: {
+ selected: 1,
+ labelStyle: {color: 'var(--black)'},
+ inputStyle: {color: '#03a9f4'}
+ },
+ chart: {
+ backgroundColor: 'transparent'
+ },
+ title: {
+ text: header,
+ style: {color: 'var(--black)'}
+ },
+ xAxis: {
+ labels: {
+ style: {
+ color: '#03a9f4'
+ }
+ }
+ },
+ yAxis: {
+ labels: {
+ style: {
+ color: '#03a9f4'
+ }
+ }
+ },
+ series: [{
+ name: 'QORT / DOGE',
+ data: dogeStockPriceData,
+ tooltip: {
+ valueDecimals: 8
+ }
+ }]
+ })
+ }
+
+ async open() {
+ await this.loadTradesChart()
+ this.shadowRoot.getElementById('dogeChartDialog').open()
+ }
+
+ changeTheme() {
+ const checkTheme = localStorage.getItem('qortalTheme')
+ this.theme = (checkTheme === 'dark') ? 'dark' : 'light'
+ document.querySelector('html').setAttribute('theme', this.theme);
+ }
+
+ changeLanguage() {
+ const checkLanguage = localStorage.getItem('qortalLanguage')
+
+ if (checkLanguage === null || checkLanguage.length === 0) {
+ localStorage.setItem('qortalLanguage', 'us')
+ use('us')
+ } else {
+ use(checkLanguage)
+ }
+ }
+
+ round(number) {
+ let result = (Math.round(parseFloat(number) * 1e8) / 1e8).toFixed(8)
+ return result
+ }
+}
+
+window.customElements.define('doge-charts', DogeCharts)
+
+const chartsdoge = document.createElement('doge-charts')
+dogeChartDialog = document.body.appendChild(chartsdoge)
+
+export default dogeChartDialog
\ No newline at end of file
diff --git a/qortal-ui-plugins/plugins/core/trade-portal/charts/ltc-charts.js b/qortal-ui-plugins/plugins/core/trade-portal/charts/ltc-charts.js
new file mode 100644
index 00000000..08ffd339
--- /dev/null
+++ b/qortal-ui-plugins/plugins/core/trade-portal/charts/ltc-charts.js
@@ -0,0 +1,214 @@
+import { LitElement, html, css } from 'lit'
+import { render } from 'lit/html.js'
+import { Epml } from '../../../../epml.js'
+import { use, get, translate, translateUnsafeHTML, registerTranslateConfig } from 'lit-translate'
+
+registerTranslateConfig({
+ loader: lang => fetch(`/language/${lang}.json`).then(res => res.json())
+})
+
+import '@polymer/paper-dialog/paper-dialog.js'
+import * as Highcharts from 'highcharts'
+import Exporting from 'highcharts/modules/exporting'
+Exporting(Highcharts)
+import StockChart from 'highcharts/modules/stock'
+StockChart(Highcharts)
+import 'highcharts/highcharts-more.js'
+import 'highcharts/modules/accessibility.js'
+import 'highcharts/modules/boost.js'
+import 'highcharts/modules/data.js'
+import 'highcharts/modules/export-data.js'
+import 'highcharts/modules/offline-exporting.js'
+
+let ltcChartDialog
+
+const parentEpml = new Epml({ type: 'WINDOW', source: window.parent })
+
+class LtcCharts extends LitElement {
+ static get properties() {
+ return {
+ isLoadingTradesChart: { type: Boolean },
+ ltcTrades: { type: Array },
+ ltcPrice: { type: Array }
+ }
+ }
+
+ static get styles() {
+ return css`
+ .loadingContainer {
+ height: 100%;
+ width: 100%;
+ }
+
+ .trades-chart {
+ color: var(--black);
+ background: var(--white);
+ border: 1px solid var(--black);
+ border-radius: 25px;
+ padding: 15px;
+ }
+
+ .chart-container {
+ margin: auto;
+ color: var(--black);
+ text-align: center;
+ padding: 15px;
+ height: 30vh;
+ width: 80vw;
+ }
+
+ .chart-info-wrapper {
+ background: transparent;
+ height: 38vh;
+ width: 83vw;
+ overflow: auto;
+ }
+
+ .chart-loading-wrapper {
+ color: var(--black);
+ background: var(--white);
+ border: 1px solid var(--black);
+ border-radius: 15px;
+ }
+ `
+ }
+
+ constructor() {
+ super()
+ this.theme = localStorage.getItem('qortalTheme') ? localStorage.getItem('qortalTheme') : 'light'
+ this.isLoadingTradesChart = false
+ this.ltcTrades = []
+ this.ltcPrice = []
+ }
+
+ render() {
+ return html`
+
+
+ ${translate("login.loading")}
+
+
+
+
+
+ `
+ }
+
+ async firstUpdated() {
+ this.changeTheme()
+ this.changeLanguage()
+
+ window.addEventListener('storage', () => {
+ const checkLanguage = localStorage.getItem('qortalLanguage')
+ const checkTheme = localStorage.getItem('qortalTheme')
+
+ use(checkLanguage)
+
+ this.theme = (checkTheme === 'dark') ? 'dark' : 'light'
+ document.querySelector('html').setAttribute('theme', this.theme)
+ })
+ }
+
+ async loadTradesChart() {
+ this.isLoadingTradesChart = true
+ this.shadowRoot.getElementById('loadChartDialog').open()
+ await this.getLtcTrades()
+ this.isLoadingTradesChart = false
+ this.shadowRoot.getElementById('loadChartDialog').close()
+ this.enableLtcStockPriceChart()
+ }
+
+ async getLtcTrades() {
+ let currentLtcTimestamp = Date.now()
+ const monthBackLtc = currentLtcTimestamp - 31556952000
+ await parentEpml.request("apiCall", { url: `/crosschain/trades?foreignBlockchain=LITECOIN&minimumTimestamp=${monthBackLtc}&limit=0&reverse=false` }).then((res) => {
+ this.ltcTrades = res
+ })
+ this.ltcPrice = this.ltcTrades.map(item => {
+ const ltcSellPrice = this.round(parseFloat(item.foreignAmount) / parseFloat(item.qortAmount))
+ return [item.tradeTimestamp, parseFloat(ltcSellPrice)]
+ }).filter(item => !!item)
+ }
+
+ enableLtcStockPriceChart() {
+ const ltcStockPriceData = this.ltcPrice
+ const header = 'QORT / LTC ' + get("tradepage.tchange49")
+ Highcharts.stockChart(this.shadowRoot.querySelector('#ltcStockPriceContainer'), {
+ accessibility: {
+ enabled: false
+ },
+ credits: {
+ enabled: false
+ },
+ rangeSelector: {
+ selected: 1,
+ labelStyle: {color: 'var(--black)'},
+ inputStyle: {color: '#03a9f4'}
+ },
+ chart: {
+ backgroundColor: 'transparent'
+ },
+ title: {
+ text: header,
+ style: {color: 'var(--black)'}
+ },
+ xAxis: {
+ labels: {
+ style: {
+ color: '#03a9f4'
+ }
+ }
+ },
+ yAxis: {
+ labels: {
+ style: {
+ color: '#03a9f4'
+ }
+ }
+ },
+ series: [{
+ name: 'QORT / LTC',
+ data: ltcStockPriceData,
+ tooltip: {
+ valueDecimals: 8
+ }
+ }]
+ })
+ }
+
+ async open() {
+ await this.loadTradesChart()
+ this.shadowRoot.getElementById('ltcChartDialog').open()
+ }
+
+ changeTheme() {
+ const checkTheme = localStorage.getItem('qortalTheme')
+ this.theme = (checkTheme === 'dark') ? 'dark' : 'light'
+ document.querySelector('html').setAttribute('theme', this.theme);
+ }
+
+ changeLanguage() {
+ const checkLanguage = localStorage.getItem('qortalLanguage')
+
+ if (checkLanguage === null || checkLanguage.length === 0) {
+ localStorage.setItem('qortalLanguage', 'us')
+ use('us')
+ } else {
+ use(checkLanguage)
+ }
+ }
+
+ round(number) {
+ let result = (Math.round(parseFloat(number) * 1e8) / 1e8).toFixed(8)
+ return result
+ }
+}
+
+window.customElements.define('ltc-charts', LtcCharts)
+
+const chartsltc = document.createElement('ltc-charts')
+ltcChartDialog = document.body.appendChild(chartsltc)
+
+export default ltcChartDialog
\ No newline at end of file
diff --git a/qortal-ui-plugins/plugins/core/trade-portal/charts/rvn-charts.js b/qortal-ui-plugins/plugins/core/trade-portal/charts/rvn-charts.js
new file mode 100644
index 00000000..1110bdfc
--- /dev/null
+++ b/qortal-ui-plugins/plugins/core/trade-portal/charts/rvn-charts.js
@@ -0,0 +1,214 @@
+import { LitElement, html, css } from 'lit'
+import { render } from 'lit/html.js'
+import { Epml } from '../../../../epml.js'
+import { use, get, translate, translateUnsafeHTML, registerTranslateConfig } from 'lit-translate'
+
+registerTranslateConfig({
+ loader: lang => fetch(`/language/${lang}.json`).then(res => res.json())
+})
+
+import '@polymer/paper-dialog/paper-dialog.js'
+import * as Highcharts from 'highcharts'
+import Exporting from 'highcharts/modules/exporting'
+Exporting(Highcharts)
+import StockChart from 'highcharts/modules/stock'
+StockChart(Highcharts)
+import 'highcharts/highcharts-more.js'
+import 'highcharts/modules/accessibility.js'
+import 'highcharts/modules/boost.js'
+import 'highcharts/modules/data.js'
+import 'highcharts/modules/export-data.js'
+import 'highcharts/modules/offline-exporting.js'
+
+let rvnChartDialog
+
+const parentEpml = new Epml({ type: 'WINDOW', source: window.parent })
+
+class RvnCharts extends LitElement {
+ static get properties() {
+ return {
+ isLoadingTradesChart: { type: Boolean },
+ rvnTrades: { type: Array },
+ rvnPrice: { type: Array }
+ }
+ }
+
+ static get styles() {
+ return css`
+ .loadingContainer {
+ height: 100%;
+ width: 100%;
+ }
+
+ .trades-chart {
+ color: var(--black);
+ background: var(--white);
+ border: 1px solid var(--black);
+ border-radius: 25px;
+ padding: 15px;
+ }
+
+ .chart-container {
+ margin: auto;
+ color: var(--black);
+ text-align: center;
+ padding: 15px;
+ height: 30vh;
+ width: 80vw;
+ }
+
+ .chart-info-wrapper {
+ background: transparent;
+ height: 38vh;
+ width: 83vw;
+ overflow: auto;
+ }
+
+ .chart-loading-wrapper {
+ color: var(--black);
+ background: var(--white);
+ border: 1px solid var(--black);
+ border-radius: 15px;
+ }
+ `
+ }
+
+ constructor() {
+ super()
+ this.theme = localStorage.getItem('qortalTheme') ? localStorage.getItem('qortalTheme') : 'light'
+ this.isLoadingTradesChart = false
+ this.rvnTrades = []
+ this.rvnPrice = []
+ }
+
+ render() {
+ return html`
+
+
+ ${translate("login.loading")}
+
+
+
+
+
+ `
+ }
+
+ async firstUpdated() {
+ this.changeTheme()
+ this.changeLanguage()
+
+ window.addEventListener('storage', () => {
+ const checkLanguage = localStorage.getItem('qortalLanguage')
+ const checkTheme = localStorage.getItem('qortalTheme')
+
+ use(checkLanguage)
+
+ this.theme = (checkTheme === 'dark') ? 'dark' : 'light'
+ document.querySelector('html').setAttribute('theme', this.theme)
+ })
+ }
+
+ async loadTradesChart() {
+ this.isLoadingTradesChart = true
+ this.shadowRoot.getElementById('loadChartDialog').open()
+ await this.getRvnTrades()
+ this.isLoadingTradesChart = false
+ this.shadowRoot.getElementById('loadChartDialog').close()
+ this.enableRvnStockPriceChart()
+ }
+
+ async getRvnTrades() {
+ let currentRvnTimestamp = Date.now()
+ const monthBackRvn = currentRvnTimestamp - 31556952000
+ await parentEpml.request("apiCall", { url: `/crosschain/trades?foreignBlockchain=RAVENCOIN&minimumTimestamp=${monthBackRvn}&limit=0&reverse=false` }).then((res) => {
+ this.rvnTrades = res
+ })
+ this.rvnPrice = this.rvnTrades.map(item => {
+ const rvnSellPrice = this.round(parseFloat(item.foreignAmount) / parseFloat(item.qortAmount))
+ return [item.tradeTimestamp, parseFloat(rvnSellPrice)]
+ }).filter(item => !!item)
+ }
+
+ enableRvnStockPriceChart() {
+ const rvnStockPriceData = this.rvnPrice
+ const header = 'QORT / RVN ' + get("tradepage.tchange49")
+ Highcharts.stockChart(this.shadowRoot.querySelector('#rvnStockPriceContainer'), {
+ accessibility: {
+ enabled: false
+ },
+ credits: {
+ enabled: false
+ },
+ rangeSelector: {
+ selected: 1,
+ labelStyle: {color: 'var(--black)'},
+ inputStyle: {color: '#03a9f4'}
+ },
+ chart: {
+ backgroundColor: 'transparent'
+ },
+ title: {
+ text: header,
+ style: {color: 'var(--black)'}
+ },
+ xAxis: {
+ labels: {
+ style: {
+ color: '#03a9f4'
+ }
+ }
+ },
+ yAxis: {
+ labels: {
+ style: {
+ color: '#03a9f4'
+ }
+ }
+ },
+ series: [{
+ name: 'QORT / RVN',
+ data: rvnStockPriceData,
+ tooltip: {
+ valueDecimals: 8
+ }
+ }]
+ })
+ }
+
+ async open() {
+ await this.loadTradesChart()
+ this.shadowRoot.getElementById('rvnChartDialog').open()
+ }
+
+ changeTheme() {
+ const checkTheme = localStorage.getItem('qortalTheme')
+ this.theme = (checkTheme === 'dark') ? 'dark' : 'light'
+ document.querySelector('html').setAttribute('theme', this.theme);
+ }
+
+ changeLanguage() {
+ const checkLanguage = localStorage.getItem('qortalLanguage')
+
+ if (checkLanguage === null || checkLanguage.length === 0) {
+ localStorage.setItem('qortalLanguage', 'us')
+ use('us')
+ } else {
+ use(checkLanguage)
+ }
+ }
+
+ round(number) {
+ let result = (Math.round(parseFloat(number) * 1e8) / 1e8).toFixed(8)
+ return result
+ }
+}
+
+window.customElements.define('rvn-charts', RvnCharts)
+
+const chartsrvn = document.createElement('rvn-charts')
+rvnChartDialog = document.body.appendChild(chartsrvn)
+
+export default rvnChartDialog
\ No newline at end of file
diff --git a/qortal-ui-plugins/plugins/core/trade-portal/trade-portal.src.js b/qortal-ui-plugins/plugins/core/trade-portal/trade-portal.src.js
index 47a97042..bcfd5130 100644
--- a/qortal-ui-plugins/plugins/core/trade-portal/trade-portal.src.js
+++ b/qortal-ui-plugins/plugins/core/trade-portal/trade-portal.src.js
@@ -21,6 +21,12 @@ import '@polymer/paper-icon-button/paper-icon-button.js'
import '@polymer/paper-spinner/paper-spinner-lite.js'
import '@vaadin/grid'
import '@vaadin/grid/vaadin-grid-sorter'
+import chartsbtc from './charts/btc-charts.js'
+import chartsltc from './charts/ltc-charts.js'
+import chartsdoge from './charts/doge-charts.js'
+import chartsdgb from './charts/dgb-charts.js'
+import chartsrvn from './charts/rvn-charts.js'
+import chartsarrr from './charts/arrr-charts.js'
const parentEpml = new Epml({ type: 'WINDOW', source: window.parent })
@@ -1160,8 +1166,11 @@ class TradePortal extends LitElement {
QORT / DOGE
QORT / DGB
QORT / RVN
- QORT / ARRR
+ QORT / ARRR
+
+ ${this.chartShowCoin()}
+
@@ -1412,6 +1421,31 @@ class TradePortal extends LitElement {
return html`
NOT ENOUGH ${this.listedCoins.get(this.selectedCoin).coinCode}`
}
+ chartShowCoin() {
+ switch(this.listedCoins.get(this.selectedCoin).coinCode) {
+ case "BTC":
+ return html`
chartsbtc.open()}>`
+ break
+ case "LTC":
+ return html`
chartsltc.open()}>`
+ break
+ case "DOGE":
+ return html`
chartsdoge.open()}>`
+ break
+ case "DGB":
+ return html`
chartsdgb.open()}>`
+ break
+ case "RVN":
+ return html`
chartsrvn.open()}>`
+ break
+ case "ARRR":
+ return html`
chartsarrr.open()}>`
+ break
+ default:
+ break
+ }
+ }
+
exchangeRateQort() {
switch(this.listedCoins.get(this.selectedCoin).coinCode) {
case "BTC":
diff --git a/qortal-ui-plugins/plugins/core/wallet/wallet-app.src.js b/qortal-ui-plugins/plugins/core/wallet/wallet-app.src.js
index de4e77ed..e8c6a955 100644
--- a/qortal-ui-plugins/plugins/core/wallet/wallet-app.src.js
+++ b/qortal-ui-plugins/plugins/core/wallet/wallet-app.src.js
@@ -10,8 +10,8 @@ registerTranslateConfig({
import '../components/ButtonIconCopy.js'
import '../components/QortalQrcodeGenerator.js'
import '../components/frag-file-input.js'
+import '../components/time-elements/index.js'
import FileSaver from 'file-saver'
-import '@github/time-elements'
import '@material/mwc-button'
import '@material/mwc-checkbox'
import '@material/mwc-dialog'
@@ -55,7 +55,7 @@ class MultiWallet extends LitElement {
dogeAmount: { type: Number },
dgbRecipient: { type: String },
dgbAmount: { type: Number },
- rvnRecipient: { type: String },
+ rvnRecipient: { type: String },
rvnAmount: { type: Number },
arrrRecipient: { type: String },
arrrAmount: { type: Number },
@@ -73,7 +73,7 @@ class MultiWallet extends LitElement {
ltcFeePerByte: { type: Number },
dogeFeePerByte: { type: Number },
dgbFeePerByte: { type: Number },
- rvnFeePerByte: { type: Number },
+ rvnFeePerByte: { type: Number },
qortBook: { type: Array },
btcBook: { type: Array },
ltcBook: { type: Array },
@@ -472,7 +472,7 @@ class MultiWallet extends LitElement {
background-image: url('/img/dgb.png');
}
- .rvn .currency-image {
+ .rvn .currency-image {
background-image: url('/img/rvn.png');
}
@@ -508,12 +508,12 @@ class MultiWallet extends LitElement {
.btn-clear-success {
--mdc-icon-button-size: 32px;
color: red;
- }
+ }
.btn-clear-error {
--mdc-icon-button-size: 32px;
color: green;
- }
+ }
@keyframes fade-in {
0% {
@@ -680,7 +680,7 @@ class MultiWallet extends LitElement {
this.isValidAmount = false
this.btnDisable = false
this.qortWarning = false
- this.balance = 0
+ this.balance = 0
this.amount = 0
this.btcAmount = 0
this.ltcAmount = 0
@@ -1092,7 +1092,7 @@ class MultiWallet extends LitElement {
-
+
${translate("walletpage.wchange5")}
@@ -1577,7 +1577,7 @@ class MultiWallet extends LitElement {
-
+
![](/img/rvn.png)
@@ -2759,7 +2759,7 @@ class MultiWallet extends LitElement {
checkSelectedTextAndShowMenu()
})
- this.shadowRoot.getElementById('rvnAmountInput').addEventListener('contextmenu', (event) => {
+ this.shadowRoot.getElementById('rvnAmountInput').addEventListener('contextmenu', (event) => {
const getSelectedText = () => {
var text = ''
if (typeof window.getSelection != 'undefined') {
@@ -2809,7 +2809,7 @@ class MultiWallet extends LitElement {
checkSelectedTextAndShowMenu()
})
- this.shadowRoot.getElementById('arrrAmountInput').addEventListener('contextmenu', (event) => {
+ this.shadowRoot.getElementById('arrrAmountInput').addEventListener('contextmenu', (event) => {
const getSelectedText = () => {
var text = ''
if (typeof window.getSelection != 'undefined') {
@@ -4499,7 +4499,7 @@ class MultiWallet extends LitElement {
case 'ltc':
case 'doge':
case 'dgb':
- case 'rvn':
+ case 'rvn':
this.balanceString = this.renderFetchText()
const walletName = `${coin}Wallet`
parentEpml.request('apiCall', {
@@ -4844,9 +4844,9 @@ class MultiWallet extends LitElement {
render(this.renderDogeTransactions(this.wallets.get(this._selectedWallet).transactions, this._selectedWallet), this.transactionsDOM)
} else if (this._selectedWallet === 'dgb') {
render(this.renderDgbTransactions(this.wallets.get(this._selectedWallet).transactions, this._selectedWallet), this.transactionsDOM)
- } else if (this._selectedWallet === 'rvn') {
+ } else if (this._selectedWallet === 'rvn') {
render(this.renderRvnTransactions(this.wallets.get(this._selectedWallet).transactions, this._selectedWallet), this.transactionsDOM)
- } else if (this._selectedWallet === 'arrr') {
+ } else if (this._selectedWallet === 'arrr') {
render(this.renderArrrTransactions(this.wallets.get(this._selectedWallet).transactions, this._selectedWallet), this.transactionsDOM)
}
}