Merge branch 'feature/implement-logic-edit-reply-messages' into feature/group-features

This commit is contained in:
Phillip 2023-01-10 15:45:19 -05:00
commit e19322f601
41 changed files with 3711 additions and 149 deletions

View File

@ -19,12 +19,12 @@
"install_link:all": "(cd qortal-ui-core && yarn install && yarn link) && (cd qortal-ui-plugins && yarn install && yarn link) && (cd qortal-ui-crypto && yarn install && yarn link) && (yarn link qortal-ui-core && yarn link qortal-ui-plugins && yarn link qortal-ui-crypto)",
"dev": "node server.js",
"prebuild": "node -p \"'export const UI_VERSION = ' + JSON.stringify(require('./package.json').version) + ';'\" > qortal-ui-core/src/redux/app/version.js",
"build-dev": "node build.js",
"build": "NODE_ENV=production node build.js",
"server": "NODE_ENV=production node server.js",
"watch": "node watch.js",
"watch-inline": "node watch-inline.js",
"start-electron": "NODE_ENV=production electron .",
"build-dev": "node --max-old-space-size=8192 build.js",
"build": "NODE_ENV=production node --max-old-space-size=8192 build.js",
"server": "NODE_ENV=production node --max-old-space-size=8192 server.js",
"watch": "node --max-old-space-size=8192 watch.js",
"watch-inline": "node --max-old-space-size=8192 watch-inline.js",
"start-electron": "NODE_ENV=production electron --js-flags=--max-old-space-size=8192 .",
"build-electron": "electron-builder build --publish never",
"deploy-electron": "electron-builder build --win --publish never",
"release": "NODE_ENV=production electron-builder build --publish never",
@ -46,4 +46,4 @@
"engines": {
"node": ">=16.17.1"
}
}
}

View File

@ -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",
@ -329,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",
@ -799,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!"
}
}

View File

@ -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",
@ -329,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",
@ -799,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!"
}
}

View File

@ -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",
@ -329,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",
@ -799,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 !"
}
}

View File

@ -129,7 +129,11 @@
"snack2":"यूआई नोड से जुड़ा",
"snack3":"कस्टम नोड को सफलतापूर्वक जोड़ा और सहेजा गया",
"snack4":"नोड्स सफलतापूर्वक सहेजे गए",
"snack5":"नोड्स सफलतापूर्वक आयात किए गए"
"snack5":"नोड्स सफलतापूर्वक आयात किए गए",
"exp1":"निजी मास्टर कुंजी निर्यात करें",
"exp2":"निर्यात मास्टर कुंजी",
"exp3":"निर्यात",
"exp4":"निजी मास्टर कुंजी का बैकअप लेने के लिए कृपया एक वॉलेट चुनें।"
},
"appinfo":{
"blockheight":"ब्लॉक ऊँचाई",
@ -330,7 +334,8 @@
"tchange45":"ऑटो के साथ खरीदें",
"tchange46":"ऑटो खरीदें",
"tchange47":"इस कीमत पर बेचें",
"tchange48":"पर्याप्त नहीं"
"tchange48":"पर्याप्त नहीं",
"tchange49":"मूल्य चार्ट"
},
"rewardsharepage":{
"rchange1":"रिवॉर्डशेयर",
@ -799,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":"पुष्टि करें दबाने पर, रद्द करने का अनुरोध भेजा जाएगा!"
}
}

View File

@ -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",
@ -329,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)",
@ -798,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!"
}
}

View File

@ -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",
@ -329,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",
@ -799,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!"
}
}

View File

@ -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",
@ -329,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",
@ -799,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!"
}
}

View File

