Merge remote-tracking branch 'main/master' into feature/improve-q-chat-speed

This commit is contained in:
PhilReact 2023-09-02 22:20:00 -07:00
commit 657409a028
58 changed files with 2367 additions and 1151 deletions

View File

@ -27,7 +27,7 @@ Easiest way to install the lastest required packages on Linux is via nvm.
``` source ~/.profile ``` (For Debian based distro) <br/> ``` source ~/.profile ``` (For Debian based distro) <br/>
``` source ~/.bashrc ``` (For Fedora / CentOS) <br/> ``` source ~/.bashrc ``` (For Fedora / CentOS) <br/>
``` nvm ls-remote ``` (Fetch list of available versions) <br/> ``` nvm ls-remote ``` (Fetch list of available versions) <br/>
``` nvm install v18.15.0 ``` (LTS: Hydrogen supported by Electron) <br/> ``` nvm install v18.16.1 ``` (LTS: Hydrogen supported by Electron) <br/>
``` npm --location=global install npm@9.8.1 ``` <br/> ``` npm --location=global install npm@9.8.1 ``` <br/>
Adding via binary package mirror will only work if you have set the package path. You can do a node or java build via ports instead by downloading ports with portsnap fetch method. Adding via binary package mirror will only work if you have set the package path. You can do a node or java build via ports instead by downloading ports with portsnap fetch method.

View File

