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

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

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -101,7 +101,7 @@ function processText(input) {
// Store the URL in a data attribute
link.setAttribute('data-url', 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.cursor = 'pointer'
@@ -125,7 +125,7 @@ function processText(input) {
url: `qdn/browser/index.html${query}`,
id: uid(),
myPlugObj: {
"url": service === 'WEBSITE' ? "websites" : "qapps",
"url": "myapp",
"domain": "core",
"page": `qdn/browser/index.html${query}`,
"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}>
${html`<img class="emoji" draggable="false" alt="😀" src="/emoji/svg/1f600.svg" />`}
</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 ? (
html`
<div style="margin-bottom: 10px">

View File

@@ -19,6 +19,7 @@ export class TipUser extends LitElement {
errorMessage: { type: String },
successMessage: { type: String },
setOpenTipUser: { attribute: false },
qortPaymentFee: { type: Number }
}
}
@@ -29,12 +30,14 @@ export class TipUser extends LitElement {
this.errorMessage = ""
this.successMessage = ""
this.myAddress = window.parent.reduxStore.getState().app.selectedAddress
this.qortPaymentFee = 0.01
}
static styles = [tipUserStyles]
async firstUpdated() {
await this.fetchWalletDetails()
this.paymentFee()
}
updated(changedProperties) {
@@ -55,6 +58,28 @@ export class TipUser extends LitElement {
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() {
return html`${translate("chatpage.cchange55")}`
}
@@ -79,62 +104,69 @@ export class TipUser extends LitElement {
this.sendMoneyLoading = true
this.btnDisable = true
if (parseFloat(amount) + parseFloat(0.001) > parseFloat(this.walletBalance)) {
this.sendMoneyLoading = false
this.btnDisable = false
let snack1string = get("chatpage.cchange51")
parentEpml.request('showSnackBar', `${snack1string}`)
return false
if (parseFloat(amount) + parseFloat(0.011) > parseFloat(this.walletBalance)) {
this.sendMoneyLoading = false
this.btnDisable = false
let snack1string = get("chatpage.cchange51")
parentEpml.request('showSnackBar', `${snack1string}`)
return false
}
if (parseFloat(amount) <= 0) {
this.sendMoneyLoading = false
this.btnDisable = false
let snack2string = get("chatpage.cchange52")
parentEpml.request('showSnackBar', `${snack2string}`)
return false
}
if (recipient.length === 0) {
this.sendMoneyLoading = false
this.btnDisable = false
let snack3string = get("chatpage.cchange53")
parentEpml.request('showSnackBar', `${snack3string}`)
return false
}
const validateName = async (receiverName) => {
let myRes
let myNameRes = await parentEpml.request('apiCall', {
type: 'api',
url: `/names/${receiverName}`,
})
if (myNameRes.error === 401) {
myRes = false
} else {
myRes = myNameRes
}
return myRes
}
const validateAddress = async (receiverAddress) => {
let myAddress = await window.parent.validateAddress(receiverAddress)
return myAddress
}
const validateReceiver = async (recipient) => {
let lastRef = await this.getLastRef()
let theFee = await this.getSendQortFee()
let isAddress
try {
isAddress = await validateAddress(recipient)
} catch (err) {
isAddress = false
}
if (parseFloat(amount) <= 0) {
this.sendMoneyLoading = false
this.btnDisable = false
let snack2string = get("chatpage.cchange52")
parentEpml.request('showSnackBar', `${snack2string}`)
return false
}
if (recipient.length === 0) {
this.sendMoneyLoading = false
this.btnDisable = false
let snack3string = get("chatpage.cchange53")
parentEpml.request('showSnackBar', `${snack3string}`)
return false
}
const validateName = async (receiverName) => {
let myRes
let myNameRes = await parentEpml.request('apiCall', {
type: 'api',
url: `/names/${receiverName}`,
})
if (myNameRes.error === 401) {
myRes = false
} else {
myRes = myNameRes
}
return myRes;
}
const validateAddress = async (receiverAddress) => {
let myAddress = await window.parent.validateAddress(receiverAddress)
return myAddress
}
const validateReceiver = async (recipient) => {
let lastRef = await this.getLastRef()
let isAddress
try {
isAddress = await validateAddress(recipient)
} catch (err) {
isAddress = false
}
if (isAddress) {
let myTransaction = await makeTransactionRequest(recipient, lastRef)
if (isAddress) {
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)
} else {
let myNameRes = await validateName(recipient)
@@ -220,8 +252,61 @@ export class TipUser extends LitElement {
validateReceiver(recipient)
}
render() {
return html`
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() {
return html`
<div class="tip-user-header">
<img src="/img/qort.png" width="32" height="32">
<p class="tip-user-header-font">${translate("chatpage.cchange43")} ${this.userName}</p>
@@ -229,9 +314,9 @@ export class TipUser extends LitElement {
<div class="tip-user-body">
<p class="tip-available">${translate("chatpage.cchange47")}: ${this.walletBalance} QORT</p>
<input id="amountInput" class="tip-input" type="number" placeholder="${translate("chatpage.cchange46")}" />
<p class="tip-available">${translate("chatpage.cchange49")}: 0.001 QORT</p>
${this.sendMoneyLoading ?
html`
<p class="tip-available">${translate("chatpage.cchange49")}: ${this.qortPaymentFee} QORT</p>
${this.sendMoneyLoading ?
html`
<paper-progress indeterminate style="width: 100%; margin: 4px;">
</paper-progress>`
: html`

View File

@@ -50,4 +50,13 @@ export const DECRYPT_DATA_GROUP = 'DECRYPT_DATA_GROUP'
export const SAVE_FILE = 'SAVE_FILE'
//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'] })
const parentEpml = new Epml({ type: 'WINDOW', source: window.parent })
class Chat extends LitElement {
static get properties() {
return {
@@ -912,4 +911,4 @@ class Chat extends LitElement {
}
}
window.customElements.define('q-chat', Chat)
window.customElements.define('q-chat', Chat)

View File

@@ -3,6 +3,7 @@ import { render } from 'lit/html.js'
import { Epml } from '../../../../epml'
import isElectron from 'is-electron'
import { use, get, translate, translateUnsafeHTML, registerTranslateConfig } from 'lit-translate'
import ShortUniqueId from 'short-unique-id';
registerTranslateConfig({
loader: (lang) => fetch(`/language/${lang}.json`).then((res) => res.json())
@@ -131,6 +132,7 @@ class WebBrowser extends LitElement {
constructor() {
super();
this.url = 'about:blank';
this.uid = new ShortUniqueId()
this.myAddress = window.parent.reduxStore.getState().app.selectedAddress
this._publicKey = { key: '', hasPubKey: false }
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() {
return html`
@@ -425,6 +465,23 @@ class WebBrowser extends LitElement {
const joinFee = (Number(data) / 1e8).toFixed(8)
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() {
const myNode = window.parent.reduxStore.getState().app.nodeConfig.knownNodes[window.parent.reduxStore.getState().app.nodeConfig.node]
const nodeUrl = myNode.protocol + '://' + myNode.domain + ':' + myNode.port
@@ -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 getLastRef = async () => {
let myRef = await parentEpml.request('apiCall', {
@@ -510,13 +567,15 @@ class WebBrowser extends LitElement {
}
const makeTransactionRequest = async (lastRef) => {
let groupdialog1 = get("transactions.groupdialog1")
let groupdialog2 = get("transactions.groupdialog2")
let deployAtdialog1 = get("transactions.deployAtdialog1")
let deployAtdialog2 = get("transactions.deployAtdialog2")
let deployAtdialog3 = get("transactions.deployAtdialog3")
let deployAtdialog4 = get("walletpage.wchange12")
let myTxnrequest = await parentEpml.request('transaction', {
type: 16,
nonce: this.selectedAddress.nonce,
params: {
fee: fee || deployAtFee,
fee: deployAtFee,
rName: name,
rDescription: description,
rTags: tags,
@@ -525,8 +584,10 @@ class WebBrowser extends LitElement {
rCreationBytes: creationBytes,
atType: atType,
lastReference: lastRef,
atDeployDialog1: groupdialog1,
atDeployDialog2: groupdialog2
atDeployDialog1: deployAtdialog1,
atDeployDialog2: deployAtdialog2,
atDeployDialog3: deployAtdialog3,
atDeployDialog4: deployAtdialog4
},
apiVersion: 2
})
@@ -975,6 +1036,7 @@ class WebBrowser extends LitElement {
const tag3 = data.tag3;
const tag4 = data.tag4;
const tag5 = data.tag5;
let feeAmount = null
if (data.identifier == null) {
identifier = 'default';
}
@@ -994,6 +1056,8 @@ class WebBrowser extends LitElement {
if (data.file) {
data64 = await fileToBase64(data.file)
}
const getArbitraryFee = await this.getArbitraryFee()
feeAmount = getArbitraryFee.fee
if (data.encrypt) {
try {
@@ -1014,6 +1078,7 @@ class WebBrowser extends LitElement {
}
const res2 = await showModalAndWait(
@@ -1022,7 +1087,8 @@ class WebBrowser extends LitElement {
name,
identifier,
service,
encrypt: data.encrypt
encrypt: data.encrypt,
feeAmount: getArbitraryFee.feeToShow
}
);
if (res2.action === 'accept') {
@@ -1052,7 +1118,8 @@ class WebBrowser extends LitElement {
tag4,
tag5,
apiVersion: 2,
withFee: res2.userData.isWithFee === true ? true : false
withFee: res2.userData.isWithFee === true ? true : false,
feeAmount: feeAmount
});
response = JSON.stringify(resPublish);
@@ -1080,7 +1147,7 @@ class WebBrowser extends LitElement {
case actions.PUBLISH_MULTIPLE_QDN_RESOURCES: {
const requiredFields = ['resources'];
const missingFields = [];
let feeAmount = null
requiredFields.forEach((field) => {
if (!data[field]) {
missingFields.push(field);
@@ -1114,11 +1181,14 @@ class WebBrowser extends LitElement {
response = JSON.stringify(data);
break
}
const getArbitraryFee = await this.getArbitraryFee()
feeAmount = getArbitraryFee.fee
const res2 = await showModalAndWait(
actions.PUBLISH_MULTIPLE_QDN_RESOURCES,
{
resources,
encrypt: data.encrypt
encrypt: data.encrypt,
feeAmount: getArbitraryFee.feeToShow
}
);
@@ -1217,7 +1287,8 @@ class WebBrowser extends LitElement {
tag4,
tag5,
apiVersion: 2,
withFee: res2.userData.isWithFee === true ? true : false
withFee: res2.userData.isWithFee === true ? true : false,
feeAmount: feeAmount
});
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"}`
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: {
const message = data.message;
const recipient = data.destinationAddress;
@@ -1618,41 +1776,41 @@ class WebBrowser extends LitElement {
break;
}
// case 'DEPLOY_AT': {
// const requiredFields = ['name', 'description', 'tags', 'creationBytes', 'amount', 'assetId', 'type'];
// const missingFields = [];
case 'DEPLOY_AT': {
const requiredFields = ['name', 'description', 'tags', 'creationBytes', 'amount', 'assetId', 'type'];
const missingFields = [];
// requiredFields.forEach((field) => {
// if (!data[field]) {
// missingFields.push(field);
// }
// });
requiredFields.forEach((field) => {
if (!data[field] && data[field] !== 0) {
missingFields.push(field);
}
});
// if (missingFields.length > 0) {
// const missingFieldsString = missingFields.join(', ');
// const errorMsg = `Missing fields: ${missingFieldsString}`
// let data = {};
// data['error'] = errorMsg;
// response = JSON.stringify(data);
// break
// }
if (missingFields.length > 0) {
const missingFieldsString = missingFields.join(', ');
const errorMsg = `Missing fields: ${missingFieldsString}`
let data = {};
data['error'] = errorMsg;
response = JSON.stringify(data);
break
}
// try {
// 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)
// response = JSON.stringify(resJoinGroup);
// } catch (error) {
// const obj = {};
// const errorMsg = error.message || 'Failed to join the group.';
// obj['error'] = errorMsg;
// response = JSON.stringify(obj);
// } finally {
// this.loader.hide();
// }
// break;
// }
try {
this.loader.show();
const resDeployAt = await this._deployAt(data.name, data.description, data.tags, data.creationBytes, data.amount, data.assetId, data.type)
response = JSON.stringify(resDeployAt);
} catch (error) {
const obj = {};
const errorMsg = error.message || 'Failed to join the group.';
obj['error'] = errorMsg;
response = JSON.stringify(obj);
} finally {
this.loader.hide();
}
break;
}
case actions.GET_WALLET_BALANCE: {
@@ -2707,6 +2865,46 @@ class WebBrowser extends LitElement {
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() {
// 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('')}
</table>
<div class="checkbox-row">
<label for="isWithFee" id="isWithFeeLabel" style="color: var(--black);">
${get('browserpage.bchange33')} ${data.resources.length * 0.001} QORT fee
</label>
<mwc-checkbox checked style="margin-right: -15px;" id="isWithFee"></mwc-checkbox>
<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>
</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.bchange45")}:</span> ${data.encrypt ? true : false}</p>
<div class="checkbox-row">
<label for="isWithFee" id="isWithFeeLabel" style="color: var(--black);">
${get('browserpage.bchange29')}
</label>
<mwc-checkbox checked style="margin-right: -15px;" id="isWithFee"></mwc-checkbox>
<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>
` : ''}
@@ -3064,7 +3256,12 @@ async function showModalAndWait(type, data) {
<p class="modal-paragraph">${get("browserpage.bchange46")}: <span> ${data.filename}</span></p>
</div>
` : ''}
${type === actions.NOTIFICATIONS_PERMISSION ? `
<div class="modal-subcontainer">
<p class="modal-paragraph">${get("browserpage.bchange48")}</p>
</div>
` : ''}
${type === actions.DELETE_LIST_ITEM ? `
<div class="modal-subcontainer">
<p class="modal-paragraph">${get("browserpage.bchange44")}</p>
@@ -3091,7 +3288,8 @@ async function showModalAndWait(type, data) {
const userData = {};
if (type === actions.PUBLISH_QDN_RESOURCE || type === actions.PUBLISH_MULTIPLE_QDN_RESOURCES) {
const isWithFeeCheckbox = modal.querySelector('#isWithFee');
userData.isWithFee = isWithFeeCheckbox.checked;
// userData.isWithFee = isWithFeeCheckbox.checked;
userData.isWithFee = true
}
if (modal.parentNode === document.body) {
document.body.removeChild(modal);

View File

@@ -14,6 +14,7 @@ import '@material/mwc-select'
import '@material/mwc-dialog'
import '@material/mwc-list/mwc-list-item.js'
import '@polymer/paper-progress/paper-progress.js'
import { modalHelper } from '../../../utils/publish-modal'
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>
${this.loading ? html` <paper-progress indeterminate style="width:100%; margin:4px;"></paper-progress> ` : ''}
<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="send" @click=${() => this.shadowRoot.querySelector('#publishWithFeeDialog').show()}> ${translate("publishpage.pchange11")}</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=${(e) => {
this.doPublish(e, false, true)
}}> ${translate("publishpage.pchange11")}</mwc-button>
</div>
</div>
</div>
@@ -310,7 +313,7 @@ class PublishData extends LitElement {
<mwc-button slot="primaryAction" @click="${(e) => this.feeDialogNo(e, false, false)}" class="red">
${translate("general.no")}
</mwc-button>
<mwc-button slot="secondaryAction" @click="${(e) => this.feeDialogYes(e, false, true)}" class="green">
<mwc-button slot="secondaryAction" @click="${(e) => this.feeDialogYes(e, false, true)}" class="green">
${translate("general.yes")}
</mwc-button>
</mwc-dialog>
@@ -418,7 +421,7 @@ class PublishData extends LitElement {
this.shadowRoot.querySelector('#publishWithFeeDialog').close()
}
doPublish(e, preview, fee) {
async doPublish(e, preview, fee) {
let registeredName = this.shadowRoot.getElementById('registeredName').value
let service = this.shadowRoot.getElementById('service').value
let identifier = this.shadowRoot.getElementById('identifier').value
@@ -464,7 +467,22 @@ class PublishData extends LitElement {
parentEpml.request('showSnackBar', `${err5string}`)
}
else {
this.publishData(registeredName, path, file, service, identifier, preview, fee)
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)
} catch (error) {
this.shadowRoot.querySelector('#publishWithFeeDialog').close()
}
}
}
@@ -488,6 +506,17 @@ class PublishData extends LitElement {
this.successMessage = ''
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 () => {
let validNameRes = await validateName(registeredName)
@@ -501,8 +530,17 @@ class PublishData extends LitElement {
this.generalMessage = `${err6string}`
let transactionBytes
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) {
let err7string = get("publishpage.pchange20")
@@ -531,12 +569,13 @@ class PublishData extends LitElement {
if (fee) {
let err9string = get("publishpage.pchange26")
this.generalMessage = `${err9string}`
} else {
let err9string = get("publishpage.pchange22")
this.generalMessage = `${err9string}`
}
let signAndProcessRes = await signAndProcess(transactionBytes, fee)
let signAndProcessRes = await signAndProcess(transactionBytes, fee, feeAmount)
if (signAndProcessRes.error) {
let err10string = get("publishpage.pchange20")
@@ -554,7 +593,9 @@ class PublishData extends LitElement {
this.successMessage = `${err11string}`
}
const uploadData = async (registeredName, path, file, preview, fee) => {
const uploadData = async (registeredName, path, file, preview, fee, feeAmount) => {
let postBody = path
let urlSuffix = ""
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()}`
}
} 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) {
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 {
uploadDataUrl = `/arbitrary/${this.service}/${registeredName}${urlSuffix}?${metadataQueryString}&apiKey=${this.getApiKey()}`

View File

@@ -2104,6 +2104,7 @@ class TradeBotPortal extends LitElement {
this.changeTheme()
this.changeLanguage()
this.tradeFee()
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() {
const myNode = window.parent.reduxStore.getState().app.nodeConfig.knownNodes[window.parent.reduxStore.getState().app.nodeConfig.node];
let apiKey = myNode.apiKey;

View File

@@ -708,7 +708,7 @@ class TradePortal extends LitElement {
balance: "0",
coinCode: "QORT",
coinAmount: this.amountString,
tradeFee: "0.002"
tradeFee: "0.02"
}
let bitcoin = {
@@ -1421,6 +1421,7 @@ class TradePortal extends LitElement {
this.changeTheme()
this.changeLanguage()
this.tradeFee()
this.tradeHelperMessage = this.renderTradeHelperPass()
@@ -2698,6 +2699,7 @@ class TradePortal extends LitElement {
async sellAction() {
this.isSellLoading = true
this.sellBtnDisable = true
await this.tradeFee()
const sellAmountInput = this.shadowRoot.getElementById('sellAmountInput').value
const sellTotalInput = this.shadowRoot.getElementById('sellTotalInput').value
const fundingQortAmount = this.round(parseFloat(sellAmountInput) + 0.001)
@@ -2732,7 +2734,7 @@ class TradePortal extends LitElement {
fundingQortAmount: parseFloat(fundingQortAmount),
foreignBlockchain: this.selectedCoin,
foreignAmount: parseFloat(sellTotalInput),
tradeTimeout: 60,
tradeTimeout: 120,
receivingAddress: _receivingAddress,
})
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.sellBtnDisable = false
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() {
const myNode = window.parent.reduxStore.getState().app.nodeConfig.knownNodes[window.parent.reduxStore.getState().app.nodeConfig.node];
let apiKey = myNode.apiKey;

View File

@@ -75,6 +75,7 @@ class MultiWallet extends LitElement {
isValidAmount: { type: Boolean },
balance: { type: Number },
balanceString: { type: String },
qortPaymentFee: { type: Number },
btcFeePerByte: { type: Number },
ltcFeePerByte: { type: Number },
dogeFeePerByte: { type: Number },
@@ -795,6 +796,7 @@ class MultiWallet extends LitElement {
this.dgbAmount = 0
this.rvnAmount = 0
this.arrrAmount = 0
this.qortPaymentFee = 0.001
this.btcFeePerByte = 100
this.btcSatMinFee = 20
this.btcSatMaxFee = 150
@@ -1352,7 +1354,7 @@ class MultiWallet extends LitElement {
</mwc-textfield>
</p>
<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>
${this.renderClearSuccess()}
${this.renderClearError()}
@@ -2821,6 +2823,7 @@ class MultiWallet extends LitElement {
firstUpdated() {
this.changeTheme()
this.changeLanguage()
this.paymentFee()
this.bookQortalAddress = window.parent.reduxStore.getState().app.selectedAddress.address
this.bookBitcoinAddress = window.parent.reduxStore.getState().app.selectedAddress.btcWallet.address
@@ -2906,6 +2909,23 @@ class MultiWallet extends LitElement {
setInterval(() => {
this.clearConsole()
}, 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() {
@@ -4048,11 +4068,11 @@ class MultiWallet extends LitElement {
calculateQortAll() {
this.amount = 0
this.shadowRoot.getElementById('amountInput').value = this.amount
if (this.balance < 0.00110000) {
if (this.balance < 0.01100000) {
let not_enough_string = get("walletpage.wchange26")
parentEpml.request('showSnackBar', `${not_enough_string}`)
} 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').blur()
this.shadowRoot.getElementById('amountInput').focus()
@@ -4238,7 +4258,7 @@ class MultiWallet extends LitElement {
} else {
const checkQortAmountInput = this.shadowRoot.getElementById('amountInput').value
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)) {
this.shadowRoot.getElementById('amountInput').value = checkQortAmountInput
this.btnDisable = false
@@ -4269,7 +4289,7 @@ class MultiWallet extends LitElement {
} else {
const checkQortAmountInput = this.shadowRoot.getElementById('amountInput').value
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)) {
this.shadowRoot.getElementById('amountInput').value = checkQortAmountInput
this.btnDisable = false
@@ -4287,7 +4307,7 @@ class MultiWallet extends LitElement {
} else {
const checkQortAmountInput = this.shadowRoot.getElementById('amountInput').value
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)) {
this.shadowRoot.getElementById('amountInput').value = checkQortAmountInput
this.btnDisable = false
@@ -4302,6 +4322,7 @@ class MultiWallet extends LitElement {
}
async sendQort() {
const sendFee = this.qortPaymentFee
const amount = this.shadowRoot.getElementById('amountInput').value
let recipient = this.shadowRoot.getElementById('recipient').value
@@ -4390,11 +4411,10 @@ class MultiWallet extends LitElement {
const getName = async (recipient)=> {
try {
const getNames = await parentEpml.request("apiCall", {
type: "api",
url: `/names/address/${recipient}`,
})
type: "api",
url: `/names/address/${recipient}`,
})
if(getNames?.length > 0 ){
return getNames[0].name
} else {
@@ -4421,7 +4441,7 @@ class MultiWallet extends LitElement {
recipientName: recipientName,
amount: amount,
lastReference: mylastRef,
fee: 0.001,
fee: sendFee,
dialogamount: dialogamount,
dialogto: dialogto,
dialogAddress,

View File

@@ -28,7 +28,8 @@ export const publishData = async ({
tag2,
tag3,
tag4,
tag5
tag5,
feeAmount
}) => {
const validateName = async (receiverName) => {
let nameRes = await parentEpml.request("apiCall", {
@@ -48,6 +49,17 @@ export const publishData = async ({
})
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) => {
let convertedBytesBase58 = await convertBytesForSigning(
@@ -125,7 +137,18 @@ export const publishData = async ({
if (validNameRes.error) {
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) {
throw new Error(transactionBytes.message || 'Error when uploading');
} else if (
@@ -149,7 +172,7 @@ export const publishData = async ({
return signAndProcessRes
}
const uploadData = async (registeredName, path, file) => {
const uploadData = async (registeredName, path, file, fee) => {
if (identifier != null && identifier.trim().length > 0) {
let postBody = path
let urlSuffix = ""
@@ -181,7 +204,7 @@ export const publishData = async ({
}
if(withFee){
uploadDataUrl = uploadDataUrl + '&fee=100000'
uploadDataUrl = uploadDataUrl + `&fee=${fee}`
}
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();