@ -128,7 +128,11 @@
"snack2":"노드에 연결된 UI",
"snack3":"사용자 정의 노드를 성공적으로 추가하고 저장했습니다.",
"snack4":"노드가 다음으로 성공적으로 저장되었습니다",
"snack5":"노드를 성공적으로 가져왔습니다."
"snack5":"노드를 성공적으로 가져왔습니다.",
"exp1":"개인 마스터 키 내보내기",
"exp2":"마스터 키 내보내기",
"exp3":"내보내기",
"exp4":"개인 마스터 키를 백업할 지갑을 선택하세요."
},
"appinfo":{
"blockheight":"블록 높이",
@ -329,7 +333,8 @@
"tchange45":"자동 구매",
"tchange46":"자동 구매",
"tchange47":"이 가격에 팔아요",
"tchange48":"부족한"
"tchange48":"부족한",
"tchange49":"가격 차트"
},
"rewardsharepage":{
"rchange1":"보상 공유",
@ -799,6 +804,15 @@
"mg49":"확인을 누르면 초대 취소 요청이 전송됩니다!",
"mg50":"출시 예정...",
"mg51":"최소 3자 / 최대 32자",
"mg52":"최대 128자"
"mg52":"최대 128자",
"mg53":"귀하의 오픈 조인 요청",
"mg54":"오픈 조인 요청 없음",
"mg55":"이 회원의 가입 요청을 수락하시겠습니까?",
"mg56":"확인을 누르면 가입 수락 요청이 전송됩니다!",
"mg57":"가입 요청이 성공적으로 수락됨",
"mg58":"뭔가 잘못되었습니다",
"mg59":"가입 요청 취소 성공",
"mg60":"이 회원의 가입 요청을 취소하시겠습니까?",
"mg61":"확인을 누르면 가입 취소 요청이 전송됩니다!"
}
}

View File

@ -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",
@ -329,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",
@ -799,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!"
}
}

View File

@ -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",
@ -329,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",
@ -798,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!"
}
}

View File

@ -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",
@ -329,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",
@ -799,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!"
}
}

View File

@ -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",
@ -329,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",
@ -799,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ă!"
}
}

View File

@ -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",
@ -329,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",
@ -799,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!"
}
}

View File

@ -128,7 +128,11 @@
"snack2":"Пользовательский интерфейс, подключенный к узлу",
"snack3":"Пользовательский узел успешно добавлен и сохранен",
"snack4":"Узлы успешно сохранены как",
"snack5":"Узлы успешно импортированы"
"snack5":"Узлы успешно импортированы",
"exp1":"Экспорт закрытого мастер-ключа",
"exp2":"Экспорт мастер-ключа",
"exp3":"Экспорт",
"exp4":"Пожалуйста, выберите кошелек для резервного копирования приватного главного ключа."
},
"appinfo":{
"blockheight":"Высота блока",
@ -329,7 +333,8 @@
"tchange45":"АВТО КУПИТЬ С",
"tchange46":"АВТО КУПИТЬ",
"tchange47":"Продать по этой цене",
"tchange48":"НЕДОСТАТОЧНО"
"tchange48":"НЕДОСТАТОЧНО",
"tchange49":"График цен"
},
"rewardsharepage":{
"rchange1":"Вознаграждения",
@ -799,6 +804,15 @@
"mg49":"При нажатии подтверждения будет отправлен запрос на отмену приглашения!",
"mg50":"Скоро...",
"mg51":"Минимум 3 символа / максимум 32 символа",
"mg52":"Максимум 128 символов"
"mg52":"Максимум 128 символов",
"mg53":"Ваши открытые запросы на вступление",
"mg54":"Открытых запросов на присоединение нет",
"mg55":"Вы уверены, что принимаете запрос на вступление от этого участника?",
"mg56":"При нажатии подтверждения будет отправлен запрос на присоединение!",
"mg57":"Запрос на присоединение успешно принят",
"mg58":"ЧТО-ТО ПОШЛО НЕ ТАК",
"mg59":"Запрос на отмену присоединения успешно принят",
"mg60":"Вы уверены, что отмените запрос на вступление от этого участника?",
"mg61":"При нажатии кнопки подтверждения будет отправлен запрос на отмену присоединения!"
}
}

View File

@ -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",
@ -329,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",
@ -822,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!"
}
}

View File