@ -64,6 +64,7 @@ html {
--app-background-2: #09c6f9; --app-background-2: #09c6f9;
--app-icon: #ffffff; --app-icon: #ffffff;
--app-hr: rgba(0, 0, 0, .3); --app-hr: rgba(0, 0, 0, .3);
--code-block-text-color: #008fd5;
} }
html[theme="dark"] { html[theme="dark"] {
@ -132,4 +133,5 @@ html[theme="dark"] {
--app-background-2: #0d324d; --app-background-2: #0d324d;
--app-icon: #03a9f4; --app-icon: #03a9f4;
--app-hr: rgba(255, 255, 255, .3); --app-hr: rgba(255, 255, 255, .3);
--code-block-text-color: #008fd5;
} }

View File

@ -717,7 +717,8 @@
"bchange43": "Erteilen Sie dieser Anwendung die Erlaubnis, diese Liste hinzuzufügen?", "bchange43": "Erteilen Sie dieser Anwendung die Erlaubnis, diese Liste hinzuzufügen?",
"bchange44": "Erteilen Sie dieser Anwendung die Erlaubnis, sie von dieser Liste zu löschen?", "bchange44": "Erteilen Sie dieser Anwendung die Erlaubnis, sie von dieser Liste zu löschen?",
"bchange45": "Verschlüsseln", "bchange45": "Verschlüsseln",
"bchange46": "Geben Sie dieser Anwendung die Erlaubnis, die folgende Datei zu speichern?" "bchange46": "Geben Sie dieser Anwendung die Erlaubnis, die folgende Datei zu speichern?",
"bchange47": "Sofortige Veröffentlichung erforderlich"
}, },
"datapage": { "datapage": {
"dchange1": "Datenmanagement", "dchange1": "Datenmanagement",

View File

@ -717,7 +717,8 @@
"bchange43": "¿Le das permiso a esta aplicación para agregar a esta lista?", "bchange43": "¿Le das permiso a esta aplicación para agregar a esta lista?",
"bchange44": "¿Le das permiso a esta aplicación para eliminar de esta lista?", "bchange44": "¿Le das permiso a esta aplicación para eliminar de esta lista?",
"bchange45": "Cifrar", "bchange45": "Cifrar",
"bchange46": "¿Le das permiso a esta aplicación para guardar el siguiente archivo?" "bchange46": "¿Le das permiso a esta aplicación para guardar el siguiente archivo?",
"bchange47": "Publicación instantánea - requiere"
}, },
"datapage": { "datapage": {
"dchange1": "Gestión de datos", "dchange1": "Gestión de datos",

View File

@ -717,7 +717,8 @@
"bchange43": "Autorisez-vous cette application à ajouter à cette liste ?", "bchange43": "Autorisez-vous cette application à ajouter à cette liste ?",
"bchange44": "Autorisez-vous cette application à supprimer de cette liste ?", "bchange44": "Autorisez-vous cette application à supprimer de cette liste ?",
"bchange45": "Crypter", "bchange45": "Crypter",
"bchange46": "Autorisez-vous cette application à enregistrer le fichier suivant" "bchange46": "Autorisez-vous cette application à enregistrer le fichier suivant",
"bchange47": "Publication instantanée - nécessite"
}, },
"datapage": { "datapage": {
"dchange1": "Gestion de données", "dchange1": "Gestion de données",

View File

@ -718,7 +718,8 @@
"bchange43": "क्या आप इस एप्लिकेशन को इस सूची में जोड़ने की अनुमति देते हैं?", "bchange43": "क्या आप इस एप्लिकेशन को इस सूची में जोड़ने की अनुमति देते हैं?",
"bchange44": "क्या आप इस एप्लिकेशन को इस सूची से हटाने की अनुमति देते हैं?", "bchange44": "क्या आप इस एप्लिकेशन को इस सूची से हटाने की अनुमति देते हैं?",
"bchange45": "एन्क्रिप्ट", "bchange45": "एन्क्रिप्ट",
"bchange46": "क्या आप इस एप्लिकेशन को निम्न फ़ाइल सहेजने की अनुमति देते हैं" "bchange46": "क्या आप इस एप्लिकेशन को निम्न फ़ाइल सहेजने की अनुमति देते हैं",
"bchange47": "तत्काल प्रकाशन - आवश्यक है"
}, },
"datapage": { "datapage": {
"dchange1": "डाटा प्रबंधन", "dchange1": "डाटा प्रबंधन",

View File

@ -717,7 +717,8 @@
"bchange43": "Dajete li ovoj aplikaciji dopuštenje za dodavanje na ovaj popis?", "bchange43": "Dajete li ovoj aplikaciji dopuštenje za dodavanje na ovaj popis?",
"bchange44": "Dajete li ovoj aplikaciji dopuštenje za brisanje s ovog popisa?", "bchange44": "Dajete li ovoj aplikaciji dopuštenje za brisanje s ovog popisa?",
"bchange45": "Šifriraj", "bchange45": "Šifriraj",
"bchange46": "Dajete li ovoj aplikaciji dopuštenje za spremanje sljedeće datoteke" "bchange46": "Dajete li ovoj aplikaciji dopuštenje za spremanje sljedeće datoteke",
"bchange47": "Trenutno objavljivanje - zahtijeva"
}, },
"datapage": { "datapage": {
"dchange1": "Upravljanje podacima", "dchange1": "Upravljanje podacima",

View File

@ -717,7 +717,8 @@
"bchange43": "Engedélyt ad ennek az alkalmazásnak, hogy felvegye ezt a listát?", "bchange43": "Engedélyt ad ennek az alkalmazásnak, hogy felvegye ezt a listát?",
"bchange44": "Engedélyezi ennek az alkalmazásnak, hogy töröljön erről a listáról?", "bchange44": "Engedélyezi ennek az alkalmazásnak, hogy töröljön erről a listáról?",
"bchange45": "Titkosítás", "bchange45": "Titkosítás",
"bchange46": "Engedélyezi ennek az alkalmazásnak a következő fájl mentését" "bchange46": "Engedélyezi ennek az alkalmazásnak a következő fájl mentését",
"bchange47": "Azonnali közzététel szükséges"
}, },
"datapage": { "datapage": {
"dchange1": "Adatkezelés", "dchange1": "Adatkezelés",

View File

@ -717,7 +717,8 @@
"bchange43": "Concedi a questa applicazione il permesso di aggiungersi a questa lista?", "bchange43": "Concedi a questa applicazione il permesso di aggiungersi a questa lista?",
"bchange44": "Concedi a questa applicazione il permesso di eliminare da questo elenco?", "bchange44": "Concedi a questa applicazione il permesso di eliminare da questo elenco?",
"bchange45": "Cripta", "bchange45": "Cripta",
"bchange46": "Concedi a questa applicazione il permesso di salvare il seguente file" "bchange46": "Concedi a questa applicazione il permesso di salvare il seguente file",
"bchange47": "Pubblicazione istantanea - richiede"
}, },
"datapage": { "datapage": {
"dchange1": "Gestione dati", "dchange1": "Gestione dati",

View File

@ -720,7 +720,8 @@
"bchange43": "このアプリケーションにこのリストに追加する事を許可しますか?", "bchange43": "このアプリケーションにこのリストに追加する事を許可しますか?",
"bchange44": "このアプリケーションにこのリストから削除する事を許可しますか?", "bchange44": "このアプリケーションにこのリストから削除する事を許可しますか?",
"bchange45": "暗号化", "bchange45": "暗号化",
"bchange46": "このアプリケーションに次のファイルを保存する事を許可しますか?" "bchange46": "このアプリケーションに次のファイルを保存する事を許可しますか?",
"bchange47": "インスタント公開 - が必要です"
}, },
"datapage": { "datapage": {
"dchange1": "データ管理", "dchange1": "データ管理",

View File

@ -717,7 +717,8 @@
"bchange43": "이 목록에 추가할 수 있는 권한을 이 응용 프로그램에 부여하시겠습니까?", "bchange43": "이 목록에 추가할 수 있는 권한을 이 응용 프로그램에 부여하시겠습니까?",
"bchange44": "이 목록에서 삭제할 수 있는 권한을 이 애플리케이션에 부여하시겠습니까?", "bchange44": "이 목록에서 삭제할 수 있는 권한을 이 애플리케이션에 부여하시겠습니까?",
"bchange45": "암호화", "bchange45": "암호화",
"bchange46": "이 응용 프로그램에 다음 파일을 저장할 권한을 부여하시겠습니까?" "bchange46": "이 응용 프로그램에 다음 파일을 저장할 권한을 부여하시겠습니까?",
"bchange47": "즉시 게시 - 필요"
}, },
"datapage": { "datapage": {
"dchange1": "데이터 관리", "dchange1": "데이터 관리",

View File

@ -717,7 +717,8 @@
"bchange43": "Gir du denne applikasjonen tillatelse til å legge til denne listen?", "bchange43": "Gir du denne applikasjonen tillatelse til å legge til denne listen?",
"bchange44": "Gir du denne applikasjonen tillatelse til å slette fra denne listen?", "bchange44": "Gir du denne applikasjonen tillatelse til å slette fra denne listen?",
"bchange45": "Krypter", "bchange45": "Krypter",
"bchange46": "Gir du dette programmet tillatelse til å lagre følgende fil" "bchange46": "Gir du dette programmet tillatelse til å lagre følgende fil",
"bchange47": "Øyeblikkelig publisering - krever"
}, },
"datapage": { "datapage": {
"dchange1": "Data-administrasjon", "dchange1": "Data-administrasjon",

View File

@ -717,7 +717,8 @@
"bchange43": "Czy zezwalasz tej aplikacji na dodanie do tej listy?", "bchange43": "Czy zezwalasz tej aplikacji na dodanie do tej listy?",
"bchange44": "Czy zezwalasz tej aplikacji na usunięcie z tej listy?", "bchange44": "Czy zezwalasz tej aplikacji na usunięcie z tej listy?",
"bchange45": "Szyfruj", "bchange45": "Szyfruj",
"bchange46": "Czy zezwalasz tej aplikacji na zapisanie następującego pliku" "bchange46": "Czy zezwalasz tej aplikacji na zapisanie następującego pliku",
"bchange47": "Błyskawiczna publikacja - wymaga"
}, },
"datapage": { "datapage": {
"dchange1": "Zarządzanie danymi", "dchange1": "Zarządzanie danymi",

View File

@ -717,7 +717,8 @@
"bchange43": "Você dá a este aplicativo permissão para adicionar a esta lista?", "bchange43": "Você dá a este aplicativo permissão para adicionar a esta lista?",
"bchange44": "Você dá a este aplicativo permissão para deletar desta lista?", "bchange44": "Você dá a este aplicativo permissão para deletar desta lista?",
"bchange45": "Criptografar", "bchange45": "Criptografar",
"bchange46": "Você concede permissão a este aplicativo para salvar o seguinte arquivo" "bchange46": "Você concede permissão a este aplicativo para salvar o seguinte arquivo",
"bchange47": "Publicação instantânea - requer"
}, },
"datapage": { "datapage": {
"dchange1": "Gerenciamento de Dados", "dchange1": "Gerenciamento de Dados",

View File

@ -717,7 +717,8 @@
"bchange43": "Oferiți acestei aplicații permisiunea de a adăuga la această listă?", "bchange43": "Oferiți acestei aplicații permisiunea de a adăuga la această listă?",
"bchange44": "Oferiți acestei aplicații permisiunea de a șterge din această listă?", "bchange44": "Oferiți acestei aplicații permisiunea de a șterge din această listă?",
"bchange45": "Criptați", "bchange45": "Criptați",
"bchange46": "Dați această aplicație permisiunea de a salva următorul fișier" "bchange46": "Dați această aplicație permisiunea de a salva următorul fișier",
"bchange47": "Publicare instantanee - necesită"
}, },
"datapage": { "datapage": {
"dchange1": "Gestionare date", "dchange1": "Gestionare date",

View File

@ -717,7 +717,8 @@
"bchange43": "Da li dajete ovoj aplikaciji dozvolu za dodavanje na ovu listu?", "bchange43": "Da li dajete ovoj aplikaciji dozvolu za dodavanje na ovu listu?",
"bchange44": "Da li ovoj aplikaciji dajete dozvolu za brisanje sa ove liste?", "bchange44": "Da li ovoj aplikaciji dajete dozvolu za brisanje sa ove liste?",
"bchange45": "Šifrovanje", "bchange45": "Šifrovanje",
"bchange46": "Da li ovoj aplikaciji dajete dozvolu da sačuva sledeću datoteku" "bchange46": "Da li ovoj aplikaciji dajete dozvolu da sačuva sledeću datoteku",
"bchange47": "Trenutno objavljivanje - zahteva"
}, },
"datapage": { "datapage": {
"dchange1": "Upravljanje podacima", "dchange1": "Upravljanje podacima",

View File

@ -717,7 +717,8 @@
"bchange43": "Даете ли вы этому приложению разрешение на добавление в этот список?", "bchange43": "Даете ли вы этому приложению разрешение на добавление в этот список?",
"bchange44": "Даете ли вы этому приложению разрешение на удаление из этого списка?", "bchange44": "Даете ли вы этому приложению разрешение на удаление из этого списка?",
"bchange45": "Шифровать", "bchange45": "Шифровать",
"bchange46": "Даете ли вы этому приложению разрешение на сохранение следующего файла?" "bchange46": "Даете ли вы этому приложению разрешение на сохранение следующего файла?",
"bchange47": "Мгновенная публикация - требуется"
}, },
"datapage": { "datapage": {
"dchange1": "Управление данными", "dchange1": "Управление данными",

View File

@ -203,7 +203,8 @@
"exp2": "Export Master Key", "exp2": "Export Master Key",
"exp3": "Export", "exp3": "Export",
"exp4": "Please choose a wallet to backup the private master key.", "exp4": "Please choose a wallet to backup the private master key.",
"core": "Start Core Settings" "core": "Start Core Settings",
"qappNotification1": "Q-App notifications"
}, },
"appinfo": { "appinfo": {
"blockheight": "Block Height", "blockheight": "Block Height",
@ -719,7 +720,9 @@
"bchange43": "Do you give this application permission to add to this list?", "bchange43": "Do you give this application permission to add to this list?",
"bchange44": "Do you give this application permission to delete from this list?", "bchange44": "Do you give this application permission to delete from this list?",
"bchange45": "Encrypt", "bchange45": "Encrypt",
"bchange46": "Do you give this application permission to save the following file" "bchange46": "Do you give this application permission to save the following file",
"bchange47": "Instant publish - requires",
"bchange48": "Do you give this application permission to send you notifications"
}, },
"datapage": { "datapage": {
"dchange1": "Data Management", "dchange1": "Data Management",
@ -1007,7 +1010,10 @@
"rewarddialog3": "If yes, you will need to save the key below in order to mint. It can be supplied to any node in order to allow it to mint on your behalf.", "rewarddialog3": "If yes, you will need to save the key below in order to mint. It can be supplied to any node in order to allow it to mint on your behalf.",
"rewarddialog4": "On pressing confirm, the rewardshare will be created, but you will still need to supply the above key to a node in order to mint with the account.", "rewarddialog4": "On pressing confirm, the rewardshare will be created, but you will still need to supply the above key to a node in order to mint with the account.",
"rewarddialog5": "You are removing a reward share transaction associated with account:", "rewarddialog5": "You are removing a reward share transaction associated with account:",
"rewarddialog6": "On pressing confirm, the rewardshare will be removed and the minting key will become invalid." "rewarddialog6": "On pressing confirm, the rewardshare will be removed and the minting key will become invalid.",
"deployAtdialog1": "You are deploying the AT",
"deployAtdialog2": "On pressing confirm, the AT will be deployed!",
"deployAtdialog3": "Initial amount balance"
}, },
"sponsorshipspage": { "sponsorshipspage": {
"schange1": "Active Sponsorships", "schange1": "Active Sponsorships",

View File

@ -717,7 +717,8 @@
"bchange43": "您授予此应用程序添加到此列表的权限吗?", "bchange43": "您授予此应用程序添加到此列表的权限吗?",
"bchange44": "您授予此应用程序从列表中删除的权限吗?", "bchange44": "您授予此应用程序从列表中删除的权限吗?",
"bchange45": "加密", "bchange45": "加密",
"bchange46": "您是否授予此应用程序保存以下文件的权限" "bchange46": "您是否授予此应用程序保存以下文件的权限",
"bchange47": "即时发布 - 需要"
}, },
"datapage": { "datapage": {
"dchange1": "资料管理", "dchange1": "资料管理",

View File

@ -717,7 +717,8 @@
"bchange43": "您授予此應用程序添加到此列表的權限嗎?", "bchange43": "您授予此應用程序添加到此列表的權限嗎?",
"bchange44": "您授予此應用程序從列表中刪除的權限嗎?", "bchange44": "您授予此應用程序從列表中刪除的權限嗎?",
"bchange45": "加密", "bchange45": "加密",
"bchange46": "您是否授予此應用程序保存以下文件的權限" "bchange46": "您是否授予此應用程序保存以下文件的權限",
"bchange47": "即時發布 - 需要"
}, },
"datapage": { "datapage": {
"dchange1": "資料管理", "dchange1": "資料管理",

View File

@ -119,11 +119,11 @@ class AppInfo extends connect(store)(LitElement) {
this.setStorage() this.setStorage()
this.getNodeInfo() this.getNodeInfo()
this.getCoreInfo() this.getCoreInfo()
try { // try {
this.confirmPublicKeyOnChain(store.getState().app.selectedAddress.address) // this.confirmPublicKeyOnChain(store.getState().app.selectedAddress.address)
} catch (error) { // } catch (error) {
console.error(error) // console.error(error)
} // }
setInterval(() => { setInterval(() => {
this.getNodeInfo() this.getNodeInfo()

View File

@ -3048,3 +3048,4 @@ class AppView extends connect(store)(LitElement) {
} }
window.customElements.define('app-view', AppView) window.customElements.define('app-view', AppView)

View File

@ -140,7 +140,9 @@ class NotificationBell extends connect(store)(LitElement) {
${this.notificationCount ? html` ${this.notificationCount ? html`
<paper-icon-button style="color: green;" icon="icons:mail" @click=${() => this._toggleNotifications()} title="Q-Mail"></paper-icon-button> <paper-icon-button style="color: green;" icon="icons:mail" @click=${() => this._toggleNotifications()} title="Q-Mail"></paper-icon-button>
` : html` ` : html`
<paper-icon-button icon="icons:mail" @click=${() => this._toggleNotifications()} title="Q-Mail"></paper-icon-button> <paper-icon-button icon="icons:mail" @click=${() => {
this._openTabQmail()
}} title="Q-Mail"></paper-icon-button>
`} `}
${this.notificationCount ? html` ${this.notificationCount ? html`
@ -188,6 +190,23 @@ class NotificationBell extends connect(store)(LitElement) {
if (this.notifications.length === 0) return if (this.notifications.length === 0) return
this.showNotifications = !this.showNotifications this.showNotifications = !this.showNotifications
} }
_openTabQmail() {
const query = `?service=APP&name=Q-Mail`
store.dispatch(setNewTab({
url: `qdn/browser/index.html${query}`,
id: 'q-mail-notification',
myPlugObj: {
"url": "qapps",
"domain": "core",
"page": `qdn/browser/index.html${query}`,
"title": "Q-Mail",
"icon": "vaadin:mailbox",
"mwcicon": "mail_outline",
"menus": [],
"parent": false
}
}))
}
static styles = css` static styles = css`
.layout { .layout {

View File

@ -14,7 +14,8 @@ class NotificationsView extends connect(store)(LitElement) {
notificationConfig: { type: Object }, notificationConfig: { type: Object },
q_chatConfig: { type: Object }, q_chatConfig: { type: Object },
blockConfig: { type: Object }, blockConfig: { type: Object },
theme: { type: String, reflect: true } theme: { type: String, reflect: true },
appNotificationList: {type: Array}
} }
} }
@ -24,6 +25,11 @@ class NotificationsView extends connect(store)(LitElement) {
this.q_chatConfig = {} this.q_chatConfig = {}
this.blockConfig = {} this.blockConfig = {}
this.theme = localStorage.getItem('qortalTheme') ? localStorage.getItem('qortalTheme') : 'light' this.theme = localStorage.getItem('qortalTheme') ? localStorage.getItem('qortalTheme') : 'light'
this.appNotificationList = [] // Fetch the list of apps from local storage
}
firstUpdated(){
this.appNotificationList = this.getAppsFromStorage();
} }
static get styles() { static get styles() {
@ -42,7 +48,7 @@ class NotificationsView extends connect(store)(LitElement) {
text-align: center; text-align: center;
} }
@media(min-width: 1150px) { @media(min-width: 1400px) {
.notification-box { .notification-box {
display: grid; display: grid;
grid-template-columns: 1fr 1fr; grid-template-columns: 1fr 1fr;
@ -106,6 +112,18 @@ class NotificationsView extends connect(store)(LitElement) {
transition: all .2s; transition: all .2s;
position: relative; position: relative;
} }
.remove-button {
font-family: Roboto, sans-serif;
font-size: 16px;
color: var(--mdc-theme-primary);
background-color: transparent;
padding: 8px 10px;
border-radius: 5px;
border: none;
transition: all 0.3s ease-in-out;
cursor: pointer;
}
` `
} }
@ -144,6 +162,16 @@ class NotificationsView extends connect(store)(LitElement) {
<mwc-checkbox indeterminate disabled id="blockShowNotification"></mwc-checkbox> <mwc-checkbox indeterminate disabled id="blockShowNotification"></mwc-checkbox>
<label for="blockShowNotification">${translate("settings.shownotifications")}</label> <label for="blockShowNotification">${translate("settings.shownotifications")}</label>
</div> </div>
</div>
<div class="content-box">
<h4>${translate("settings.qappNotification1")}</h4>
${this.appNotificationList.map((app)=> html`
<div style="display: flex; justify-content: space-between; margin-top: 10px;">
${app}
<button class="remove-button" @click=${() => this.removeApp(app)}>Remove</button>
</div>
`)}
</div> </div>
</div> </div>
${this.renderSetCoreButton()} ${this.renderSetCoreButton()}
@ -151,6 +179,31 @@ class NotificationsView extends connect(store)(LitElement) {
` `
} }
getAppsFromStorage() {
// Your method to fetch the list of apps from local storage
// Example:
const address= store.getState().app.selectedAddress.address
const id = `appNotificationList-${address}`;
const data = localStorage.getItem(id);
return data ? Object.keys(JSON.parse(data)) : [];
}
removeApp(appName) {
// Remove the app from local storage
this.removeAppFromStorage(appName);
// Update the apps list in the component
this.appNotificationList = this.appNotificationList.filter(app => app !== appName);
}
removeAppFromStorage(appName) {
// Your method to remove the app from local storage
const address= store.getState().app.selectedAddress.address
const id = `appNotificationList-${address}`;
const data = JSON.parse(localStorage.getItem(id) || '{}');
delete data[appName];
localStorage.setItem(id, JSON.stringify(data));
}
renderSetCoreButton() { renderSetCoreButton() {
if (!isElectron()) { if (!isElectron()) {
return html`` return html``

View File

@ -1,7 +1,7 @@
import { LitElement, html, css } from 'lit' import { LitElement, html, css } from 'lit'
import { connect } from 'pwa-helpers' import { connect } from 'pwa-helpers'
import { store } from '../../store.js' import { store } from '../../store.js'
import { allowQAPPAutoAuth, removeQAPPAutoAuth, removeQAPPAutoLists, allowQAPPAutoLists } from '../../redux/app/app-actions.js' import { allowQAPPAutoAuth, removeQAPPAutoAuth, removeQAPPAutoLists, allowQAPPAutoLists, setIsOpenDevDialog } from '../../redux/app/app-actions.js'
import { use, get, translate, translateUnsafeHTML, registerTranslateConfig } from 'lit-translate' import { use, get, translate, translateUnsafeHTML, registerTranslateConfig } from 'lit-translate'
import snackbar from '../../functional-components/snackbar.js' import snackbar from '../../functional-components/snackbar.js'
import FileSaver from 'file-saver' import FileSaver from 'file-saver'
@ -15,7 +15,8 @@ class SecurityView extends connect(store)(LitElement) {
static get properties() { static get properties() {
return { return {
theme: { type: String, reflect: true }, theme: { type: String, reflect: true },
backupErrorMessage: { type: String } backupErrorMessage: { type: String },
closeSettings: {attribute: false}
} }
} }
@ -75,6 +76,23 @@ class SecurityView extends connect(store)(LitElement) {
transition: all .2s; transition: all .2s;
position: relative; position: relative;
} }
.add-dev-button {
margin-top: 4px;
max-height: 28px;
padding: 5px 5px;
font-size: 14px;
background-color: #03a9f4;
color: white;
border: 1px solid transparent;
border-radius: 3px;
cursor: pointer;
}
.add-dev-button:hover {
opacity: 0.8;
cursor: pointer;
}
` `
} }
@ -122,6 +140,14 @@ class SecurityView extends connect(store)(LitElement) {
</label> </label>
<mwc-checkbox style="margin-right: -15px;" id="authButton" @click=${(e) => this.checkForLists(e)} ?checked=${store.getState().app.qAPPAutoLists}></mwc-checkbox> <mwc-checkbox style="margin-right: -15px;" id="authButton" @click=${(e) => this.checkForLists(e)} ?checked=${store.getState().app.qAPPAutoLists}></mwc-checkbox>
</div> </div>
<div class="checkbox-row">
<button
class="add-dev-button"
title="${translate('tabmenu.tm18')}"
@click=${this.openDevDialog}
>${translate('tabmenu.tm38')}</button>
</div>
</div> </div>
` `
} }
@ -155,6 +181,11 @@ class SecurityView extends connect(store)(LitElement) {
} }
} }
openDevDialog() {
this.closeSettings()
store.dispatch(setIsOpenDevDialog(true))
}
async downloadBackup() { async downloadBackup() {
let backupname = '' let backupname = ''
this.backupErrorMessage = '' this.backupErrorMessage = ''

View File

@ -157,7 +157,9 @@ class UserSettings extends connect(store)(LitElement) {
font-size: 16px; font-size: 16px;
text-align: center; text-align: center;
min-height: 460px; min-height: 460px;
height: 60vh; height: auto;
overflow: auto;
} }
@media(max-width:700px) { @media(max-width:700px) {
@ -252,7 +254,7 @@ class UserSettings extends connect(store)(LitElement) {
if (selectedView.id === 'info') { if (selectedView.id === 'info') {
return html`<account-view></account-view>` return html`<account-view></account-view>`
} else if (selectedView.id === 'security') { } else if (selectedView.id === 'security') {
return html`<security-view></security-view>` return html`<security-view .closeSettings=${()=> this.closeSettings()}></security-view>`
} else if (selectedView.id === 'export') { } else if (selectedView.id === 'export') {
return html`<export-keys></export-keys>` return html`<export-keys></export-keys>`
} else if (selectedView.id === 'notification') { } else if (selectedView.id === 'notification') {

View File

@ -6,7 +6,7 @@ import { Epml } from '../epml.js'
import { addPluginRoutes } from '../plugins/addPluginRoutes.js' import { addPluginRoutes } from '../plugins/addPluginRoutes.js'
import { repeat } from 'lit/directives/repeat.js'; import { repeat } from 'lit/directives/repeat.js';
import ShortUniqueId from 'short-unique-id'; import ShortUniqueId from 'short-unique-id';
import { setNewTab } from '../redux/app/app-actions.js' import { setIsOpenDevDialog, setNewTab } from '../redux/app/app-actions.js'
import localForage from 'localforage' import localForage from 'localforage'
import FileSaver from 'file-saver' import FileSaver from 'file-saver'
import { use, get, translate, translateUnsafeHTML, registerTranslateConfig } from 'lit-translate' import { use, get, translate, translateUnsafeHTML, registerTranslateConfig } from 'lit-translate'
@ -46,7 +46,8 @@ class ShowPlugin extends connect(store)(LitElement) {
tabInfo: { type: Object }, tabInfo: { type: Object },
chatLastSeen: { type: Array }, chatLastSeen: { type: Array },
chatHeads: { type: Array }, chatHeads: { type: Array },
proxyPort: { type: Number } proxyPort: { type: Number },
isOpenDevDialog: {type: Boolean}
} }
} }
@ -334,9 +335,11 @@ class ShowPlugin extends connect(store)(LitElement) {
this.chatLastSeen = [] this.chatLastSeen = []
this.chatHeads = [] this.chatHeads = []
this.proxyPort = 0 this.proxyPort = 0
this.isOpenDevDialog = false
} }
render() { render() {
const plugSrc = (myPlug) => { const plugSrc = (myPlug) => {
return myPlug === undefined ? 'about:blank' : `${window.location.origin}/plugin/${myPlug.domain}/${myPlug.page}${this.linkParam}` return myPlug === undefined ? 'about:blank' : `${window.location.origin}/plugin/${myPlug.domain}/${myPlug.page}${this.linkParam}`
} }
@ -396,14 +399,15 @@ class ShowPlugin extends connect(store)(LitElement) {
icon = 'tab' icon = 'tab'
} }
if (tab.myPlugObj && (tab.myPlugObj.url === 'websites' || tab.myPlugObj.url === 'qapps') && this.tabInfo[tab.id]) { if (tab.myPlugObj && (tab.myPlugObj.url === 'myapp') && this.tabInfo[tab.id]) {
title = this.tabInfo[tab.id].name title = this.tabInfo[tab.id].name
} }
if (tab.myPlugObj && (tab.myPlugObj.url === 'websites' || tab.myPlugObj.url === 'qapps') && this.tabInfo[tab.id]) { if (tab.myPlugObj && (tab.myPlugObj.url === 'myapp') && this.tabInfo[tab.id]) {
count = this.tabInfo[tab.id].count count = this.tabInfo[tab.id].count
} }
if (tab.myPlugObj && tab.myPlugObj.url === 'q-chat') { if (tab.myPlugObj && tab.myPlugObj.url === 'q-chat') {
for (const chat of this.chatHeads) { for (const chat of this.chatHeads) {
@ -458,11 +462,6 @@ class ShowPlugin extends connect(store)(LitElement) {
this.currentTab = lengthOfTabs this.currentTab = lengthOfTabs
}} }}
>+</button> >+</button>
<button
class="add-dev-button"
title="${translate('tabmenu.tm18')}"
@click=${this.openDevDialog}
>${translate('tabmenu.tm38')}</button>
</div> </div>
${repeat(this.tabs, (tab) => tab.id, (tab, index) => html` ${repeat(this.tabs, (tab) => tab.id, (tab, index) => html`
@ -484,7 +483,15 @@ class ShowPlugin extends connect(store)(LitElement) {
</nav-bar> </nav-bar>
</div> </div>
`)} `)}
<mwc-dialog id="addDevDialog"> <mwc-dialog id="addDevDialog"
?open=${this.isOpenDevDialog}
@closed=${() => {
this.shadowRoot.getElementById('domainInput').value = ''
this.shadowRoot.getElementById('portInput').value = ''
this.isOpenDevDialog = false
store.dispatch(setIsOpenDevDialog(false))
}}
>
<div style="text-align: center;"> <div style="text-align: center;">
<h2>${translate('tabmenu.tm39')}</h2> <h2>${translate('tabmenu.tm39')}</h2>
<hr> <hr>
@ -551,12 +558,6 @@ class ShowPlugin extends connect(store)(LitElement) {
return true return true
} }
openDevDialog() {
this.shadowRoot.getElementById('domainInput').value = ''
this.shadowRoot.getElementById('portInput').value = ''
this.shadowRoot.querySelector("#addDevDialog").show()
}
async getProxyPort() { async getProxyPort() {
this.proxyPort = 0 this.proxyPort = 0
let framework = '' let framework = ''
@ -733,11 +734,9 @@ class ShowPlugin extends connect(store)(LitElement) {
const myPlugObj = plugArr.find(pagePlug => { const myPlugObj = plugArr.find(pagePlug => {
return pagePlug.url === this.url return pagePlug.url === this.url
}) })
if (this.tabs.length === 0) { if (this.tabs.length === 0) {
this.addTab({ this.addTab({
url: this.url, url: "",
myPlugObj,
id: this.uid() id: this.uid()
}) })
} else { } else {
@ -780,7 +779,7 @@ class ShowPlugin extends connect(store)(LitElement) {
} }
if (split[0] === '' && split[1] === 'app' && split[2] === undefined) { if (split[0] === '' && split[1] === 'app' && split[2] === undefined) {
newUrl = 'wallet' newUrl = ''
newLinkParam = '' newLinkParam = ''
} else if (split.length === 5 && split[1] === 'app') { } else if (split.length === 5 && split[1] === 'app') {
newUrl = split[2] newUrl = split[2]
@ -792,7 +791,6 @@ class ShowPlugin extends connect(store)(LitElement) {
newUrl = '404' newUrl = '404'
newLinkParam = '' newLinkParam = ''
} }
if (newUrl !== this.url) { if (newUrl !== this.url) {
this.url = newUrl this.url = newUrl
} }
@ -838,6 +836,9 @@ class ShowPlugin extends connect(store)(LitElement) {
//clear newTab //clear newTab
} }
} }
if(state.app.isOpenDevDialog){
this.isOpenDevDialog = state.app.isOpenDevDialog
}
} }
} }

View File

@ -1,7 +1,7 @@
import config from './config' import config from './config'
import { dispatcher } from './dispatcher' import { dispatcher } from './dispatcher'
import snackbar from '../functional-components/snackbar.js' import snackbar from '../functional-components/snackbar.js'
import { NEW_MESSAGE, NEW_MESSAGE_NOTIFICATION_QAPP } from './types' import { NEW_MESSAGE, NEW_MESSAGE_NOTIFICATION_QAPP, NEW_MESSAGE_NOTIFICATION_QAPP_LOCAL } from './types'
let initial = 0 let initial = 0
let _state let _state
@ -43,8 +43,8 @@ const notificationCheck = function () {
*/ */
export const doNewMessage = function (req) { export const doNewMessage = function (req) {
const newMessage = () => { const newMessage = () => {
let data let data
if (req.type && req.type === 'qapp') { if (req.type && req.type === 'qapp') {
data = req data = req
@ -74,9 +74,15 @@ export const doNewMessage = function (req) {
_state = notificationState _state = notificationState
} }
} }
const page = window.top.location.href const page = window.top.location.href
if (!document.hasFocus()) { if(req.type && req.type === 'qapp-local-notification'){
try {
dispatcher({ type: NEW_MESSAGE_NOTIFICATION_QAPP_LOCAL, data: req })
} catch (error) {
console.log('error', error)
}
}else if (!document.hasFocus()) {
newMessage() newMessage()
} else { } else {
if (page.includes(req.url) === false) { if (page.includes(req.url) === false) {

View File

@ -1,5 +1,5 @@
import { NEW_MESSAGE, NEW_MESSAGE_NOTIFICATION_QAPP } from './types' import { NEW_MESSAGE, NEW_MESSAGE_NOTIFICATION_QAPP, NEW_MESSAGE_NOTIFICATION_QAPP_LOCAL } from './types'
import { newMessage, newMessageNotificationQapp } from './notification-actions' import { newMessage, newMessageNotificationQapp, newMessageNotificationQappLocal } from './notification-actions'
export const dispatcher = function (notificationState) { export const dispatcher = function (notificationState) {
@ -8,6 +8,8 @@ export const dispatcher = function (notificationState) {
return newMessage(notificationState.data) return newMessage(notificationState.data)
case NEW_MESSAGE_NOTIFICATION_QAPP: case NEW_MESSAGE_NOTIFICATION_QAPP:
return newMessageNotificationQapp(notificationState.data) return newMessageNotificationQapp(notificationState.data)
case NEW_MESSAGE_NOTIFICATION_QAPP_LOCAL:
return newMessageNotificationQappLocal(notificationState.data)
default: default:
} }
} }

View File

@ -1 +1 @@
export { newMessage, newMessageNotificationQapp } from './new-message' export { newMessage, newMessageNotificationQapp, newMessageNotificationQappLocal } from './new-message'

View File

@ -1,6 +1,9 @@
import { store } from '../../store.js' import { store } from '../../store.js'
import { doPageUrl, setNewTab } from '../../redux/app/app-actions.js' import { doPageUrl, setNewTab } from '../../redux/app/app-actions.js'
import isElectron from 'is-electron' import isElectron from 'is-electron'
import ShortUniqueId from 'short-unique-id';
const uid = new ShortUniqueId()
export const newMessage = (data) => { export const newMessage = (data) => {
const alert = playSound(data.sound) const alert = playSound(data.sound)
@ -101,6 +104,157 @@ export const newMessageNotificationQapp = (data) => {
} }
} }
}
const extractComponents= async (url)=> {
if (!url.startsWith("qortal://")) {
return null;
}
url = url.replace(/^(qortal\:\/\/)/, "");
if (url.includes("/")) {
let parts = url.split("/");
const service = parts[0].toUpperCase();
parts.shift();
const name = parts[0];
parts.shift();
let identifier;
if (parts.length > 0) {
identifier = parts[0]; // Do not shift yet
// Check if a resource exists with this service, name and identifier combination
let responseObj = await parentEpml.request('apiCall', {
url: `/arbitrary/resource/status/${service}/${name}/${identifier}?apiKey=${this.getApiKey()}`
})
if (responseObj.totalChunkCount > 0) {
// Identifier exists, so don't include it in the path
parts.shift();
}
else {
identifier = null;
}
}
const path = parts.join("/");
const components = {};
components["service"] = service;
components["name"] = name;
components["identifier"] = identifier;
components["path"] = path;
return components;
}
return null;
}
export const newMessageNotificationQappLocal = (data) => {
const alert = playSound(data.sound)
// Should I show notification ?
if (store.getState().user.notifications.q_chat.showNotification) {
// Yes, I can but should I play sound ?
if (store.getState().user.notifications.q_chat.playSound) {
const notify = new Notification(data.title, data.options)
notify.onshow = (e) => {
alert.play()
}
notify.onclick = async(e) => {
const url = data?.url
const value = url
let newQuery = value;
if (newQuery.endsWith('/')) {
newQuery = newQuery.slice(0, -1);
}
const res = await extractComponents(newQuery)
if (!res) return
const { service, name, identifier, path } = res
let query = `?service=${service}`
if (name) {
query = query + `&name=${name}`
}
if (identifier) {
query = query + `&identifier=${identifier}`
}
if (path) {
query = query + `&path=${path}`
}
const tab = {
url: `qdn/browser/index.html${query}`,
id: uid(),
myPlugObj: {
"url": service === 'WEBSITE' ? "websites" : "qapps",
"domain": "core",
"page": `qdn/browser/index.html${query}`,
"title": name,
"icon": service === 'WEBSITE' ? 'vaadin:desktop' : 'vaadin:external-browser',
"mwcicon": service === 'WEBSITE' ? 'desktop_mac' : 'open_in_browser',
"menus": [],
"parent": false
}
}
store.dispatch(setNewTab(tab))
if (!isElectron()) {
window.focus();
} else {
window.electronAPI.focusApp()
}
}
} else {
const notify = new Notification(data.title, data.options)
notify.onclick = async(e) => {
const url = data?.url
const value = url
let newQuery = value;
if (newQuery.endsWith('/')) {
newQuery = newQuery.slice(0, -1);
}
const res = await extractComponents(newQuery)
if (!res) return
const { service, name, identifier, path } = res
let query = `?service=${service}`
if (name) {
query = query + `&name=${name}`
}
if (identifier) {
query = query + `&identifier=${identifier}`
}
if (path) {
query = query + `&path=${path}`
}
const tab = {
url: `qdn/browser/index.html${query}`,
id: uid(),
myPlugObj: {
"url": service === 'WEBSITE' ? "websites" : "qapps",
"domain": "core",
"page": `qdn/browser/index.html${query}`,
"title": name,
"icon": service === 'WEBSITE' ? 'vaadin:desktop' : 'vaadin:external-browser',
"mwcicon": service === 'WEBSITE' ? 'desktop_mac' : 'open_in_browser',
"menus": [],
"parent": false
}
}
store.dispatch(setNewTab(tab))
if (!isElectron()) {
window.focus();
} else {
window.electronAPI.focusApp()
}
}
}
}
} }
const playSound = (soundUrl) => { const playSound = (soundUrl) => {

View File

@ -1,2 +1,3 @@
export const NEW_MESSAGE = 'NEW_MESSAGE' export const NEW_MESSAGE = 'NEW_MESSAGE'
export const NEW_MESSAGE_NOTIFICATION_QAPP = 'NEW_MESSAGE_NOTIFICATION_QAPP' export const NEW_MESSAGE_NOTIFICATION_QAPP = 'NEW_MESSAGE_NOTIFICATION_QAPP'
export const NEW_MESSAGE_NOTIFICATION_QAPP_LOCAL = 'NEW_MESSAGE_NOTIFICATION_QAPP_LOCAL'

View File

@ -1,5 +1,5 @@
// Core App Actions here... // Core App Actions here...
import { UPDATE_BLOCK_INFO, UPDATE_NODE_STATUS, UPDATE_NODE_INFO, CHAT_HEADS, ACCOUNT_INFO, ADD_AUTO_LOAD_IMAGES_CHAT, REMOVE_AUTO_LOAD_IMAGES_CHAT, ALLOW_QAPP_AUTO_AUTH, REMOVE_QAPP_AUTO_AUTH, SET_CHAT_LAST_SEEN, ADD_CHAT_LAST_SEEN, ALLOW_QAPP_AUTO_LISTS, REMOVE_QAPP_AUTO_LISTS, SET_NEW_TAB, ADD_TAB_INFO, SET_TAB_NOTIFICATIONS } from '../app-action-types.js' import { UPDATE_BLOCK_INFO, UPDATE_NODE_STATUS, UPDATE_NODE_INFO, CHAT_HEADS, ACCOUNT_INFO, ADD_AUTO_LOAD_IMAGES_CHAT, REMOVE_AUTO_LOAD_IMAGES_CHAT, ALLOW_QAPP_AUTO_AUTH, REMOVE_QAPP_AUTO_AUTH, SET_CHAT_LAST_SEEN, ADD_CHAT_LAST_SEEN, ALLOW_QAPP_AUTO_LISTS, REMOVE_QAPP_AUTO_LISTS, SET_NEW_TAB, ADD_TAB_INFO, SET_TAB_NOTIFICATIONS, IS_OPEN_DEV_DIALOG } from '../app-action-types.js'
export const doUpdateBlockInfo = (blockObj) => { export const doUpdateBlockInfo = (blockObj) => {
return (dispatch, getState) => { return (dispatch, getState) => {
@ -126,7 +126,12 @@ export const setNewTab = (payload) => {
payload payload
} }
} }
export const setIsOpenDevDialog = (payload)=> {
return {
type: IS_OPEN_DEV_DIALOG,
payload
}
}
export const addTabInfo = (payload) => { export const addTabInfo = (payload) => {
return { return {
type: ADD_TAB_INFO, type: ADD_TAB_INFO,

View File

@ -28,3 +28,4 @@ export const ADD_CHAT_LAST_SEEN = 'ADD_CHAT_LAST_SEEN'
export const SET_NEW_TAB = 'SET_NEW_TAB' export const SET_NEW_TAB = 'SET_NEW_TAB'
export const ADD_TAB_INFO = 'ADD_TAB_INFO' export const ADD_TAB_INFO = 'ADD_TAB_INFO'
export const SET_TAB_NOTIFICATIONS = 'SET_TAB_NOTIFICATIONS' export const SET_TAB_NOTIFICATIONS = 'SET_TAB_NOTIFICATIONS'
export const IS_OPEN_DEV_DIALOG = 'IS_OPEN_DEV_DIALOG'

View File

@ -1,6 +1,6 @@
// Loading state, login state, isNavDrawOpen state etc. None of this needs to be saved to localstorage. // Loading state, login state, isNavDrawOpen state etc. None of this needs to be saved to localstorage.
import { loadStateFromLocalStorage, saveStateToLocalStorage } from '../../localStorageHelpers.js' import { loadStateFromLocalStorage, saveStateToLocalStorage } from '../../localStorageHelpers.js'
import { LOG_IN, LOG_OUT, NETWORK_CONNECTION_STATUS, INIT_WORKERS, ADD_PLUGIN_URL, ADD_PLUGIN, ADD_NEW_PLUGIN_URL, NAVIGATE, SELECT_ADDRESS, ACCOUNT_INFO, CHAT_HEADS, UPDATE_BLOCK_INFO, UPDATE_NODE_STATUS, UPDATE_NODE_INFO, LOAD_NODE_CONFIG, SET_NODE, ADD_NODE, PAGE_URL, ADD_AUTO_LOAD_IMAGES_CHAT, REMOVE_AUTO_LOAD_IMAGES_CHAT, ALLOW_QAPP_AUTO_AUTH, REMOVE_QAPP_AUTO_AUTH, SET_CHAT_LAST_SEEN, ADD_CHAT_LAST_SEEN, ALLOW_QAPP_AUTO_LISTS, REMOVE_QAPP_AUTO_LISTS, SET_NEW_TAB, ADD_TAB_INFO, SET_TAB_NOTIFICATIONS } from './app-action-types.js' import { LOG_IN, LOG_OUT, NETWORK_CONNECTION_STATUS, INIT_WORKERS, ADD_PLUGIN_URL, ADD_PLUGIN, ADD_NEW_PLUGIN_URL, NAVIGATE, SELECT_ADDRESS, ACCOUNT_INFO, CHAT_HEADS, UPDATE_BLOCK_INFO, UPDATE_NODE_STATUS, UPDATE_NODE_INFO, LOAD_NODE_CONFIG, SET_NODE, ADD_NODE, PAGE_URL, ADD_AUTO_LOAD_IMAGES_CHAT, REMOVE_AUTO_LOAD_IMAGES_CHAT, ALLOW_QAPP_AUTO_AUTH, REMOVE_QAPP_AUTO_AUTH, SET_CHAT_LAST_SEEN, ADD_CHAT_LAST_SEEN, ALLOW_QAPP_AUTO_LISTS, REMOVE_QAPP_AUTO_LISTS, SET_NEW_TAB, ADD_TAB_INFO, SET_TAB_NOTIFICATIONS, IS_OPEN_DEV_DIALOG } from './app-action-types.js'
import { initWorkersReducer } from './reducers/init-workers.js' import { initWorkersReducer } from './reducers/init-workers.js'
import { loginReducer } from './reducers/login-reducer.js' import { loginReducer } from './reducers/login-reducer.js'
import { setNode, addNode } from './reducers/manage-node.js' import { setNode, addNode } from './reducers/manage-node.js'
@ -49,7 +49,8 @@ const INITIAL_STATE = {
qAPPAutoLists: loadStateFromLocalStorage('qAPPAutoLists') || false, qAPPAutoLists: loadStateFromLocalStorage('qAPPAutoLists') || false,
chatLastSeen: [], chatLastSeen: [],
newTab: null, newTab: null,
tabInfo: {} tabInfo: {},
isOpenDevDialog: false
} }
export default (state = INITIAL_STATE, action) => { export default (state = INITIAL_STATE, action) => {
@ -231,6 +232,12 @@ export default (state = INITIAL_STATE, action) => {
newTab: action.payload newTab: action.payload
} }
} }
case IS_OPEN_DEV_DIALOG: {
return {
...state,
isOpenDevDialog: action.payload
}
}
case ADD_TAB_INFO: { case ADD_TAB_INFO: {
const newTabInfo = action.payload const newTabInfo = action.payload
if (state.tabInfo[newTabInfo.id] && state.tabInfo[newTabInfo.id].name && newTabInfo.name === state.tabInfo[newTabInfo.id].name) break if (state.tabInfo[newTabInfo.id] && state.tabInfo[newTabInfo.id].name && newTabInfo.name === state.tabInfo[newTabInfo.id].name) break

View File

@ -1 +0,0 @@
export const UI_VERSION = "4.0.4";

View File

@ -161,6 +161,10 @@ const PROXY_URL = "/proxy/"
// Chat reference timestamp // Chat reference timestamp
const CHAT_REFERENCE_FEATURE_TRIGGER_TIMESTAMP = 1674316800000 const CHAT_REFERENCE_FEATURE_TRIGGER_TIMESTAMP = 1674316800000
// Dynamic fee timestamp
const DYNAMIC_FEE_TIMESTAMP = 1692118800000
// Used as a salt for all Qora addresses. Salts used for storing your private keys in local storage will be randomly generated // Used as a salt for all Qora addresses. Salts used for storing your private keys in local storage will be randomly generated
const STATIC_SALT = new Uint8Array([54, 190, 201, 206, 65, 29, 123, 129, 147, 231, 180, 166, 171, 45, 95, 165, 78, 200, 208, 194, 44, 207, 221, 146, 45, 238, 68, 68, 69, 102, 62, 6]) const STATIC_SALT = new Uint8Array([54, 190, 201, 206, 65, 29, 123, 129, 147, 231, 180, 166, 171, 45, 95, 165, 78, 200, 208, 194, 44, 207, 221, 146, 45, 238, 68, 68, 69, 102, 62, 6])
const BCRYPT_ROUNDS = 10 // Remember that the total work spent on key derivation is BCRYPT_ROUNDS * KDF_THREADS const BCRYPT_ROUNDS = 10 // Remember that the total work spent on key derivation is BCRYPT_ROUNDS * KDF_THREADS
@ -168,4 +172,4 @@ const BCRYPT_VERSION = "2a"
const STATIC_BCRYPT_SALT = `$${BCRYPT_VERSION}$${BCRYPT_ROUNDS}$IxVE941tXVUD4cW0TNVm.O` const STATIC_BCRYPT_SALT = `$${BCRYPT_VERSION}$${BCRYPT_ROUNDS}$IxVE941tXVUD4cW0TNVm.O`
const KDF_THREADS = 16 const KDF_THREADS = 16
export { TX_TYPES, ERROR_CODES, QORT_DECIMALS, PROXY_URL, STATIC_SALT, ADDRESS_VERSION, KDF_THREADS, STATIC_BCRYPT_SALT, CHAT_REFERENCE_FEATURE_TRIGGER_TIMESTAMP } export { TX_TYPES, ERROR_CODES, QORT_DECIMALS, PROXY_URL, STATIC_SALT, ADDRESS_VERSION, KDF_THREADS, STATIC_BCRYPT_SALT, CHAT_REFERENCE_FEATURE_TRIGGER_TIMESTAMP, DYNAMIC_FEE_TIMESTAMP }

View File

@ -1,6 +1,7 @@
'use strict' 'use strict'
import TransactionBase from './TransactionBase.js' import TransactionBase from './TransactionBase.js'
import { store } from '../../api.js' import { store } from '../../api.js'
import { QORT_DECIMALS } from '../constants.js'
export default class DeployAtTransaction extends TransactionBase { export default class DeployAtTransaction extends TransactionBase {
constructor() { constructor() {
@ -8,17 +9,22 @@ export default class DeployAtTransaction extends TransactionBase {
this.type = 16 this.type = 16
} }
render(html) { render(html) {
return html` return html`
${this._groupdialog5} ${this._atDeployDialog1}:
<div style="background: #eee; padding: 8px; margin: 8px 0; border-radius: 5px;"> <div style="background: #eee; padding: 8px; margin: 8px 0; border-radius: 5px;">
<div>${this._atDeployDialog1}: <span style="color: #000;">${this._rName}</span></div> <span style="color: #000;">${this._rName}</span>
<br>
<div>${this.atDeployDialog2}: <span style="color: #000;">${this._rDescription}</span></div>
<br>
</div> </div>
${this._atDeployDialog4}:
<div style="background: #eee; padding: 8px; margin: 8px 0; border-radius: 5px;">
<span style="color: #000;">${this._feeToShow}</span>
</div> </div>
${this._groupdialog6} ${this._atDeployDialog3}:
<div style="background: #eee; padding: 8px; margin: 8px 0; border-radius: 5px;">
<span style="color: #000;">${this._amountToShow}</span>
</div>
${this._atDeployDialog2}
` `
} }
@ -28,12 +34,20 @@ export default class DeployAtTransaction extends TransactionBase {
set atDeployDialog2(atDeployDialog2) { set atDeployDialog2(atDeployDialog2) {
this._atDeployDialog2 = atDeployDialog2 this._atDeployDialog2 = atDeployDialog2
} }
set atDeployDialog3(atDeployDialog3) {
this._atDeployDialog3 = atDeployDialog3
}
set atDeployDialog4(atDeployDialog4) {
this._atDeployDialog4 = atDeployDialog4
}
set fee(fee) { set fee(fee) {
this._fee = fee this._feeToShow = fee
this._fee = fee * QORT_DECIMALS
this._feeBytes = this.constructor.utils.int64ToBytes(this._fee) this._feeBytes = this.constructor.utils.int64ToBytes(this._fee)
} }
set rAmount(rAmount) { set rAmount(rAmount) {
this._amountToShow = rAmount
this._rAmount = Math.round(rAmount * store.getState().config.coin.decimals) this._rAmount = Math.round(rAmount * store.getState().config.coin.decimals)
this._rAmountBytes = this.constructor.utils.int64ToBytes(this._rAmount) this._rAmountBytes = this.constructor.utils.int64ToBytes(this._rAmount)
} }

View File

@ -2,6 +2,7 @@
import TransactionBase from '../TransactionBase.js' import TransactionBase from '../TransactionBase.js'
import publicKeyToAddress from '../../wallet/publicKeyToAddress.js' import publicKeyToAddress from '../../wallet/publicKeyToAddress.js'
import { Base58 } from '../../deps/deps.js' import { Base58 } from '../../deps/deps.js'
import { DYNAMIC_FEE_TIMESTAMP } from '../../constants.js'
export default class RemoveRewardShareTransaction extends TransactionBase { export default class RemoveRewardShareTransaction extends TransactionBase {
constructor() { constructor() {
@ -34,8 +35,13 @@ export default class RemoveRewardShareTransaction extends TransactionBase {
set recipient(recipient) { set recipient(recipient) {
const _address = publicKeyToAddress(this._keyPair.publicKey) const _address = publicKeyToAddress(this._keyPair.publicKey)
this._recipient = recipient instanceof Uint8Array ? recipient : this.constructor.Base58.decode(recipient) this._recipient = recipient instanceof Uint8Array ? recipient : this.constructor.Base58.decode(recipient)
if (new Date(this._timestamp).getTime() >= DYNAMIC_FEE_TIMESTAMP) {
this.fee = _address === recipient ? 0 : 0.01
} else {
this.fee = _address === recipient ? 0 : 0.001 this.fee = _address === recipient ? 0 : 0.001
} }
}
set percentageShare(share) { set percentageShare(share) {
this._percentageShare = share * 100 this._percentageShare = share * 100

View File

@ -4,6 +4,7 @@ import TransactionBase from "../TransactionBase.js"
import nacl from '../../deps/nacl-fast.js' import nacl from '../../deps/nacl-fast.js'
import ed2curve from '../../deps/ed2curve.js' import ed2curve from '../../deps/ed2curve.js'
import { Sha256 } from 'asmcrypto.js' import { Sha256 } from 'asmcrypto.js'
import { DYNAMIC_FEE_TIMESTAMP } from '../../constants.js'
export default class RewardShareTransaction extends TransactionBase { export default class RewardShareTransaction extends TransactionBase {
constructor() { constructor() {
@ -54,6 +55,12 @@ export default class RewardShareTransaction extends TransactionBase {
this._base58RewardShareSeed = this.constructor.Base58.encode(this._rewardShareSeed) this._base58RewardShareSeed = this.constructor.Base58.encode(this._rewardShareSeed)
this._rewardShareKeyPair = nacl.sign.keyPair.fromSeed(this._rewardShareSeed) this._rewardShareKeyPair = nacl.sign.keyPair.fromSeed(this._rewardShareSeed)
if (new Date(this._timestamp).getTime() >= DYNAMIC_FEE_TIMESTAMP) {
this.fee = (recipientPublicKey === this.constructor.Base58.encode(this._keyPair.publicKey) ? 0 : 0.01)
} else {
this.fee = (recipientPublicKey === this.constructor.Base58.encode(this._keyPair.publicKey) ? 0 : 0.001)
}
} }
set recipient(recipient) { set recipient(recipient) {

View File

@ -845,7 +845,7 @@ function createWindow() {
}) })
myWindow.maximize() myWindow.maximize()
myWindow.show() myWindow.show()
myWindow.loadURL('http://localhost:12388/app/wallet') myWindow.loadURL('http://localhost:12388/app')
myWindow.on('closed', function () { myWindow.on('closed', function () {
myWindow = null myWindow = null
}) })
@ -886,7 +886,7 @@ function createNewWindow() {
show: false show: false
}) })
newWindow.show() newWindow.show()
newWindow.loadURL('http://localhost:12388/app/wallet') newWindow.loadURL('http://localhost:12388/app')
newWindow.on('closed', function () { newWindow.on('closed', function () {
newWindow = null newWindow = null
}) })

810
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{ {
"name": "qortal-ui", "name": "qortal-ui",
"version": "4.3.0", "version": "4.3.2",
"description": "Qortal Project - decentralize the world - Data storage, communications, web hosting, decentralized trading, complete infrastructure for the future blockchain-based Internet", "description": "Qortal Project - decentralize the world - Data storage, communications, web hosting, decentralized trading, complete infrastructure for the future blockchain-based Internet",
"keywords": [ "keywords": [
"QORT", "QORT",
@ -41,7 +41,7 @@
"electron-store": "8.1.0", "electron-store": "8.1.0",
"emoji-picker-js": "https://github.com/Qortal/emoji-picker-js", "emoji-picker-js": "https://github.com/Qortal/emoji-picker-js",
"extract-zip": "2.0.1", "extract-zip": "2.0.1",
"jssha": "3.3.0", "jssha": "3.3.1",
"localforage": "1.10.0", "localforage": "1.10.0",
"lodash": "4.17.21", "lodash": "4.17.21",
"node-fetch": "2.6.9", "node-fetch": "2.6.9",
@ -54,9 +54,9 @@
"prosemirror-model": "1.19.3", "prosemirror-model": "1.19.3",
"prosemirror-schema-list": "1.3.0", "prosemirror-schema-list": "1.3.0",
"prosemirror-state": "1.4.3", "prosemirror-state": "1.4.3",
"prosemirror-transform": "1.7.4", "prosemirror-transform": "1.7.5",
"prosemirror-view": "1.31.7", "prosemirror-view": "1.31.7",
"sass": "1.64.2", "sass": "1.66.1",
"short-unique-id": "4.4.4", "short-unique-id": "4.4.4",
"@hapi/hapi": "21.3.2", "@hapi/hapi": "21.3.2",
"@hapi/inert": "7.1.0", "@hapi/inert": "7.1.0",
@ -71,10 +71,10 @@
"@tiptap/starter-kit": "2.0.4" "@tiptap/starter-kit": "2.0.4"
}, },
"devDependencies": { "devDependencies": {
"axios": "1.4.0", "axios": "1.5.0",
"electron": "25.4.0", "electron": "26.1.0",
"electron-builder": "24.6.3", "electron-builder": "24.6.3",
"electron-packager": "17.1.1", "electron-packager": "17.1.2",
"epml": "0.3.3", "epml": "0.3.3",
"file-saver": "2.0.5", "file-saver": "2.0.5",
"highcharts": "11.1.0", "highcharts": "11.1.0",
@ -86,12 +86,12 @@
"passive-events-support": "1.1.0", "passive-events-support": "1.1.0",
"redux": "4.2.1", "redux": "4.2.1",
"redux-thunk": "2.4.2", "redux-thunk": "2.4.2",
"rollup": "3.27.2", "rollup": "3.28.1",
"rollup-plugin-node-globals": "1.4.0", "rollup-plugin-node-globals": "1.4.0",
"rollup-plugin-progress": "1.1.2", "rollup-plugin-progress": "1.1.2",
"rollup-plugin-scss": "3.0.0", "rollup-plugin-scss": "3.0.0",
"shelljs": "0.8.5", "shelljs": "0.8.5",
"@babel/core": "7.22.9", "@babel/core": "7.22.11",
"@material/mwc-button": "0.27.0", "@material/mwc-button": "0.27.0",
"@material/mwc-checkbox": "0.27.0", "@material/mwc-checkbox": "0.27.0",
"@material/mwc-dialog": "0.27.0", "@material/mwc-dialog": "0.27.0",
@ -130,19 +130,19 @@
"@qortal/rollup-plugin-web-worker-loader": "1.6.4", "@qortal/rollup-plugin-web-worker-loader": "1.6.4",
"@rollup/plugin-alias": "5.0.0", "@rollup/plugin-alias": "5.0.0",
"@rollup/plugin-babel": "6.0.3", "@rollup/plugin-babel": "6.0.3",
"@rollup/plugin-commonjs": "25.0.3", "@rollup/plugin-commonjs": "25.0.4",
"@rollup/plugin-node-resolve": "15.1.0", "@rollup/plugin-node-resolve": "15.2.1",
"@rollup/plugin-replace": "5.0.2", "@rollup/plugin-replace": "5.0.2",
"@rollup/plugin-terser": "0.4.3", "@rollup/plugin-terser": "0.4.3",
"@vaadin/avatar": "24.1.4", "@vaadin/avatar": "24.1.6",
"@vaadin/button": "24.1.4", "@vaadin/button": "24.1.6",
"@vaadin/grid": "24.1.4", "@vaadin/grid": "24.1.6",
"@vaadin/icons": "24.1.4", "@vaadin/icons": "24.1.6",
"@vaadin/password-field": "24.1.4", "@vaadin/password-field": "24.1.6",
"@vaadin/tooltip": "24.1.4", "@vaadin/tooltip": "24.1.6",
"@zip.js/zip.js": "2.7.20" "@zip.js/zip.js": "2.7.27"
}, },
"engines": { "engines": {
"node": ">=18.15.0" "node": ">=18.16.1"
} }
} }

View File

@ -36,14 +36,13 @@ import './ChatLeaveGroup.js'
import './ChatGroupSettings.js' import './ChatGroupSettings.js'
import './ChatRightPanel.js' import './ChatRightPanel.js'
import './ChatSearchResults.js' import './ChatSearchResults.js'
import './ChatGifs/ChatGifs.js'
import '@material/mwc-button' import '@material/mwc-button'
import '@material/mwc-dialog' import '@material/mwc-dialog'
import '@material/mwc-icon' import '@material/mwc-icon'
import '@polymer/paper-dialog/paper-dialog.js' import '@polymer/paper-dialog/paper-dialog.js'
import '@polymer/paper-spinner/paper-spinner-lite.js' import '@polymer/paper-spinner/paper-spinner-lite.js'
import { RequestQueue } from '../../utils/queue.js' import { RequestQueue } from '../../utils/queue.js'
import { modalHelper } from '../../utils/publish-modal.js'
const chatLastSeen = localForage.createInstance({ const chatLastSeen = localForage.createInstance({
name: "chat-last-seen", name: "chat-last-seen",
@ -1449,8 +1448,6 @@ class ChatPage extends LitElement {
<div style="position: fixed; top:${parseInt(this.isLoadingGoToRepliedMessage.top)}px;left: ${parseInt(this.isLoadingGoToRepliedMessage.left)}px" class=${`smallLoading marginLoader`}></div> <div style="position: fixed; top:${parseInt(this.isLoadingGoToRepliedMessage.top)}px;left: ${parseInt(this.isLoadingGoToRepliedMessage.left)}px" class=${`smallLoading marginLoader`}></div>
` : ''} ` : ''}
<div class="chat-text-area" style="${`${(this.repliedToMessageObj || this.editedMessageObj) && "min-height: 120px"}`}"> <div class="chat-text-area" style="${`${(this.repliedToMessageObj || this.editedMessageObj) && "min-height: 120px"}`}">
<!-- gif div -->
<div <div
class='last-message-ref' class='last-message-ref'
style=${(this.lastMessageRefVisible && !this.imageFile && !this.openGifModal) ? 'opacity: 1;' : 'opacity: 0;'}> style=${(this.lastMessageRefVisible && !this.imageFile && !this.openGifModal) ? 'opacity: 1;' : 'opacity: 0;'}>
@ -1880,7 +1877,7 @@ class ChatPage extends LitElement {
return memberItem return memberItem
}) })
const membersWithName = await Promise.all(getMembersWithName) const membersWithName = await Promise.all(getMembersWithName)
this.groupMembers = membersWithName this.groupMembers = [...this.groupMembers, ...membersWithName]
this.pageNumber = this.pageNumber + 1 this.pageNumber = this.pageNumber + 1
} catch (error) { } catch (error) {
} }
@ -3177,6 +3174,7 @@ viewElement.scrollTop = originalScrollTop + heightDifference;
} }
async _sendMessage(outSideMsg, msg) { async _sendMessage(outSideMsg, msg) {
try {
if (this.isReceipient) { if (this.isReceipient) {
let hasPublicKey = true let hasPublicKey = true
if (!this._publicKey.hasPubKey) { if (!this._publicKey.hasPubKey) {
@ -3286,6 +3284,13 @@ viewElement.scrollTop = originalScrollTop + heightDifference;
}, },
}) })
}) })
const arbitraryFeeData = await modalHelper.getArbitraryFee()
const res = await modalHelper.showModalAndWaitPublish(
{
feeAmount: arbitraryFeeData.feeToShow
}
);
if (res.action !== 'accept') throw new Error('User declined publish')
try { try {
await publishData({ await publishData({
registeredName: userName, registeredName: userName,
@ -3296,7 +3301,9 @@ viewElement.scrollTop = originalScrollTop + heightDifference;
metaData: undefined, metaData: undefined,
uploadType: 'file', uploadType: 'file',
selectedAddress: this.selectedAddress, selectedAddress: this.selectedAddress,
worker: this.webWorkerFile worker: this.webWorkerFile,
withFee: true,
feeAmount: arbitraryFeeData.fee
}) })
this.isDeletingImage = false this.isDeletingImage = false
} catch (error) { } catch (error) {
@ -3374,7 +3381,13 @@ viewElement.scrollTop = originalScrollTop + heightDifference;
}, },
}) })
}) })
const arbitraryFeeData = await modalHelper.getArbitraryFee()
const res = await modalHelper.showModalAndWaitPublish(
{
feeAmount: arbitraryFeeData.feeToShow
}
);
if (res.action !== 'accept') throw new Error('User declined publish')
try { try {
await publishData({ await publishData({
registeredName: userName, registeredName: userName,
@ -3385,7 +3398,9 @@ viewElement.scrollTop = originalScrollTop + heightDifference;
metaData: undefined, metaData: undefined,
uploadType: 'file', uploadType: 'file',
selectedAddress: this.selectedAddress, selectedAddress: this.selectedAddress,
worker: this.webWorkerFile worker: this.webWorkerFile,
withFee: true,
feeAmount: arbitraryFeeData.fee
}) })
this.isDeletingAttachment = false this.isDeletingAttachment = false
} catch (error) { } catch (error) {
@ -3423,6 +3438,13 @@ viewElement.scrollTop = originalScrollTop + heightDifference;
this.imageFile = null this.imageFile = null
return return
} }
const arbitraryFeeData = await modalHelper.getArbitraryFee()
const res = await modalHelper.showModalAndWaitPublish(
{
feeAmount: arbitraryFeeData.feeToShow
}
);
if (res.action !== 'accept') throw new Error('User declined publish')
if (this.webWorkerFile) { if (this.webWorkerFile) {
this.webWorkerFile.terminate() this.webWorkerFile.terminate()
@ -3457,7 +3479,9 @@ viewElement.scrollTop = originalScrollTop + heightDifference;
this.isUploadingImage = false this.isUploadingImage = false
return return
} }
try { try {
await publishData({ await publishData({
registeredName: userName, registeredName: userName,
file: compressedFile, file: compressedFile,
@ -3467,7 +3491,9 @@ viewElement.scrollTop = originalScrollTop + heightDifference;
metaData: undefined, metaData: undefined,
uploadType: 'file', uploadType: 'file',
selectedAddress: this.selectedAddress, selectedAddress: this.selectedAddress,
worker: this.webWorkerFile worker: this.webWorkerFile,
withFee: true,
feeAmount: arbitraryFeeData.fee
}) })
this.isUploadingImage = false this.isUploadingImage = false
this.removeImage() this.removeImage()
@ -3537,6 +3563,13 @@ viewElement.scrollTop = originalScrollTop + heightDifference;
this.isUploadingAttachment = false this.isUploadingAttachment = false
return return
} }
const arbitraryFeeData = await modalHelper.getArbitraryFee()
const res = await modalHelper.showModalAndWaitPublish(
{
feeAmount: arbitraryFeeData.feeToShow
}
);
if (res.action !== 'accept') throw new Error('User declined publish')
try { try {
await publishData({ await publishData({
registeredName: userName, registeredName: userName,
@ -3547,7 +3580,9 @@ viewElement.scrollTop = originalScrollTop + heightDifference;
metaData: undefined, metaData: undefined,
uploadType: 'file', uploadType: 'file',
selectedAddress: this.selectedAddress, selectedAddress: this.selectedAddress,
worker: this.webWorkerFile worker: this.webWorkerFile,
withFee: true,
feeAmount: arbitraryFeeData.fee
}) })
this.isUploadingAttachment = false this.isUploadingAttachment = false
this.removeAttachment() this.removeAttachment()
@ -3682,6 +3717,12 @@ viewElement.scrollTop = originalScrollTop + heightDifference;
this.sendMessage(stringifyMessageObject, typeMessage) this.sendMessage(stringifyMessageObject, typeMessage)
} }
} }
} catch (error) {
this.isLoading = false
this.isUploadingImage = false
return
}
} }
async sendMessage(messageText, typeMessage, chatReference, isForward) { async sendMessage(messageText, typeMessage, chatReference, isForward) {

View File

@ -225,7 +225,6 @@ class ChatRightPanel extends LitElement {
if(this.groupMembers.length < 20){ if(this.groupMembers.length < 20){
return return
} }
console.log('this.leaveGroupObjp', this.leaveGroupObj)
this.getMoreMembers(this.leaveGroupObj.groupId) this.getMoreMembers(this.leaveGroupObj.groupId)
} }
} }
@ -252,7 +251,6 @@ class ChatRightPanel extends LitElement {
activeChatHeadUrl="" activeChatHeadUrl=""
.setActiveChatHeadUrl=${(val) => { .setActiveChatHeadUrl=${(val) => {
if (val.address === this.myAddress) return; if (val.address === this.myAddress) return;
console.log({ val });
this.selectedHead = val; this.selectedHead = val;
this.setOpenUserInfo(true); this.setOpenUserInfo(true);
this.setUserName({ this.setUserName({
@ -269,7 +267,6 @@ class ChatRightPanel extends LitElement {
activeChatHeadUrl="" activeChatHeadUrl=""
.setActiveChatHeadUrl=${(val) => { .setActiveChatHeadUrl=${(val) => {
if (val.address === this.myAddress) return; if (val.address === this.myAddress) return;
console.log({ val });
this.selectedHead = val; this.selectedHead = val;
this.setOpenUserInfo(true); this.setOpenUserInfo(true);
this.setUserName({ this.setUserName({
@ -286,7 +283,6 @@ class ChatRightPanel extends LitElement {
activeChatHeadUrl="" activeChatHeadUrl=""
.setActiveChatHeadUrl=${(val) => { .setActiveChatHeadUrl=${(val) => {
if (val.address === this.myAddress) return; if (val.address === this.myAddress) return;
console.log({ val });
this.selectedHead = val; this.selectedHead = val;
this.setOpenUserInfo(true); this.setOpenUserInfo(true);
this.setUserName({ this.setUserName({

View File

@ -101,7 +101,7 @@ function processText(input) {
// Store the URL in a data attribute // Store the URL in a data attribute
link.setAttribute('data-url', part) link.setAttribute('data-url', part)
link.textContent = part link.textContent = part
link.style.color = 'var(--nav-text-color)' link.style.color = 'var(--code-block-text-color)'
link.style.textDecoration = 'underline' link.style.textDecoration = 'underline'
link.style.cursor = 'pointer' link.style.cursor = 'pointer'
@ -125,7 +125,7 @@ function processText(input) {
url: `qdn/browser/index.html${query}`, url: `qdn/browser/index.html${query}`,
id: uid(), id: uid(),
myPlugObj: { myPlugObj: {
"url": service === 'WEBSITE' ? "websites" : "qapps", "url": "myapp",
"domain": "core", "domain": "core",
"page": `qdn/browser/index.html${query}`, "page": `qdn/browser/index.html${query}`,
"title": name, "title": name,

View File

@ -506,21 +506,6 @@ mwc-checkbox::shadow .mdc-checkbox::after, mwc-checkbox::shadow .mdc-checkbox::b
<button class="emoji-button" ?disabled=${this.isLoading || this.isLoadingMessages}> <button class="emoji-button" ?disabled=${this.isLoading || this.isLoadingMessages}>
${html`<img class="emoji" draggable="false" alt="😀" src="/emoji/svg/1f600.svg" />`} ${html`<img class="emoji" draggable="false" alt="😀" src="/emoji/svg/1f600.svg" />`}
</button> </button>
${this.setOpenGifModal ?
html`
<button
class="emoji-button"
@click=${()=> {
if (!this.userName) {
parentEpml.request('showSnackBar', get("gifs.gchange26"));
return;
}
this.setOpenGifModal(true)
}}>
<span style="font-size: 30px" class="material-symbols-outlined">&#xe7a3;</span>
</button>
`
: ''}
${this.editedMessageObj ? ( ${this.editedMessageObj ? (
html` html`
<div style="margin-bottom: 10px"> <div style="margin-bottom: 10px">

View File

@ -19,6 +19,7 @@ export class TipUser extends LitElement {
errorMessage: { type: String }, errorMessage: { type: String },
successMessage: { type: String }, successMessage: { type: String },
setOpenTipUser: { attribute: false }, setOpenTipUser: { attribute: false },
qortPaymentFee: { type: Number }
} }
} }
@ -29,12 +30,14 @@ export class TipUser extends LitElement {
this.errorMessage = "" this.errorMessage = ""
this.successMessage = "" this.successMessage = ""
this.myAddress = window.parent.reduxStore.getState().app.selectedAddress this.myAddress = window.parent.reduxStore.getState().app.selectedAddress
this.qortPaymentFee = 0.01
} }
static styles = [tipUserStyles] static styles = [tipUserStyles]
async firstUpdated() { async firstUpdated() {
await this.fetchWalletDetails() await this.fetchWalletDetails()
this.paymentFee()
} }
updated(changedProperties) { updated(changedProperties) {
@ -55,6 +58,28 @@ export class TipUser extends LitElement {
return myRef return myRef
} }
async getSendQortFee() {
let sendFee = await parentEpml.request('apiCall', {
type: "api",
url: `/transactions/unitfee?txType=PAYMENT`
})
return (Number(sendFee) / 1e8).toFixed(8)
}
async paymentFee() {
const myNode = window.parent.reduxStore.getState().app.nodeConfig.knownNodes[window.parent.reduxStore.getState().app.nodeConfig.node]
const nodeUrl = myNode.protocol + '://' + myNode.domain + ':' + myNode.port
const url = `${nodeUrl}/transactions/unitfee?txType=PAYMENT`
await fetch(url).then((response) => {
if (response.ok) {
return response.json()
}
return Promise.reject(response)
}).then((json) => {
this.qortPaymentFee = (Number(json) / 1e8).toFixed(8)
})
}
renderSuccessText() { renderSuccessText() {
return html`${translate("chatpage.cchange55")}` return html`${translate("chatpage.cchange55")}`
} }
@ -79,7 +104,7 @@ export class TipUser extends LitElement {
this.sendMoneyLoading = true this.sendMoneyLoading = true
this.btnDisable = true this.btnDisable = true
if (parseFloat(amount) + parseFloat(0.001) > parseFloat(this.walletBalance)) { if (parseFloat(amount) + parseFloat(0.011) > parseFloat(this.walletBalance)) {
this.sendMoneyLoading = false this.sendMoneyLoading = false
this.btnDisable = false this.btnDisable = false
let snack1string = get("chatpage.cchange51") let snack1string = get("chatpage.cchange51")
@ -115,7 +140,7 @@ export class TipUser extends LitElement {
} else { } else {
myRes = myNameRes myRes = myNameRes
} }
return myRes; return myRes
} }
const validateAddress = async (receiverAddress) => { const validateAddress = async (receiverAddress) => {
@ -125,6 +150,7 @@ export class TipUser extends LitElement {
const validateReceiver = async (recipient) => { const validateReceiver = async (recipient) => {
let lastRef = await this.getLastRef() let lastRef = await this.getLastRef()
let theFee = await this.getSendQortFee()
let isAddress let isAddress
try { try {
@ -134,7 +160,13 @@ export class TipUser extends LitElement {
} }
if (isAddress) { if (isAddress) {
let myTransaction = await makeTransactionRequest(recipient, lastRef) let myTransaction = await makeTransactionRequest(recipient, lastRef, theFee)
getTxnRequestResponse(myTransaction)
} else {
let myNameRes = await validateName(recipient)
if (myNameRes !== false) {
let myNameAddress = myNameRes.owner
let myTransaction = await makeTransactionRequest(myNameAddress, lastRef, theFee)
getTxnRequestResponse(myTransaction) getTxnRequestResponse(myTransaction)
} else { } else {
let myNameRes = await validateName(recipient) let myNameRes = await validateName(recipient)
@ -220,6 +252,59 @@ export class TipUser extends LitElement {
validateReceiver(recipient) validateReceiver(recipient)
} }
const makeTransactionRequest = async (receiver, lastRef, theFee) => {
let myReceiver = receiver
let mylastRef = lastRef
let myFee = theFee
let dialogamount = get("transactions.amount")
let dialogAddress = get("login.address")
let dialogName = get("login.name")
let dialogto = get("transactions.to")
let recipientName = await getName(myReceiver)
let myTxnrequest = await parentEpml.request('transaction', {
type: 2,
nonce: this.myAddress.nonce,
params: {
recipient: myReceiver,
recipientName: recipientName,
amount: amount,
lastReference: mylastRef,
fee: myFee,
dialogamount: dialogamount,
dialogto: dialogto,
dialogAddress,
dialogName
},
})
return myTxnrequest
}
const getTxnRequestResponse = (txnResponse) => {
if (txnResponse.success === false && txnResponse.message) {
this.errorMessage = txnResponse.message
this.sendMoneyLoading = false
this.btnDisable = false
throw new Error(txnResponse)
} else if (txnResponse.success === true && !txnResponse.data.error) {
this.shadowRoot.getElementById('amountInput').value = ''
this.errorMessage = ''
this.successMessage = this.renderSuccessText()
this.sendMoneyLoading = false
this.btnDisable = false
setTimeout(() => {
this.setOpenTipUser(false)
this.successMessage = ""
}, 3000)
} else {
this.errorMessage = txnResponse.data.message
this.sendMoneyLoading = false
this.btnDisable = false
throw new Error(txnResponse)
}
}
validateReceiver(recipient)
}
render() { render() {
return html` return html`
<div class="tip-user-header"> <div class="tip-user-header">
@ -229,7 +314,7 @@ export class TipUser extends LitElement {
<div class="tip-user-body"> <div class="tip-user-body">
<p class="tip-available">${translate("chatpage.cchange47")}: ${this.walletBalance} QORT</p> <p class="tip-available">${translate("chatpage.cchange47")}: ${this.walletBalance} QORT</p>
<input id="amountInput" class="tip-input" type="number" placeholder="${translate("chatpage.cchange46")}" /> <input id="amountInput" class="tip-input" type="number" placeholder="${translate("chatpage.cchange46")}" />
<p class="tip-available">${translate("chatpage.cchange49")}: 0.001 QORT</p> <p class="tip-available">${translate("chatpage.cchange49")}: ${this.qortPaymentFee} QORT</p>
${this.sendMoneyLoading ? ${this.sendMoneyLoading ?
html` html`
<paper-progress indeterminate style="width: 100%; margin: 4px;"> <paper-progress indeterminate style="width: 100%; margin: 4px;">

View File

@ -51,3 +51,12 @@ export const SAVE_FILE = 'SAVE_FILE'
//SET_TAB_NOTIFICATIONS //SET_TAB_NOTIFICATIONS
export const SET_TAB_NOTIFICATIONS = 'SET_TAB_NOTIFICATIONS' export const SET_TAB_NOTIFICATIONS = 'SET_TAB_NOTIFICATIONS'
//OPEN_NEW_TAB
export const OPEN_NEW_TAB = 'OPEN_NEW_TAB'
//NOTIFICATIONS_PERMISSION
export const NOTIFICATIONS_PERMISSION = 'NOTIFICATIONS_PERMISSION'
//SEND_LOCAL_NOTIFICATION
export const SEND_LOCAL_NOTIFICATION = 'SEND_LOCAL_NOTIFICATION'

View File

@ -30,7 +30,6 @@ import '@vaadin/grid'
passiveSupport({ events: ['touchstart'] }) passiveSupport({ events: ['touchstart'] })
const parentEpml = new Epml({ type: 'WINDOW', source: window.parent }) const parentEpml = new Epml({ type: 'WINDOW', source: window.parent })
class Chat extends LitElement { class Chat extends LitElement {
static get properties() { static get properties() {
return { return {

View File

@ -3,6 +3,7 @@ import { render } from 'lit/html.js'
import { Epml } from '../../../../epml' import { Epml } from '../../../../epml'
import isElectron from 'is-electron' import isElectron from 'is-electron'
import { use, get, translate, translateUnsafeHTML, registerTranslateConfig } from 'lit-translate' import { use, get, translate, translateUnsafeHTML, registerTranslateConfig } from 'lit-translate'
import ShortUniqueId from 'short-unique-id';
registerTranslateConfig({ registerTranslateConfig({
loader: (lang) => fetch(`/language/${lang}.json`).then((res) => res.json()) loader: (lang) => fetch(`/language/${lang}.json`).then((res) => res.json())
@ -131,6 +132,7 @@ class WebBrowser extends LitElement {
constructor() { constructor() {
super(); super();
this.url = 'about:blank'; this.url = 'about:blank';
this.uid = new ShortUniqueId()
this.myAddress = window.parent.reduxStore.getState().app.selectedAddress this.myAddress = window.parent.reduxStore.getState().app.selectedAddress
this._publicKey = { key: '', hasPubKey: false } this._publicKey = { key: '', hasPubKey: false }
const urlParams = new URLSearchParams(window.location.search) const urlParams = new URLSearchParams(window.location.search)
@ -318,6 +320,44 @@ class WebBrowser extends LitElement {
} }
} }
async linkOpenNewTab(link) {
const value = link
let newQuery = value;
if (newQuery.endsWith('/')) {
newQuery = newQuery.slice(0, -1);
}
const res = await this.extractComponents(newQuery)
if (!res) return
const { service, name, identifier, path } = res
let query = `?service=${service}`
if (name) {
query = query + `&name=${name}`
}
if (identifier) {
query = query + `&identifier=${identifier}`
}
if (path) {
query = query + `&path=${path}`
}
window.parent.reduxStore.dispatch(window.parent.reduxAction.setNewTab({
url: `qdn/browser/index.html${query}`,
id: this.uid(),
myPlugObj: {
"url": service === 'WEBSITE' ? "websites" : "qapps",
"domain": "core",
"page": `qdn/browser/index.html${query}`,
"title": name,
"icon": service === 'WEBSITE' ? 'vaadin:desktop' : 'vaadin:external-browser',
"mwcicon": service === 'WEBSITE' ? 'desktop_mac' : 'open_in_browser',
"menus": [],
"parent": false
}
}))
}
render() { render() {
return html` return html`
@ -424,6 +464,23 @@ class WebBrowser extends LitElement {
const data = await response.json() const data = await response.json()
const joinFee = (Number(data) / 1e8).toFixed(8) const joinFee = (Number(data) / 1e8).toFixed(8)
return joinFee return joinFee
}
async getArbitraryFee (){
const timestamp = Date.now()
const myNode = window.parent.reduxStore.getState().app.nodeConfig.knownNodes[window.parent.reduxStore.getState().app.nodeConfig.node]
const nodeUrl = myNode.protocol + '://' + myNode.domain + ':' + myNode.port
const url = `${nodeUrl}/transactions/unitfee?txType=ARBITRARY&timestamp=${timestamp}`
const response = await fetch(url)
if (!response.ok) {
throw new Error('Error when fetching arbitrary fee');
}
const data = await response.json()
const arbitraryFee = (Number(data) / 1e8).toFixed(8)
return {
timestamp,
fee : Number(data),
feeToShow: arbitraryFee
}
} }
async sendQortFee() { async sendQortFee() {
const myNode = window.parent.reduxStore.getState().app.nodeConfig.knownNodes[window.parent.reduxStore.getState().app.nodeConfig.node] const myNode = window.parent.reduxStore.getState().app.nodeConfig.knownNodes[window.parent.reduxStore.getState().app.nodeConfig.node]
@ -492,7 +549,7 @@ class WebBrowser extends LitElement {
} }
async _deployAt(name, description, tags, creationBytes, amount, assetId, fee, atType) { async _deployAt(name, description, tags, creationBytes, amount, assetId, atType) {
const deployAtFee = await this.deployAtFee() const deployAtFee = await this.deployAtFee()
const getLastRef = async () => { const getLastRef = async () => {
let myRef = await parentEpml.request('apiCall', { let myRef = await parentEpml.request('apiCall', {
@ -510,13 +567,15 @@ class WebBrowser extends LitElement {
} }
const makeTransactionRequest = async (lastRef) => { const makeTransactionRequest = async (lastRef) => {
let groupdialog1 = get("transactions.groupdialog1") let deployAtdialog1 = get("transactions.deployAtdialog1")
let groupdialog2 = get("transactions.groupdialog2") let deployAtdialog2 = get("transactions.deployAtdialog2")
let deployAtdialog3 = get("transactions.deployAtdialog3")
let deployAtdialog4 = get("walletpage.wchange12")
let myTxnrequest = await parentEpml.request('transaction', { let myTxnrequest = await parentEpml.request('transaction', {
type: 16, type: 16,
nonce: this.selectedAddress.nonce, nonce: this.selectedAddress.nonce,
params: { params: {
fee: fee || deployAtFee, fee: deployAtFee,
rName: name, rName: name,
rDescription: description, rDescription: description,
rTags: tags, rTags: tags,
@ -525,8 +584,10 @@ class WebBrowser extends LitElement {
rCreationBytes: creationBytes, rCreationBytes: creationBytes,
atType: atType, atType: atType,
lastReference: lastRef, lastReference: lastRef,
atDeployDialog1: groupdialog1, atDeployDialog1: deployAtdialog1,
atDeployDialog2: groupdialog2 atDeployDialog2: deployAtdialog2,
atDeployDialog3: deployAtdialog3,
atDeployDialog4: deployAtdialog4
}, },
apiVersion: 2 apiVersion: 2
}) })
@ -975,6 +1036,7 @@ class WebBrowser extends LitElement {
const tag3 = data.tag3; const tag3 = data.tag3;
const tag4 = data.tag4; const tag4 = data.tag4;
const tag5 = data.tag5; const tag5 = data.tag5;
let feeAmount = null
if (data.identifier == null) { if (data.identifier == null) {
identifier = 'default'; identifier = 'default';
} }
@ -994,6 +1056,8 @@ class WebBrowser extends LitElement {
if (data.file) { if (data.file) {
data64 = await fileToBase64(data.file) data64 = await fileToBase64(data.file)
} }
const getArbitraryFee = await this.getArbitraryFee()
feeAmount = getArbitraryFee.fee
if (data.encrypt) { if (data.encrypt) {
try { try {
@ -1016,13 +1080,15 @@ class WebBrowser extends LitElement {
const res2 = await showModalAndWait( const res2 = await showModalAndWait(
actions.PUBLISH_QDN_RESOURCE, actions.PUBLISH_QDN_RESOURCE,
{ {
name, name,
identifier, identifier,
service, service,
encrypt: data.encrypt encrypt: data.encrypt,
feeAmount: getArbitraryFee.feeToShow
} }
); );
if (res2.action === 'accept') { if (res2.action === 'accept') {
@ -1052,7 +1118,8 @@ class WebBrowser extends LitElement {
tag4, tag4,
tag5, tag5,
apiVersion: 2, apiVersion: 2,
withFee: res2.userData.isWithFee === true ? true : false withFee: res2.userData.isWithFee === true ? true : false,
feeAmount: feeAmount
}); });
response = JSON.stringify(resPublish); response = JSON.stringify(resPublish);
@ -1080,7 +1147,7 @@ class WebBrowser extends LitElement {
case actions.PUBLISH_MULTIPLE_QDN_RESOURCES: { case actions.PUBLISH_MULTIPLE_QDN_RESOURCES: {
const requiredFields = ['resources']; const requiredFields = ['resources'];
const missingFields = []; const missingFields = [];
let feeAmount = null
requiredFields.forEach((field) => { requiredFields.forEach((field) => {
if (!data[field]) { if (!data[field]) {
missingFields.push(field); missingFields.push(field);
@ -1114,11 +1181,14 @@ class WebBrowser extends LitElement {
response = JSON.stringify(data); response = JSON.stringify(data);
break break
} }
const getArbitraryFee = await this.getArbitraryFee()
feeAmount = getArbitraryFee.fee
const res2 = await showModalAndWait( const res2 = await showModalAndWait(
actions.PUBLISH_MULTIPLE_QDN_RESOURCES, actions.PUBLISH_MULTIPLE_QDN_RESOURCES,
{ {
resources, resources,
encrypt: data.encrypt encrypt: data.encrypt,
feeAmount: getArbitraryFee.feeToShow
} }
); );
@ -1217,7 +1287,8 @@ class WebBrowser extends LitElement {
tag4, tag4,
tag5, tag5,
apiVersion: 2, apiVersion: 2,
withFee: res2.userData.isWithFee === true ? true : false withFee: res2.userData.isWithFee === true ? true : false,
feeAmount: feeAmount
}); });
worker.terminate(); worker.terminate();
@ -1253,8 +1324,95 @@ class WebBrowser extends LitElement {
// If they decline, send back JSON that includes an `error` key, such as `{"error": "User declined request"}` // If they decline, send back JSON that includes an `error` key, such as `{"error": "User declined request"}`
break; break;
} }
case actions.OPEN_NEW_TAB: {
if(!data.qortalLink){
const obj = {};
const errorMsg = 'Please enter a qortal link - qortal://...';
obj['error'] = errorMsg;
response = JSON.stringify(obj);
break
}
try {
await this.linkOpenNewTab(data.qortalLink)
response = true
break;
} catch (error) {
console.log('error', error)
const obj = {};
const errorMsg = "Invalid qortal link";
obj['error'] = errorMsg;
response = JSON.stringify(obj);
break;
}
}
case actions.NOTIFICATIONS_PERMISSION: {
try {
const res = await showModalAndWait(
actions.NOTIFICATIONS_PERMISSION,
{
name: this.name
}
);
if (res.action === 'accept'){
this.addAppToNotificationList(this.name)
response = true
break;
} else {
response = false
break;
}
} catch (error) {
break;
}
}
case actions.SEND_LOCAL_NOTIFICATION: {
const {title, url, icon, message} = data
try {
const id = `appNotificationList-${this.selectedAddress.address}`
const checkData = localStorage.getItem(id) ? JSON.parse(localStorage.getItem(id)) : null;
if(!checkData || !checkData[this.name]) throw new Error('App not on permission list')
const appInfo = checkData[this.name]
const lastNotification = appInfo.lastNotification
const interval = appInfo.interval
if (lastNotification && interval) {
const timeDifference = Date.now() - lastNotification;
if (timeDifference > interval) {
parentEpml.request('showNotification', {
title, type: "qapp-local-notification", sound: '', url, options: { body: message, icon, badge: icon }
})
response = true
this.updateLastNotification(id, this.name)
break;
} else {
throw new Error(`duration until another notification can be sent: ${interval - timeDifference}`)
}
} else if(!lastNotification){
parentEpml.request('showNotification', {
title, type: "qapp-local-notification", sound: '', url, options: { body: message, icon, badge: icon }
})
response = true
this.updateLastNotification(id)
break;
} else {
throw new Error(`invalid data`)
}
} catch (error) {
const obj = {};
const errorMsg = error.message || "error in pushing notification";
obj['error'] = errorMsg;
response = JSON.stringify(obj);
break;
}
}
case actions.SEND_CHAT_MESSAGE: { case actions.SEND_CHAT_MESSAGE: {
const message = data.message; const message = data.message;
const recipient = data.destinationAddress; const recipient = data.destinationAddress;
@ -1618,41 +1776,41 @@ class WebBrowser extends LitElement {
break; break;
} }
// case 'DEPLOY_AT': { case 'DEPLOY_AT': {
// const requiredFields = ['name', 'description', 'tags', 'creationBytes', 'amount', 'assetId', 'type']; const requiredFields = ['name', 'description', 'tags', 'creationBytes', 'amount', 'assetId', 'type'];
// const missingFields = []; const missingFields = [];
// requiredFields.forEach((field) => { requiredFields.forEach((field) => {
// if (!data[field]) { if (!data[field] && data[field] !== 0) {
// missingFields.push(field); missingFields.push(field);
// } }
// }); });
// if (missingFields.length > 0) { if (missingFields.length > 0) {
// const missingFieldsString = missingFields.join(', '); const missingFieldsString = missingFields.join(', ');
// const errorMsg = `Missing fields: ${missingFieldsString}` const errorMsg = `Missing fields: ${missingFieldsString}`
// let data = {}; let data = {};
// data['error'] = errorMsg; data['error'] = errorMsg;
// response = JSON.stringify(data); response = JSON.stringify(data);
// break break
// } }
// try { try {
// this.loader.show(); this.loader.show();
// const fee = data.fee || undefined
// const resJoinGroup = await this._deployAt(data.name, data.description, data.tags, data.creationBytes, data.amount, data.assetId, fee, data.type) const resDeployAt = await this._deployAt(data.name, data.description, data.tags, data.creationBytes, data.amount, data.assetId, data.type)
// response = JSON.stringify(resJoinGroup); response = JSON.stringify(resDeployAt);
// } catch (error) { } catch (error) {
// const obj = {}; const obj = {};
// const errorMsg = error.message || 'Failed to join the group.'; const errorMsg = error.message || 'Failed to join the group.';
// obj['error'] = errorMsg; obj['error'] = errorMsg;
// response = JSON.stringify(obj); response = JSON.stringify(obj);
// } finally { } finally {
// this.loader.hide(); this.loader.hide();
// } }
// break; break;
// } }
case actions.GET_WALLET_BALANCE: { case actions.GET_WALLET_BALANCE: {
@ -2707,6 +2865,46 @@ class WebBrowser extends LitElement {
use(checkLanguage); use(checkLanguage);
} }
} }
addAppToNotificationList(appName) {
if(!appName) throw new Error('unknown app name')
const id = `appNotificationList-${this.selectedAddress.address}`;
const checkData = localStorage.getItem(id) ? JSON.parse(localStorage.getItem(id)) : null;
if (!checkData) {
const newData = {
[appName]: {
interval: 900000, // 15mins in milliseconds
lastNotification: null,
},
};
localStorage.setItem(id, JSON.stringify(newData));
} else {
const copyData = { ...checkData };
copyData[appName] = {
interval: 900000, // 15mins in milliseconds
lastNotification: null,
};
localStorage.setItem(id, JSON.stringify(copyData));
}
}
updateLastNotification(id, appName) {
const checkData = localStorage.getItem(id) ? JSON.parse(localStorage.getItem(id)) : null;
if (checkData) {
const copyData = { ...checkData };
if (copyData[appName]) {
copyData[appName].lastNotification = Date.now(); // Make sure to use Date.now(), not date.now()
} else {
copyData[appName] = {
interval: 900000, // 15mins in milliseconds
lastNotification: Date.now(),
};
}
localStorage.setItem(id, JSON.stringify(copyData));
}
}
renderFollowUnfollowButton() { renderFollowUnfollowButton() {
// Only show the follow/unfollow button if we have permission to modify the list on this node // Only show the follow/unfollow button if we have permission to modify the list on this node
@ -3000,10 +3198,7 @@ async function showModalAndWait(type, data) {
`).join('')} `).join('')}
</table> </table>
<div class="checkbox-row"> <div class="checkbox-row">
<label for="isWithFee" id="isWithFeeLabel" style="color: var(--black);"> <p style="font-size: 16px;overflow-wrap: anywhere;" class="modal-paragraph">${get('browserpage.bchange47')} <span style="font-weight: bold">${data.resources.length * data.feeAmount} QORT fee</span></p>
${get('browserpage.bchange33')} ${data.resources.length * 0.001} QORT fee
</label>
<mwc-checkbox checked style="margin-right: -15px;" id="isWithFee"></mwc-checkbox>
</div> </div>
</div> </div>
` : ''} ` : ''}
@ -3016,10 +3211,7 @@ async function showModalAndWait(type, data) {
<p style="font-size: 16px;overflow-wrap: anywhere;" class="modal-paragraph"><span style="font-weight: bold">${get("browserpage.bchange32")}:</span> ${data.identifier}</p> <p style="font-size: 16px;overflow-wrap: anywhere;" class="modal-paragraph"><span style="font-weight: bold">${get("browserpage.bchange32")}:</span> ${data.identifier}</p>
<p style="font-size: 16px;overflow-wrap: anywhere;" class="modal-paragraph"><span style="font-weight: bold">${get("browserpage.bchange45")}:</span> ${data.encrypt ? true : false}</p> <p style="font-size: 16px;overflow-wrap: anywhere;" class="modal-paragraph"><span style="font-weight: bold">${get("browserpage.bchange45")}:</span> ${data.encrypt ? true : false}</p>
<div class="checkbox-row"> <div class="checkbox-row">
<label for="isWithFee" id="isWithFeeLabel" style="color: var(--black);"> <p style="font-size: 16px;overflow-wrap: anywhere;" class="modal-paragraph">${get('browserpage.bchange47')} <span style="font-weight: bold">${data.feeAmount} QORT fee</span></p>
${get('browserpage.bchange29')}
</label>
<mwc-checkbox checked style="margin-right: -15px;" id="isWithFee"></mwc-checkbox>
</div> </div>
</div> </div>
` : ''} ` : ''}
@ -3064,6 +3256,11 @@ async function showModalAndWait(type, data) {
<p class="modal-paragraph">${get("browserpage.bchange46")}: <span> ${data.filename}</span></p> <p class="modal-paragraph">${get("browserpage.bchange46")}: <span> ${data.filename}</span></p>
</div> </div>
` : ''} ` : ''}
${type === actions.NOTIFICATIONS_PERMISSION ? `
<div class="modal-subcontainer">
<p class="modal-paragraph">${get("browserpage.bchange48")}</p>
</div>
` : ''}
${type === actions.DELETE_LIST_ITEM ? ` ${type === actions.DELETE_LIST_ITEM ? `
<div class="modal-subcontainer"> <div class="modal-subcontainer">
@ -3091,7 +3288,8 @@ async function showModalAndWait(type, data) {
const userData = {}; const userData = {};
if (type === actions.PUBLISH_QDN_RESOURCE || type === actions.PUBLISH_MULTIPLE_QDN_RESOURCES) { if (type === actions.PUBLISH_QDN_RESOURCE || type === actions.PUBLISH_MULTIPLE_QDN_RESOURCES) {
const isWithFeeCheckbox = modal.querySelector('#isWithFee'); const isWithFeeCheckbox = modal.querySelector('#isWithFee');
userData.isWithFee = isWithFeeCheckbox.checked; // userData.isWithFee = isWithFeeCheckbox.checked;
userData.isWithFee = true
} }
if (modal.parentNode === document.body) { if (modal.parentNode === document.body) {
document.body.removeChild(modal); document.body.removeChild(modal);

View File

@ -14,6 +14,7 @@ import '@material/mwc-select'
import '@material/mwc-dialog' import '@material/mwc-dialog'
import '@material/mwc-list/mwc-list-item.js' import '@material/mwc-list/mwc-list-item.js'
import '@polymer/paper-progress/paper-progress.js' import '@polymer/paper-progress/paper-progress.js'
import { modalHelper } from '../../../utils/publish-modal'
const parentEpml = new Epml({ type: 'WINDOW', source: window.parent }) const parentEpml = new Epml({ type: 'WINDOW', source: window.parent })
@ -299,8 +300,10 @@ class PublishData extends LitElement {
<p style="color: green; word-break: break-word;">${this.successMessage}</p> <p style="color: green; word-break: break-word;">${this.successMessage}</p>
${this.loading ? html` <paper-progress indeterminate style="width:100%; margin:4px;"></paper-progress> ` : ''} ${this.loading ? html` <paper-progress indeterminate style="width:100%; margin:4px;"></paper-progress> ` : ''}
<div class="buttons"> <div class="buttons">
<mwc-button ?disabled=${this.btnDisable} style="width:49%;" raised icon="science" @click=${(e) => this.doPublish(e, true, false)}> ${translate("appspage.schange40")}</mwc-button> <mwc-button ?disabled=${this.btnDisable} style="width:49%;" raised icon="science" @click=${(e) => this.shadowRoot.querySelector('#publishWithFeeDialog').close()}> ${translate("appspage.schange40")}</mwc-button>
<mwc-button ?disabled=${this.btnDisable} style="width:49%;" raised icon="send" @click=${() => this.shadowRoot.querySelector('#publishWithFeeDialog').show()}> ${translate("publishpage.pchange11")}</mwc-button> <mwc-button ?disabled=${this.btnDisable} style="width:49%;" raised icon="send" @click=${(e) => {
this.doPublish(e, false, true)
}}> ${translate("publishpage.pchange11")}</mwc-button>
</div> </div>
</div> </div>
</div> </div>
@ -418,7 +421,7 @@ class PublishData extends LitElement {
this.shadowRoot.querySelector('#publishWithFeeDialog').close() this.shadowRoot.querySelector('#publishWithFeeDialog').close()
} }
doPublish(e, preview, fee) { async doPublish(e, preview, fee) {
let registeredName = this.shadowRoot.getElementById('registeredName').value let registeredName = this.shadowRoot.getElementById('registeredName').value
let service = this.shadowRoot.getElementById('service').value let service = this.shadowRoot.getElementById('service').value
let identifier = this.shadowRoot.getElementById('identifier').value let identifier = this.shadowRoot.getElementById('identifier').value
@ -464,7 +467,22 @@ class PublishData extends LitElement {
parentEpml.request('showSnackBar', `${err5string}`) parentEpml.request('showSnackBar', `${err5string}`)
} }
else { else {
try {
if(!preview){
const arbitraryFeeData = await modalHelper.getArbitraryFee()
const res = await modalHelper.showModalAndWaitPublish(
{
feeAmount: arbitraryFeeData.feeToShow
}
);
if (res.action !== 'accept') throw new Error('User declined publish')
}
this.publishData(registeredName, path, file, service, identifier, preview, fee) this.publishData(registeredName, path, file, service, identifier, preview, fee)
} catch (error) {
this.shadowRoot.querySelector('#publishWithFeeDialog').close()
}
} }
} }
@ -488,6 +506,17 @@ class PublishData extends LitElement {
this.successMessage = '' this.successMessage = ''
console.error(errorMessage) console.error(errorMessage)
} }
const getArbitraryFee = async () => {
const timestamp = Date.now()
let fee = await parentEpml.request('apiCall', {
url: `/transactions/unitfee?txType=ARBITRARY&timestamp=${timestamp}`
})
return {
timestamp,
fee : Number(fee),
feeToShow: (Number(fee) / 1e8).toFixed(8)
}
}
const validate = async () => { const validate = async () => {
let validNameRes = await validateName(registeredName) let validNameRes = await validateName(registeredName)
@ -501,8 +530,17 @@ class PublishData extends LitElement {
this.generalMessage = `${err6string}` this.generalMessage = `${err6string}`
let transactionBytes let transactionBytes
let previewUrlPath let previewUrlPath
let feeAmount = null
let uploadDataRes = await uploadData(registeredName, path, file, preview, fee) if(fee){
const res = await getArbitraryFee()
if(res.fee){
feeAmount= res.fee
} else {
throw new Error('unable to get fee')
}
}
let uploadDataRes = await uploadData(registeredName, path, file, preview, fee, feeAmount)
if (uploadDataRes.error) { if (uploadDataRes.error) {
let err7string = get("publishpage.pchange20") let err7string = get("publishpage.pchange20")
@ -531,12 +569,13 @@ class PublishData extends LitElement {
if (fee) { if (fee) {
let err9string = get("publishpage.pchange26") let err9string = get("publishpage.pchange26")
this.generalMessage = `${err9string}` this.generalMessage = `${err9string}`
} else { } else {
let err9string = get("publishpage.pchange22") let err9string = get("publishpage.pchange22")
this.generalMessage = `${err9string}` this.generalMessage = `${err9string}`
} }
let signAndProcessRes = await signAndProcess(transactionBytes, fee) let signAndProcessRes = await signAndProcess(transactionBytes, fee, feeAmount)
if (signAndProcessRes.error) { if (signAndProcessRes.error) {
let err10string = get("publishpage.pchange20") let err10string = get("publishpage.pchange20")
@ -554,7 +593,9 @@ class PublishData extends LitElement {
this.successMessage = `${err11string}` this.successMessage = `${err11string}`
} }
const uploadData = async (registeredName, path, file, preview, fee) => {
const uploadData = async (registeredName, path, file, preview, fee, feeAmount) => {
let postBody = path let postBody = path
let urlSuffix = "" let urlSuffix = ""
if (file != null) { if (file != null) {
@ -592,9 +633,9 @@ class PublishData extends LitElement {
uploadDataUrl = `/arbitrary/${service}/${registeredName}/${this.identifier}${urlSuffix}?${metadataQueryString}&apiKey=${this.getApiKey()}&preview=${new Boolean(preview).toString()}` uploadDataUrl = `/arbitrary/${service}/${registeredName}/${this.identifier}${urlSuffix}?${metadataQueryString}&apiKey=${this.getApiKey()}&preview=${new Boolean(preview).toString()}`
} }
} else if (fee) { } else if (fee) {
uploadDataUrl = `/arbitrary/${this.service}/${registeredName}${urlSuffix}?${metadataQueryString}&fee=100000&apiKey=${this.getApiKey()}` uploadDataUrl = `/arbitrary/${this.service}/${registeredName}${urlSuffix}?${metadataQueryString}&fee=${feeAmount}&apiKey=${this.getApiKey()}`
if (identifier != null && identifier.trim().length > 0) { if (identifier != null && identifier.trim().length > 0) {
uploadDataUrl = `/arbitrary/${service}/${registeredName}/${this.identifier}${urlSuffix}?${metadataQueryString}&fee=100000&apiKey=${this.getApiKey()}` uploadDataUrl = `/arbitrary/${service}/${registeredName}/${this.identifier}${urlSuffix}?${metadataQueryString}&fee=${feeAmount}&apiKey=${this.getApiKey()}`
} }
} else { } else {
uploadDataUrl = `/arbitrary/${this.service}/${registeredName}${urlSuffix}?${metadataQueryString}&apiKey=${this.getApiKey()}` uploadDataUrl = `/arbitrary/${this.service}/${registeredName}${urlSuffix}?${metadataQueryString}&apiKey=${this.getApiKey()}`

View File

@ -2104,6 +2104,7 @@ class TradeBotPortal extends LitElement {
this.changeTheme() this.changeTheme()
this.changeLanguage() this.changeLanguage()
this.tradeFee()
this.autoHelperMessage = this.renderAutoHelperPass() this.autoHelperMessage = this.renderAutoHelperPass()
@ -3736,6 +3737,20 @@ class TradeBotPortal extends LitElement {
} }
} }
async tradeFee() {
const myNode = window.parent.reduxStore.getState().app.nodeConfig.knownNodes[window.parent.reduxStore.getState().app.nodeConfig.node]
const nodeUrl = myNode.protocol + '://' + myNode.domain + ':' + myNode.port
const url = `${nodeUrl}/transactions/unitfee?txType=DEPLOY_AT`
await fetch(url).then((response) => {
if (response.ok) {
return response.json()
}
return Promise.reject(response)
}).then((json) => {
this.listedCoins.get("QORTAL").tradeFee = (Number(json) + 100000) / 1e8
})
}
getApiKey() { getApiKey() {
const myNode = window.parent.reduxStore.getState().app.nodeConfig.knownNodes[window.parent.reduxStore.getState().app.nodeConfig.node]; const myNode = window.parent.reduxStore.getState().app.nodeConfig.knownNodes[window.parent.reduxStore.getState().app.nodeConfig.node];
let apiKey = myNode.apiKey; let apiKey = myNode.apiKey;

View File

@ -708,7 +708,7 @@ class TradePortal extends LitElement {
balance: "0", balance: "0",
coinCode: "QORT", coinCode: "QORT",
coinAmount: this.amountString, coinAmount: this.amountString,
tradeFee: "0.002" tradeFee: "0.02"
} }
let bitcoin = { let bitcoin = {
@ -1421,6 +1421,7 @@ class TradePortal extends LitElement {
this.changeTheme() this.changeTheme()
this.changeLanguage() this.changeLanguage()
this.tradeFee()
this.tradeHelperMessage = this.renderTradeHelperPass() this.tradeHelperMessage = this.renderTradeHelperPass()
@ -2698,6 +2699,7 @@ class TradePortal extends LitElement {
async sellAction() { async sellAction() {
this.isSellLoading = true this.isSellLoading = true
this.sellBtnDisable = true this.sellBtnDisable = true
await this.tradeFee()
const sellAmountInput = this.shadowRoot.getElementById('sellAmountInput').value const sellAmountInput = this.shadowRoot.getElementById('sellAmountInput').value
const sellTotalInput = this.shadowRoot.getElementById('sellTotalInput').value const sellTotalInput = this.shadowRoot.getElementById('sellTotalInput').value
const fundingQortAmount = this.round(parseFloat(sellAmountInput) + 0.001) const fundingQortAmount = this.round(parseFloat(sellAmountInput) + 0.001)
@ -2732,7 +2734,7 @@ class TradePortal extends LitElement {
fundingQortAmount: parseFloat(fundingQortAmount), fundingQortAmount: parseFloat(fundingQortAmount),
foreignBlockchain: this.selectedCoin, foreignBlockchain: this.selectedCoin,
foreignAmount: parseFloat(sellTotalInput), foreignAmount: parseFloat(sellTotalInput),
tradeTimeout: 60, tradeTimeout: 120,
receivingAddress: _receivingAddress, receivingAddress: _receivingAddress,
}) })
return response return response
@ -2758,7 +2760,7 @@ class TradePortal extends LitElement {
} }
} }
if (this.round(parseFloat(fundingQortAmount) + parseFloat(0.002)) > parseFloat(this.listedCoins.get("QORTAL").balance)) { if (this.round(parseFloat(fundingQortAmount) + parseFloat(this.listedCoins.get("QORTAL").tradeFee)) > parseFloat(this.listedCoins.get("QORTAL").balance)) {
this.isSellLoading = false this.isSellLoading = false
this.sellBtnDisable = false this.sellBtnDisable = false
let snack4string = get("tradepage.tchange22") let snack4string = get("tradepage.tchange22")
@ -3021,6 +3023,21 @@ class TradePortal extends LitElement {
} }
} }
async tradeFee() {
const myNode = window.parent.reduxStore.getState().app.nodeConfig.knownNodes[window.parent.reduxStore.getState().app.nodeConfig.node]
const nodeUrl = myNode.protocol + '://' + myNode.domain + ':' + myNode.port
const url = `${nodeUrl}/transactions/unitfee?txType=DEPLOY_AT`
await fetch(url).then((response) => {
if (response.ok) {
return response.json()
}
return Promise.reject(response)
}).then((json) => {
this.listedCoins.get("QORTAL").tradeFee = (Number(json) * 2) / 1e8
})
}
getApiKey() { getApiKey() {
const myNode = window.parent.reduxStore.getState().app.nodeConfig.knownNodes[window.parent.reduxStore.getState().app.nodeConfig.node]; const myNode = window.parent.reduxStore.getState().app.nodeConfig.knownNodes[window.parent.reduxStore.getState().app.nodeConfig.node];
let apiKey = myNode.apiKey; let apiKey = myNode.apiKey;

View File

@ -75,6 +75,7 @@ class MultiWallet extends LitElement {
isValidAmount: { type: Boolean }, isValidAmount: { type: Boolean },
balance: { type: Number }, balance: { type: Number },
balanceString: { type: String }, balanceString: { type: String },
qortPaymentFee: { type: Number },
btcFeePerByte: { type: Number }, btcFeePerByte: { type: Number },
ltcFeePerByte: { type: Number }, ltcFeePerByte: { type: Number },
dogeFeePerByte: { type: Number }, dogeFeePerByte: { type: Number },
@ -795,6 +796,7 @@ class MultiWallet extends LitElement {
this.dgbAmount = 0 this.dgbAmount = 0
this.rvnAmount = 0 this.rvnAmount = 0
this.arrrAmount = 0 this.arrrAmount = 0
this.qortPaymentFee = 0.001
this.btcFeePerByte = 100 this.btcFeePerByte = 100
this.btcSatMinFee = 20 this.btcSatMinFee = 20
this.btcSatMaxFee = 150 this.btcSatMaxFee = 150
@ -1352,7 +1354,7 @@ class MultiWallet extends LitElement {
</mwc-textfield> </mwc-textfield>
</p> </p>
<div style="margin-bottom: 10px;"> <div style="margin-bottom: 10px;">
<p style="margin-bottom: 0;">${translate("walletpage.wchange21")} <span style="font-weight: bold;">0.001 QORT<span></p> <p style="margin-bottom: 0;">${translate("walletpage.wchange21")} <span style="font-weight: bold;">${this.qortPaymentFee} QORT<span></p>
</div> </div>
${this.renderClearSuccess()} ${this.renderClearSuccess()}
${this.renderClearError()} ${this.renderClearError()}
@ -2821,6 +2823,7 @@ class MultiWallet extends LitElement {
firstUpdated() { firstUpdated() {
this.changeTheme() this.changeTheme()
this.changeLanguage() this.changeLanguage()
this.paymentFee()
this.bookQortalAddress = window.parent.reduxStore.getState().app.selectedAddress.address this.bookQortalAddress = window.parent.reduxStore.getState().app.selectedAddress.address
this.bookBitcoinAddress = window.parent.reduxStore.getState().app.selectedAddress.btcWallet.address this.bookBitcoinAddress = window.parent.reduxStore.getState().app.selectedAddress.btcWallet.address
@ -2906,6 +2909,23 @@ class MultiWallet extends LitElement {
setInterval(() => { setInterval(() => {
this.clearConsole() this.clearConsole()
}, 60000) }, 60000)
setInterval(() => {
this.paymentFee()
}, 600000)
}
async paymentFee() {
const myNode = window.parent.reduxStore.getState().app.nodeConfig.knownNodes[window.parent.reduxStore.getState().app.nodeConfig.node]
const nodeUrl = myNode.protocol + '://' + myNode.domain + ':' + myNode.port
const url = `${nodeUrl}/transactions/unitfee?txType=PAYMENT`
await fetch(url).then((response) => {
if (response.ok) {
return response.json()
}
return Promise.reject(response)
}).then((json) => {
this.qortPaymentFee = (Number(json) / 1e8).toFixed(8)
})
} }
clearConsole() { clearConsole() {
@ -4048,11 +4068,11 @@ class MultiWallet extends LitElement {
calculateQortAll() { calculateQortAll() {
this.amount = 0 this.amount = 0
this.shadowRoot.getElementById('amountInput').value = this.amount this.shadowRoot.getElementById('amountInput').value = this.amount
if (this.balance < 0.00110000) { if (this.balance < 0.01100000) {
let not_enough_string = get("walletpage.wchange26") let not_enough_string = get("walletpage.wchange26")
parentEpml.request('showSnackBar', `${not_enough_string}`) parentEpml.request('showSnackBar', `${not_enough_string}`)
} else { } else {
this.amount = (this.balance - 0.00110000).toFixed(8) this.amount = (this.balance - 0.01100000).toFixed(8)
this.shadowRoot.getElementById('amountInput').value = this.amount this.shadowRoot.getElementById('amountInput').value = this.amount
this.shadowRoot.getElementById('amountInput').blur() this.shadowRoot.getElementById('amountInput').blur()
this.shadowRoot.getElementById('amountInput').focus() this.shadowRoot.getElementById('amountInput').focus()
@ -4238,7 +4258,7 @@ class MultiWallet extends LitElement {
} else { } else {
const checkQortAmountInput = this.shadowRoot.getElementById('amountInput').value const checkQortAmountInput = this.shadowRoot.getElementById('amountInput').value
const checkQortAmount = this.round(parseFloat(checkQortAmountInput)) const checkQortAmount = this.round(parseFloat(checkQortAmountInput))
const myFunds = this.round(parseFloat(this.balance - 0.00110000)) const myFunds = this.round(parseFloat(this.balance - 0.01100000))
if (Number(myFunds) >= Number(checkQortAmount)) { if (Number(myFunds) >= Number(checkQortAmount)) {
this.shadowRoot.getElementById('amountInput').value = checkQortAmountInput this.shadowRoot.getElementById('amountInput').value = checkQortAmountInput
this.btnDisable = false this.btnDisable = false
@ -4269,7 +4289,7 @@ class MultiWallet extends LitElement {
} else { } else {
const checkQortAmountInput = this.shadowRoot.getElementById('amountInput').value const checkQortAmountInput = this.shadowRoot.getElementById('amountInput').value
const checkQortAmount = this.round(parseFloat(checkQortAmountInput)) const checkQortAmount = this.round(parseFloat(checkQortAmountInput))
const myFunds = this.round(parseFloat(this.balance - 0.00110000)) const myFunds = this.round(parseFloat(this.balance - 0.01100000))
if (Number(myFunds) >= Number(checkQortAmount)) { if (Number(myFunds) >= Number(checkQortAmount)) {
this.shadowRoot.getElementById('amountInput').value = checkQortAmountInput this.shadowRoot.getElementById('amountInput').value = checkQortAmountInput
this.btnDisable = false this.btnDisable = false
@ -4287,7 +4307,7 @@ class MultiWallet extends LitElement {
} else { } else {
const checkQortAmountInput = this.shadowRoot.getElementById('amountInput').value const checkQortAmountInput = this.shadowRoot.getElementById('amountInput').value
const checkQortAmount = this.round(parseFloat(checkQortAmountInput)) const checkQortAmount = this.round(parseFloat(checkQortAmountInput))
const myFunds = this.round(parseFloat(this.balance - 0.00110000)) const myFunds = this.round(parseFloat(this.balance - 0.01100000))
if (Number(myFunds) >= Number(checkQortAmount)) { if (Number(myFunds) >= Number(checkQortAmount)) {
this.shadowRoot.getElementById('amountInput').value = checkQortAmountInput this.shadowRoot.getElementById('amountInput').value = checkQortAmountInput
this.btnDisable = false this.btnDisable = false
@ -4302,6 +4322,7 @@ class MultiWallet extends LitElement {
} }
async sendQort() { async sendQort() {
const sendFee = this.qortPaymentFee
const amount = this.shadowRoot.getElementById('amountInput').value const amount = this.shadowRoot.getElementById('amountInput').value
let recipient = this.shadowRoot.getElementById('recipient').value let recipient = this.shadowRoot.getElementById('recipient').value
@ -4390,7 +4411,6 @@ class MultiWallet extends LitElement {
const getName = async (recipient)=> { const getName = async (recipient)=> {
try { try {
const getNames = await parentEpml.request("apiCall", { const getNames = await parentEpml.request("apiCall", {
type: "api", type: "api",
url: `/names/address/${recipient}`, url: `/names/address/${recipient}`,
@ -4421,7 +4441,7 @@ class MultiWallet extends LitElement {
recipientName: recipientName, recipientName: recipientName,
amount: amount, amount: amount,
lastReference: mylastRef, lastReference: mylastRef,
fee: 0.001, fee: sendFee,
dialogamount: dialogamount, dialogamount: dialogamount,
dialogto: dialogto, dialogto: dialogto,
dialogAddress, dialogAddress,

View File

@ -28,7 +28,8 @@ export const publishData = async ({
tag2, tag2,
tag3, tag3,
tag4, tag4,
tag5 tag5,
feeAmount
}) => { }) => {
const validateName = async (receiverName) => { const validateName = async (receiverName) => {
let nameRes = await parentEpml.request("apiCall", { let nameRes = await parentEpml.request("apiCall", {
@ -48,6 +49,17 @@ export const publishData = async ({
}) })
return convertedBytes return convertedBytes
} }
const getArbitraryFee = async () => {
const timestamp = Date.now()
let fee = await parentEpml.request('apiCall', {
url: `/transactions/unitfee?txType=ARBITRARY&timestamp=${timestamp}`
})
return {
timestamp,
fee : Number(fee),
feeToShow: (Number(fee) / 1e8).toFixed(8)
}
}
const signAndProcess = async (transactionBytesBase58) => { const signAndProcess = async (transactionBytesBase58) => {
let convertedBytesBase58 = await convertBytesForSigning( let convertedBytesBase58 = await convertBytesForSigning(
@ -125,7 +137,18 @@ export const publishData = async ({
if (validNameRes.error) { if (validNameRes.error) {
throw new Error('Name not found'); throw new Error('Name not found');
} }
let transactionBytes = await uploadData(registeredName, path, file) let fee = null
if(withFee && feeAmount){
fee= feeAmount
} else if(withFee){
const res = await getArbitraryFee()
if(res.fee){
fee= res.fee
} else {
throw new Error('unable to get fee')
}
}
let transactionBytes = await uploadData(registeredName, path, file, fee)
if (transactionBytes.error) { if (transactionBytes.error) {
throw new Error(transactionBytes.message || 'Error when uploading'); throw new Error(transactionBytes.message || 'Error when uploading');
} else if ( } else if (
@ -149,7 +172,7 @@ export const publishData = async ({
return signAndProcessRes return signAndProcessRes
} }
const uploadData = async (registeredName, path, file) => { const uploadData = async (registeredName, path, file, fee) => {
if (identifier != null && identifier.trim().length > 0) { if (identifier != null && identifier.trim().length > 0) {
let postBody = path let postBody = path
let urlSuffix = "" let urlSuffix = ""
@ -181,7 +204,7 @@ export const publishData = async ({
} }
if(withFee){ if(withFee){
uploadDataUrl = uploadDataUrl + '&fee=100000' uploadDataUrl = uploadDataUrl + `&fee=${fee}`
} }
if(filename != null && filename != "undefined"){ if(filename != null && filename != "undefined"){

View File

@ -0,0 +1,147 @@
.backdrop {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgb(186 186 186 / 26%);
overflow: hidden;
animation: backdrop_blur cubic-bezier(0.22, 1, 0.36, 1) 1s forwards;
z-index: 1000000;
}
@keyframes backdrop_blur {
0% {
backdrop-filter: blur(0px);
background: transparent;
}
100% {
backdrop-filter: blur(5px);
background: rgb(186 186 186 / 26%);
}
}
@keyframes modal_transition {
0% {
visibility: hidden;
opacity: 0;
}
100% {
visibility: visible;
opacity: 1;
}
}
.modal {
position: relative;
display: flex;
justify-content: center;
align-items: center;
width: 100%;
height: 100%;
animation: 1s cubic-bezier(0.22, 1, 0.36, 1) 0s 1 normal forwards running modal_transition;
z-index: 1000001;
}
@keyframes modal_transition {
0% {
visibility: hidden;
opacity: 0;
}
100% {
visibility: visible;
opacity: 1;
}
}
.modal-content {
background-color: var(--white);
border-radius: 10px;
padding: 20px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.3);
max-width: 80%;
min-width: 300px;
display: flex;
flex-direction: column;
justify-content: space-between;
}
.modal-body {
padding: 25px;
}
.modal-subcontainer {
color: var(--black);
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 15px;
}
.modal-subcontainer-error {
color: var(--black);
display: flex;
flex-direction: column;
align-items: center;
gap: 15px;
}
.modal-paragraph-error {
font-family: Roboto, sans-serif;
font-size: 20px;
letter-spacing: 0.3px;
font-weight: 700;
color: var(--black);
margin: 0;
}
.modal-paragraph {
font-family: Roboto, sans-serif;
font-size: 18px;
letter-spacing: 0.3px;
font-weight: 300;
color: var(--black);
margin: 0;
word-wrap: break-word;
overflow-wrap: break-word;
}
.capitalize-first {
text-transform: capitalize;
}
.checkbox-row {
display: flex;
align-items: center;
font-family: Montserrat, sans-serif;
font-weight: 600;
color: var(--black);
}
.modal-buttons {
display: flex;
justify-content: space-between;
margin-top: 20px;
}
.modal-buttons button {
background-color: #4caf50;
border: none;
color: #fff;
padding: 10px 20px;
border-radius: 5px;
cursor: pointer;
transition: background-color 0.2s;
}
.modal-buttons button:hover {
background-color: #3e8e41;
}
#cancel-button {
background-color: #f44336;
}
#cancel-button:hover {
background-color: #d32f2f;
}

View File

@ -0,0 +1,270 @@
import { get } from 'lit-translate';
export class ModalHelper {
constructor() {
this.initializeStyles();
}
async getArbitraryFee() {
const timestamp = Date.now();
const myNode = window.parent.reduxStore.getState().app.nodeConfig.knownNodes[window.parent.reduxStore.getState().app.nodeConfig.node];
const nodeUrl = `${myNode.protocol}://${myNode.domain}:${myNode.port}`;
const url = `${nodeUrl}/transactions/unitfee?txType=ARBITRARY&timestamp=${timestamp}`;
const response = await fetch(url);
if (!response.ok) {
throw new Error('Error when fetching arbitrary fee');
}
const data = await response.json();
const arbitraryFee = (Number(data) / 1e8).toFixed(8);
return {
timestamp,
fee: Number(data),
feeToShow: arbitraryFee
};
}
async showModalAndWaitPublish(data) {
return new Promise((resolve) => {
const modal = this.createModal(data);
document.body.appendChild(modal);
this.addModalEventListeners(modal, resolve);
});
}
createModal(data) {
const modal = document.createElement('div');
modal.id = "backdrop";
modal.classList.add("backdrop");
modal.innerHTML = `
<div class="modal my-modal-class">
<div class="modal-content">
<div class="modal-body">
<div class="modal-subcontainer">
<div class="checkbox-row">
<p style="font-size: 16px;overflow-wrap: anywhere;" class="modal-paragraph">${get('browserpage.bchange47')} <span style="font-weight: bold">${data.feeAmount} QORT fee</span></p>
</div>
</div>
</div>
<div class="modal-buttons">
<button id="cancel-button">${get("browserpage.bchange27")}</button>
<button id="ok-button">${get("browserpage.bchange28")}</button>
</div>
</div>
</div>
`;
return modal;
}
addModalEventListeners(modal, resolve) {
// Event listener for the 'OK' button
const okButton = modal.querySelector('#ok-button');
okButton.addEventListener('click', () => {
const userData = { isWithFee: true };
if (modal.parentNode === document.body) {
document.body.removeChild(modal);
}
resolve({ action: 'accept', userData });
});
// Prevent modal content from closing the modal
const modalContent = modal.querySelector('.modal-content');
modalContent.addEventListener('click', e => {
e.stopPropagation();
});
// Event listeners for backdrop and 'Cancel' button
const backdropClick = document.getElementById('backdrop');
backdropClick.addEventListener('click', () => {
if (modal.parentNode === document.body) {
document.body.removeChild(modal);
}
resolve({ action: 'reject' });
});
const cancelButton = modal.querySelector('#cancel-button');
cancelButton.addEventListener('click', () => {
if (modal.parentNode === document.body) {
document.body.removeChild(modal);
}
resolve({ action: 'reject' });
});
}
initializeStyles() {
const styles = `
* {
--mdc-theme-primary: rgb(3, 169, 244);
--mdc-theme-secondary: var(--mdc-theme-primary);
--paper-input-container-focus-color: var(--mdc-theme-primary);
--mdc-checkbox-unchecked-color: var(--black);
--mdc-theme-on-surface: var(--black);
--mdc-checkbox-disabled-color: var(--black);
--mdc-checkbox-ink-color: var(--black);
}
.backdrop {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgb(186 186 186 / 26%);
overflow: hidden;
animation: backdrop_blur cubic-bezier(0.22, 1, 0.36, 1) 0.1s forwards;
z-index: 1000000;
}
@keyframes backdrop_blur {
0% {
backdrop-filter: blur(0px);
background: transparent;
}
100% {
backdrop-filter: blur(5px);
background: rgb(186 186 186 / 26%);
}
}
@keyframes modal_transition {
0% {
visibility: hidden;
opacity: 0;
}
100% {
visibility: visible;
opacity: 1;
}
}
.modal {
position: relative;
display: flex;
justify-content: center;
align-items: center;
width: 100%;
height: 100%;
animation: 0.1s cubic-bezier(0.22, 1, 0.36, 1) 0s 1 normal forwards running modal_transition;
z-index: 1000001;
}
@keyframes modal_transition {
0% {
visibility: hidden;
opacity: 0;
}
100% {
visibility: visible;
opacity: 1;
}
}
.modal-content {
background-color: var(--white);
border-radius: 10px;
padding: 20px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.3);
max-width: 80%;
min-width: 300px;
display: flex;
flex-direction: column;
justify-content: space-between;
}
.modal-body {
padding: 25px;
}
.modal-subcontainer {
color: var(--black);
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 15px;
}
.modal-subcontainer-error {
color: var(--black);
display: flex;
flex-direction: column;
align-items: center;
gap: 15px;
}
.modal-paragraph-error {
font-family: Roboto, sans-serif;
font-size: 20px;
letter-spacing: 0.3px;
font-weight: 700;
color: var(--black);
margin: 0;
}
.modal-paragraph {
font-family: Roboto, sans-serif;
font-size: 18px;
letter-spacing: 0.3px;
font-weight: 300;
color: var(--black);
margin: 0;
word-wrap: break-word;
overflow-wrap: break-word;
}
.capitalize-first {
text-transform: capitalize;
}
.checkbox-row {
display: flex;
align-items: center;
font-family: Montserrat, sans-serif;
font-weight: 600;
color: var(--black);
}
.modal-buttons {
display: flex;
justify-content: space-between;
margin-top: 20px;
}
.modal-buttons button {
background-color: #4caf50;
border: none;
color: #fff;
padding: 10px 20px;
border-radius: 5px;
cursor: pointer;
transition: background-color 0.2s;
}
.modal-buttons button:hover {
background-color: #3e8e41;
}
#cancel-button {
background-color: #f44336;
}
#cancel-button:hover {
background-color: #d32f2f;
}
`;
const styleSheet = new CSSStyleSheet();
styleSheet.replaceSync(styles);
document.adoptedStyleSheets = [styleSheet];
}
static getInstance() {
if (!ModalHelper.instance) {
ModalHelper.instance = new ModalHelper();
}
return ModalHelper.instance;
}
}
export const modalHelper = ModalHelper.getInstance();