@ -128,7 +128,11 @@
"snack2":"连接到节点的 UI",
"snack3":"成功添加并保存自定义节点",
"snack4":"节点成功保存为",
"snack5":"节点成功导入"
"snack5":"节点成功导入",
"exp1":"导出主密钥",
"exp2":"导出主密钥",
"exp3":"导出",
"exp4":"请选择一个钱包来备份私钥。"
},
"appinfo":{
"blockheight":"区块高度",
@ -329,7 +333,8 @@
"tchange45":"自动购买",
"tchange46":"自动购买",
"tchange47":"以这个价格出售",
"tchange48":"不够"
"tchange48":"不够",
"tchange49":"价格图表"
},
"rewardsharepage":{
"rchange1":"铸币密钥",
@ -799,6 +804,15 @@
"mg49":"按下确认后,将发送取消邀请请求!",
"mg50":"即将推出……",
"mg51":"最少 3 个字符 / 最多 32 个字符",
"mg52":"最多 128 个字符"
"mg52":"最多 128 个字符",
"mg53":"您的公开加入请求",
"mg54":"没有开放的加入请求",
"mg55":"您确定接受该会员的加入请求吗?",
"mg56":"按下确认后,将发送接受加入请求!",
"mg57":"成功接受加入请求",
"mg58":"出了点问题",
"mg59":"取消加入请求已成功接受",
"mg60":"您确定要取消该会员的加入请求吗?",
"mg61":"按下确认后,将发送取消加入请求!"
}
}

View File

@ -128,7 +128,11 @@
"snack2":"連接到節點的 UI",
"snack3":"成功添加並保存自定義節點",
"snack4":"節點成功保存為",
"snack5":"節點成功導入"
"snack5":"節點成功導入",
"exp1":"導出主密鑰",
"exp2":"導出主密鑰",
"exp3":"導出",
"exp4":"請選擇一個錢包來備份私鑰。"
},
"appinfo":{
"blockheight":"區塊高度",
@ -329,7 +333,8 @@
"tchange45":"自動購買",
"tchange46":"自動購買",
"tchange47":"以這個價格出售",
"tchange48":"不夠"
"tchange48":"不夠",
"tchange49":"價格圖表"
},
"rewardsharepage":{
"rchange1":"鑄幣密鑰",
@ -799,6 +804,15 @@
"mg49":"按下確認後,將發送取消邀請請求!",
"mg50":"即將推出……",
"mg51":"最少 3 個字符 / 最多 32 個字符",
"mg52":"最多 128 個字符"
"mg52":"最多 128 個字符",
"mg53":"您的公開加入請求",
"mg54":"沒有開放的加入請求",
"mg55":"您確定接受該會員的加入請求嗎?",
"mg56":"按下確認後,將發送接受加入請求!",
"mg57":"成功接受加入請求",
"mg58":"出了點問題",
"mg59":"取消加入請求已成功接受",
"mg60":"您確定要取消該會員的加入請求嗎?",
"mg61":"按下確認後,將發送取消加入請求!"
}
}

View File

@ -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",
@ -58,28 +58,27 @@
"@rollup/plugin-commonjs": "24.0.0",
"@rollup/plugin-node-resolve": "15.0.1",
"@rollup/plugin-replace": "5.0.2",
"@rollup/plugin-terser": "0.2.1",
"@vaadin/button": "23.3.2",
"@vaadin/grid": "23.3.2",
"@vaadin/icons": "23.3.2",
"@vaadin/password-field": "23.3.2",
"@vaadin/tooltip": "23.3.2",
"@rollup/plugin-terser": "0.3.0",
"@vaadin/button": "23.3.3",
"@vaadin/grid": "23.3.3",
"@vaadin/icons": "23.3.3",
"@vaadin/password-field": "23.3.3",
"@vaadin/tooltip": "23.3.3",
"asmcrypto.js": "2.3.2",
"bcryptjs": "2.4.3",
"epml": "0.3.3",
"file-saver": "2.0.5",
"lit": "2.5.0",
"lit": "2.6.0",
"lit-translate": "2.0.1",
"pwa-helpers": "0.9.1",
"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",
"rollup-plugin-terser": "7.0.2",
"rollup-plugin-web-worker-loader": "^1.6.1"
"rollup-plugin-web-worker-loader": "1.6.1"
},
"engines": {
"node": ">=16.17.1"

View File

@ -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`
<div>
<div>
<p>
${translate("settings.exp4")}
</p>
</div>
<div class="sub-main">
<div class="center-box">
<div class="content-box">
<div style="display: flex; align-items: center; justify-content: center;">
<img src="/img/btc.png" style="width: 32px; height: 32px;">&nbsp;&nbsp;${this.btcWALLET}<br>
</div>
<div @click=${() => this.checkForPmkDownload(this.btcWALLET, this.btcPMK, this.btcName, this.btcShort)} class="export-button"> ${translate("settings.exp2")} </div>
</div>
<div class="content-box">
<div style="display: flex; align-items: center; justify-content: center;">
<img src="/img/ltc.png" style="width: 32px; height: 32px;">&nbsp;&nbsp;${this.ltcWALLET}<br>
</div>
<div @click=${() => this.checkForPmkDownload(this.ltcWALLET, this.ltcPMK, this.ltcName, this.ltcShort)} class="export-button"> ${translate("settings.exp2")} </div>
</div>
<div class="content-box">
<div style="display: flex; align-items: center; justify-content: center;">
<img src="/img/doge.png" style="width: 32px; height: 32px;">&nbsp;&nbsp;${this.dogeWALLET}<br>
</div>
<div @click=${() => this.checkForPmkDownload(this.dogeWALLET, this.dogePMK, this.dogeName, this.dogeShort)} class="export-button"> ${translate("settings.exp2")} </div>
</div>
<div class="content-box">
<div style="display: flex; align-items: center; justify-content: center;">
<img src="/img/dgb.png" style="width: 32px; height: 32px;">&nbsp;&nbsp;${this.dgbWALLET}<br>
</div>
<div @click=${() => this.checkForPmkDownload(this.dgbWALLET, this.dgbPMK, this.dgbName, this.dgbShort)} class="export-button"> ${translate("settings.exp2")} </div>
</div>
<div class="content-box">
<div style="display: flex; align-items: center; justify-content: center;">
<img src="/img/rvn.png" style="width: 32px; height: 32px;">&nbsp;&nbsp;${this.rvnWALLET}<br>
</div>
<div @click=${() => this.checkForPmkDownload(this.rvnWALLET, this.rvnPMK, this.rvnName, this.rvnShort)} class="export-button"> ${translate("settings.exp2")} </div>
</div>
</div>
</div>
<mwc-dialog id="savePkmDialog" scrimClickAction="" escapeKeyAction="">
<img src="/img/${this.dCoinShort}.png" style="width: 32px; height: 32px;">
<h3>${this.dCoinName} ${translate("settings.exp2")}</h3>
<hr>
<h4>${translate("settings.address")}: ${this.dWalletAddress}</h4>
<mwc-button
slot="primaryAction"
@click="${() => this.closeSavePkmDialog()}"
class="red"
>
${translate("general.close")}
</mwc-button>
<mwc-button
slot="secondaryAction"
@click="${() => this.exportKey(this.dPrivateKey, this.dCoinName, this.dWalletAddress)}"
>
${translate("settings.exp3")}
</mwc-button>
</mwc-dialog>
</div>
`
}
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)

View File

@ -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) {
<ul>
<li @click=${ () => this.setSettingsView('info')} ><a class=${this.selectedView.id === 'info' ? 'active' : ''} href="javascript:void(0)">${translate("settings.account")}</a></li>
<li @click=${ () => this.setSettingsView('security')} ><a class=${this.selectedView.id === 'security' ? 'active' : ''} href="javascript:void(0)">${translate("settings.security")}</a></li>
<li @click=${ () => this.setSettingsView('export')} ><a class=${this.selectedView.id === 'export' ? 'active' : ''} href="javascript:void(0)">${translate("settings.exp1") }</a></li>
<li @click=${ () => this.setSettingsView('qr-login')} ><a class=${this.selectedView.id === 'qr-login' ? 'active' : ''} href="javascript:void(0)">${translate("settings.qr_login_menu_item") }</a></li>
<li @click=${ () => this.setSettingsView('notification')} ><a class=${this.selectedView.id === 'notification' ? 'active' : ''} href="javascript:void(0)">${translate("settings.notifications")}</a></li>
</ul>
@ -247,25 +250,29 @@ class UserSettings extends connect(store)(LitElement) {
renderSettingViews(selectedView) {
if (selectedView.id === 'info') {
return html`<account-view></account-view>`;
return html`<account-view></account-view>`
} else if (selectedView.id === 'security') {
return html`<security-view></security-view>`;
return html`<security-view></security-view>`
} else if (selectedView.id === 'export') {
return html`<export-keys></export-keys>`
} else if (selectedView.id === 'notification') {
return html`<notifications-view></notifications-view>`;
return html`<notifications-view></notifications-view>`
} else if (selectedView.id === 'qr-login') {
return html`<qr-login-view></qr-login-view>`;
return html`<qr-login-view></qr-login-view>`
}
}
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') {

View File

@ -21,14 +21,13 @@
"@material/mwc-list": "0.27.0",
"@material/mwc-select": "0.27.0",
"asmcrypto.js": "2.3.2",
"compressorjs": "^1.1.1",
"compressorjs": "1.1.1",
"emoji-picker-js": "https://github.com/Qortal/emoji-picker-js",
"localforage": "^1.10.0",
"short-unique-id": "^4.4.4"
"localforage": "1.10.0",
"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",
@ -54,25 +53,24 @@
"@rollup/plugin-commonjs": "24.0.0",
"@rollup/plugin-node-resolve": "15.0.1",
"@rollup/plugin-replace": "5.0.2",
"@rollup/plugin-terser": "0.2.1",
"@vaadin/avatar": "23.3.2",
"@vaadin/button": "23.3.2",
"@vaadin/grid": "23.3.2",
"@vaadin/icons": "23.3.2",
"@vaadin/tooltip": "23.3.2",
"@rollup/plugin-terser": "0.3.0",
"@vaadin/avatar": "23.3.3",
"@vaadin/button": "23.3.3",
"@vaadin/grid": "23.3.3",
"@vaadin/icons": "23.3.3",
"@vaadin/tooltip": "23.3.3",
"epml": "0.3.3",
"file-saver": "2.0.5",
"highcharts": "10.3.2",
"html-escaper": "3.0.3",
"lit": "2.5.0",
"lit": "2.6.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",
"rollup-plugin-web-worker-loader": "^1.6.1"
"rollup-plugin-web-worker-loader": "1.6.1"
},
"engines": {
"node": ">=16.17.1"
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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 })
@ -38,6 +38,7 @@ class GroupManagement extends LitElement {
newAdminsList: { type: Array },
newBannedList: { type: Array },
newGroupInvitesList: { type: Array },
newGroupJoinsList: { type: Array },
recipientPublicKey: { type: String },
selectedAddress: { type: Object },
manageGroupObj: { type: Object },
@ -155,6 +156,11 @@ class GroupManagement extends LitElement {
color: red;
}
.success-icon {
font-size: 48px;
color: #198754;
}
.close-icon {
font-size: 36px;
}
@ -436,6 +442,7 @@ class GroupManagement extends LitElement {
this.newAdminsList = []
this.newBannedList = []
this.newGroupInvitesList = []
this.newGroupJoinsList = []
this.manageGroupObj = {}
this.joinGroupObj = {}
this.leaveGroupObj = {}
@ -836,6 +843,7 @@ class GroupManagement extends LitElement {
groupInviteTemplate() {
return html`
<h3 style="margin: 0; margin-bottom: 1em; text-align: center;">${translate("managegroup.mg36")}</h3>
<vaadin-grid theme="large" id="groupInvitesGrid" ?hidden="${this.isEmptyArray(this.newGroupInvitesList)}" .items="${this.newGroupInvitesList}" aria-label="Group Invites" all-rows-visible>
<vaadin-grid-column
width="6rem"
@ -880,6 +888,54 @@ class GroupManagement extends LitElement {
${this.isEmptyArray(this.newGroupInvitesList) ? html`
<span style="color: var(--black);">${translate("managegroup.mg35")}</span>
` : html``}
<br><hr><br>
<h3 style="margin: 0; margin-bottom: 1em; text-align: center;">${translate("managegroup.mg53")}</h3>
<vaadin-grid theme="large" id="groupJoinsGrid" ?hidden="${this.isEmptyArray(this.newGroupJoinsList)}" .items="${this.newGroupJoinsList}" aria-label="Group Join Requests" all-rows-visible>
<vaadin-grid-column
width="6rem"
flex-grow="0"
header="${translate("websitespage.schange5")}"
.renderer=${(root, column, data) => {
render(html`${this.renderAvatar(data.item)}`, root)
}}
></vaadin-grid-column>
<vaadin-grid-column
auto-width
resizable
header="${translate("puzzlepage.pchange4")}"
.renderer=${(root, column, data) => {
render(html`${data.item.name}`, root)
}}
></vaadin-grid-column>
<vaadin-grid-column
auto-width
resizable
header="${translate("login.address")}"
.renderer=${(root, column, data) => {
render(html`${data.item.owner}`, root)
}}
></vaadin-grid-column>
<vaadin-grid-column
width="12rem"
flex-grow="0"
header="${translate("websitespage.schange8")}"
.renderer=${(root, column, data) => {
render(html`${this.renderConfirmRequestButton(data.item)}`, root)
}}
></vaadin-grid-column>
<vaadin-grid-column
width="12rem"
flex-grow="0"
.renderer=${(root, column, data) => {
render(html`${this.renderDeclineRequestButton(data.item)}`, root)
}}
></vaadin-grid-column>
</vaadin-grid>
${this.isEmptyArray(this.newGroupJoinsList) ? html`
<span style="color: var(--black);">${translate("managegroup.mg54")}</span>
` : html``}
<br>
<hr>
<div style="padding-top: 20px;">
<vaadin-button theme="primary medium" @click=${() => this.openInviteMemberToGroupDialog()}>
${translate("managegroup.mg2")}
@ -1033,6 +1089,72 @@ class GroupManagement extends LitElement {
${translate("general.close")}
</mwc-button>
</mwc-dialog>
<mwc-dialog id="successJoinDialog" scrimClickAction="" escapeKeyAction="">
<div class="card-container">
<mwc-icon class="success-icon">group_add</mwc-icon>
<h2>${translate("managegroup.mg57")}</h2>
<h4>${translate("walletpage.wchange43")}</h4>
</div>
<mwc-button
slot="primaryAction"
@click=${() => this.closeSuccessJoinDialog()}
class="red"
>
${translate("general.close")}
</mwc-button>
</mwc-dialog>
<mwc-dialog id="errorJoinDialog" scrimClickAction="" escapeKeyAction="">
<div class="card-container">
<mwc-icon class="error-icon">warning</mwc-icon>
<h2>${translate("managegroup.mg58")}</h2>
<h4>${this.errorMessage}</h4>
<h4>${translate("walletpage.wchange44")}</h4>
</div>
<mwc-button
slot="primaryAction"
@click=${() => this.closeErrorJoinDialog()}
class="red"
>
${translate("general.close")}
</mwc-button>
</mwc-dialog>
<mwc-dialog id="cancelSuccessJoinDialog" scrimClickAction="" escapeKeyAction="">
<div class="card-container">
<mwc-icon class="success-icon">person_remove</mwc-icon>
<h2>${translate("managegroup.mg59")}</h2>
<h4>${translate("walletpage.wchange43")}</h4>
</div>
<mwc-button
slot="primaryAction"
@click=${() => this.closeCancelSuccessJoinDialog()}
class="red"
>
${translate("general.close")}
</mwc-button>
</mwc-dialog>
<mwc-dialog id="cancelErrorJoinDialog" scrimClickAction="" escapeKeyAction="">
<div class="card-container">
<mwc-icon class="error-icon">warning</mwc-icon>
<h2>${translate("managegroup.mg58")}</h2>
<h4>${this.errorMessage}</h4>
<h4>${translate("walletpage.wchange44")}</h4>
</div>
<mwc-button
slot="primaryAction"
@click=${() => this.closeCancelErrorJoinDialog()}
class="red"
>
${translate("general.close")}
</mwc-button>
</mwc-dialog>
`
}
@ -1987,6 +2109,38 @@ class GroupManagement extends LitElement {
this.errorMessage = ''
}
renderConfirmRequestButton(joinObj) {
return html`<mwc-button class="green" @click=${() => this.createAcceptJoinGroupMember(joinObj)}><mwc-icon>add_task</mwc-icon>&nbsp;${translate("transpage.tchange3")}</mwc-button>`
}
renderDeclineRequestButton(joinObj) {
return html`<mwc-button class="red" @click=${() => this.kickJoinGroupMember(joinObj)}><mwc-icon>cancel</mwc-icon>&nbsp;${translate("transpage.tchange2")}</mwc-button>`
}
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
@ -2362,6 +2516,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()
@ -2390,6 +2595,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()
}
@ -3058,6 +3264,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

View File

@ -94,7 +94,7 @@ export const qchatStyles = css`
}
.people-list ul {
padding: 0;
padding: 0px 0px 60px 0px;
height: 85vh;
overflow-y: auto;
overflow-x: hidden;

View File

@ -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`
<paper-dialog id="loadChartDialog" class="chart-loading-wrapper" modal>
<div class="loadingContainer" style="display:${this.isLoadingTradesChart ? 'inline-block' : 'none'}">
<span style="color: var(--black);">${translate("login.loading")}</span>
</div>
</paper-dialog>
<paper-dialog id="arrrChartDialog" class="chart-info-wrapper">
<div class="chart-container">
<div id='arrrStockPriceContainer' class='trades-chart'></div>
</div>
</paper-dialog>
`
}
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

View File

@ -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`
<paper-dialog id="loadChartDialog" class="chart-loading-wrapper" modal>
<div class="loadingContainer" style="display:${this.isLoadingTradesChart ? 'inline-block' : 'none'}">
<span style="color: var(--black);">${translate("login.loading")}</span>
</div>
</paper-dialog>
<paper-dialog id="btcChartDialog" class="chart-info-wrapper">
<div class="chart-container">
<div id='btcStockPriceContainer' class='trades-chart'></div>
</div>
</paper-dialog>
`
}
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

View File

@ -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`
<paper-dialog id="loadChartDialog" class="chart-loading-wrapper" modal>
<div class="loadingContainer" style="display:${this.isLoadingTradesChart ? 'inline-block' : 'none'}">
<span style="color: var(--black);">${translate("login.loading")}</span>
</div>
</paper-dialog>
<paper-dialog id="dgbChartDialog" class="chart-info-wrapper">
<div class="chart-container">
<div id='dgbStockPriceContainer' class='trades-chart'></div>
</div>
</paper-dialog>
`
}
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

View File

@ -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`
<paper-dialog id="loadChartDialog" class="chart-loading-wrapper" modal>
<div class="loadingContainer" style="display:${this.isLoadingTradesChart ? 'inline-block' : 'none'}">
<span style="color: var(--black);">${translate("login.loading")}</span>
</div>
</paper-dialog>
<paper-dialog id="dogeChartDialog" class="chart-info-wrapper">
<div class="chart-container">
<div id='dogeStockPriceContainer' class='trades-chart'></div>
</div>
</paper-dialog>
`
}
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

View File

@ -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`
<paper-dialog id="loadChartDialog" class="chart-loading-wrapper" modal>
<div class="loadingContainer" style="display:${this.isLoadingTradesChart ? 'inline-block' : 'none'}">
<span style="color: var(--black);">${translate("login.loading")}</span>
</div>
</paper-dialog>
<paper-dialog id="ltcChartDialog" class="chart-info-wrapper">
<div class="chart-container">
<div id='ltcStockPriceContainer' class='trades-chart'></div>
</div>
</paper-dialog>
`
}
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

View File

@ -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`
<paper-dialog id="loadChartDialog" class="chart-loading-wrapper" modal>
<div class="loadingContainer" style="display:${this.isLoadingTradesChart ? 'inline-block' : 'none'}">
<span style="color: var(--black);">${translate("login.loading")}</span>
</div>
</paper-dialog>
<paper-dialog id="rvnChartDialog" class="chart-info-wrapper">
<div class="chart-container">
<div id='rvnStockPriceContainer' class='trades-chart'></div>
</div>
</paper-dialog>
`
}
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

View File

@ -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 {
<mwc-list-item value="DOGECOIN"><span class="coinName doge" style="color: var(--black);">QORT / DOGE</span></mwc-list-item>
<mwc-list-item value="DIGIBYTE"><span class="coinName dgb" style="color: var(--black);">QORT / DGB</span></mwc-list-item>
<mwc-list-item value="RAVENCOIN"><span class="coinName rvn" style="color: var(--black);">QORT / RVN</span></mwc-list-item>
<mwc-list-item value="PIRATECHAIN"><span class="coinName arrr" style="color: var(--black);">QORT / ARRR</span></mwc-list-item>
<mwc-list-item value="PIRATECHAIN"><span class="coinName arrr" style="color: var(--black);">QORT / ARRR</span></mwc-list-item>
</mwc-select>
<div style="padding-left: 25px; padding-top: 15px;">
${this.chartShowCoin()}
</div>
</div>
<div id="trade-portal">
<div id="first-trade-section">
@ -1412,6 +1421,31 @@ class TradePortal extends LitElement {
return html`<span class="warning-text">NOT ENOUGH ${this.listedCoins.get(this.selectedCoin).coinCode}</span>`
}
chartShowCoin() {
switch(this.listedCoins.get(this.selectedCoin).coinCode) {
case "BTC":
return html`<mwc-button dense unelevated label="BTC ${translate("tradepage.tchange49")}" @click=${() => chartsbtc.open()}></mwc-button>`
break
case "LTC":
return html`<mwc-button dense unelevated label="LTC ${translate("tradepage.tchange49")}" @click=${() => chartsltc.open()}></mwc-button>`
break
case "DOGE":
return html`<mwc-button dense unelevated label="DOGE ${translate("tradepage.tchange49")}" @click=${() => chartsdoge.open()}></mwc-button>`
break
case "DGB":
return html`<mwc-button dense unelevated label="DGB ${translate("tradepage.tchange49")}" @click=${() => chartsdgb.open()}></mwc-button>`
break
case "RVN":
return html`<mwc-button dense unelevated label="RVN ${translate("tradepage.tchange49")}" @click=${() => chartsrvn.open()}></mwc-button>`
break
case "ARRR":
return html`<mwc-button dense unelevated label="ARRR ${translate("tradepage.tchange49")}" @click=${() => chartsarrr.open()}></mwc-button>`
break
default:
break
}
}
exchangeRateQort() {
switch(this.listedCoins.get(this.selectedCoin).coinCode) {
case "BTC":

View File

@ -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 {
</mwc-button>
</mwc-dialog>
<mwc-dialog id="showRvnTransactionDetailsDialog" scrimClickAction="${this.showRvnTransactionDetailsLoading ? '' : 'close'}">
<mwc-dialog id="showRvnTransactionDetailsDialog" scrimClickAction="${this.showRvnTransactionDetailsLoading ? '' : 'close'}">
<div style="text-align: center;">
<h1>${translate("walletpage.wchange5")}</h1>
<hr />
@ -1577,7 +1577,7 @@ class MultiWallet extends LitElement {
</mwc-button>
</mwc-dialog>
<mwc-dialog id="sendRvnDialog" scrimClickAction="" escapeKeyAction="">
<mwc-dialog id="sendRvnDialog" scrimClickAction="" escapeKeyAction="">
<div class="send-coin-dialog">
<div style="text-align: center;">
<img src="/img/rvn.png" width="32" height="32">
@ -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)
}
}