From 9705febe65ce6ff5fe6d2ec41a7f0580bcaa9169 Mon Sep 17 00:00:00 2001 From: Phillip Date: Wed, 9 Aug 2023 23:52:26 +0300 Subject: [PATCH 01/57] testing speed --- plugins/plugins/core/components/ChatModals.js | 246 ++++++------- plugins/plugins/core/components/ChatPage.js | 176 +++++---- .../core/components/ChatWelcomePage.js | 18 +- .../plugins/core/components/LevelFounder.js | 18 +- plugins/plugins/core/components/NameMenu.js | 26 +- plugins/plugins/core/components/TipUser.js | 346 +++++++++--------- 6 files changed, 399 insertions(+), 431 deletions(-) diff --git a/plugins/plugins/core/components/ChatModals.js b/plugins/plugins/core/components/ChatModals.js index 2074c911..1da17e7c 100644 --- a/plugins/plugins/core/components/ChatModals.js +++ b/plugins/plugins/core/components/ChatModals.js @@ -9,29 +9,29 @@ import '@material/mwc-dialog' const parentEpml = new Epml({ type: 'WINDOW', source: window.parent }) class ChatModals extends LitElement { - static get properties() { - return { - openDialogPrivateMessage: {type: Boolean}, - openDialogBlockUser: {type: Boolean}, - isLoading: { type: Boolean }, - nametodialog: { type: String, attribute: true }, - hidePrivateMessageModal: {type: Function}, - hideBlockUserModal: {type: Function}, - toblockaddress: { type: String, attribute: true }, - chatBlockedAdresses: { type: Array } + static get properties() { + return { + openDialogPrivateMessage: { type: Boolean }, + openDialogBlockUser: { type: Boolean }, + isLoading: { type: Boolean }, + nametodialog: { type: String, attribute: true }, + hidePrivateMessageModal: { type: Function }, + hideBlockUserModal: { type: Function }, + toblockaddress: { type: String, attribute: true }, + chatBlockedAdresses: { type: Array } + } } - } - constructor() { - super() - this.isLoading = false - this.hidePrivateMessageModal = () => {} - this.hideBlockUserModal = () => {} - this.chatBlockedAdresses = [] - } + constructor() { + super() + this.isLoading = false + this.hidePrivateMessageModal = () => { } + this.hideBlockUserModal = () => { } + this.chatBlockedAdresses = [] + } - static get styles() { - return css` + static get styles() { + return css` .input { width: 90%; border: none; @@ -60,52 +60,48 @@ class ChatModals extends LitElement { --mdc-theme-primary: red; } ` - } - - firstUpdated() { - - const stopKeyEventPropagation = (e) => { - e.stopPropagation(); - return false; } - this.shadowRoot.getElementById('sendTo').addEventListener('keydown', stopKeyEventPropagation); - this.shadowRoot.getElementById('messageBox').addEventListener('keydown', stopKeyEventPropagation); + firstUpdated() { + + const stopKeyEventPropagation = (e) => { + e.stopPropagation(); + return false; + } + + this.shadowRoot.getElementById('sendTo').addEventListener('keydown', stopKeyEventPropagation); + this.shadowRoot.getElementById('messageBox').addEventListener('keydown', stopKeyEventPropagation); + + parentEpml.ready().then(() => { + parentEpml.subscribe('selected_address', async selectedAddress => { + this.selectedAddress = {} + selectedAddress = JSON.parse(selectedAddress) + if (!selectedAddress || Object.entries(selectedAddress).length === 0) return + this.selectedAddress = selectedAddress + }) - parentEpml.ready().then(() => { - parentEpml.subscribe('selected_address', async selectedAddress => { - this.selectedAddress = {} - selectedAddress = JSON.parse(selectedAddress) - if (!selectedAddress || Object.entries(selectedAddress).length === 0) return - this.selectedAddress = selectedAddress }) - parentEpml.request('apiCall', { - url: `/addresses/balance/${window.parent.reduxStore.getState().app.selectedAddress.address}` - }).then(res => { - this.balance = res - }) - }) - parentEpml.imReady() + parentEpml.imReady() } - // Send Private Message + // Send Private Message - _sendMessage() { - this.isLoading = true; + _sendMessage() { + this.isLoading = true; - const recipient = this.shadowRoot.getElementById('sendTo').value; - const messageBox = this.shadowRoot.getElementById('messageBox'); - const messageText = messageBox.value; + const recipient = this.shadowRoot.getElementById('sendTo').value; + const messageBox = this.shadowRoot.getElementById('messageBox'); + const messageText = messageBox.value; - if (recipient.length === 0) { - this.isLoading = false - } else if (messageText.length === 0) { - this.isLoading = false - } else { - this.sendMessage() + if (recipient.length === 0) { + this.isLoading = false + } else if (messageText.length === 0) { + this.isLoading = false + } else { + this.sendMessage() + } } - } async sendMessage() { this.isLoading = true @@ -172,77 +168,77 @@ class ChatModals extends LitElement { } }; - const sendMessageRequest = async (isEncrypted, _publicKey) => { - const messageObject = { - messageText, - images: [''], - repliedTo: '', - version: 1 - } - const stringifyMessageObject = JSON.stringify(messageObject) - let chatResponse = await parentEpml.request('chat', { - type: 18, - nonce: this.selectedAddress.nonce, - params: { - timestamp: sendTimestamp, - recipient: recipient, - recipientPublicKey: _publicKey, - hasChatReference: 0, - message: stringifyMessageObject, - lastReference: reference, - proofOfWorkNonce: 0, - isEncrypted: isEncrypted, - isText: 1 + const sendMessageRequest = async (isEncrypted, _publicKey) => { + const messageObject = { + messageText, + images: [''], + repliedTo: '', + version: 1 } - }) - _computePow(chatResponse) - } + const stringifyMessageObject = JSON.stringify(messageObject) + let chatResponse = await parentEpml.request('chat', { + type: 18, + nonce: this.selectedAddress.nonce, + params: { + timestamp: sendTimestamp, + recipient: recipient, + recipientPublicKey: _publicKey, + hasChatReference: 0, + message: stringifyMessageObject, + lastReference: reference, + proofOfWorkNonce: 0, + isEncrypted: isEncrypted, + isText: 1 + } + }) + _computePow(chatResponse) + } - const _computePow = async (chatBytes) => { + const _computePow = async (chatBytes) => { - const _chatBytesArray = Object.keys(chatBytes).map(function (key) { return chatBytes[key]; }) - const chatBytesArray = new Uint8Array(_chatBytesArray) - const chatBytesHash = new window.parent.Sha256().process(chatBytesArray).finish().result - const hashPtr = window.parent.sbrk(32, window.parent.heap) - const hashAry = new Uint8Array(window.parent.memory.buffer, hashPtr, 32) - hashAry.set(chatBytesHash) + const _chatBytesArray = Object.keys(chatBytes).map(function (key) { return chatBytes[key]; }) + const chatBytesArray = new Uint8Array(_chatBytesArray) + const chatBytesHash = new window.parent.Sha256().process(chatBytesArray).finish().result + const hashPtr = window.parent.sbrk(32, window.parent.heap) + const hashAry = new Uint8Array(window.parent.memory.buffer, hashPtr, 32) + hashAry.set(chatBytesHash) - const difficulty = this.balance < 4 ? 18 : 8 + const difficulty = this.balance < 4 ? 18 : 8 - const workBufferLength = 8 * 1024 * 1024; - const workBufferPtr = window.parent.sbrk(workBufferLength, window.parent.heap) + const workBufferLength = 8 * 1024 * 1024; + const workBufferPtr = window.parent.sbrk(workBufferLength, window.parent.heap) - let nonce = window.parent.computePow(hashPtr, workBufferPtr, workBufferLength, difficulty) + let nonce = window.parent.computePow(hashPtr, workBufferPtr, workBufferLength, difficulty) - let _response = await parentEpml.request('sign_chat', { - nonce: this.selectedAddress.nonce, - chatBytesArray: chatBytesArray, - chatNonce: nonce - }) - getSendChatResponse(_response) - } + let _response = await parentEpml.request('sign_chat', { + nonce: this.selectedAddress.nonce, + chatBytesArray: chatBytesArray, + chatNonce: nonce + }) + getSendChatResponse(_response) + } - const getSendChatResponse = (response) => { + const getSendChatResponse = (response) => { - if (response === true) { - messageBox.value = '' - let err2string = get('welcomepage.wcchange8') - parentEpml.request('showSnackBar', `${err2string}`) - this.isLoading = false - this.shadowRoot.querySelector('#startPmDialog').close() - } else if (response.error) { - parentEpml.request('showSnackBar', response.message) - this.isLoading = false - this.shadowRoot.querySelector('#startPmDialog').close() - } else { - let err3string = get('welcomepage.wcchange9') - parentEpml.request('showSnackBar', `${err3string}`) - this.isLoading = false - this.shadowRoot.querySelector('#startPmDialog').close() - } + if (response === true) { + messageBox.value = '' + let err2string = get('welcomepage.wcchange8') + parentEpml.request('showSnackBar', `${err2string}`) + this.isLoading = false + this.shadowRoot.querySelector('#startPmDialog').close() + } else if (response.error) { + parentEpml.request('showSnackBar', response.message) + this.isLoading = false + this.shadowRoot.querySelector('#startPmDialog').close() + } else { + let err3string = get('welcomepage.wcchange9') + parentEpml.request('showSnackBar', `${err3string}`) + this.isLoading = false + this.shadowRoot.querySelector('#startPmDialog').close() + } - } - getAddressPublicKey() + } + getAddressPublicKey() } _textArea(e) { @@ -276,8 +272,8 @@ class ChatModals extends LitElement { fetch(`${nodeUrl}/names/address/${item}?limit=0&reverse=true`).then(res => { return res.json() }).then(jsonRes => { - if(jsonRes.length) { - jsonRes.map (item => { + if (jsonRes.length) { + jsonRes.map(item => { obj.push(item) }) } else { @@ -291,7 +287,7 @@ class ChatModals extends LitElement { relMessages() { setTimeout(() => { - window.location.href = window.location.href.split( '#' )[0] + window.location.href = window.location.href.split('#')[0] }, 500) } @@ -345,8 +341,8 @@ class ChatModals extends LitElement { return ret } - render() { - return html` + render() { + return html` this._textArea(e)} ?disabled=${this.isLoading} id='messageBox' placeholder='${translate('welcomepage.wcchange5')}' rows='1'>

{ - this._sendMessage(); - } - }>${translate('welcomepage.wcchange6')} + this._sendMessage(); + } + }>${translate('welcomepage.wcchange6')}
`; - } + } } customElements.define('chat-modals', ChatModals) \ No newline at end of file diff --git a/plugins/plugins/core/components/ChatPage.js b/plugins/plugins/core/components/ChatPage.js index c11f2aa8..23286834 100644 --- a/plugins/plugins/core/components/ChatPage.js +++ b/plugins/plugins/core/components/ChatPage.js @@ -118,7 +118,7 @@ class ChatPage extends LitElement { } static get styles() { - return css` + return css` html { scroll-behavior: smooth; } @@ -1443,14 +1443,7 @@ class ChatPage extends LitElement { ` : ''}
- this.setGifsLoading(val)} - .sendMessage=${(val) => this._sendMessage(val)} - .setOpenGifModal=${(val) => this.setOpenGifModal(val)}> - +
@@ -1600,18 +1593,18 @@ class ChatPage extends LitElement {
@@ -240,9 +240,9 @@ class ChatWelcomePage extends LitElement {

{ - this._sendMessage(); - } - }> + this._sendMessage(); + } + }> ${translate("welcomepage.wcchange6")} { - this.balance = res - }) + }) parentEpml.imReady() diff --git a/plugins/plugins/core/components/LevelFounder.js b/plugins/plugins/core/components/LevelFounder.js index ade53a0f..e2e4c1eb 100644 --- a/plugins/plugins/core/components/LevelFounder.js +++ b/plugins/plugins/core/components/LevelFounder.js @@ -101,7 +101,7 @@ class LevelFounder extends LitElement { } firstUpdated() { - this.checkAddressInfo() + console.log('levelFounder') parentEpml.ready().then(() => { parentEpml.subscribe('selected_address', async selectedAddress => { @@ -115,28 +115,28 @@ class LevelFounder extends LitElement { } async checkAddressInfo() { - let toCheck = this.checkleveladdress - const memberInfo = await parentEpml.request('apiCall', { - url: `/addresses/${toCheck}` - }) - this.memberInfo = memberInfo + // let toCheck = this.checkleveladdress + // const memberInfo = await parentEpml.request('apiCall', { + // url: `/addresses/${toCheck}` + // }) + // this.memberInfo = memberInfo } renderFounder() { let adressfounder = this.memberInfo.flags if (adressfounder === 1) { - return html ` + return html` F FOUNDER ` } else { - return html `` + return html`` } } renderLevel() { let adresslevel = this.memberInfo.level - return adresslevel ? html ` + return adresslevel ? html` ${`badge-${adresslevel}`} ${translate("mintingpage.mchange27")} ${adresslevel} diff --git a/plugins/plugins/core/components/NameMenu.js b/plugins/plugins/core/components/NameMenu.js index c4359637..9a0d2e5a 100644 --- a/plugins/plugins/core/components/NameMenu.js +++ b/plugins/plugins/core/components/NameMenu.js @@ -228,9 +228,9 @@ class NameMenu extends LitElement {

{ - this._sendMessage(); - } - }> + this._sendMessage(); + } + }> ${translate("welcomepage.wcchange6")} { - this.getChatBlockedAdresses() - }, 60000) + setInterval(() => { + this.getChatBlockedAdresses() + }, 60000) - window.onclick = function(event) { + window.onclick = function (event) { if (!event.target.matches('.block')) { var dropdowns = document.getElementsByClassName('dropdown-content'); var i; @@ -290,11 +290,7 @@ class NameMenu extends LitElement { if (!selectedAddress || Object.entries(selectedAddress).length === 0) return this.selectedAddress = selectedAddress }) - parentEpml.request('apiCall', { - url: `/addresses/balance/${window.parent.reduxStore.getState().app.selectedAddress.address}` - }).then(res => { - this.balance = res - }) + }) parentEpml.imReady() } @@ -333,7 +329,7 @@ class NameMenu extends LitElement { relMessages() { setTimeout(() => { - window.location.href = window.location.href.split( '#' )[0] + window.location.href = window.location.href.split('#')[0] }, 500) } @@ -407,8 +403,8 @@ class NameMenu extends LitElement { fetch(`${nodeUrl}/names/address/${item}?limit=0&reverse=true`).then(res => { return res.json() }).then(jsonRes => { - if(jsonRes.length) { - jsonRes.map (item => { + if (jsonRes.length) { + jsonRes.map(item => { obj.push(item) }) } else { diff --git a/plugins/plugins/core/components/TipUser.js b/plugins/plugins/core/components/TipUser.js index d7b79b7f..b900cfc5 100644 --- a/plugins/plugins/core/components/TipUser.js +++ b/plugins/plugins/core/components/TipUser.js @@ -9,21 +9,21 @@ import { use, get, translate, translateUnsafeHTML, registerTranslateConfig } fro const parentEpml = new Epml({ type: "WINDOW", source: window.parent }); export class TipUser extends LitElement { - static get properties() { - return { - userName: { type: String }, - walletBalance: { type: Number }, - sendMoneyLoading: { type: Boolean }, - closeTipUser: { type: Boolean }, - btnDisable: { type: Boolean }, - errorMessage: { type: String }, - successMessage: { type: String }, - setOpenTipUser: { attribute: false }, + static get properties() { + return { + userName: { type: String }, + walletBalance: { type: Number }, + sendMoneyLoading: { type: Boolean }, + closeTipUser: { type: Boolean }, + btnDisable: { type: Boolean }, + errorMessage: { type: String }, + successMessage: { type: String }, + setOpenTipUser: { attribute: false }, } - } + } - constructor() { - super() + constructor() { + super() this.sendMoneyLoading = false this.btnDisable = false this.errorMessage = "" @@ -33,8 +33,8 @@ export class TipUser extends LitElement { static styles = [tipUserStyles] - async firstUpdated() { - await this.fetchWalletDetails() + async firstUpdated() { + await this.fetchWalletDetails() } updated(changedProperties) { @@ -48,12 +48,12 @@ export class TipUser extends LitElement { } async getLastRef() { - let myRef = await parentEpml.request("apiCall", { - type: "api", - url: `/addresses/lastreference/${this.myAddress.address}`, - }) - return myRef - } + let myRef = await parentEpml.request("apiCall", { + type: "api", + url: `/addresses/lastreference/${this.myAddress.address}`, + }) + return myRef + } renderSuccessText() { return html`${translate("chatpage.cchange55")}` @@ -70,168 +70,158 @@ export class TipUser extends LitElement { } async fetchWalletDetails() { - await parentEpml.request('apiCall', { - url: `/addresses/balance/${this.myAddress.address}?apiKey=${this.getApiKey()}`, - }) - .then((res) => { - if (isNaN(Number(res))) { - let snack4string = get("chatpage.cchange48") - parentEpml.request('showSnackBar', `${snack4string}`) - } else { - this.walletBalance = Number(res).toFixed(8) - } - }) + } async sendQort() { - const amount = this.shadowRoot.getElementById("amountInput").value - let recipient = this.userName - this.sendMoneyLoading = true - this.btnDisable = true + const amount = this.shadowRoot.getElementById("amountInput").value + let recipient = this.userName + 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) <= 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 (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 (isAddress) { - let myTransaction = await makeTransactionRequest(recipient, lastRef) - getTxnRequestResponse(myTransaction) - } else { - let myNameRes = await validateName(recipient) - if (myNameRes !== false) { - let myNameAddress = myNameRes.owner - let myTransaction = await makeTransactionRequest(myNameAddress, lastRef) + 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) getTxnRequestResponse(myTransaction) } else { - console.error(this.renderReceiverText()) - this.errorMessage = this.renderReceiverText() + let myNameRes = await validateName(recipient) + if (myNameRes !== false) { + let myNameAddress = myNameRes.owner + let myTransaction = await makeTransactionRequest(myNameAddress, lastRef) + getTxnRequestResponse(myTransaction) + } else { + console.error(this.renderReceiverText()) + this.errorMessage = this.renderReceiverText() + this.sendMoneyLoading = false + this.btnDisable = false + } + } + } + + const getName = async (recipient) => { + try { + const getNames = await parentEpml.request("apiCall", { + type: "api", + url: `/names/address/${recipient}`, + }); + + if (getNames?.length > 0) { + return getNames[0].name + } else { + return '' + } + } catch (error) { + return "" + } + } + + const makeTransactionRequest = async (receiver, lastRef) => { + let myReceiver = receiver + let mylastRef = lastRef + 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: 0.001, + 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 - } - } - } - - const getName = async (recipient)=> { - try { - const getNames = await parentEpml.request("apiCall", { - type: "api", - url: `/names/address/${recipient}`, - }); - - if (getNames?.length > 0 ) { - return getNames[0].name + 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 { - return '' + this.errorMessage = txnResponse.data.message + this.sendMoneyLoading = false + this.btnDisable = false + throw new Error(txnResponse) } - } catch (error) { - return "" } + validateReceiver(recipient) } - const makeTransactionRequest = async (receiver, lastRef) => { - let myReceiver = receiver - let mylastRef = lastRef - 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: 0.001, - 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` + render() { + return html`

${translate("chatpage.cchange43")} ${this.userName}

@@ -240,11 +230,11 @@ export class TipUser extends LitElement {

${translate("chatpage.cchange47")}: ${this.walletBalance} QORT

${translate("chatpage.cchange49")}: 0.001 QORT

- ${this.sendMoneyLoading ? - html` + ${this.sendMoneyLoading ? + html` - ` - : html` + ` + : html`
`} - ${this.successMessage ? - html` + ${this.successMessage ? + html`

${this.successMessage}

` - : this.errorMessage ? - html` + : this.errorMessage ? + html`

${this.errorMessage}

` - : null} + : null}
`; - } + } } customElements.define('tip-user', TipUser) From 950817b8776f947ae8fd10a5f145e9a205c6c811 Mon Sep 17 00:00:00 2001 From: PhilReact Date: Sat, 2 Sep 2023 22:02:31 -0700 Subject: [PATCH 02/57] fetch updates in bg --- plugins/plugins/core/components/ChatPage.js | 168 ++++++++++------ .../plugins/core/components/ChatScroller.js | 10 +- .../plugins/core/components/LevelFounder.js | 1 - .../core/messaging/q-chat/q-chat.src.js | 2 + plugins/plugins/utils/queue.js | 34 ++++ .../plugins/utils/replace-messages-edited.js | 188 ++++++++++++++++-- 6 files changed, 330 insertions(+), 73 deletions(-) create mode 100644 plugins/plugins/utils/queue.js diff --git a/plugins/plugins/core/components/ChatPage.js b/plugins/plugins/core/components/ChatPage.js index 23286834..d0dd3a78 100644 --- a/plugins/plugins/core/components/ChatPage.js +++ b/plugins/plugins/core/components/ChatPage.js @@ -43,6 +43,7 @@ import '@material/mwc-dialog' import '@material/mwc-icon' import '@polymer/paper-dialog/paper-dialog.js' import '@polymer/paper-spinner/paper-spinner-lite.js' +import { RequestQueue } from '../../utils/queue.js' const chatLastSeen = localForage.createInstance({ name: "chat-last-seen", @@ -50,6 +51,9 @@ const chatLastSeen = localForage.createInstance({ const parentEpml = new Epml({ type: 'WINDOW', source: window.parent }) +export const queue = new RequestQueue(); + + class ChatPage extends LitElement { static get properties() { return { @@ -113,7 +117,8 @@ class ChatPage extends LitElement { openGifModal: { type: Boolean }, gifsLoading: { type: Boolean }, goToRepliedMessage: { attribute: false }, - isLoadingGoToRepliedMessage: { type: Object } + isLoadingGoToRepliedMessage: { type: Object }, + updateMessageHash: { type: Object} } } @@ -1348,6 +1353,8 @@ class ChatPage extends LitElement { offsetHeight: 0 } this.theme = localStorage.getItem('qortalTheme') ? localStorage.getItem('qortalTheme') : 'light' + this.updateMessageHash = {} + this.addToUpdateMessageHashmap = this.addToUpdateMessageHashmap.bind(this) } setOpenGifModal(value) { @@ -2502,6 +2509,7 @@ class ChatPage extends LitElement { .selectedHead=${this.selectedHead} .goToRepliedMessage=${(val, val2) => this.goToRepliedMessage(val, val2)} .getOldMessageAfter=${(val) => this.getOldMessageAfter(val)} + .updateMessageHash=${this.updateMessageHash} > ` @@ -2548,13 +2556,15 @@ class ChatPage extends LitElement { return this.decodeMessage(eachMessage) }) - // const replacedMessages = await replaceMessagesEdited({ - // decodedMessages: decodeMsgs, - // parentEpml, - // isReceipient: this.isReceipient, - // decodeMessageFunc: this.decodeMessage, - // _publicKey: this._publicKey - // }) + + queue.push(() => replaceMessagesEdited({ + decodedMessages: decodeMsgs, + parentEpml, + isReceipient: this.isReceipient, + decodeMessageFunc: this.decodeMessage, + _publicKey: this._publicKey, + addToUpdateMessageHashmap: this.addToUpdateMessageHashmap + })); this.messagesRendered = [...decodeMsgs, ...this.messagesRendered].sort(function (a, b) { return a.timestamp @@ -2578,13 +2588,15 @@ class ChatPage extends LitElement { return this.decodeMessage(eachMessage) }) - // const replacedMessages = await replaceMessagesEdited({ - // decodedMessages: decodeMsgs, - // parentEpml, - // isReceipient: this.isReceipient, - // decodeMessageFunc: this.decodeMessage, - // _publicKey: this._publicKey - // }) + queue.push(() => replaceMessagesEdited({ + decodedMessages: decodeMsgs, + parentEpml, + isReceipient: this.isReceipient, + decodeMessageFunc: this.decodeMessage, + _publicKey: this._publicKey, + addToUpdateMessageHashmap: this.addToUpdateMessageHashmap + })); + this.messagesRendered = [...decodeMsgs, ...this.messagesRendered].sort(function (a, b) { return a.timestamp @@ -2612,13 +2624,15 @@ class ChatPage extends LitElement { return this.decodeMessage(eachMessage) }) - // const replacedMessages = await replaceMessagesEdited({ - // decodedMessages: decodeMsgs, - // parentEpml, - // isReceipient: this.isReceipient, - // decodeMessageFunc: this.decodeMessage, - // _publicKey: this._publicKey - // }) + + queue.push(() => replaceMessagesEdited({ + decodedMessages: decodeMsgs, + parentEpml, + isReceipient: this.isReceipient, + decodeMessageFunc: this.decodeMessage, + _publicKey: this._publicKey, + addToUpdateMessageHashmap: this.addToUpdateMessageHashmap + })); this.messagesRendered = [...decodeMsgs, ...this.messagesRendered].sort(function (a, b) { return a.timestamp @@ -2644,13 +2658,15 @@ class ChatPage extends LitElement { return this.decodeMessage(eachMessage) }) - // const replacedMessages = await replaceMessagesEdited({ - // decodedMessages: decodeMsgs, - // parentEpml, - // isReceipient: this.isReceipient, - // decodeMessageFunc: this.decodeMessage, - // _publicKey: this._publicKey - // }) + + queue.push(() => replaceMessagesEdited({ + decodedMessages: decodeMsgs, + parentEpml, + isReceipient: this.isReceipient, + decodeMessageFunc: this.decodeMessage, + _publicKey: this._publicKey, + addToUpdateMessageHashmap: this.addToUpdateMessageHashmap + })); this.messagesRendered = [...decodeMsgs, ...this.messagesRendered].sort(function (a, b) { return a.timestamp @@ -2668,6 +2684,27 @@ class ChatPage extends LitElement { } } + async addToUpdateMessageHashmap(array){ + console.log({array}) + const viewElement = this.shadowRoot.querySelector('chat-scroller').shadowRoot.getElementById('viewElement') + const originalScrollTop = viewElement.scrollTop; +const originalScrollHeight = viewElement.scrollHeight; + + const newObj = {} + + array.forEach((item)=> { + const signature = item.originalSignature || item.signature + newObj[signature] = item + }) + this.updateMessageHash = { + ...this.updateMessageHash, + ...newObj + } + await this.getUpdateComplete() + const heightDifference = viewElement.scrollHeight - originalScrollHeight; +viewElement.scrollTop = originalScrollTop + heightDifference; + } + async getOldMessageAfter(scrollElement) { if (this.isReceipient) { const getInitialMessages = await parentEpml.request('apiCall', { @@ -2679,13 +2716,15 @@ class ChatPage extends LitElement { return this.decodeMessage(eachMessage) }) - // const replacedMessages = await replaceMessagesEdited({ - // decodedMessages: decodeMsgs, - // parentEpml, - // isReceipient: this.isReceipient, - // decodeMessageFunc: this.decodeMessage, - // _publicKey: this._publicKey - // }) + + queue.push(() => replaceMessagesEdited({ + decodedMessages: decodeMsgs, + parentEpml, + isReceipient: this.isReceipient, + decodeMessageFunc: this.decodeMessage, + _publicKey: this._publicKey, + addToUpdateMessageHashmap: this.addToUpdateMessageHashmap + })); this.messagesRendered = [...this.messagesRendered, ...decodeMsgs].sort(function (a, b) { return a.timestamp @@ -2711,13 +2750,15 @@ class ChatPage extends LitElement { return this.decodeMessage(eachMessage) }) - // const replacedMessages = await replaceMessagesEdited({ - // decodedMessages: decodeMsgs, - // parentEpml, - // isReceipient: this.isReceipient, - // decodeMessageFunc: this.decodeMessage, - // _publicKey: this._publicKey - // }) + + queue.push(() => replaceMessagesEdited({ + decodedMessages: decodeMsgs, + parentEpml, + isReceipient: this.isReceipient, + decodeMessageFunc: this.decodeMessage, + _publicKey: this._publicKey, + addToUpdateMessageHashmap: this.addToUpdateMessageHashmap + })); this.messagesRendered = [...this.messagesRendered, ...decodeMsgs].sort(function (a, b) { return a.timestamp @@ -2751,13 +2792,21 @@ class ChatPage extends LitElement { }) if (isInitial) { this.chatEditorPlaceholder = await this.renderPlaceholder() - // const replacedMessages = await replaceMessagesEdited({ - // decodedMessages: decodedMessages, - // parentEpml, - // isReceipient: isReceipient, - // decodeMessageFunc: this.decodeMessage, - // _publicKey: this._publicKey - // }) + + + try { + queue.push(() => replaceMessagesEdited({ + decodedMessages: decodedMessages, + parentEpml, + isReceipient: isReceipient, + decodeMessageFunc: this.decodeMessage, + _publicKey: this._publicKey, + addToUpdateMessageHashmap: this.addToUpdateMessageHashmap + })); + } catch (error) { + console.log({error}) + } + this._messages = decodedMessages.sort(function (a, b) { return a.timestamp @@ -2771,14 +2820,17 @@ class ChatPage extends LitElement { setTimeout(() => this.downElementObserver(), 500) } else { - // const replacedMessages = await replaceMessagesEdited({ - // decodedMessages: decodedMessages, - // parentEpml, - // isReceipient: isReceipient, - // decodeMessageFunc: this.decodeMessage, - // _publicKey: this._publicKey, - // isNotInitial: true - // }) + + + queue.push(() => replaceMessagesEdited({ + decodedMessages: decodedMessages, + parentEpml, + isReceipient: isReceipient, + decodeMessageFunc: this.decodeMessage, + _publicKey: this._publicKey, + isNotInitial: true, + addToUpdateMessageHashmap: this.addToUpdateMessageHashmap + })); const renderEachMessage = decodedMessages.map(async (msg) => { await this.renderNewMessage(msg) diff --git a/plugins/plugins/core/components/ChatScroller.js b/plugins/plugins/core/components/ChatScroller.js index f5ee24de..01b4b92e 100644 --- a/plugins/plugins/core/components/ChatScroller.js +++ b/plugins/plugins/core/components/ChatScroller.js @@ -196,7 +196,8 @@ class ChatScroller extends LitElement { selectedHead: { type: Object }, goToRepliedMessage: { attribute: false }, getOldMessageAfter: { attribute: false }, - listSeenMessages: { type: Array } + listSeenMessages: { type: Array }, + updateMessageHash: {type: Object} } } @@ -223,6 +224,10 @@ class ChatScroller extends LitElement { render() { let formattedMessages = this.messages.reduce((messageArray, message, index) => { + if(this.updateMessageHash[message.signature]){ + + message = this.updateMessageHash[message.signature] + } const lastGroupedMessage = messageArray[messageArray.length - 1] let timestamp let sender @@ -319,6 +324,9 @@ class ChatScroller extends LitElement { if (changedProperties.has('userName')) { return true } + if (changedProperties.has('updateMessageHash')) { + return true + } // Only update element if prop1 changed. return changedProperties.has('messages') } diff --git a/plugins/plugins/core/components/LevelFounder.js b/plugins/plugins/core/components/LevelFounder.js index e2e4c1eb..3c8224e9 100644 --- a/plugins/plugins/core/components/LevelFounder.js +++ b/plugins/plugins/core/components/LevelFounder.js @@ -101,7 +101,6 @@ class LevelFounder extends LitElement { } firstUpdated() { - console.log('levelFounder') parentEpml.ready().then(() => { parentEpml.subscribe('selected_address', async selectedAddress => { diff --git a/plugins/plugins/core/messaging/q-chat/q-chat.src.js b/plugins/plugins/core/messaging/q-chat/q-chat.src.js index 1f60e079..7ef7d494 100644 --- a/plugins/plugins/core/messaging/q-chat/q-chat.src.js +++ b/plugins/plugins/core/messaging/q-chat/q-chat.src.js @@ -465,6 +465,7 @@ class Chat extends LitElement { url: `/addresses/balance/${window.parent.reduxStore.getState().app.selectedAddress.address}` }).then(res => { this.balance = res + this.requestUpdate() }) }) parentEpml.imReady() @@ -834,6 +835,7 @@ class Chat extends LitElement { chatId=${this.activeChatHeadUrl} .setOpenPrivateMessage=${(val) => this.setOpenPrivateMessage(val)} .setActiveChatHeadUrl=${(val)=> this.setActiveChatHeadUrl(val)} + balance=${this.balance} > ` diff --git a/plugins/plugins/utils/queue.js b/plugins/plugins/utils/queue.js new file mode 100644 index 00000000..d6130f8c --- /dev/null +++ b/plugins/plugins/utils/queue.js @@ -0,0 +1,34 @@ +export class RequestQueue { + constructor(maxConcurrent = 5) { + this.queue = []; + this.maxConcurrent = maxConcurrent; + this.currentConcurrent = 0; + } + + push(request) { + return new Promise((resolve, reject) => { + this.queue.push({ + request, + resolve, + reject, + }); + this.checkQueue(); + }); + } + + checkQueue() { + if (this.queue.length === 0 || this.currentConcurrent >= this.maxConcurrent) return; + + const { request, resolve, reject } = this.queue.shift(); + this.currentConcurrent++; + + request() + .then(resolve) + .catch(reject) + .finally(() => { + this.currentConcurrent--; + this.checkQueue(); + }); + } +} + diff --git a/plugins/plugins/utils/replace-messages-edited.js b/plugins/plugins/utils/replace-messages-edited.js index ad815fe8..d27364d4 100644 --- a/plugins/plugins/utils/replace-messages-edited.js +++ b/plugins/plugins/utils/replace-messages-edited.js @@ -1,11 +1,161 @@ +// export const replaceMessagesEdited = async ({ +// decodedMessages, +// parentEpml, +// isReceipient, +// decodeMessageFunc, +// _publicKey, +// addToUpdateMessageHashmap +// }) => { +// const findNewMessages = decodedMessages.map(async (msg) => { +// let msgItem = null +// try { +// let msgQuery = `&involving=${msg.recipient}&involving=${msg.sender}` +// if (!isReceipient) { +// msgQuery = `&txGroupId=${msg.txGroupId}` +// } +// const response = await parentEpml.request("apiCall", { +// type: "api", +// url: `/chat/messages?chatreference=${msg.signature}&reverse=true${msgQuery}&limit=1&sender=${msg.sender}&encoding=BASE64`, +// }) + +// if (response && Array.isArray(response) && response.length !== 0) { +// let responseItem = { ...response[0] } +// const decodeResponseItem = decodeMessageFunc(responseItem, isReceipient, _publicKey) +// delete decodeResponseItem.timestamp + +// msgItem = { +// ...msg, +// ...decodeResponseItem, +// senderName: msg.senderName, +// sender: msg.sender, +// editedTimestamp: response[0].timestamp, +// originalSignature: msg.signature +// } +// } +// } catch (error) { +// } + +// return msgItem +// }) +// const updateMessages = await Promise.all(findNewMessages) +// const filterOutNull = updateMessages.filter((item)=> item !== 'null' && item !== null) + +// const findNewMessages2 = filterOutNull.map(async (msg) => { +// let parsedMessageObj = msg +// try { +// parsedMessageObj = JSON.parse(msg.decodedMessage) +// } catch (error) { +// return msg +// } +// let msgItem = msg +// try { +// let msgQuery = `&involving=${msg.recipient}&involving=${msg.sender}` +// if (!isReceipient) { +// msgQuery = `&txGroupId=${msg.txGroupId}` +// } +// if (parsedMessageObj.repliedTo) { +// let originalReply +// if(+parsedMessageObj.version > 2){ +// originalReply = await parentEpml.request("apiCall", { +// type: "api", +// url: `/chat/message/${parsedMessageObj.repliedTo}?encoding=BASE64`, +// }) +// } +// if(+parsedMessageObj.version < 3){ +// originalReply = await parentEpml.request("apiCall", { +// type: "api", +// url: `/chat/messages?reference=${parsedMessageObj.repliedTo}&reverse=true${msgQuery}&encoding=BASE64`, +// }) +// } + + + + +// const originalReplyMessage = originalReply.timestamp ? originalReply : originalReply.length !== 0 ? originalReply[0] : null + +// const response = await parentEpml.request("apiCall", { +// type: "api", +// url: `/chat/messages?chatreference=${parsedMessageObj.repliedTo}&reverse=true${msgQuery}&limit=1&sender=${originalReplyMessage.sender}&encoding=BASE64`, +// }) + +// if ( +// originalReplyMessage && +// response && +// Array.isArray(response) && +// response.length !== 0 +// ) { +// const decodeOriginalReply = decodeMessageFunc(originalReplyMessage, isReceipient, _publicKey) + +// const decodeUpdatedReply = decodeMessageFunc(response[0], isReceipient, _publicKey) +// const formattedRepliedToData = { +// ...decodeUpdatedReply, +// senderName: decodeOriginalReply.senderName, +// sender: decodeOriginalReply.sender, +// } +// msgItem = { +// ...msg, +// repliedToData: formattedRepliedToData, +// } +// } else { + + +// if ( +// originalReplyMessage +// ) { + +// msgItem = { +// ...msg, +// repliedToData: decodeMessageFunc(originalReplyMessage, isReceipient, _publicKey), +// } +// } +// } +// } +// } catch (error) { +// } + +// return msgItem +// }) +// const updateMessages2 = await Promise.all(findNewMessages2) +// console.log({updateMessages2}) +// updateMessages2.forEach((item)=> { +// addToUpdateMessageHashmap(item.originalSignature, item) +// }) +// return updateMessages2 +// } + + export const replaceMessagesEdited = async ({ - decodedMessages, - parentEpml, + decodedMessages, + parentEpml, isReceipient, decodeMessageFunc, - _publicKey + _publicKey, + addToUpdateMessageHashmap }) => { - const findNewMessages = decodedMessages.map(async (msg) => { + const MAX_CONCURRENT_REQUESTS = 5; // Maximum number of concurrent requests + + const executeWithConcurrencyLimit = async (array, asyncFn) => { + const results = []; + const concurrencyPool = []; + + for (const item of array) { + const promise = asyncFn(item); + concurrencyPool.push(promise); + + if (concurrencyPool.length >= MAX_CONCURRENT_REQUESTS) { + results.push(...await Promise.all(concurrencyPool)); + concurrencyPool.length = 0; // Clear the concurrency pool + } + } + + if (concurrencyPool.length > 0) { + results.push(...await Promise.all(concurrencyPool)); + } + + return results; + }; + + const findNewMessages = async (msg) => { let msgItem = msg try { let msgQuery = `&involving=${msg.recipient}&involving=${msg.sender}` @@ -28,15 +178,16 @@ export const replaceMessagesEdited = async ({ senderName: msg.senderName, sender: msg.sender, editedTimestamp: response[0].timestamp, + originalSignature: msg.signature } } } catch (error) { } return msgItem - }) - const updateMessages = await Promise.all(findNewMessages) - const findNewMessages2 = updateMessages.map(async (msg) => { + }; + + const findNewMessages2 = async (msg) => { let parsedMessageObj = msg try { parsedMessageObj = JSON.parse(msg.decodedMessage) @@ -74,6 +225,7 @@ export const replaceMessagesEdited = async ({ url: `/chat/messages?chatreference=${parsedMessageObj.repliedTo}&reverse=true${msgQuery}&limit=1&sender=${originalReplyMessage.sender}&encoding=BASE64`, }) + if ( originalReplyMessage && response && @@ -98,7 +250,6 @@ export const replaceMessagesEdited = async ({ if ( originalReplyMessage ) { - msgItem = { ...msg, repliedToData: decodeMessageFunc(originalReplyMessage, isReceipient, _publicKey), @@ -109,9 +260,20 @@ export const replaceMessagesEdited = async ({ } catch (error) { } - return msgItem - }) - const updateMessages2 = await Promise.all(findNewMessages2) - return updateMessages2 -} + return msgItem + }; + + + + const sortedMessages = decodedMessages.sort((a, b) => b.timestamp - a.timestamp); + + // Execute the functions with concurrency limit + const updateMessages = await executeWithConcurrencyLimit(sortedMessages, findNewMessages); + const updateMessages2 = await executeWithConcurrencyLimit(updateMessages.filter(item => item !== 'null' && item !== null), findNewMessages2); + + addToUpdateMessageHashmap(updateMessages2) + + + return updateMessages2; +}; From adfe69cce3f220af6838ee3a6cce13bee9d322fd Mon Sep 17 00:00:00 2001 From: PhilReact Date: Sun, 3 Sep 2023 19:17:15 -0700 Subject: [PATCH 03/57] ability to fetch after and before, max 100 msgs --- plugins/plugins/core/components/ChatPage.js | 81 ++++++++++++++++++- .../plugins/core/components/ChatScroller.js | 14 ++-- 2 files changed, 86 insertions(+), 9 deletions(-) diff --git a/plugins/plugins/core/components/ChatPage.js b/plugins/plugins/core/components/ChatPage.js index cd485c1a..0a094713 100644 --- a/plugins/plugins/core/components/ChatPage.js +++ b/plugins/plugins/core/components/ChatPage.js @@ -1354,6 +1354,7 @@ class ChatPage extends LitElement { this.theme = localStorage.getItem('qortalTheme') ? localStorage.getItem('qortalTheme') : 'light' this.updateMessageHash = {} this.addToUpdateMessageHashmap = this.addToUpdateMessageHashmap.bind(this) + this.getAfterMessages = this.getAfterMessages.bind(this) } setOpenGifModal(value) { @@ -2488,6 +2489,7 @@ class ChatPage extends LitElement { .messages=${this.messagesRendered} .escapeHTML=${escape} .getOldMessage=${this.getOldMessage} + .getAfterMessages=${this.getAfterMessages} .setRepliedToMessageObj=${(val) => this.setRepliedToMessageObj(val)} .setEditedMessageObj=${(val) => this.setEditedMessageObj(val)} .sendMessage=${(val) => this._sendMessage(val)} @@ -2630,8 +2632,8 @@ class ChatPage extends LitElement { _publicKey: this._publicKey, addToUpdateMessageHashmap: this.addToUpdateMessageHashmap })); - - this.messagesRendered = [...decodeMsgs, ...this.messagesRendered].sort(function (a, b) { + const lengthOfExistingMsgs = this.messagesRendered + this.messagesRendered = [...decodeMsgs, ...this.messagesRendered.slice(0, 80)].sort(function (a, b) { return a.timestamp - b.timestamp }) @@ -2665,7 +2667,79 @@ class ChatPage extends LitElement { addToUpdateMessageHashmap: this.addToUpdateMessageHashmap })); - this.messagesRendered = [...decodeMsgs, ...this.messagesRendered].sort(function (a, b) { + this.messagesRendered = [...decodeMsgs, ...this.messagesRendered.slice(0,80)].sort(function (a, b) { + return a.timestamp + - b.timestamp + }) + + this.isLoadingOldMessages = false + await this.getUpdateComplete() + const marginElements = Array.from(this.shadowRoot.querySelector('chat-scroller').shadowRoot.querySelectorAll('message-template')) + const findElement = marginElements.find((item) => item.messageObj.signature === scrollElement.messageObj.signature) + + if (findElement) { + findElement.scrollIntoView({ behavior: 'auto', block: 'center' }) + } + } + } + async getAfterMessages(scrollElement) { + const firstMsg = this.messagesRendered.at(-1) + const timestamp = scrollElement.messageObj.timestamp + + if (this.isReceipient) { + const getInitialMessages = await parentEpml.request('apiCall', { + type: 'api', + url: `/chat/messages?involving=${window.parent.reduxStore.getState().app.selectedAddress.address}&involving=${this._chatId}&limit=20&reverse=true&after=${timestamp}&haschatreference=false&encoding=BASE64` + }) + + const decodeMsgs = getInitialMessages.map((eachMessage) => { + return this.decodeMessage(eachMessage) + }) + + + queue.push(() => replaceMessagesEdited({ + decodedMessages: decodeMsgs, + parentEpml, + isReceipient: this.isReceipient, + decodeMessageFunc: this.decodeMessage, + _publicKey: this._publicKey, + addToUpdateMessageHashmap: this.addToUpdateMessageHashmap + })); + this.messagesRendered = [ ...this.messagesRendered.slice(-80), ...decodeMsgs].sort(function (a, b) { + return a.timestamp + - b.timestamp + }) + + this.isLoadingOldMessages = false + await this.getUpdateComplete() + const marginElements = Array.from(this.shadowRoot.querySelector('chat-scroller').shadowRoot.querySelectorAll('message-template')) + + const findElement = marginElements.find((item) => item.messageObj.signature === scrollElement.messageObj.signature) + + if (findElement) { + findElement.scrollIntoView({ behavior: 'auto', block: 'center' }) + } + } else { + const getInitialMessages = await parentEpml.request('apiCall', { + type: 'api', + url: `/chat/messages?txGroupId=${Number(this._chatId)}&limit=20&reverse=true&after=${timestamp}&haschatreference=false&encoding=BASE64` + }) + + const decodeMsgs = getInitialMessages.map((eachMessage) => { + return this.decodeMessage(eachMessage) + }) + + + queue.push(() => replaceMessagesEdited({ + decodedMessages: decodeMsgs, + parentEpml, + isReceipient: this.isReceipient, + decodeMessageFunc: this.decodeMessage, + _publicKey: this._publicKey, + addToUpdateMessageHashmap: this.addToUpdateMessageHashmap + })); + + this.messagesRendered = [...this.messagesRendered.slice(-80), ...decodeMsgs].sort(function (a, b) { return a.timestamp - b.timestamp }) @@ -2682,7 +2756,6 @@ class ChatPage extends LitElement { } async addToUpdateMessageHashmap(array){ - console.log({array}) const viewElement = this.shadowRoot.querySelector('chat-scroller').shadowRoot.getElementById('viewElement') const originalScrollTop = viewElement.scrollTop; const originalScrollHeight = viewElement.scrollHeight; diff --git a/plugins/plugins/core/components/ChatScroller.js b/plugins/plugins/core/components/ChatScroller.js index 7495441b..d5fec678 100644 --- a/plugins/plugins/core/components/ChatScroller.js +++ b/plugins/plugins/core/components/ChatScroller.js @@ -172,6 +172,7 @@ class ChatScroller extends LitElement { theme: { type: String, reflect: true }, getNewMessage: { attribute: false }, getOldMessage: { attribute: false }, + getAfterMessages: {attribute: false}, escapeHTML: { attribute: false }, messages: { type: Array }, hideMessages: { type: Array }, @@ -304,6 +305,8 @@ class ChatScroller extends LitElement { ) })}
+ + ` } @@ -399,6 +402,9 @@ class ChatScroller extends LitElement { _getOldMessage(_scrollElement) { this.getOldMessage(_scrollElement) } + _getAfterMessages(_scrollElement) { + this.getAfterMessages(_scrollElement) + } _getOldMessageAfter(_scrollElement) { this.getOldMessageAfter(_scrollElement) @@ -417,11 +423,11 @@ class ChatScroller extends LitElement { _downObserverHandler(entries) { if (!entries[0].isIntersecting) { - let _scrollElement = entries[0].target.previousElementSibling - // this._getOldMessageAfter(_scrollElement) this.showLastMessageRefScroller(true) } else { + let _scrollElement = entries[0].target.previousElementSibling this.showLastMessageRefScroller(false) + this._getAfterMessages(_scrollElement) } } @@ -437,9 +443,7 @@ class ChatScroller extends LitElement { downElementObserver() { const options = { - root: this.viewElement, - rootMargin: '0px', - threshold: 1 + } // identify an element to observe const elementToObserve = this.downObserverElement From 8de559c955136943ce75b106ed072b7b5a6a67f4 Mon Sep 17 00:00:00 2001 From: PhilReact Date: Sun, 3 Sep 2023 19:36:58 -0700 Subject: [PATCH 04/57] use new observer --- .../plugins/core/components/ChatScroller.js | 26 +++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/plugins/plugins/core/components/ChatScroller.js b/plugins/plugins/core/components/ChatScroller.js index d5fec678..a78a0b3f 100644 --- a/plugins/plugins/core/components/ChatScroller.js +++ b/plugins/plugins/core/components/ChatScroller.js @@ -211,6 +211,7 @@ class ChatScroller extends LitElement { this.messages = [] this._upObserverhandler = this._upObserverhandler.bind(this) this._downObserverHandler = this._downObserverHandler.bind(this) + this.__bottomObserverForFetchingMessagesHandler = this.__bottomObserverForFetchingMessagesHandler.bind(this) this.myAddress = window.parent.reduxStore.getState().app.selectedAddress.address this.hideMessages = JSON.parse(localStorage.getItem("MessageBlockedAddresses") || "[]") this.openTipUser = false @@ -304,8 +305,9 @@ class ChatScroller extends LitElement { ` ) })} +
+
- ` @@ -369,9 +371,11 @@ class ChatScroller extends LitElement { this.viewElement = this.shadowRoot.getElementById('viewElement') this.upObserverElement = this.shadowRoot.getElementById('upObserver') this.downObserverElement = this.shadowRoot.getElementById('downObserver') + this.bottomObserverForFetchingMessages = this.shadowRoot.getElementById('bottomObserverForFetchingMessages') // Intialize Observers this.upElementObserver() this.downElementObserver() + this.bottomObserver() await this.getUpdateComplete() this.viewElement.scrollTop = this.viewElement.scrollHeight + 50 @@ -425,8 +429,14 @@ class ChatScroller extends LitElement { if (!entries[0].isIntersecting) { this.showLastMessageRefScroller(true) } else { - let _scrollElement = entries[0].target.previousElementSibling this.showLastMessageRefScroller(false) + } + } + + __bottomObserverForFetchingMessagesHandler(entries){ + if (!entries[0].isIntersecting) { + } else { + let _scrollElement = entries[0].target.previousElementSibling this._getAfterMessages(_scrollElement) } } @@ -453,6 +463,18 @@ class ChatScroller extends LitElement { // passing it the element to observe, and the options object observer.observe(elementToObserve) } + bottomObserver() { + const options = { + + } + // identify an element to observe + const elementToObserve = this.bottomObserverForFetchingMessages + // passing it a callback function + const observer = new IntersectionObserver(this.__bottomObserverForFetchingMessagesHandler, options) + // call `observe()` on that MutationObserver instance, + // passing it the element to observe, and the options object + observer.observe(elementToObserve) + } } window.customElements.define('chat-scroller', ChatScroller) From 61f92f454c1770043594c56a9c8ecfba2beeaedf Mon Sep 17 00:00:00 2001 From: PhilReact Date: Tue, 5 Sep 2023 22:19:50 -0500 Subject: [PATCH 05/57] add sorting to webworker --- plugins/plugins/core/components/ChatPage.js | 100 +- .../plugins/core/components/ChatScroller.js | 1 + .../components/webworkerDecodeMessages.js | 2853 +++++++++++++++++ .../core/components/webworkerSortMessages.js | 15 + .../core/messaging/q-chat/q-chat.src.js | 15 +- 5 files changed, 2960 insertions(+), 24 deletions(-) create mode 100644 plugins/plugins/core/components/webworkerDecodeMessages.js create mode 100644 plugins/plugins/core/components/webworkerSortMessages.js diff --git a/plugins/plugins/core/components/ChatPage.js b/plugins/plugins/core/components/ChatPage.js index 0a094713..50313737 100644 --- a/plugins/plugins/core/components/ChatPage.js +++ b/plugins/plugins/core/components/ChatPage.js @@ -20,6 +20,9 @@ import Placeholder from '@tiptap/extension-placeholder' import Highlight from '@tiptap/extension-highlight' import WebWorker from 'web-worker:./computePowWorker.js' import WebWorkerFile from 'web-worker:./computePowWorkerFile.js' +import WebWorkerSortMessages from 'web-worker:./webworkerSortMessages.js' +import WebWorkerDecodeMessages from 'web-worker:./webworkerDecodeMessages.js' + import ShortUniqueId from 'short-unique-id' import Compressor from 'compressorjs' @@ -1340,6 +1343,8 @@ class ChatPage extends LitElement { } this.webWorker = null this.webWorkerFile = null + this.webWorkerSortMessages = null + this.webWorkerDecodeMessages = null this.currentEditor = '_chatEditorDOM' this.initialChat = this.initialChat.bind(this) this.setOpenGifModal = this.setOpenGifModal.bind(this) @@ -1401,6 +1406,7 @@ class ChatPage extends LitElement { } render() { + console.log('chatpage') return html`
{ - parentEpml.subscribe('selected_address', async selectedAddress => { - this.selectedAddress = {} - selectedAddress = JSON.parse(selectedAddress) - if (!selectedAddress || Object.entries(selectedAddress).length === 0) return - this.selectedAddress = selectedAddress - }) + // parentEpml.ready().then(() => { + // parentEpml.subscribe('selected_address', async selectedAddress => { + // this.selectedAddress = {} + // selectedAddress = JSON.parse(selectedAddress) + // if (!selectedAddress || Object.entries(selectedAddress).length === 0) return + // this.selectedAddress = selectedAddress + // }) - }) + // }) parentEpml.imReady() const isEnabledChatEnter = localStorage.getItem('isEnabledChatEnter') @@ -2437,6 +2451,19 @@ class ChatPage extends LitElement { } } + + shouldUpdate(changedProperties) { + if (changedProperties.has('setActiveChatHeadUrl')) { + return false + } + if (changedProperties.has('setOpenPrivateMessage')) { + return false + } + + return true + + } + async getName(recipient) { try { const getNames = await parentEpml.request("apiCall", { @@ -2653,10 +2680,29 @@ class ChatPage extends LitElement { url: `/chat/messages?txGroupId=${Number(this._chatId)}&limit=20&reverse=true&before=${scrollElement.messageObj.timestamp}&haschatreference=false&encoding=BASE64` }) - const decodeMsgs = getInitialMessages.map((eachMessage) => { - return this.decodeMessage(eachMessage) - }) + let decodeMsgs = [] + try { + await new Promise((res, rej) => { + console.log('this.webWorkerDecodeMessages2.', this.webWorkerDecodeMessages) + this.webWorkerDecodeMessages.postMessage({messages: getInitialMessages, isReceipient: this.isReceipient, _publicKey: this._publicKey, privateKey: window.parent.reduxStore.getState().app.selectedAddress.keyPair.privateKey }); + + this.webWorkerDecodeMessages.onmessage = e => { + decodeMsgs = e.data + res() + + } + this.webWorkerDecodeMessages.onerror = e => { + console.log('e',e) + rej() + + } + }) + + } catch (error) { + console.log({error}) + } + queue.push(() => replaceMessagesEdited({ decodedMessages: decodeMsgs, @@ -2666,12 +2712,25 @@ class ChatPage extends LitElement { _publicKey: this._publicKey, addToUpdateMessageHashmap: this.addToUpdateMessageHashmap })); - - this.messagesRendered = [...decodeMsgs, ...this.messagesRendered.slice(0,80)].sort(function (a, b) { - return a.timestamp - - b.timestamp - }) - + let list = [...decodeMsgs, ...this.messagesRendered.slice(0,80)] + // this.messagesRendered = [...decodeMsgs, ...this.messagesRendered.slice(0,80)].sort(function (a, b) { + // return a.timestamp + // - b.timestamp + // }) + await new Promise((res, rej) => { + + this.webWorkerSortMessages.postMessage({list}); + + this.webWorkerSortMessages.onmessage = e => { + console.log('e',e) + + list = e.data + res() + + } + }) + console.log({list}) + this.messagesRendered = list this.isLoadingOldMessages = false await this.getUpdateComplete() const marginElements = Array.from(this.shadowRoot.querySelector('chat-scroller').shadowRoot.querySelectorAll('message-template')) @@ -2685,6 +2744,7 @@ class ChatPage extends LitElement { async getAfterMessages(scrollElement) { const firstMsg = this.messagesRendered.at(-1) const timestamp = scrollElement.messageObj.timestamp + console.log('getAfterMessages') if (this.isReceipient) { const getInitialMessages = await parentEpml.request('apiCall', { @@ -2756,6 +2816,9 @@ class ChatPage extends LitElement { } async addToUpdateMessageHashmap(array){ + console.log({array}) + const chatscrollerEl = this.shadowRoot.querySelector('chat-scroller') + if(!chatscrollerEl) return const viewElement = this.shadowRoot.querySelector('chat-scroller').shadowRoot.getElementById('viewElement') const originalScrollTop = viewElement.scrollTop; const originalScrollHeight = viewElement.scrollHeight; @@ -3013,6 +3076,7 @@ viewElement.scrollTop = originalScrollTop + heightDifference; isReceipientVar = isReceipient _publicKeyVar = _publicKey } + console.log({_publicKeyVar}) let decodedMessageObj = {} @@ -4088,6 +4152,8 @@ viewElement.scrollTop = originalScrollTop + heightDifference; } downElementObserver() { + const chatscrollerEl = this.shadowRoot.querySelector('chat-scroller') + if(!chatscrollerEl) return const downObserver = this.shadowRoot.querySelector('chat-scroller').shadowRoot.getElementById('downObserver') const options = { diff --git a/plugins/plugins/core/components/ChatScroller.js b/plugins/plugins/core/components/ChatScroller.js index a78a0b3f..a3afe95d 100644 --- a/plugins/plugins/core/components/ChatScroller.js +++ b/plugins/plugins/core/components/ChatScroller.js @@ -631,6 +631,7 @@ class MessageTemplate extends LitElement { } render() { + const hidemsg = this.hideMessages let message = "" let messageVersion2 = "" diff --git a/plugins/plugins/core/components/webworkerDecodeMessages.js b/plugins/plugins/core/components/webworkerDecodeMessages.js new file mode 100644 index 00000000..36883f9f --- /dev/null +++ b/plugins/plugins/core/components/webworkerDecodeMessages.js @@ -0,0 +1,2853 @@ +import { Sha256 } from 'asmcrypto.js'; +const nacl = {} +//(function(nacl) { +'use strict'; + +// Ported in 2014 by Dmitry Chestnykh and Devi Mandiri. +// Public domain. +// +// Implementation derived from TweetNaCl version 20140427. +// See for details: http://tweetnacl.cr.yp.to/ + +var gf = function(init) { + var i, r = new Float64Array(16); + if (init) for (i = 0; i < init.length; i++) r[i] = init[i]; + return r; +}; + +// Pluggable, initialized in high-level API below. +var randombytes = function(/* x, n */) { throw new Error('no PRNG'); }; + +var _0 = new Uint8Array(16); +var _9 = new Uint8Array(32); _9[0] = 9; + +var gf0 = gf(), + gf1 = gf([1]), + _121665 = gf([0xdb41, 1]), + D = gf([0x78a3, 0x1359, 0x4dca, 0x75eb, 0xd8ab, 0x4141, 0x0a4d, 0x0070, 0xe898, 0x7779, 0x4079, 0x8cc7, 0xfe73, 0x2b6f, 0x6cee, 0x5203]), + D2 = gf([0xf159, 0x26b2, 0x9b94, 0xebd6, 0xb156, 0x8283, 0x149a, 0x00e0, 0xd130, 0xeef3, 0x80f2, 0x198e, 0xfce7, 0x56df, 0xd9dc, 0x2406]), + X = gf([0xd51a, 0x8f25, 0x2d60, 0xc956, 0xa7b2, 0x9525, 0xc760, 0x692c, 0xdc5c, 0xfdd6, 0xe231, 0xc0a4, 0x53fe, 0xcd6e, 0x36d3, 0x2169]), + Y = gf([0x6658, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666]), + I = gf([0xa0b0, 0x4a0e, 0x1b27, 0xc4ee, 0xe478, 0xad2f, 0x1806, 0x2f43, 0xd7a7, 0x3dfb, 0x0099, 0x2b4d, 0xdf0b, 0x4fc1, 0x2480, 0x2b83]); + +function ts64(x, i, h, l) { + x[i] = (h >> 24) & 0xff; + x[i+1] = (h >> 16) & 0xff; + x[i+2] = (h >> 8) & 0xff; + x[i+3] = h & 0xff; + x[i+4] = (l >> 24) & 0xff; + x[i+5] = (l >> 16) & 0xff; + x[i+6] = (l >> 8) & 0xff; + x[i+7] = l & 0xff; +} + +function vn(x, xi, y, yi, n) { + var i,d = 0; + for (i = 0; i < n; i++) d |= x[xi+i]^y[yi+i]; + return (1 & ((d - 1) >>> 8)) - 1; +} + +function crypto_verify_16(x, xi, y, yi) { + return vn(x,xi,y,yi,16); +} + +function crypto_verify_32(x, xi, y, yi) { + return vn(x,xi,y,yi,32); +} + +function core_salsa20(o, p, k, c) { + var j0 = c[ 0] & 0xff | (c[ 1] & 0xff)<<8 | (c[ 2] & 0xff)<<16 | (c[ 3] & 0xff)<<24, + j1 = k[ 0] & 0xff | (k[ 1] & 0xff)<<8 | (k[ 2] & 0xff)<<16 | (k[ 3] & 0xff)<<24, + j2 = k[ 4] & 0xff | (k[ 5] & 0xff)<<8 | (k[ 6] & 0xff)<<16 | (k[ 7] & 0xff)<<24, + j3 = k[ 8] & 0xff | (k[ 9] & 0xff)<<8 | (k[10] & 0xff)<<16 | (k[11] & 0xff)<<24, + j4 = k[12] & 0xff | (k[13] & 0xff)<<8 | (k[14] & 0xff)<<16 | (k[15] & 0xff)<<24, + j5 = c[ 4] & 0xff | (c[ 5] & 0xff)<<8 | (c[ 6] & 0xff)<<16 | (c[ 7] & 0xff)<<24, + j6 = p[ 0] & 0xff | (p[ 1] & 0xff)<<8 | (p[ 2] & 0xff)<<16 | (p[ 3] & 0xff)<<24, + j7 = p[ 4] & 0xff | (p[ 5] & 0xff)<<8 | (p[ 6] & 0xff)<<16 | (p[ 7] & 0xff)<<24, + j8 = p[ 8] & 0xff | (p[ 9] & 0xff)<<8 | (p[10] & 0xff)<<16 | (p[11] & 0xff)<<24, + j9 = p[12] & 0xff | (p[13] & 0xff)<<8 | (p[14] & 0xff)<<16 | (p[15] & 0xff)<<24, + j10 = c[ 8] & 0xff | (c[ 9] & 0xff)<<8 | (c[10] & 0xff)<<16 | (c[11] & 0xff)<<24, + j11 = k[16] & 0xff | (k[17] & 0xff)<<8 | (k[18] & 0xff)<<16 | (k[19] & 0xff)<<24, + j12 = k[20] & 0xff | (k[21] & 0xff)<<8 | (k[22] & 0xff)<<16 | (k[23] & 0xff)<<24, + j13 = k[24] & 0xff | (k[25] & 0xff)<<8 | (k[26] & 0xff)<<16 | (k[27] & 0xff)<<24, + j14 = k[28] & 0xff | (k[29] & 0xff)<<8 | (k[30] & 0xff)<<16 | (k[31] & 0xff)<<24, + j15 = c[12] & 0xff | (c[13] & 0xff)<<8 | (c[14] & 0xff)<<16 | (c[15] & 0xff)<<24; + + var x0 = j0, x1 = j1, x2 = j2, x3 = j3, x4 = j4, x5 = j5, x6 = j6, x7 = j7, + x8 = j8, x9 = j9, x10 = j10, x11 = j11, x12 = j12, x13 = j13, x14 = j14, + x15 = j15, u; + + for (var i = 0; i < 20; i += 2) { + u = x0 + x12 | 0; + x4 ^= u<<7 | u>>>(32-7); + u = x4 + x0 | 0; + x8 ^= u<<9 | u>>>(32-9); + u = x8 + x4 | 0; + x12 ^= u<<13 | u>>>(32-13); + u = x12 + x8 | 0; + x0 ^= u<<18 | u>>>(32-18); + + u = x5 + x1 | 0; + x9 ^= u<<7 | u>>>(32-7); + u = x9 + x5 | 0; + x13 ^= u<<9 | u>>>(32-9); + u = x13 + x9 | 0; + x1 ^= u<<13 | u>>>(32-13); + u = x1 + x13 | 0; + x5 ^= u<<18 | u>>>(32-18); + + u = x10 + x6 | 0; + x14 ^= u<<7 | u>>>(32-7); + u = x14 + x10 | 0; + x2 ^= u<<9 | u>>>(32-9); + u = x2 + x14 | 0; + x6 ^= u<<13 | u>>>(32-13); + u = x6 + x2 | 0; + x10 ^= u<<18 | u>>>(32-18); + + u = x15 + x11 | 0; + x3 ^= u<<7 | u>>>(32-7); + u = x3 + x15 | 0; + x7 ^= u<<9 | u>>>(32-9); + u = x7 + x3 | 0; + x11 ^= u<<13 | u>>>(32-13); + u = x11 + x7 | 0; + x15 ^= u<<18 | u>>>(32-18); + + u = x0 + x3 | 0; + x1 ^= u<<7 | u>>>(32-7); + u = x1 + x0 | 0; + x2 ^= u<<9 | u>>>(32-9); + u = x2 + x1 | 0; + x3 ^= u<<13 | u>>>(32-13); + u = x3 + x2 | 0; + x0 ^= u<<18 | u>>>(32-18); + + u = x5 + x4 | 0; + x6 ^= u<<7 | u>>>(32-7); + u = x6 + x5 | 0; + x7 ^= u<<9 | u>>>(32-9); + u = x7 + x6 | 0; + x4 ^= u<<13 | u>>>(32-13); + u = x4 + x7 | 0; + x5 ^= u<<18 | u>>>(32-18); + + u = x10 + x9 | 0; + x11 ^= u<<7 | u>>>(32-7); + u = x11 + x10 | 0; + x8 ^= u<<9 | u>>>(32-9); + u = x8 + x11 | 0; + x9 ^= u<<13 | u>>>(32-13); + u = x9 + x8 | 0; + x10 ^= u<<18 | u>>>(32-18); + + u = x15 + x14 | 0; + x12 ^= u<<7 | u>>>(32-7); + u = x12 + x15 | 0; + x13 ^= u<<9 | u>>>(32-9); + u = x13 + x12 | 0; + x14 ^= u<<13 | u>>>(32-13); + u = x14 + x13 | 0; + x15 ^= u<<18 | u>>>(32-18); + } + x0 = x0 + j0 | 0; + x1 = x1 + j1 | 0; + x2 = x2 + j2 | 0; + x3 = x3 + j3 | 0; + x4 = x4 + j4 | 0; + x5 = x5 + j5 | 0; + x6 = x6 + j6 | 0; + x7 = x7 + j7 | 0; + x8 = x8 + j8 | 0; + x9 = x9 + j9 | 0; + x10 = x10 + j10 | 0; + x11 = x11 + j11 | 0; + x12 = x12 + j12 | 0; + x13 = x13 + j13 | 0; + x14 = x14 + j14 | 0; + x15 = x15 + j15 | 0; + + o[ 0] = x0 >>> 0 & 0xff; + o[ 1] = x0 >>> 8 & 0xff; + o[ 2] = x0 >>> 16 & 0xff; + o[ 3] = x0 >>> 24 & 0xff; + + o[ 4] = x1 >>> 0 & 0xff; + o[ 5] = x1 >>> 8 & 0xff; + o[ 6] = x1 >>> 16 & 0xff; + o[ 7] = x1 >>> 24 & 0xff; + + o[ 8] = x2 >>> 0 & 0xff; + o[ 9] = x2 >>> 8 & 0xff; + o[10] = x2 >>> 16 & 0xff; + o[11] = x2 >>> 24 & 0xff; + + o[12] = x3 >>> 0 & 0xff; + o[13] = x3 >>> 8 & 0xff; + o[14] = x3 >>> 16 & 0xff; + o[15] = x3 >>> 24 & 0xff; + + o[16] = x4 >>> 0 & 0xff; + o[17] = x4 >>> 8 & 0xff; + o[18] = x4 >>> 16 & 0xff; + o[19] = x4 >>> 24 & 0xff; + + o[20] = x5 >>> 0 & 0xff; + o[21] = x5 >>> 8 & 0xff; + o[22] = x5 >>> 16 & 0xff; + o[23] = x5 >>> 24 & 0xff; + + o[24] = x6 >>> 0 & 0xff; + o[25] = x6 >>> 8 & 0xff; + o[26] = x6 >>> 16 & 0xff; + o[27] = x6 >>> 24 & 0xff; + + o[28] = x7 >>> 0 & 0xff; + o[29] = x7 >>> 8 & 0xff; + o[30] = x7 >>> 16 & 0xff; + o[31] = x7 >>> 24 & 0xff; + + o[32] = x8 >>> 0 & 0xff; + o[33] = x8 >>> 8 & 0xff; + o[34] = x8 >>> 16 & 0xff; + o[35] = x8 >>> 24 & 0xff; + + o[36] = x9 >>> 0 & 0xff; + o[37] = x9 >>> 8 & 0xff; + o[38] = x9 >>> 16 & 0xff; + o[39] = x9 >>> 24 & 0xff; + + o[40] = x10 >>> 0 & 0xff; + o[41] = x10 >>> 8 & 0xff; + o[42] = x10 >>> 16 & 0xff; + o[43] = x10 >>> 24 & 0xff; + + o[44] = x11 >>> 0 & 0xff; + o[45] = x11 >>> 8 & 0xff; + o[46] = x11 >>> 16 & 0xff; + o[47] = x11 >>> 24 & 0xff; + + o[48] = x12 >>> 0 & 0xff; + o[49] = x12 >>> 8 & 0xff; + o[50] = x12 >>> 16 & 0xff; + o[51] = x12 >>> 24 & 0xff; + + o[52] = x13 >>> 0 & 0xff; + o[53] = x13 >>> 8 & 0xff; + o[54] = x13 >>> 16 & 0xff; + o[55] = x13 >>> 24 & 0xff; + + o[56] = x14 >>> 0 & 0xff; + o[57] = x14 >>> 8 & 0xff; + o[58] = x14 >>> 16 & 0xff; + o[59] = x14 >>> 24 & 0xff; + + o[60] = x15 >>> 0 & 0xff; + o[61] = x15 >>> 8 & 0xff; + o[62] = x15 >>> 16 & 0xff; + o[63] = x15 >>> 24 & 0xff; +} + +function core_hsalsa20(o,p,k,c) { + var j0 = c[ 0] & 0xff | (c[ 1] & 0xff)<<8 | (c[ 2] & 0xff)<<16 | (c[ 3] & 0xff)<<24, + j1 = k[ 0] & 0xff | (k[ 1] & 0xff)<<8 | (k[ 2] & 0xff)<<16 | (k[ 3] & 0xff)<<24, + j2 = k[ 4] & 0xff | (k[ 5] & 0xff)<<8 | (k[ 6] & 0xff)<<16 | (k[ 7] & 0xff)<<24, + j3 = k[ 8] & 0xff | (k[ 9] & 0xff)<<8 | (k[10] & 0xff)<<16 | (k[11] & 0xff)<<24, + j4 = k[12] & 0xff | (k[13] & 0xff)<<8 | (k[14] & 0xff)<<16 | (k[15] & 0xff)<<24, + j5 = c[ 4] & 0xff | (c[ 5] & 0xff)<<8 | (c[ 6] & 0xff)<<16 | (c[ 7] & 0xff)<<24, + j6 = p[ 0] & 0xff | (p[ 1] & 0xff)<<8 | (p[ 2] & 0xff)<<16 | (p[ 3] & 0xff)<<24, + j7 = p[ 4] & 0xff | (p[ 5] & 0xff)<<8 | (p[ 6] & 0xff)<<16 | (p[ 7] & 0xff)<<24, + j8 = p[ 8] & 0xff | (p[ 9] & 0xff)<<8 | (p[10] & 0xff)<<16 | (p[11] & 0xff)<<24, + j9 = p[12] & 0xff | (p[13] & 0xff)<<8 | (p[14] & 0xff)<<16 | (p[15] & 0xff)<<24, + j10 = c[ 8] & 0xff | (c[ 9] & 0xff)<<8 | (c[10] & 0xff)<<16 | (c[11] & 0xff)<<24, + j11 = k[16] & 0xff | (k[17] & 0xff)<<8 | (k[18] & 0xff)<<16 | (k[19] & 0xff)<<24, + j12 = k[20] & 0xff | (k[21] & 0xff)<<8 | (k[22] & 0xff)<<16 | (k[23] & 0xff)<<24, + j13 = k[24] & 0xff | (k[25] & 0xff)<<8 | (k[26] & 0xff)<<16 | (k[27] & 0xff)<<24, + j14 = k[28] & 0xff | (k[29] & 0xff)<<8 | (k[30] & 0xff)<<16 | (k[31] & 0xff)<<24, + j15 = c[12] & 0xff | (c[13] & 0xff)<<8 | (c[14] & 0xff)<<16 | (c[15] & 0xff)<<24; + + var x0 = j0, x1 = j1, x2 = j2, x3 = j3, x4 = j4, x5 = j5, x6 = j6, x7 = j7, + x8 = j8, x9 = j9, x10 = j10, x11 = j11, x12 = j12, x13 = j13, x14 = j14, + x15 = j15, u; + + for (var i = 0; i < 20; i += 2) { + u = x0 + x12 | 0; + x4 ^= u<<7 | u>>>(32-7); + u = x4 + x0 | 0; + x8 ^= u<<9 | u>>>(32-9); + u = x8 + x4 | 0; + x12 ^= u<<13 | u>>>(32-13); + u = x12 + x8 | 0; + x0 ^= u<<18 | u>>>(32-18); + + u = x5 + x1 | 0; + x9 ^= u<<7 | u>>>(32-7); + u = x9 + x5 | 0; + x13 ^= u<<9 | u>>>(32-9); + u = x13 + x9 | 0; + x1 ^= u<<13 | u>>>(32-13); + u = x1 + x13 | 0; + x5 ^= u<<18 | u>>>(32-18); + + u = x10 + x6 | 0; + x14 ^= u<<7 | u>>>(32-7); + u = x14 + x10 | 0; + x2 ^= u<<9 | u>>>(32-9); + u = x2 + x14 | 0; + x6 ^= u<<13 | u>>>(32-13); + u = x6 + x2 | 0; + x10 ^= u<<18 | u>>>(32-18); + + u = x15 + x11 | 0; + x3 ^= u<<7 | u>>>(32-7); + u = x3 + x15 | 0; + x7 ^= u<<9 | u>>>(32-9); + u = x7 + x3 | 0; + x11 ^= u<<13 | u>>>(32-13); + u = x11 + x7 | 0; + x15 ^= u<<18 | u>>>(32-18); + + u = x0 + x3 | 0; + x1 ^= u<<7 | u>>>(32-7); + u = x1 + x0 | 0; + x2 ^= u<<9 | u>>>(32-9); + u = x2 + x1 | 0; + x3 ^= u<<13 | u>>>(32-13); + u = x3 + x2 | 0; + x0 ^= u<<18 | u>>>(32-18); + + u = x5 + x4 | 0; + x6 ^= u<<7 | u>>>(32-7); + u = x6 + x5 | 0; + x7 ^= u<<9 | u>>>(32-9); + u = x7 + x6 | 0; + x4 ^= u<<13 | u>>>(32-13); + u = x4 + x7 | 0; + x5 ^= u<<18 | u>>>(32-18); + + u = x10 + x9 | 0; + x11 ^= u<<7 | u>>>(32-7); + u = x11 + x10 | 0; + x8 ^= u<<9 | u>>>(32-9); + u = x8 + x11 | 0; + x9 ^= u<<13 | u>>>(32-13); + u = x9 + x8 | 0; + x10 ^= u<<18 | u>>>(32-18); + + u = x15 + x14 | 0; + x12 ^= u<<7 | u>>>(32-7); + u = x12 + x15 | 0; + x13 ^= u<<9 | u>>>(32-9); + u = x13 + x12 | 0; + x14 ^= u<<13 | u>>>(32-13); + u = x14 + x13 | 0; + x15 ^= u<<18 | u>>>(32-18); + } + + o[ 0] = x0 >>> 0 & 0xff; + o[ 1] = x0 >>> 8 & 0xff; + o[ 2] = x0 >>> 16 & 0xff; + o[ 3] = x0 >>> 24 & 0xff; + + o[ 4] = x5 >>> 0 & 0xff; + o[ 5] = x5 >>> 8 & 0xff; + o[ 6] = x5 >>> 16 & 0xff; + o[ 7] = x5 >>> 24 & 0xff; + + o[ 8] = x10 >>> 0 & 0xff; + o[ 9] = x10 >>> 8 & 0xff; + o[10] = x10 >>> 16 & 0xff; + o[11] = x10 >>> 24 & 0xff; + + o[12] = x15 >>> 0 & 0xff; + o[13] = x15 >>> 8 & 0xff; + o[14] = x15 >>> 16 & 0xff; + o[15] = x15 >>> 24 & 0xff; + + o[16] = x6 >>> 0 & 0xff; + o[17] = x6 >>> 8 & 0xff; + o[18] = x6 >>> 16 & 0xff; + o[19] = x6 >>> 24 & 0xff; + + o[20] = x7 >>> 0 & 0xff; + o[21] = x7 >>> 8 & 0xff; + o[22] = x7 >>> 16 & 0xff; + o[23] = x7 >>> 24 & 0xff; + + o[24] = x8 >>> 0 & 0xff; + o[25] = x8 >>> 8 & 0xff; + o[26] = x8 >>> 16 & 0xff; + o[27] = x8 >>> 24 & 0xff; + + o[28] = x9 >>> 0 & 0xff; + o[29] = x9 >>> 8 & 0xff; + o[30] = x9 >>> 16 & 0xff; + o[31] = x9 >>> 24 & 0xff; +} + +function crypto_core_salsa20(out,inp,k,c) { + core_salsa20(out,inp,k,c); +} + +function crypto_core_hsalsa20(out,inp,k,c) { + core_hsalsa20(out,inp,k,c); +} + +var sigma = new Uint8Array([101, 120, 112, 97, 110, 100, 32, 51, 50, 45, 98, 121, 116, 101, 32, 107]); + // "expand 32-byte k" + +function crypto_stream_salsa20_xor(c,cpos,m,mpos,b,n,k) { + var z = new Uint8Array(16), x = new Uint8Array(64); + var u, i; + for (i = 0; i < 16; i++) z[i] = 0; + for (i = 0; i < 8; i++) z[i] = n[i]; + while (b >= 64) { + crypto_core_salsa20(x,z,k,sigma); + for (i = 0; i < 64; i++) c[cpos+i] = m[mpos+i] ^ x[i]; + u = 1; + for (i = 8; i < 16; i++) { + u = u + (z[i] & 0xff) | 0; + z[i] = u & 0xff; + u >>>= 8; + } + b -= 64; + cpos += 64; + mpos += 64; + } + if (b > 0) { + crypto_core_salsa20(x,z,k,sigma); + for (i = 0; i < b; i++) c[cpos+i] = m[mpos+i] ^ x[i]; + } + return 0; +} + +function crypto_stream_salsa20(c,cpos,b,n,k) { + var z = new Uint8Array(16), x = new Uint8Array(64); + var u, i; + for (i = 0; i < 16; i++) z[i] = 0; + for (i = 0; i < 8; i++) z[i] = n[i]; + while (b >= 64) { + crypto_core_salsa20(x,z,k,sigma); + for (i = 0; i < 64; i++) c[cpos+i] = x[i]; + u = 1; + for (i = 8; i < 16; i++) { + u = u + (z[i] & 0xff) | 0; + z[i] = u & 0xff; + u >>>= 8; + } + b -= 64; + cpos += 64; + } + if (b > 0) { + crypto_core_salsa20(x,z,k,sigma); + for (i = 0; i < b; i++) c[cpos+i] = x[i]; + } + return 0; +} + +function crypto_stream(c,cpos,d,n,k) { + var s = new Uint8Array(32); + crypto_core_hsalsa20(s,n,k,sigma); + var sn = new Uint8Array(8); + for (var i = 0; i < 8; i++) sn[i] = n[i+16]; + return crypto_stream_salsa20(c,cpos,d,sn,s); +} + +function crypto_stream_xor(c,cpos,m,mpos,d,n,k) { + var s = new Uint8Array(32); + crypto_core_hsalsa20(s,n,k,sigma); + var sn = new Uint8Array(8); + for (var i = 0; i < 8; i++) sn[i] = n[i+16]; + return crypto_stream_salsa20_xor(c,cpos,m,mpos,d,sn,s); +} + +/* +* Port of Andrew Moon's Poly1305-donna-16. Public domain. +* https://github.com/floodyberry/poly1305-donna +*/ + +var poly1305 = function(key) { + this.buffer = new Uint8Array(16); + this.r = new Uint16Array(10); + this.h = new Uint16Array(10); + this.pad = new Uint16Array(8); + this.leftover = 0; + this.fin = 0; + + var t0, t1, t2, t3, t4, t5, t6, t7; + + t0 = key[ 0] & 0xff | (key[ 1] & 0xff) << 8; this.r[0] = ( t0 ) & 0x1fff; + t1 = key[ 2] & 0xff | (key[ 3] & 0xff) << 8; this.r[1] = ((t0 >>> 13) | (t1 << 3)) & 0x1fff; + t2 = key[ 4] & 0xff | (key[ 5] & 0xff) << 8; this.r[2] = ((t1 >>> 10) | (t2 << 6)) & 0x1f03; + t3 = key[ 6] & 0xff | (key[ 7] & 0xff) << 8; this.r[3] = ((t2 >>> 7) | (t3 << 9)) & 0x1fff; + t4 = key[ 8] & 0xff | (key[ 9] & 0xff) << 8; this.r[4] = ((t3 >>> 4) | (t4 << 12)) & 0x00ff; + this.r[5] = ((t4 >>> 1)) & 0x1ffe; + t5 = key[10] & 0xff | (key[11] & 0xff) << 8; this.r[6] = ((t4 >>> 14) | (t5 << 2)) & 0x1fff; + t6 = key[12] & 0xff | (key[13] & 0xff) << 8; this.r[7] = ((t5 >>> 11) | (t6 << 5)) & 0x1f81; + t7 = key[14] & 0xff | (key[15] & 0xff) << 8; this.r[8] = ((t6 >>> 8) | (t7 << 8)) & 0x1fff; + this.r[9] = ((t7 >>> 5)) & 0x007f; + + this.pad[0] = key[16] & 0xff | (key[17] & 0xff) << 8; + this.pad[1] = key[18] & 0xff | (key[19] & 0xff) << 8; + this.pad[2] = key[20] & 0xff | (key[21] & 0xff) << 8; + this.pad[3] = key[22] & 0xff | (key[23] & 0xff) << 8; + this.pad[4] = key[24] & 0xff | (key[25] & 0xff) << 8; + this.pad[5] = key[26] & 0xff | (key[27] & 0xff) << 8; + this.pad[6] = key[28] & 0xff | (key[29] & 0xff) << 8; + this.pad[7] = key[30] & 0xff | (key[31] & 0xff) << 8; +}; + +poly1305.prototype.blocks = function(m, mpos, bytes) { + var hibit = this.fin ? 0 : (1 << 11); + var t0, t1, t2, t3, t4, t5, t6, t7, c; + var d0, d1, d2, d3, d4, d5, d6, d7, d8, d9; + + var h0 = this.h[0], + h1 = this.h[1], + h2 = this.h[2], + h3 = this.h[3], + h4 = this.h[4], + h5 = this.h[5], + h6 = this.h[6], + h7 = this.h[7], + h8 = this.h[8], + h9 = this.h[9]; + + var r0 = this.r[0], + r1 = this.r[1], + r2 = this.r[2], + r3 = this.r[3], + r4 = this.r[4], + r5 = this.r[5], + r6 = this.r[6], + r7 = this.r[7], + r8 = this.r[8], + r9 = this.r[9]; + + while (bytes >= 16) { + t0 = m[mpos+ 0] & 0xff | (m[mpos+ 1] & 0xff) << 8; h0 += ( t0 ) & 0x1fff; + t1 = m[mpos+ 2] & 0xff | (m[mpos+ 3] & 0xff) << 8; h1 += ((t0 >>> 13) | (t1 << 3)) & 0x1fff; + t2 = m[mpos+ 4] & 0xff | (m[mpos+ 5] & 0xff) << 8; h2 += ((t1 >>> 10) | (t2 << 6)) & 0x1fff; + t3 = m[mpos+ 6] & 0xff | (m[mpos+ 7] & 0xff) << 8; h3 += ((t2 >>> 7) | (t3 << 9)) & 0x1fff; + t4 = m[mpos+ 8] & 0xff | (m[mpos+ 9] & 0xff) << 8; h4 += ((t3 >>> 4) | (t4 << 12)) & 0x1fff; + h5 += ((t4 >>> 1)) & 0x1fff; + t5 = m[mpos+10] & 0xff | (m[mpos+11] & 0xff) << 8; h6 += ((t4 >>> 14) | (t5 << 2)) & 0x1fff; + t6 = m[mpos+12] & 0xff | (m[mpos+13] & 0xff) << 8; h7 += ((t5 >>> 11) | (t6 << 5)) & 0x1fff; + t7 = m[mpos+14] & 0xff | (m[mpos+15] & 0xff) << 8; h8 += ((t6 >>> 8) | (t7 << 8)) & 0x1fff; + h9 += ((t7 >>> 5)) | hibit; + + c = 0; + + d0 = c; + d0 += h0 * r0; + d0 += h1 * (5 * r9); + d0 += h2 * (5 * r8); + d0 += h3 * (5 * r7); + d0 += h4 * (5 * r6); + c = (d0 >>> 13); d0 &= 0x1fff; + d0 += h5 * (5 * r5); + d0 += h6 * (5 * r4); + d0 += h7 * (5 * r3); + d0 += h8 * (5 * r2); + d0 += h9 * (5 * r1); + c += (d0 >>> 13); d0 &= 0x1fff; + + d1 = c; + d1 += h0 * r1; + d1 += h1 * r0; + d1 += h2 * (5 * r9); + d1 += h3 * (5 * r8); + d1 += h4 * (5 * r7); + c = (d1 >>> 13); d1 &= 0x1fff; + d1 += h5 * (5 * r6); + d1 += h6 * (5 * r5); + d1 += h7 * (5 * r4); + d1 += h8 * (5 * r3); + d1 += h9 * (5 * r2); + c += (d1 >>> 13); d1 &= 0x1fff; + + d2 = c; + d2 += h0 * r2; + d2 += h1 * r1; + d2 += h2 * r0; + d2 += h3 * (5 * r9); + d2 += h4 * (5 * r8); + c = (d2 >>> 13); d2 &= 0x1fff; + d2 += h5 * (5 * r7); + d2 += h6 * (5 * r6); + d2 += h7 * (5 * r5); + d2 += h8 * (5 * r4); + d2 += h9 * (5 * r3); + c += (d2 >>> 13); d2 &= 0x1fff; + + d3 = c; + d3 += h0 * r3; + d3 += h1 * r2; + d3 += h2 * r1; + d3 += h3 * r0; + d3 += h4 * (5 * r9); + c = (d3 >>> 13); d3 &= 0x1fff; + d3 += h5 * (5 * r8); + d3 += h6 * (5 * r7); + d3 += h7 * (5 * r6); + d3 += h8 * (5 * r5); + d3 += h9 * (5 * r4); + c += (d3 >>> 13); d3 &= 0x1fff; + + d4 = c; + d4 += h0 * r4; + d4 += h1 * r3; + d4 += h2 * r2; + d4 += h3 * r1; + d4 += h4 * r0; + c = (d4 >>> 13); d4 &= 0x1fff; + d4 += h5 * (5 * r9); + d4 += h6 * (5 * r8); + d4 += h7 * (5 * r7); + d4 += h8 * (5 * r6); + d4 += h9 * (5 * r5); + c += (d4 >>> 13); d4 &= 0x1fff; + + d5 = c; + d5 += h0 * r5; + d5 += h1 * r4; + d5 += h2 * r3; + d5 += h3 * r2; + d5 += h4 * r1; + c = (d5 >>> 13); d5 &= 0x1fff; + d5 += h5 * r0; + d5 += h6 * (5 * r9); + d5 += h7 * (5 * r8); + d5 += h8 * (5 * r7); + d5 += h9 * (5 * r6); + c += (d5 >>> 13); d5 &= 0x1fff; + + d6 = c; + d6 += h0 * r6; + d6 += h1 * r5; + d6 += h2 * r4; + d6 += h3 * r3; + d6 += h4 * r2; + c = (d6 >>> 13); d6 &= 0x1fff; + d6 += h5 * r1; + d6 += h6 * r0; + d6 += h7 * (5 * r9); + d6 += h8 * (5 * r8); + d6 += h9 * (5 * r7); + c += (d6 >>> 13); d6 &= 0x1fff; + + d7 = c; + d7 += h0 * r7; + d7 += h1 * r6; + d7 += h2 * r5; + d7 += h3 * r4; + d7 += h4 * r3; + c = (d7 >>> 13); d7 &= 0x1fff; + d7 += h5 * r2; + d7 += h6 * r1; + d7 += h7 * r0; + d7 += h8 * (5 * r9); + d7 += h9 * (5 * r8); + c += (d7 >>> 13); d7 &= 0x1fff; + + d8 = c; + d8 += h0 * r8; + d8 += h1 * r7; + d8 += h2 * r6; + d8 += h3 * r5; + d8 += h4 * r4; + c = (d8 >>> 13); d8 &= 0x1fff; + d8 += h5 * r3; + d8 += h6 * r2; + d8 += h7 * r1; + d8 += h8 * r0; + d8 += h9 * (5 * r9); + c += (d8 >>> 13); d8 &= 0x1fff; + + d9 = c; + d9 += h0 * r9; + d9 += h1 * r8; + d9 += h2 * r7; + d9 += h3 * r6; + d9 += h4 * r5; + c = (d9 >>> 13); d9 &= 0x1fff; + d9 += h5 * r4; + d9 += h6 * r3; + d9 += h7 * r2; + d9 += h8 * r1; + d9 += h9 * r0; + c += (d9 >>> 13); d9 &= 0x1fff; + + c = (((c << 2) + c)) | 0; + c = (c + d0) | 0; + d0 = c & 0x1fff; + c = (c >>> 13); + d1 += c; + + h0 = d0; + h1 = d1; + h2 = d2; + h3 = d3; + h4 = d4; + h5 = d5; + h6 = d6; + h7 = d7; + h8 = d8; + h9 = d9; + + mpos += 16; + bytes -= 16; + } + this.h[0] = h0; + this.h[1] = h1; + this.h[2] = h2; + this.h[3] = h3; + this.h[4] = h4; + this.h[5] = h5; + this.h[6] = h6; + this.h[7] = h7; + this.h[8] = h8; + this.h[9] = h9; +}; + +poly1305.prototype.finish = function(mac, macpos) { + var g = new Uint16Array(10); + var c, mask, f, i; + + if (this.leftover) { + i = this.leftover; + this.buffer[i++] = 1; + for (; i < 16; i++) this.buffer[i] = 0; + this.fin = 1; + this.blocks(this.buffer, 0, 16); + } + + c = this.h[1] >>> 13; + this.h[1] &= 0x1fff; + for (i = 2; i < 10; i++) { + this.h[i] += c; + c = this.h[i] >>> 13; + this.h[i] &= 0x1fff; + } + this.h[0] += (c * 5); + c = this.h[0] >>> 13; + this.h[0] &= 0x1fff; + this.h[1] += c; + c = this.h[1] >>> 13; + this.h[1] &= 0x1fff; + this.h[2] += c; + + g[0] = this.h[0] + 5; + c = g[0] >>> 13; + g[0] &= 0x1fff; + for (i = 1; i < 10; i++) { + g[i] = this.h[i] + c; + c = g[i] >>> 13; + g[i] &= 0x1fff; + } + g[9] -= (1 << 13); + + mask = (g[9] >>> ((2 * 8) - 1)) - 1; + for (i = 0; i < 10; i++) g[i] &= mask; + mask = ~mask; + for (i = 0; i < 10; i++) this.h[i] = (this.h[i] & mask) | g[i]; + + this.h[0] = ((this.h[0] ) | (this.h[1] << 13) ) & 0xffff; + this.h[1] = ((this.h[1] >>> 3) | (this.h[2] << 10) ) & 0xffff; + this.h[2] = ((this.h[2] >>> 6) | (this.h[3] << 7) ) & 0xffff; + this.h[3] = ((this.h[3] >>> 9) | (this.h[4] << 4) ) & 0xffff; + this.h[4] = ((this.h[4] >>> 12) | (this.h[5] << 1) | (this.h[6] << 14)) & 0xffff; + this.h[5] = ((this.h[6] >>> 2) | (this.h[7] << 11) ) & 0xffff; + this.h[6] = ((this.h[7] >>> 5) | (this.h[8] << 8) ) & 0xffff; + this.h[7] = ((this.h[8] >>> 8) | (this.h[9] << 5) ) & 0xffff; + + f = this.h[0] + this.pad[0]; + this.h[0] = f & 0xffff; + for (i = 1; i < 8; i++) { + f = (((this.h[i] + this.pad[i]) | 0) + (f >>> 16)) | 0; + this.h[i] = f & 0xffff; + } + + mac[macpos+ 0] = (this.h[0] >>> 0) & 0xff; + mac[macpos+ 1] = (this.h[0] >>> 8) & 0xff; + mac[macpos+ 2] = (this.h[1] >>> 0) & 0xff; + mac[macpos+ 3] = (this.h[1] >>> 8) & 0xff; + mac[macpos+ 4] = (this.h[2] >>> 0) & 0xff; + mac[macpos+ 5] = (this.h[2] >>> 8) & 0xff; + mac[macpos+ 6] = (this.h[3] >>> 0) & 0xff; + mac[macpos+ 7] = (this.h[3] >>> 8) & 0xff; + mac[macpos+ 8] = (this.h[4] >>> 0) & 0xff; + mac[macpos+ 9] = (this.h[4] >>> 8) & 0xff; + mac[macpos+10] = (this.h[5] >>> 0) & 0xff; + mac[macpos+11] = (this.h[5] >>> 8) & 0xff; + mac[macpos+12] = (this.h[6] >>> 0) & 0xff; + mac[macpos+13] = (this.h[6] >>> 8) & 0xff; + mac[macpos+14] = (this.h[7] >>> 0) & 0xff; + mac[macpos+15] = (this.h[7] >>> 8) & 0xff; +}; + +poly1305.prototype.update = function(m, mpos, bytes) { + var i, want; + + if (this.leftover) { + want = (16 - this.leftover); + if (want > bytes) + want = bytes; + for (i = 0; i < want; i++) + this.buffer[this.leftover + i] = m[mpos+i]; + bytes -= want; + mpos += want; + this.leftover += want; + if (this.leftover < 16) + return; + this.blocks(this.buffer, 0, 16); + this.leftover = 0; + } + + if (bytes >= 16) { + want = bytes - (bytes % 16); + this.blocks(m, mpos, want); + mpos += want; + bytes -= want; + } + + if (bytes) { + for (i = 0; i < bytes; i++) + this.buffer[this.leftover + i] = m[mpos+i]; + this.leftover += bytes; + } +}; + +function crypto_onetimeauth(out, outpos, m, mpos, n, k) { + var s = new poly1305(k); + s.update(m, mpos, n); + s.finish(out, outpos); + return 0; +} + +function crypto_onetimeauth_verify(h, hpos, m, mpos, n, k) { + var x = new Uint8Array(16); + crypto_onetimeauth(x,0,m,mpos,n,k); + return crypto_verify_16(h,hpos,x,0); +} + +function crypto_secretbox(c,m,d,n,k) { + var i; + if (d < 32) return -1; + crypto_stream_xor(c,0,m,0,d,n,k); + crypto_onetimeauth(c, 16, c, 32, d - 32, c); + for (i = 0; i < 16; i++) c[i] = 0; + return 0; +} + +function crypto_secretbox_open(m,c,d,n,k) { + var i; + var x = new Uint8Array(32); + if (d < 32) return -1; + crypto_stream(x,0,32,n,k); + if (crypto_onetimeauth_verify(c, 16,c, 32,d - 32,x) !== 0) return -1; + crypto_stream_xor(m,0,c,0,d,n,k); + for (i = 0; i < 32; i++) m[i] = 0; + return 0; +} + +function set25519(r, a) { + var i; + for (i = 0; i < 16; i++) r[i] = a[i]|0; +} + +function car25519(o) { + var i, v, c = 1; + for (i = 0; i < 16; i++) { + v = o[i] + c + 65535; + c = Math.floor(v / 65536); + o[i] = v - c * 65536; + } + o[0] += c-1 + 37 * (c-1); +} + +function sel25519(p, q, b) { + var t, c = ~(b-1); + for (var i = 0; i < 16; i++) { + t = c & (p[i] ^ q[i]); + p[i] ^= t; + q[i] ^= t; + } +} + +function pack25519(o, n) { + var i, j, b; + var m = gf(), t = gf(); + for (i = 0; i < 16; i++) t[i] = n[i]; + car25519(t); + car25519(t); + car25519(t); + for (j = 0; j < 2; j++) { + m[0] = t[0] - 0xffed; + for (i = 1; i < 15; i++) { + m[i] = t[i] - 0xffff - ((m[i-1]>>16) & 1); + m[i-1] &= 0xffff; + } + m[15] = t[15] - 0x7fff - ((m[14]>>16) & 1); + b = (m[15]>>16) & 1; + m[14] &= 0xffff; + sel25519(t, m, 1-b); + } + for (i = 0; i < 16; i++) { + o[2*i] = t[i] & 0xff; + o[2*i+1] = t[i]>>8; + } +} + +function neq25519(a, b) { + var c = new Uint8Array(32), d = new Uint8Array(32); + pack25519(c, a); + pack25519(d, b); + return crypto_verify_32(c, 0, d, 0); +} + +function par25519(a) { + var d = new Uint8Array(32); + pack25519(d, a); + return d[0] & 1; +} + +function unpack25519(o, n) { + var i; + for (i = 0; i < 16; i++) o[i] = n[2*i] + (n[2*i+1] << 8); + o[15] &= 0x7fff; +} + +function A(o, a, b) { + for (var i = 0; i < 16; i++) o[i] = a[i] + b[i]; +} + +function Z(o, a, b) { + for (var i = 0; i < 16; i++) o[i] = a[i] - b[i]; +} + +function M(o, a, b) { + var v, c, + t0 = 0, t1 = 0, t2 = 0, t3 = 0, t4 = 0, t5 = 0, t6 = 0, t7 = 0, + t8 = 0, t9 = 0, t10 = 0, t11 = 0, t12 = 0, t13 = 0, t14 = 0, t15 = 0, + t16 = 0, t17 = 0, t18 = 0, t19 = 0, t20 = 0, t21 = 0, t22 = 0, t23 = 0, + t24 = 0, t25 = 0, t26 = 0, t27 = 0, t28 = 0, t29 = 0, t30 = 0, + b0 = b[0], + b1 = b[1], + b2 = b[2], + b3 = b[3], + b4 = b[4], + b5 = b[5], + b6 = b[6], + b7 = b[7], + b8 = b[8], + b9 = b[9], + b10 = b[10], + b11 = b[11], + b12 = b[12], + b13 = b[13], + b14 = b[14], + b15 = b[15]; + + v = a[0]; + t0 += v * b0; + t1 += v * b1; + t2 += v * b2; + t3 += v * b3; + t4 += v * b4; + t5 += v * b5; + t6 += v * b6; + t7 += v * b7; + t8 += v * b8; + t9 += v * b9; + t10 += v * b10; + t11 += v * b11; + t12 += v * b12; + t13 += v * b13; + t14 += v * b14; + t15 += v * b15; + v = a[1]; + t1 += v * b0; + t2 += v * b1; + t3 += v * b2; + t4 += v * b3; + t5 += v * b4; + t6 += v * b5; + t7 += v * b6; + t8 += v * b7; + t9 += v * b8; + t10 += v * b9; + t11 += v * b10; + t12 += v * b11; + t13 += v * b12; + t14 += v * b13; + t15 += v * b14; + t16 += v * b15; + v = a[2]; + t2 += v * b0; + t3 += v * b1; + t4 += v * b2; + t5 += v * b3; + t6 += v * b4; + t7 += v * b5; + t8 += v * b6; + t9 += v * b7; + t10 += v * b8; + t11 += v * b9; + t12 += v * b10; + t13 += v * b11; + t14 += v * b12; + t15 += v * b13; + t16 += v * b14; + t17 += v * b15; + v = a[3]; + t3 += v * b0; + t4 += v * b1; + t5 += v * b2; + t6 += v * b3; + t7 += v * b4; + t8 += v * b5; + t9 += v * b6; + t10 += v * b7; + t11 += v * b8; + t12 += v * b9; + t13 += v * b10; + t14 += v * b11; + t15 += v * b12; + t16 += v * b13; + t17 += v * b14; + t18 += v * b15; + v = a[4]; + t4 += v * b0; + t5 += v * b1; + t6 += v * b2; + t7 += v * b3; + t8 += v * b4; + t9 += v * b5; + t10 += v * b6; + t11 += v * b7; + t12 += v * b8; + t13 += v * b9; + t14 += v * b10; + t15 += v * b11; + t16 += v * b12; + t17 += v * b13; + t18 += v * b14; + t19 += v * b15; + v = a[5]; + t5 += v * b0; + t6 += v * b1; + t7 += v * b2; + t8 += v * b3; + t9 += v * b4; + t10 += v * b5; + t11 += v * b6; + t12 += v * b7; + t13 += v * b8; + t14 += v * b9; + t15 += v * b10; + t16 += v * b11; + t17 += v * b12; + t18 += v * b13; + t19 += v * b14; + t20 += v * b15; + v = a[6]; + t6 += v * b0; + t7 += v * b1; + t8 += v * b2; + t9 += v * b3; + t10 += v * b4; + t11 += v * b5; + t12 += v * b6; + t13 += v * b7; + t14 += v * b8; + t15 += v * b9; + t16 += v * b10; + t17 += v * b11; + t18 += v * b12; + t19 += v * b13; + t20 += v * b14; + t21 += v * b15; + v = a[7]; + t7 += v * b0; + t8 += v * b1; + t9 += v * b2; + t10 += v * b3; + t11 += v * b4; + t12 += v * b5; + t13 += v * b6; + t14 += v * b7; + t15 += v * b8; + t16 += v * b9; + t17 += v * b10; + t18 += v * b11; + t19 += v * b12; + t20 += v * b13; + t21 += v * b14; + t22 += v * b15; + v = a[8]; + t8 += v * b0; + t9 += v * b1; + t10 += v * b2; + t11 += v * b3; + t12 += v * b4; + t13 += v * b5; + t14 += v * b6; + t15 += v * b7; + t16 += v * b8; + t17 += v * b9; + t18 += v * b10; + t19 += v * b11; + t20 += v * b12; + t21 += v * b13; + t22 += v * b14; + t23 += v * b15; + v = a[9]; + t9 += v * b0; + t10 += v * b1; + t11 += v * b2; + t12 += v * b3; + t13 += v * b4; + t14 += v * b5; + t15 += v * b6; + t16 += v * b7; + t17 += v * b8; + t18 += v * b9; + t19 += v * b10; + t20 += v * b11; + t21 += v * b12; + t22 += v * b13; + t23 += v * b14; + t24 += v * b15; + v = a[10]; + t10 += v * b0; + t11 += v * b1; + t12 += v * b2; + t13 += v * b3; + t14 += v * b4; + t15 += v * b5; + t16 += v * b6; + t17 += v * b7; + t18 += v * b8; + t19 += v * b9; + t20 += v * b10; + t21 += v * b11; + t22 += v * b12; + t23 += v * b13; + t24 += v * b14; + t25 += v * b15; + v = a[11]; + t11 += v * b0; + t12 += v * b1; + t13 += v * b2; + t14 += v * b3; + t15 += v * b4; + t16 += v * b5; + t17 += v * b6; + t18 += v * b7; + t19 += v * b8; + t20 += v * b9; + t21 += v * b10; + t22 += v * b11; + t23 += v * b12; + t24 += v * b13; + t25 += v * b14; + t26 += v * b15; + v = a[12]; + t12 += v * b0; + t13 += v * b1; + t14 += v * b2; + t15 += v * b3; + t16 += v * b4; + t17 += v * b5; + t18 += v * b6; + t19 += v * b7; + t20 += v * b8; + t21 += v * b9; + t22 += v * b10; + t23 += v * b11; + t24 += v * b12; + t25 += v * b13; + t26 += v * b14; + t27 += v * b15; + v = a[13]; + t13 += v * b0; + t14 += v * b1; + t15 += v * b2; + t16 += v * b3; + t17 += v * b4; + t18 += v * b5; + t19 += v * b6; + t20 += v * b7; + t21 += v * b8; + t22 += v * b9; + t23 += v * b10; + t24 += v * b11; + t25 += v * b12; + t26 += v * b13; + t27 += v * b14; + t28 += v * b15; + v = a[14]; + t14 += v * b0; + t15 += v * b1; + t16 += v * b2; + t17 += v * b3; + t18 += v * b4; + t19 += v * b5; + t20 += v * b6; + t21 += v * b7; + t22 += v * b8; + t23 += v * b9; + t24 += v * b10; + t25 += v * b11; + t26 += v * b12; + t27 += v * b13; + t28 += v * b14; + t29 += v * b15; + v = a[15]; + t15 += v * b0; + t16 += v * b1; + t17 += v * b2; + t18 += v * b3; + t19 += v * b4; + t20 += v * b5; + t21 += v * b6; + t22 += v * b7; + t23 += v * b8; + t24 += v * b9; + t25 += v * b10; + t26 += v * b11; + t27 += v * b12; + t28 += v * b13; + t29 += v * b14; + t30 += v * b15; + + t0 += 38 * t16; + t1 += 38 * t17; + t2 += 38 * t18; + t3 += 38 * t19; + t4 += 38 * t20; + t5 += 38 * t21; + t6 += 38 * t22; + t7 += 38 * t23; + t8 += 38 * t24; + t9 += 38 * t25; + t10 += 38 * t26; + t11 += 38 * t27; + t12 += 38 * t28; + t13 += 38 * t29; + t14 += 38 * t30; + // t15 left as is + + // first car + c = 1; + v = t0 + c + 65535; c = Math.floor(v / 65536); t0 = v - c * 65536; + v = t1 + c + 65535; c = Math.floor(v / 65536); t1 = v - c * 65536; + v = t2 + c + 65535; c = Math.floor(v / 65536); t2 = v - c * 65536; + v = t3 + c + 65535; c = Math.floor(v / 65536); t3 = v - c * 65536; + v = t4 + c + 65535; c = Math.floor(v / 65536); t4 = v - c * 65536; + v = t5 + c + 65535; c = Math.floor(v / 65536); t5 = v - c * 65536; + v = t6 + c + 65535; c = Math.floor(v / 65536); t6 = v - c * 65536; + v = t7 + c + 65535; c = Math.floor(v / 65536); t7 = v - c * 65536; + v = t8 + c + 65535; c = Math.floor(v / 65536); t8 = v - c * 65536; + v = t9 + c + 65535; c = Math.floor(v / 65536); t9 = v - c * 65536; + v = t10 + c + 65535; c = Math.floor(v / 65536); t10 = v - c * 65536; + v = t11 + c + 65535; c = Math.floor(v / 65536); t11 = v - c * 65536; + v = t12 + c + 65535; c = Math.floor(v / 65536); t12 = v - c * 65536; + v = t13 + c + 65535; c = Math.floor(v / 65536); t13 = v - c * 65536; + v = t14 + c + 65535; c = Math.floor(v / 65536); t14 = v - c * 65536; + v = t15 + c + 65535; c = Math.floor(v / 65536); t15 = v - c * 65536; + t0 += c-1 + 37 * (c-1); + + // second car + c = 1; + v = t0 + c + 65535; c = Math.floor(v / 65536); t0 = v - c * 65536; + v = t1 + c + 65535; c = Math.floor(v / 65536); t1 = v - c * 65536; + v = t2 + c + 65535; c = Math.floor(v / 65536); t2 = v - c * 65536; + v = t3 + c + 65535; c = Math.floor(v / 65536); t3 = v - c * 65536; + v = t4 + c + 65535; c = Math.floor(v / 65536); t4 = v - c * 65536; + v = t5 + c + 65535; c = Math.floor(v / 65536); t5 = v - c * 65536; + v = t6 + c + 65535; c = Math.floor(v / 65536); t6 = v - c * 65536; + v = t7 + c + 65535; c = Math.floor(v / 65536); t7 = v - c * 65536; + v = t8 + c + 65535; c = Math.floor(v / 65536); t8 = v - c * 65536; + v = t9 + c + 65535; c = Math.floor(v / 65536); t9 = v - c * 65536; + v = t10 + c + 65535; c = Math.floor(v / 65536); t10 = v - c * 65536; + v = t11 + c + 65535; c = Math.floor(v / 65536); t11 = v - c * 65536; + v = t12 + c + 65535; c = Math.floor(v / 65536); t12 = v - c * 65536; + v = t13 + c + 65535; c = Math.floor(v / 65536); t13 = v - c * 65536; + v = t14 + c + 65535; c = Math.floor(v / 65536); t14 = v - c * 65536; + v = t15 + c + 65535; c = Math.floor(v / 65536); t15 = v - c * 65536; + t0 += c-1 + 37 * (c-1); + + o[ 0] = t0; + o[ 1] = t1; + o[ 2] = t2; + o[ 3] = t3; + o[ 4] = t4; + o[ 5] = t5; + o[ 6] = t6; + o[ 7] = t7; + o[ 8] = t8; + o[ 9] = t9; + o[10] = t10; + o[11] = t11; + o[12] = t12; + o[13] = t13; + o[14] = t14; + o[15] = t15; +} + +function S(o, a) { + M(o, a, a); +} + +function inv25519(o, i) { + var c = gf(); + var a; + for (a = 0; a < 16; a++) c[a] = i[a]; + for (a = 253; a >= 0; a--) { + S(c, c); + if(a !== 2 && a !== 4) M(c, c, i); + } + for (a = 0; a < 16; a++) o[a] = c[a]; +} + +function pow2523(o, i) { + var c = gf(); + var a; + for (a = 0; a < 16; a++) c[a] = i[a]; + for (a = 250; a >= 0; a--) { + S(c, c); + if(a !== 1) M(c, c, i); + } + for (a = 0; a < 16; a++) o[a] = c[a]; +} + +function crypto_scalarmult(q, n, p) { + var z = new Uint8Array(32); + var x = new Float64Array(80), r, i; + var a = gf(), b = gf(), c = gf(), + d = gf(), e = gf(), f = gf(); + for (i = 0; i < 31; i++) z[i] = n[i]; + z[31]=(n[31]&127)|64; + z[0]&=248; + unpack25519(x,p); + for (i = 0; i < 16; i++) { + b[i]=x[i]; + d[i]=a[i]=c[i]=0; + } + a[0]=d[0]=1; + for (i=254; i>=0; --i) { + r=(z[i>>>3]>>>(i&7))&1; + sel25519(a,b,r); + sel25519(c,d,r); + A(e,a,c); + Z(a,a,c); + A(c,b,d); + Z(b,b,d); + S(d,e); + S(f,a); + M(a,c,a); + M(c,b,e); + A(e,a,c); + Z(a,a,c); + S(b,a); + Z(c,d,f); + M(a,c,_121665); + A(a,a,d); + M(c,c,a); + M(a,d,f); + M(d,b,x); + S(b,e); + sel25519(a,b,r); + sel25519(c,d,r); + } + for (i = 0; i < 16; i++) { + x[i+16]=a[i]; + x[i+32]=c[i]; + x[i+48]=b[i]; + x[i+64]=d[i]; + } + var x32 = x.subarray(32); + var x16 = x.subarray(16); + inv25519(x32,x32); + M(x16,x16,x32); + pack25519(q,x16); + return 0; +} + +function crypto_scalarmult_base(q, n) { + return crypto_scalarmult(q, n, _9); +} + +function crypto_box_keypair(y, x) { + randombytes(x, 32); + return crypto_scalarmult_base(y, x); +} + +function crypto_box_beforenm(k, y, x) { + var s = new Uint8Array(32); + crypto_scalarmult(s, x, y); + return crypto_core_hsalsa20(k, _0, s, sigma); +} + +var crypto_box_afternm = crypto_secretbox; +var crypto_box_open_afternm = crypto_secretbox_open; + +function crypto_box(c, m, d, n, y, x) { + var k = new Uint8Array(32); + crypto_box_beforenm(k, y, x); + return crypto_box_afternm(c, m, d, n, k); +} + +function crypto_box_open(m, c, d, n, y, x) { + var k = new Uint8Array(32); + crypto_box_beforenm(k, y, x); + return crypto_box_open_afternm(m, c, d, n, k); +} + +var K = [ + 0x428a2f98, 0xd728ae22, 0x71374491, 0x23ef65cd, + 0xb5c0fbcf, 0xec4d3b2f, 0xe9b5dba5, 0x8189dbbc, + 0x3956c25b, 0xf348b538, 0x59f111f1, 0xb605d019, + 0x923f82a4, 0xaf194f9b, 0xab1c5ed5, 0xda6d8118, + 0xd807aa98, 0xa3030242, 0x12835b01, 0x45706fbe, + 0x243185be, 0x4ee4b28c, 0x550c7dc3, 0xd5ffb4e2, + 0x72be5d74, 0xf27b896f, 0x80deb1fe, 0x3b1696b1, + 0x9bdc06a7, 0x25c71235, 0xc19bf174, 0xcf692694, + 0xe49b69c1, 0x9ef14ad2, 0xefbe4786, 0x384f25e3, + 0x0fc19dc6, 0x8b8cd5b5, 0x240ca1cc, 0x77ac9c65, + 0x2de92c6f, 0x592b0275, 0x4a7484aa, 0x6ea6e483, + 0x5cb0a9dc, 0xbd41fbd4, 0x76f988da, 0x831153b5, + 0x983e5152, 0xee66dfab, 0xa831c66d, 0x2db43210, + 0xb00327c8, 0x98fb213f, 0xbf597fc7, 0xbeef0ee4, + 0xc6e00bf3, 0x3da88fc2, 0xd5a79147, 0x930aa725, + 0x06ca6351, 0xe003826f, 0x14292967, 0x0a0e6e70, + 0x27b70a85, 0x46d22ffc, 0x2e1b2138, 0x5c26c926, + 0x4d2c6dfc, 0x5ac42aed, 0x53380d13, 0x9d95b3df, + 0x650a7354, 0x8baf63de, 0x766a0abb, 0x3c77b2a8, + 0x81c2c92e, 0x47edaee6, 0x92722c85, 0x1482353b, + 0xa2bfe8a1, 0x4cf10364, 0xa81a664b, 0xbc423001, + 0xc24b8b70, 0xd0f89791, 0xc76c51a3, 0x0654be30, + 0xd192e819, 0xd6ef5218, 0xd6990624, 0x5565a910, + 0xf40e3585, 0x5771202a, 0x106aa070, 0x32bbd1b8, + 0x19a4c116, 0xb8d2d0c8, 0x1e376c08, 0x5141ab53, + 0x2748774c, 0xdf8eeb99, 0x34b0bcb5, 0xe19b48a8, + 0x391c0cb3, 0xc5c95a63, 0x4ed8aa4a, 0xe3418acb, + 0x5b9cca4f, 0x7763e373, 0x682e6ff3, 0xd6b2b8a3, + 0x748f82ee, 0x5defb2fc, 0x78a5636f, 0x43172f60, + 0x84c87814, 0xa1f0ab72, 0x8cc70208, 0x1a6439ec, + 0x90befffa, 0x23631e28, 0xa4506ceb, 0xde82bde9, + 0xbef9a3f7, 0xb2c67915, 0xc67178f2, 0xe372532b, + 0xca273ece, 0xea26619c, 0xd186b8c7, 0x21c0c207, + 0xeada7dd6, 0xcde0eb1e, 0xf57d4f7f, 0xee6ed178, + 0x06f067aa, 0x72176fba, 0x0a637dc5, 0xa2c898a6, + 0x113f9804, 0xbef90dae, 0x1b710b35, 0x131c471b, + 0x28db77f5, 0x23047d84, 0x32caab7b, 0x40c72493, + 0x3c9ebe0a, 0x15c9bebc, 0x431d67c4, 0x9c100d4c, + 0x4cc5d4be, 0xcb3e42b6, 0x597f299c, 0xfc657e2a, + 0x5fcb6fab, 0x3ad6faec, 0x6c44198c, 0x4a475817 +]; + +function crypto_hashblocks_hl(hh, hl, m, n) { + var wh = new Int32Array(16), wl = new Int32Array(16), + bh0, bh1, bh2, bh3, bh4, bh5, bh6, bh7, + bl0, bl1, bl2, bl3, bl4, bl5, bl6, bl7, + th, tl, i, j, h, l, a, b, c, d; + + var ah0 = hh[0], + ah1 = hh[1], + ah2 = hh[2], + ah3 = hh[3], + ah4 = hh[4], + ah5 = hh[5], + ah6 = hh[6], + ah7 = hh[7], + + al0 = hl[0], + al1 = hl[1], + al2 = hl[2], + al3 = hl[3], + al4 = hl[4], + al5 = hl[5], + al6 = hl[6], + al7 = hl[7]; + + var pos = 0; + while (n >= 128) { + for (i = 0; i < 16; i++) { + j = 8 * i + pos; + wh[i] = (m[j+0] << 24) | (m[j+1] << 16) | (m[j+2] << 8) | m[j+3]; + wl[i] = (m[j+4] << 24) | (m[j+5] << 16) | (m[j+6] << 8) | m[j+7]; + } + for (i = 0; i < 80; i++) { + bh0 = ah0; + bh1 = ah1; + bh2 = ah2; + bh3 = ah3; + bh4 = ah4; + bh5 = ah5; + bh6 = ah6; + bh7 = ah7; + + bl0 = al0; + bl1 = al1; + bl2 = al2; + bl3 = al3; + bl4 = al4; + bl5 = al5; + bl6 = al6; + bl7 = al7; + + // add + h = ah7; + l = al7; + + a = l & 0xffff; b = l >>> 16; + c = h & 0xffff; d = h >>> 16; + + // Sigma1 + h = ((ah4 >>> 14) | (al4 << (32-14))) ^ ((ah4 >>> 18) | (al4 << (32-18))) ^ ((al4 >>> (41-32)) | (ah4 << (32-(41-32)))); + l = ((al4 >>> 14) | (ah4 << (32-14))) ^ ((al4 >>> 18) | (ah4 << (32-18))) ^ ((ah4 >>> (41-32)) | (al4 << (32-(41-32)))); + + a += l & 0xffff; b += l >>> 16; + c += h & 0xffff; d += h >>> 16; + + // Ch + h = (ah4 & ah5) ^ (~ah4 & ah6); + l = (al4 & al5) ^ (~al4 & al6); + + a += l & 0xffff; b += l >>> 16; + c += h & 0xffff; d += h >>> 16; + + // K + h = K[i*2]; + l = K[i*2+1]; + + a += l & 0xffff; b += l >>> 16; + c += h & 0xffff; d += h >>> 16; + + // w + h = wh[i%16]; + l = wl[i%16]; + + a += l & 0xffff; b += l >>> 16; + c += h & 0xffff; d += h >>> 16; + + b += a >>> 16; + c += b >>> 16; + d += c >>> 16; + + th = c & 0xffff | d << 16; + tl = a & 0xffff | b << 16; + + // add + h = th; + l = tl; + + a = l & 0xffff; b = l >>> 16; + c = h & 0xffff; d = h >>> 16; + + // Sigma0 + h = ((ah0 >>> 28) | (al0 << (32-28))) ^ ((al0 >>> (34-32)) | (ah0 << (32-(34-32)))) ^ ((al0 >>> (39-32)) | (ah0 << (32-(39-32)))); + l = ((al0 >>> 28) | (ah0 << (32-28))) ^ ((ah0 >>> (34-32)) | (al0 << (32-(34-32)))) ^ ((ah0 >>> (39-32)) | (al0 << (32-(39-32)))); + + a += l & 0xffff; b += l >>> 16; + c += h & 0xffff; d += h >>> 16; + + // Maj + h = (ah0 & ah1) ^ (ah0 & ah2) ^ (ah1 & ah2); + l = (al0 & al1) ^ (al0 & al2) ^ (al1 & al2); + + a += l & 0xffff; b += l >>> 16; + c += h & 0xffff; d += h >>> 16; + + b += a >>> 16; + c += b >>> 16; + d += c >>> 16; + + bh7 = (c & 0xffff) | (d << 16); + bl7 = (a & 0xffff) | (b << 16); + + // add + h = bh3; + l = bl3; + + a = l & 0xffff; b = l >>> 16; + c = h & 0xffff; d = h >>> 16; + + h = th; + l = tl; + + a += l & 0xffff; b += l >>> 16; + c += h & 0xffff; d += h >>> 16; + + b += a >>> 16; + c += b >>> 16; + d += c >>> 16; + + bh3 = (c & 0xffff) | (d << 16); + bl3 = (a & 0xffff) | (b << 16); + + ah1 = bh0; + ah2 = bh1; + ah3 = bh2; + ah4 = bh3; + ah5 = bh4; + ah6 = bh5; + ah7 = bh6; + ah0 = bh7; + + al1 = bl0; + al2 = bl1; + al3 = bl2; + al4 = bl3; + al5 = bl4; + al6 = bl5; + al7 = bl6; + al0 = bl7; + + if (i%16 === 15) { + for (j = 0; j < 16; j++) { + // add + h = wh[j]; + l = wl[j]; + + a = l & 0xffff; b = l >>> 16; + c = h & 0xffff; d = h >>> 16; + + h = wh[(j+9)%16]; + l = wl[(j+9)%16]; + + a += l & 0xffff; b += l >>> 16; + c += h & 0xffff; d += h >>> 16; + + // sigma0 + th = wh[(j+1)%16]; + tl = wl[(j+1)%16]; + h = ((th >>> 1) | (tl << (32-1))) ^ ((th >>> 8) | (tl << (32-8))) ^ (th >>> 7); + l = ((tl >>> 1) | (th << (32-1))) ^ ((tl >>> 8) | (th << (32-8))) ^ ((tl >>> 7) | (th << (32-7))); + + a += l & 0xffff; b += l >>> 16; + c += h & 0xffff; d += h >>> 16; + + // sigma1 + th = wh[(j+14)%16]; + tl = wl[(j+14)%16]; + h = ((th >>> 19) | (tl << (32-19))) ^ ((tl >>> (61-32)) | (th << (32-(61-32)))) ^ (th >>> 6); + l = ((tl >>> 19) | (th << (32-19))) ^ ((th >>> (61-32)) | (tl << (32-(61-32)))) ^ ((tl >>> 6) | (th << (32-6))); + + a += l & 0xffff; b += l >>> 16; + c += h & 0xffff; d += h >>> 16; + + b += a >>> 16; + c += b >>> 16; + d += c >>> 16; + + wh[j] = (c & 0xffff) | (d << 16); + wl[j] = (a & 0xffff) | (b << 16); + } + } + } + + // add + h = ah0; + l = al0; + + a = l & 0xffff; b = l >>> 16; + c = h & 0xffff; d = h >>> 16; + + h = hh[0]; + l = hl[0]; + + a += l & 0xffff; b += l >>> 16; + c += h & 0xffff; d += h >>> 16; + + b += a >>> 16; + c += b >>> 16; + d += c >>> 16; + + hh[0] = ah0 = (c & 0xffff) | (d << 16); + hl[0] = al0 = (a & 0xffff) | (b << 16); + + h = ah1; + l = al1; + + a = l & 0xffff; b = l >>> 16; + c = h & 0xffff; d = h >>> 16; + + h = hh[1]; + l = hl[1]; + + a += l & 0xffff; b += l >>> 16; + c += h & 0xffff; d += h >>> 16; + + b += a >>> 16; + c += b >>> 16; + d += c >>> 16; + + hh[1] = ah1 = (c & 0xffff) | (d << 16); + hl[1] = al1 = (a & 0xffff) | (b << 16); + + h = ah2; + l = al2; + + a = l & 0xffff; b = l >>> 16; + c = h & 0xffff; d = h >>> 16; + + h = hh[2]; + l = hl[2]; + + a += l & 0xffff; b += l >>> 16; + c += h & 0xffff; d += h >>> 16; + + b += a >>> 16; + c += b >>> 16; + d += c >>> 16; + + hh[2] = ah2 = (c & 0xffff) | (d << 16); + hl[2] = al2 = (a & 0xffff) | (b << 16); + + h = ah3; + l = al3; + + a = l & 0xffff; b = l >>> 16; + c = h & 0xffff; d = h >>> 16; + + h = hh[3]; + l = hl[3]; + + a += l & 0xffff; b += l >>> 16; + c += h & 0xffff; d += h >>> 16; + + b += a >>> 16; + c += b >>> 16; + d += c >>> 16; + + hh[3] = ah3 = (c & 0xffff) | (d << 16); + hl[3] = al3 = (a & 0xffff) | (b << 16); + + h = ah4; + l = al4; + + a = l & 0xffff; b = l >>> 16; + c = h & 0xffff; d = h >>> 16; + + h = hh[4]; + l = hl[4]; + + a += l & 0xffff; b += l >>> 16; + c += h & 0xffff; d += h >>> 16; + + b += a >>> 16; + c += b >>> 16; + d += c >>> 16; + + hh[4] = ah4 = (c & 0xffff) | (d << 16); + hl[4] = al4 = (a & 0xffff) | (b << 16); + + h = ah5; + l = al5; + + a = l & 0xffff; b = l >>> 16; + c = h & 0xffff; d = h >>> 16; + + h = hh[5]; + l = hl[5]; + + a += l & 0xffff; b += l >>> 16; + c += h & 0xffff; d += h >>> 16; + + b += a >>> 16; + c += b >>> 16; + d += c >>> 16; + + hh[5] = ah5 = (c & 0xffff) | (d << 16); + hl[5] = al5 = (a & 0xffff) | (b << 16); + + h = ah6; + l = al6; + + a = l & 0xffff; b = l >>> 16; + c = h & 0xffff; d = h >>> 16; + + h = hh[6]; + l = hl[6]; + + a += l & 0xffff; b += l >>> 16; + c += h & 0xffff; d += h >>> 16; + + b += a >>> 16; + c += b >>> 16; + d += c >>> 16; + + hh[6] = ah6 = (c & 0xffff) | (d << 16); + hl[6] = al6 = (a & 0xffff) | (b << 16); + + h = ah7; + l = al7; + + a = l & 0xffff; b = l >>> 16; + c = h & 0xffff; d = h >>> 16; + + h = hh[7]; + l = hl[7]; + + a += l & 0xffff; b += l >>> 16; + c += h & 0xffff; d += h >>> 16; + + b += a >>> 16; + c += b >>> 16; + d += c >>> 16; + + hh[7] = ah7 = (c & 0xffff) | (d << 16); + hl[7] = al7 = (a & 0xffff) | (b << 16); + + pos += 128; + n -= 128; + } + + return n; +} + +function crypto_hash(out, m, n) { + var hh = new Int32Array(8), + hl = new Int32Array(8), + x = new Uint8Array(256), + i, b = n; + + hh[0] = 0x6a09e667; + hh[1] = 0xbb67ae85; + hh[2] = 0x3c6ef372; + hh[3] = 0xa54ff53a; + hh[4] = 0x510e527f; + hh[5] = 0x9b05688c; + hh[6] = 0x1f83d9ab; + hh[7] = 0x5be0cd19; + + hl[0] = 0xf3bcc908; + hl[1] = 0x84caa73b; + hl[2] = 0xfe94f82b; + hl[3] = 0x5f1d36f1; + hl[4] = 0xade682d1; + hl[5] = 0x2b3e6c1f; + hl[6] = 0xfb41bd6b; + hl[7] = 0x137e2179; + + crypto_hashblocks_hl(hh, hl, m, n); + n %= 128; + + for (i = 0; i < n; i++) x[i] = m[b-n+i]; + x[n] = 128; + + n = 256-128*(n<112?1:0); + x[n-9] = 0; + ts64(x, n-8, (b / 0x20000000) | 0, b << 3); + crypto_hashblocks_hl(hh, hl, x, n); + + for (i = 0; i < 8; i++) ts64(out, 8*i, hh[i], hl[i]); + + return 0; +} + +function add(p, q) { + var a = gf(), b = gf(), c = gf(), + d = gf(), e = gf(), f = gf(), + g = gf(), h = gf(), t = gf(); + + Z(a, p[1], p[0]); + Z(t, q[1], q[0]); + M(a, a, t); + A(b, p[0], p[1]); + A(t, q[0], q[1]); + M(b, b, t); + M(c, p[3], q[3]); + M(c, c, D2); + M(d, p[2], q[2]); + A(d, d, d); + Z(e, b, a); + Z(f, d, c); + A(g, d, c); + A(h, b, a); + + M(p[0], e, f); + M(p[1], h, g); + M(p[2], g, f); + M(p[3], e, h); +} + +function cswap(p, q, b) { + var i; + for (i = 0; i < 4; i++) { + sel25519(p[i], q[i], b); + } +} + +function pack(r, p) { + var tx = gf(), ty = gf(), zi = gf(); + inv25519(zi, p[2]); + M(tx, p[0], zi); + M(ty, p[1], zi); + pack25519(r, ty); + r[31] ^= par25519(tx) << 7; +} + +function scalarmult(p, q, s) { + var b, i; + set25519(p[0], gf0); + set25519(p[1], gf1); + set25519(p[2], gf1); + set25519(p[3], gf0); + for (i = 255; i >= 0; --i) { + b = (s[(i/8)|0] >> (i&7)) & 1; + cswap(p, q, b); + add(q, p); + add(p, p); + cswap(p, q, b); + } +} + +function scalarbase(p, s) { + var q = [gf(), gf(), gf(), gf()]; + set25519(q[0], X); + set25519(q[1], Y); + set25519(q[2], gf1); + M(q[3], X, Y); + scalarmult(p, q, s); +} + +function crypto_sign_keypair(pk, sk, seeded) { + var d = new Uint8Array(64); + var p = [gf(), gf(), gf(), gf()]; + var i; + + if (!seeded) randombytes(sk, 32); + crypto_hash(d, sk, 32); + d[0] &= 248; + d[31] &= 127; + d[31] |= 64; + + scalarbase(p, d); + pack(pk, p); + + for (i = 0; i < 32; i++) sk[i+32] = pk[i]; + return 0; +} + +var L = new Float64Array([0xed, 0xd3, 0xf5, 0x5c, 0x1a, 0x63, 0x12, 0x58, 0xd6, 0x9c, 0xf7, 0xa2, 0xde, 0xf9, 0xde, 0x14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x10]); + +function modL(r, x) { + var carry, i, j, k; + for (i = 63; i >= 32; --i) { + carry = 0; + for (j = i - 32, k = i - 12; j < k; ++j) { + x[j] += carry - 16 * x[i] * L[j - (i - 32)]; + carry = (x[j] + 128) >> 8; + x[j] -= carry * 256; + } + x[j] += carry; + x[i] = 0; + } + carry = 0; + for (j = 0; j < 32; j++) { + x[j] += carry - (x[31] >> 4) * L[j]; + carry = x[j] >> 8; + x[j] &= 255; + } + for (j = 0; j < 32; j++) x[j] -= carry * L[j]; + for (i = 0; i < 32; i++) { + x[i+1] += x[i] >> 8; + r[i] = x[i] & 255; + } +} + +function reduce(r) { + var x = new Float64Array(64), i; + for (i = 0; i < 64; i++) x[i] = r[i]; + for (i = 0; i < 64; i++) r[i] = 0; + modL(r, x); +} + +// Note: difference from C - smlen returned, not passed as argument. +function crypto_sign(sm, m, n, sk) { + var d = new Uint8Array(64), h = new Uint8Array(64), r = new Uint8Array(64); + var i, j, x = new Float64Array(64); + var p = [gf(), gf(), gf(), gf()]; + + crypto_hash(d, sk, 32); + d[0] &= 248; + d[31] &= 127; + d[31] |= 64; + + var smlen = n + 64; + for (i = 0; i < n; i++) sm[64 + i] = m[i]; + for (i = 0; i < 32; i++) sm[32 + i] = d[32 + i]; + + crypto_hash(r, sm.subarray(32), n+32); + reduce(r); + scalarbase(p, r); + pack(sm, p); + + for (i = 32; i < 64; i++) sm[i] = sk[i]; + crypto_hash(h, sm, n + 64); + reduce(h); + + for (i = 0; i < 64; i++) x[i] = 0; + for (i = 0; i < 32; i++) x[i] = r[i]; + for (i = 0; i < 32; i++) { + for (j = 0; j < 32; j++) { + x[i+j] += h[i] * d[j]; + } + } + + modL(sm.subarray(32), x); + return smlen; +} + +function unpackneg(r, p) { + var t = gf(), chk = gf(), num = gf(), + den = gf(), den2 = gf(), den4 = gf(), + den6 = gf(); + + set25519(r[2], gf1); + unpack25519(r[1], p); + S(num, r[1]); + M(den, num, D); + Z(num, num, r[2]); + A(den, r[2], den); + + S(den2, den); + S(den4, den2); + M(den6, den4, den2); + M(t, den6, num); + M(t, t, den); + + pow2523(t, t); + M(t, t, num); + M(t, t, den); + M(t, t, den); + M(r[0], t, den); + + S(chk, r[0]); + M(chk, chk, den); + if (neq25519(chk, num)) M(r[0], r[0], I); + + S(chk, r[0]); + M(chk, chk, den); + if (neq25519(chk, num)) return -1; + + if (par25519(r[0]) === (p[31]>>7)) Z(r[0], gf0, r[0]); + + M(r[3], r[0], r[1]); + return 0; +} + +function crypto_sign_open(m, sm, n, pk) { + var i, mlen; + var t = new Uint8Array(32), h = new Uint8Array(64); + var p = [gf(), gf(), gf(), gf()], + q = [gf(), gf(), gf(), gf()]; + + mlen = -1; + if (n < 64) return -1; + + if (unpackneg(q, pk)) return -1; + + for (i = 0; i < n; i++) m[i] = sm[i]; + for (i = 0; i < 32; i++) m[i+32] = pk[i]; + crypto_hash(h, m, n); + reduce(h); + scalarmult(p, q, h); + + scalarbase(q, sm.subarray(32)); + add(p, q); + pack(t, p); + + n -= 64; + if (crypto_verify_32(sm, 0, t, 0)) { + for (i = 0; i < n; i++) m[i] = 0; + return -1; + } + + for (i = 0; i < n; i++) m[i] = sm[i + 64]; + mlen = n; + return mlen; +} + +var crypto_secretbox_KEYBYTES = 32, + crypto_secretbox_NONCEBYTES = 24, + crypto_secretbox_ZEROBYTES = 32, + crypto_secretbox_BOXZEROBYTES = 16, + crypto_scalarmult_BYTES = 32, + crypto_scalarmult_SCALARBYTES = 32, + crypto_box_PUBLICKEYBYTES = 32, + crypto_box_SECRETKEYBYTES = 32, + crypto_box_BEFORENMBYTES = 32, + crypto_box_NONCEBYTES = crypto_secretbox_NONCEBYTES, + crypto_box_ZEROBYTES = crypto_secretbox_ZEROBYTES, + crypto_box_BOXZEROBYTES = crypto_secretbox_BOXZEROBYTES, + crypto_sign_BYTES = 64, + crypto_sign_PUBLICKEYBYTES = 32, + crypto_sign_SECRETKEYBYTES = 64, + crypto_sign_SEEDBYTES = 32, + crypto_hash_BYTES = 64; + +nacl.lowlevel = { + crypto_core_hsalsa20: crypto_core_hsalsa20, + crypto_stream_xor: crypto_stream_xor, + crypto_stream: crypto_stream, + crypto_stream_salsa20_xor: crypto_stream_salsa20_xor, + crypto_stream_salsa20: crypto_stream_salsa20, + crypto_onetimeauth: crypto_onetimeauth, + crypto_onetimeauth_verify: crypto_onetimeauth_verify, + crypto_verify_16: crypto_verify_16, + crypto_verify_32: crypto_verify_32, + crypto_secretbox: crypto_secretbox, + crypto_secretbox_open: crypto_secretbox_open, + crypto_scalarmult: crypto_scalarmult, + crypto_scalarmult_base: crypto_scalarmult_base, + crypto_box_beforenm: crypto_box_beforenm, + crypto_box_afternm: crypto_box_afternm, + crypto_box: crypto_box, + crypto_box_open: crypto_box_open, + crypto_box_keypair: crypto_box_keypair, + crypto_hash: crypto_hash, + crypto_sign: crypto_sign, + crypto_sign_keypair: crypto_sign_keypair, + crypto_sign_open: crypto_sign_open, + + crypto_secretbox_KEYBYTES: crypto_secretbox_KEYBYTES, + crypto_secretbox_NONCEBYTES: crypto_secretbox_NONCEBYTES, + crypto_secretbox_ZEROBYTES: crypto_secretbox_ZEROBYTES, + crypto_secretbox_BOXZEROBYTES: crypto_secretbox_BOXZEROBYTES, + crypto_scalarmult_BYTES: crypto_scalarmult_BYTES, + crypto_scalarmult_SCALARBYTES: crypto_scalarmult_SCALARBYTES, + crypto_box_PUBLICKEYBYTES: crypto_box_PUBLICKEYBYTES, + crypto_box_SECRETKEYBYTES: crypto_box_SECRETKEYBYTES, + crypto_box_BEFORENMBYTES: crypto_box_BEFORENMBYTES, + crypto_box_NONCEBYTES: crypto_box_NONCEBYTES, + crypto_box_ZEROBYTES: crypto_box_ZEROBYTES, + crypto_box_BOXZEROBYTES: crypto_box_BOXZEROBYTES, + crypto_sign_BYTES: crypto_sign_BYTES, + crypto_sign_PUBLICKEYBYTES: crypto_sign_PUBLICKEYBYTES, + crypto_sign_SECRETKEYBYTES: crypto_sign_SECRETKEYBYTES, + crypto_sign_SEEDBYTES: crypto_sign_SEEDBYTES, + crypto_hash_BYTES: crypto_hash_BYTES +}; + +/* High-level API */ + +function checkLengths(k, n) { + if (k.length !== crypto_secretbox_KEYBYTES) throw new Error('bad key size'); + if (n.length !== crypto_secretbox_NONCEBYTES) throw new Error('bad nonce size'); +} + +function checkBoxLengths(pk, sk) { + if (pk.length !== crypto_box_PUBLICKEYBYTES) throw new Error('bad public key size'); + if (sk.length !== crypto_box_SECRETKEYBYTES) throw new Error('bad secret key size'); +} + +function checkArrayTypes() { + var t, i; + for (i = 0; i < arguments.length; i++) { + if ((t = Object.prototype.toString.call(arguments[i])) !== '[object Uint8Array]') + throw new TypeError('unexpected type ' + t + ', use Uint8Array'); + } +} + +function cleanup(arr) { + for (var i = 0; i < arr.length; i++) arr[i] = 0; +} + +nacl.util = {}; + +nacl.util.decodeUTF8 = function(s) { + var i, d = unescape(encodeURIComponent(s)), b = new Uint8Array(d.length); + for (i = 0; i < d.length; i++) b[i] = d.charCodeAt(i); + return b; +}; + +nacl.util.encodeUTF8 = function(arr) { + var i, s = []; + for (i = 0; i < arr.length; i++) s.push(String.fromCharCode(arr[i])); + return decodeURIComponent(escape(s.join(''))); +}; + +nacl.util.encodeBase64 = function(arr) { + if (typeof btoa === 'undefined') { + return (new Buffer(arr)).toString('base64'); + } else { + var i, s = [], len = arr.length; + for (i = 0; i < len; i++) s.push(String.fromCharCode(arr[i])); + return btoa(s.join('')); + } +}; + +nacl.util.decodeBase64 = function(s) { + if (typeof atob === 'undefined') { + return new Uint8Array(Array.prototype.slice.call(new Buffer(s, 'base64'), 0)); + } else { + var i, d = atob(s), b = new Uint8Array(d.length); + for (i = 0; i < d.length; i++) b[i] = d.charCodeAt(i); + return b; + } +}; + +nacl.randomBytes = function(n) { + var b = new Uint8Array(n); + randombytes(b, n); + return b; +}; + +nacl.secretbox = function(msg, nonce, key) { + checkArrayTypes(msg, nonce, key); + checkLengths(key, nonce); + var m = new Uint8Array(crypto_secretbox_ZEROBYTES + msg.length); + var c = new Uint8Array(m.length); + for (var i = 0; i < msg.length; i++) m[i+crypto_secretbox_ZEROBYTES] = msg[i]; + crypto_secretbox(c, m, m.length, nonce, key); + return c.subarray(crypto_secretbox_BOXZEROBYTES); +}; + +nacl.secretbox.open = function(box, nonce, key) { + checkArrayTypes(box, nonce, key); + checkLengths(key, nonce); + var c = new Uint8Array(crypto_secretbox_BOXZEROBYTES + box.length); + var m = new Uint8Array(c.length); + for (var i = 0; i < box.length; i++) c[i+crypto_secretbox_BOXZEROBYTES] = box[i]; + if (c.length < 32) return false; + if (crypto_secretbox_open(m, c, c.length, nonce, key) !== 0) return false; + return m.subarray(crypto_secretbox_ZEROBYTES); +}; + +nacl.secretbox.keyLength = crypto_secretbox_KEYBYTES; +nacl.secretbox.nonceLength = crypto_secretbox_NONCEBYTES; +nacl.secretbox.overheadLength = crypto_secretbox_BOXZEROBYTES; + +nacl.scalarMult = function(n, p) { + checkArrayTypes(n, p); + if (n.length !== crypto_scalarmult_SCALARBYTES) throw new Error('bad n size'); + if (p.length !== crypto_scalarmult_BYTES) throw new Error('bad p size'); + var q = new Uint8Array(crypto_scalarmult_BYTES); + crypto_scalarmult(q, n, p); + return q; +}; + +nacl.scalarMult.base = function(n) { + checkArrayTypes(n); + if (n.length !== crypto_scalarmult_SCALARBYTES) throw new Error('bad n size'); + var q = new Uint8Array(crypto_scalarmult_BYTES); + crypto_scalarmult_base(q, n); + return q; +}; + +nacl.scalarMult.scalarLength = crypto_scalarmult_SCALARBYTES; +nacl.scalarMult.groupElementLength = crypto_scalarmult_BYTES; + +nacl.box = function(msg, nonce, publicKey, secretKey) { + var k = nacl.box.before(publicKey, secretKey); + return nacl.secretbox(msg, nonce, k); +}; + +nacl.box.before = function(publicKey, secretKey) { + checkArrayTypes(publicKey, secretKey); + checkBoxLengths(publicKey, secretKey); + var k = new Uint8Array(crypto_box_BEFORENMBYTES); + crypto_box_beforenm(k, publicKey, secretKey); + return k; +}; + +nacl.box.after = nacl.secretbox; + +nacl.box.open = function(msg, nonce, publicKey, secretKey) { + var k = nacl.box.before(publicKey, secretKey); + return nacl.secretbox.open(msg, nonce, k); +}; + +nacl.box.open.after = nacl.secretbox.open; + +nacl.box.keyPair = function() { + var pk = new Uint8Array(crypto_box_PUBLICKEYBYTES); + var sk = new Uint8Array(crypto_box_SECRETKEYBYTES); + crypto_box_keypair(pk, sk); + return {publicKey: pk, secretKey: sk}; +}; + +nacl.box.keyPair.fromSecretKey = function(secretKey) { + checkArrayTypes(secretKey); + if (secretKey.length !== crypto_box_SECRETKEYBYTES) + throw new Error('bad secret key size'); + var pk = new Uint8Array(crypto_box_PUBLICKEYBYTES); + crypto_scalarmult_base(pk, secretKey); + return {publicKey: pk, secretKey: new Uint8Array(secretKey)}; +}; + +nacl.box.publicKeyLength = crypto_box_PUBLICKEYBYTES; +nacl.box.secretKeyLength = crypto_box_SECRETKEYBYTES; +nacl.box.sharedKeyLength = crypto_box_BEFORENMBYTES; +nacl.box.nonceLength = crypto_box_NONCEBYTES; +nacl.box.overheadLength = nacl.secretbox.overheadLength; + +nacl.sign = function(msg, secretKey) { + checkArrayTypes(msg, secretKey); + if (secretKey.length !== crypto_sign_SECRETKEYBYTES) + throw new Error('bad secret key size'); + var signedMsg = new Uint8Array(crypto_sign_BYTES+msg.length); + crypto_sign(signedMsg, msg, msg.length, secretKey); + return signedMsg; +}; + +nacl.sign.open = function(signedMsg, publicKey) { + if (arguments.length !== 2) + throw new Error('nacl.sign.open accepts 2 arguments; did you mean to use nacl.sign.detached.verify?'); + checkArrayTypes(signedMsg, publicKey); + if (publicKey.length !== crypto_sign_PUBLICKEYBYTES) + throw new Error('bad public key size'); + var tmp = new Uint8Array(signedMsg.length); + var mlen = crypto_sign_open(tmp, signedMsg, signedMsg.length, publicKey); + if (mlen < 0) return null; + var m = new Uint8Array(mlen); + for (var i = 0; i < m.length; i++) m[i] = tmp[i]; + return m; +}; + +nacl.sign.detached = function(msg, secretKey) { + var signedMsg = nacl.sign(msg, secretKey); + var sig = new Uint8Array(crypto_sign_BYTES); + for (var i = 0; i < sig.length; i++) sig[i] = signedMsg[i]; + return sig; +}; + +nacl.sign.detached.verify = function(msg, sig, publicKey) { + checkArrayTypes(msg, sig, publicKey); + if (sig.length !== crypto_sign_BYTES) + throw new Error('bad signature size'); + if (publicKey.length !== crypto_sign_PUBLICKEYBYTES) + throw new Error('bad public key size'); + var sm = new Uint8Array(crypto_sign_BYTES + msg.length); + var m = new Uint8Array(crypto_sign_BYTES + msg.length); + var i; + for (i = 0; i < crypto_sign_BYTES; i++) sm[i] = sig[i]; + for (i = 0; i < msg.length; i++) sm[i+crypto_sign_BYTES] = msg[i]; + return (crypto_sign_open(m, sm, sm.length, publicKey) >= 0); +}; + +nacl.sign.keyPair = function() { + var pk = new Uint8Array(crypto_sign_PUBLICKEYBYTES); + var sk = new Uint8Array(crypto_sign_SECRETKEYBYTES); + crypto_sign_keypair(pk, sk); + return {publicKey: pk, secretKey: sk}; +}; + +nacl.sign.keyPair.fromSecretKey = function(secretKey) { + checkArrayTypes(secretKey); + if (secretKey.length !== crypto_sign_SECRETKEYBYTES) + throw new Error('bad secret key size'); + var pk = new Uint8Array(crypto_sign_PUBLICKEYBYTES); + for (var i = 0; i < pk.length; i++) pk[i] = secretKey[32+i]; + return {publicKey: pk, secretKey: new Uint8Array(secretKey)}; +}; + +nacl.sign.keyPair.fromSeed = function(seed) { + checkArrayTypes(seed); + if (seed.length !== crypto_sign_SEEDBYTES) + throw new Error('bad seed size'); + var pk = new Uint8Array(crypto_sign_PUBLICKEYBYTES); + var sk = new Uint8Array(crypto_sign_SECRETKEYBYTES); + for (var i = 0; i < 32; i++) sk[i] = seed[i]; + crypto_sign_keypair(pk, sk, true); + return {publicKey: pk, secretKey: sk}; +}; + +nacl.sign.publicKeyLength = crypto_sign_PUBLICKEYBYTES; +nacl.sign.secretKeyLength = crypto_sign_SECRETKEYBYTES; +nacl.sign.seedLength = crypto_sign_SEEDBYTES; +nacl.sign.signatureLength = crypto_sign_BYTES; + +nacl.hash = function(msg) { + checkArrayTypes(msg); + var h = new Uint8Array(crypto_hash_BYTES); + crypto_hash(h, msg, msg.length); + return h; +}; + +nacl.hash.hashLength = crypto_hash_BYTES; + +nacl.verify = function(x, y) { + checkArrayTypes(x, y); + // Zero length arguments are considered not equal. + if (x.length === 0 || y.length === 0) return false; + if (x.length !== y.length) return false; + return (vn(x, 0, y, 0, x.length) === 0) ? true : false; +}; + +nacl.setPRNG = function(fn) { + randombytes = fn; +}; + +(function() { + // Initialize PRNG if environment provides CSPRNG. + // If not, methods calling randombytes will throw. + var crypto; + if (typeof window !== 'undefined') { + // Browser. + if (window.crypto && window.crypto.getRandomValues) { + crypto = window.crypto; // Standard + } else if (window.msCrypto && window.msCrypto.getRandomValues) { + crypto = window.msCrypto; // Internet Explorer 11+ + } + if (crypto) { + nacl.setPRNG(function(x, n) { + var i, v = new Uint8Array(n); + crypto.getRandomValues(v); + for (i = 0; i < n; i++) x[i] = v[i]; + cleanup(v); + }); + } + } else if (typeof require !== 'undefined') { + // Node.js. + crypto = require('crypto'); + if (crypto) { + nacl.setPRNG(function(x, n) { + var i, v = crypto.randomBytes(n); + for (i = 0; i < n; i++) x[i] = v[i]; + cleanup(v); + }); + } + } +})(); + +class Base58 { + constructor() { + this.ALPHABET = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'; + this.ALPHABET_MAP = {}; + + for (let i = 0; i < this.ALPHABET.length; i++) { + this.ALPHABET_MAP[this.ALPHABET.charAt(i)] = i; + } + } + + encode(buffer) { + buffer = new Uint8Array(buffer); + let carry, digits, j; + if (buffer.length === 0) { + return ''; + } + let i = 0; + digits = [0]; + while (i < buffer.length) { + j = 0; + while (j < digits.length) { + digits[j] <<= 8; + j++; + } + digits[0] += buffer[i]; + carry = 0; + j = 0; + while (j < digits.length) { + digits[j] += carry; + carry = (digits[j] / 58) | 0; + digits[j] %= 58; + ++j; + } + while (carry) { + digits.push(carry % 58); + carry = (carry / 58) | 0; + } + i++; + } + i = 0; + while (buffer[i] === 0 && i < buffer.length - 1) { + digits.push(0); + i++; + } + return digits.reverse().map(digit => this.ALPHABET[digit]).join(''); + } + + decode(string) { + if (string.length === 0) { + return new Uint8Array(0); + } + let bytes = [0]; + let i = 0, j, c, carry; + while (i < string.length) { + c = string[i]; + if (!(c in this.ALPHABET_MAP)) { + throw new Error(`Base58.decode received unacceptable input. Character '${c}' is not in the Base58 alphabet.`); + } + j = 0; + while (j < bytes.length) { + bytes[j] *= 58; + j++; + } + bytes[0] += this.ALPHABET_MAP[c]; + carry = 0; + j = 0; + while (j < bytes.length) { + bytes[j] += carry; + carry = bytes[j] >> 8; + bytes[j] &= 0xff; + ++j; + } + while (carry) { + bytes.push(carry & 0xff); + carry >>= 8; + } + i++; + } + i = 0; + while (string[i] === '1' && i < string.length - 1) { + bytes.push(0); + i++; + } + return new Uint8Array(bytes.reverse()); + } +} + + +class Curve25519 { + constructor() { + this.gf0 = this.gf(); + this.gf1 = this.gf([1]); + this.D = this.gf([0x78a3, 0x1359, 0x4dca, 0x75eb, 0xd8ab, 0x4141, 0x0a4d, 0x0070, 0xe898, 0x7779, 0x4079, 0x8cc7, 0xfe73, 0x2b6f, 0x6cee, 0x5203]); + this.I = this.gf([0xa0b0, 0x4a0e, 0x1b27, 0xc4ee, 0xe478, 0xad2f, 0x1806, 0x2f43, 0xd7a7, 0x3dfb, 0x0099, 0x2b4d, 0xdf0b, 0x4fc1, 0x2480, 0x2b83]); + } + + gf(init) { + let i, r = new Float64Array(16); + if (init) for (i = 0; i < init.length; i++) r[i] = init[i]; + return r; + } + + car25519(o) { + var c, i; + for (i = 0; i < 16; i++) { + o[i] += 65536; + c = Math.floor(o[i] / 65536); + o[(i + 1) * (i < 15 ? 1 : 0)] += c - 1 + 37 * (c - 1) * (i === 15 ? 1 : 0); + o[i] -= (c * 65536); + } + } + + sel25519(p, q, b) { + let t, c = ~(b - 1); + for (let i = 0; i < 16; i++) { + t = c & (p[i] ^ q[i]); + p[i] ^= t; + q[i] ^= t; + } + } + + A(o, a, b) { + for (let i = 0; i < 16; i++) o[i] = a[i] + b[i]; + } + + Z(o, a, b) { + for (let i = 0; i < 16; i++) o[i] = a[i] - b[i]; + } + + M(o, a, b) { + var i, j, t = new Float64Array(31); + for (i = 0; i < 31; i++) t[i] = 0; + for (i = 0; i < 16; i++) { + for (j = 0; j < 16; j++) { + t[i+j] += a[i] * b[j]; + } + } + for (i = 0; i < 15; i++) { + t[i] += 38 * t[i+16]; + } + for (i = 0; i < 16; i++) o[i] = t[i]; + this.car25519(o); + this.car25519(o); + } + + inv25519(o, i) { + var c = this.gf(); + var a; + for (a = 0; a < 16; a++) c[a] = i[a]; + for (a = 253; a >= 0; a--) { + this.S(c, c); + if(a !== 2 && a !== 4) this.M(c, c, i); + } + for (a = 0; a < 16; a++) o[a] = c[a]; + } + + pack25519(o, n) { + var i, j, b; + var m = gf(), t = gf(); + for (i = 0; i < 16; i++) t[i] = n[i]; + this.car25519(t); + this.car25519(t); + this.car25519(t); + for (j = 0; j < 2; j++) { + m[0] = t[0] - 0xffed; + for (i = 1; i < 15; i++) { + m[i] = t[i] - 0xffff - ((m[i-1]>>16) & 1); + m[i-1] &= 0xffff; + } + m[15] = t[15] - 0x7fff - ((m[14]>>16) & 1); + b = (m[15]>>16) & 1; + m[14] &= 0xffff; + this.sel25519(t, m, 1-b); + } + for (i = 0; i < 16; i++) { + o[2*i] = t[i] & 0xff; + o[2*i+1] = t[i] >> 8; + } + } + S(o, a) { + this.M(o, a, a); + } + + unpackneg(r, p) { + var t = this.gf(), chk = this.gf(), num = this.gf(), + den = this.gf(), den2 = this.gf(), den4 = this.gf(), + den6 = this.gf(); + + this.set25519(r[2], gf1); + this.unpack25519(r[1], p); + this.S(num, r[1]); + this.M(den, num, D); + this.Z(num, num, r[2]); + this.A(den, r[2], den); + + this.S(den2, den); + this.S(den4, den2); + this.M(den6, den4, den2); + this.M(t, den6, num); + this.M(t, t, den); + + this.pow2523(t, t); + this.M(t, t, num); + this.M(t, t, den); + this.M(t, t, den); + this.M(r[0], t, den); + + this.S(chk, r[0]); + this.M(chk, chk, den); + if (this.neq25519(chk, num)) this.M(r[0], r[0], I); + + this.S(chk, r[0]); + this.M(chk, chk, den); + if (this.neq25519(chk, num)) return -1; + + if (this.par25519(r[0]) === (p[31] >> 7)) this.Z(r[0], gf0, r[0]); + + this.M(r[3], r[0], r[1]); + return 0; + } + + neq25519(a, b) { + var c = new Uint8Array(32), d = new Uint8Array(32); + this.pack25519(c, a); + this.pack25519(d, b); + return this.crypto_verify_32(c, 0, d, 0); + } + + crypto_verify_32(x, xi, y, yi) { + return this.vn(x, xi, y, yi, 32); + } + + pow2523(o, i) { + var c = this.gf(); + var a; + for (a = 0; a < 16; a++) c[a] = i[a]; + for (a = 250; a >= 0; a--) { + this.S(c, c); + if (a !== 1) this.M(c, c, i); + } + for (a = 0; a < 16; a++) o[a] = c[a]; + } + + vn(x, xi, y, yi, n) { + var i, d = 0; + for (i = 0; i < n; i++) d |= x[xi + i] ^ y[yi + i]; + return (1 & ((d - 1) >>> 8)) - 1; + } + set25519(r, a) { + var i; + for (i = 0; i < 16; i++) r[i] = a[i] | 0; + } + + convertPublicKey(pk) { + var z = new Uint8Array(32), + q = [this.gf(), this.gf(), this.gf(), this.gf()], + a = this.gf(), b = this.gf(); + + if (this.unpackneg(q, pk)) return null; // reject invalid key + + var y = q[1]; + + this.A(a, gf1, y); + this.Z(b, gf1, y); + this.inv25519(b, b); + this.M(a, a, b); + + this.pack25519(z, a); + return z; + } + + convertSecretKey(sk) { + var d = new Uint8Array(64), o = new Uint8Array(32), i; + nacl.lowlevel.crypto_hash(d, sk, 32); + d[0] &= 248; + d[31] &= 127; + d[31] |= 64; + for (i = 0; i < 32; i++) o[i] = d[i]; + for (i = 0; i < 64; i++) d[i] = 0; + return o; + } + + convertKeyPair(edKeyPair) { + var publicKey = this.convertPublicKey(edKeyPair.publicKey); + if (!publicKey) return null; + return { + publicKey: publicKey, + secretKey: this.convertSecretKey(edKeyPair.secretKey) + }; + } +} + + + +const base58Instant = new Base58(); + +const curve25519Instance = new Curve25519(); + + +self.addEventListener('message', async (e) => { + + try { + const decodeMsgs = e.data.messages.map((eachMessage) => { + return decodeMessage( + eachMessage, + e.data.isReceipient, + e.data._publicKey, + e.data.privateKey + ); + }); + postMessage(decodeMsgs); + } catch (error) { + console.log('error', error); + postMessage({ + type: 'error', + message: error.message || 'An error occurred', + }); + } +}); + +const decode = (string) => { + const binaryString = atob(string); + const binaryLength = binaryString.length; + const bytes = new Uint8Array(binaryLength); + + for (let i = 0; i < binaryLength; i++) { + bytes[i] = binaryString.charCodeAt(i); + } + + const decoder = new TextDecoder(); + const decodedString = decoder.decode(bytes); + return decodedString; +}; + +export const decryptChatMessageBase64 = ( + encryptedMessage, + privateKey, + recipientPublicKey, + lastReference +) => { + let _encryptedMessage = atob(encryptedMessage); + const binaryLength = _encryptedMessage.length; + const bytes = new Uint8Array(binaryLength); + + for (let i = 0; i < binaryLength; i++) { + bytes[i] = _encryptedMessage.charCodeAt(i); + } + + const _base58RecipientPublicKey = + recipientPublicKey instanceof Uint8Array + ? base58Instant.encode(recipientPublicKey) + : recipientPublicKey; + const _recipientPublicKey = base58Instant.decode(_base58RecipientPublicKey); + + const _lastReference = + lastReference instanceof Uint8Array + ? lastReference + : base58Instant.decode(lastReference); + + const convertedPrivateKey = curve25519Instance.convertSecretKey(privateKey); + const convertedPublicKey = curve25519Instance.convertPublicKey(_recipientPublicKey); + const sharedSecret = new Uint8Array(32); + nacl.lowlevel.crypto_scalarmult( + sharedSecret, + convertedPrivateKey, + convertedPublicKey + ); + + const _chatEncryptionSeed = new Sha256() + .process(sharedSecret) + .finish().result; + const _decryptedMessage = nacl.secretbox.open( + bytes, + _lastReference.slice(0, 24), + _chatEncryptionSeed + ); + + if (_decryptedMessage === false) { + return _decryptedMessage; + } + return new TextDecoder('utf-8').decode(_decryptedMessage); +}; + +const decodeMessage = ( + encodedMessageObj, + isReceipient, + _publicKey, + privateKey +) => { + let isReceipientVar; + let _publicKeyVar; + try { + isReceipientVar = isReceipient; + _publicKeyVar = _publicKey; + } catch (error) { + isReceipientVar = isReceipient; + _publicKeyVar = _publicKey; + } + + let decodedMessageObj = {}; + + if (isReceipientVar === true) { + // direct chat + if ( + encodedMessageObj.isEncrypted === true && + _publicKeyVar.hasPubKey === true && + encodedMessageObj.data + ) { + let decodedMessage = decryptChatMessageBase64( + encodedMessageObj.data, + privateKey, + _publicKeyVar, + encodedMessageObj.reference + ); + decodedMessageObj = { ...encodedMessageObj, decodedMessage }; + } else if ( + encodedMessageObj.isEncrypted === false && + encodedMessageObj.data + ) { + let decodedMessage = decode(encodedMessageObj.data); + decodedMessageObj = { ...encodedMessageObj, decodedMessage }; + } else { + decodedMessageObj = { + ...encodedMessageObj, + decodedMessage: 'Cannot Decrypt Message!', + }; + } + } else { + // group chat + let decodedMessage = decode(encodedMessageObj.data); + decodedMessageObj = { ...encodedMessageObj, decodedMessage }; + } + return decodedMessageObj; +}; diff --git a/plugins/plugins/core/components/webworkerSortMessages.js b/plugins/plugins/core/components/webworkerSortMessages.js new file mode 100644 index 00000000..2f290b40 --- /dev/null +++ b/plugins/plugins/core/components/webworkerSortMessages.js @@ -0,0 +1,15 @@ + + + +self.addEventListener('message', async e => { + const response = e.data.list.sort(function (a, b) { + return a.timestamp + - b.timestamp +}) +postMessage(response) +}) + + + + + diff --git a/plugins/plugins/core/messaging/q-chat/q-chat.src.js b/plugins/plugins/core/messaging/q-chat/q-chat.src.js index 5658f412..5e784bf3 100644 --- a/plugins/plugins/core/messaging/q-chat/q-chat.src.js +++ b/plugins/plugins/core/messaging/q-chat/q-chat.src.js @@ -61,7 +61,7 @@ class Chat extends LitElement { constructor() { super() - this.selectedAddress = {} + this.selectedAddress = window.parent.reduxStore.getState().app.selectedAddress this.config = { user: { node: { @@ -179,6 +179,7 @@ class Chat extends LitElement { } render() { + console.log('q-chat') return html`
@@ -443,12 +444,7 @@ class Chat extends LitElement { let configLoaded = false parentEpml.ready().then(() => { - parentEpml.subscribe('selected_address', async selectedAddress => { - this.selectedAddress = {} - selectedAddress = JSON.parse(selectedAddress) - if (!selectedAddress || Object.entries(selectedAddress).length === 0) return - this.selectedAddress = selectedAddress - }) + parentEpml.subscribe('config', c => { if (!configLoaded) { setTimeout(getBlockedUsers, 1) @@ -474,6 +470,11 @@ class Chat extends LitElement { }, 60000) } + async updated(changedProperties) { + console.log({changedProperties}) + + } + clearConsole() { if (!isElectron()) { } else { From 45276f8fd0d188e77bcb4232740597eb95486d00 Mon Sep 17 00:00:00 2001 From: PhilReact Date: Tue, 5 Sep 2023 23:04:35 -0500 Subject: [PATCH 06/57] fix public key in webworker --- plugins/plugins/core/components/ChatPage.js | 51 ++++++++++++------- .../components/webworkerDecodeMessages.js | 37 ++++++++++++-- 2 files changed, 64 insertions(+), 24 deletions(-) diff --git a/plugins/plugins/core/components/ChatPage.js b/plugins/plugins/core/components/ChatPage.js index 50313737..f831a60e 100644 --- a/plugins/plugins/core/components/ChatPage.js +++ b/plugins/plugins/core/components/ChatPage.js @@ -2682,7 +2682,7 @@ class ChatPage extends LitElement { let decodeMsgs = [] - try { + await new Promise((res, rej) => { console.log('this.webWorkerDecodeMessages2.', this.webWorkerDecodeMessages) this.webWorkerDecodeMessages.postMessage({messages: getInitialMessages, isReceipient: this.isReceipient, _publicKey: this._publicKey, privateKey: window.parent.reduxStore.getState().app.selectedAddress.keyPair.privateKey }); @@ -2699,10 +2699,7 @@ class ChatPage extends LitElement { } }) - } catch (error) { - console.log({error}) - } - + console.log({decodeMsgs}) queue.push(() => replaceMessagesEdited({ decodedMessages: decodeMsgs, @@ -2713,10 +2710,7 @@ class ChatPage extends LitElement { addToUpdateMessageHashmap: this.addToUpdateMessageHashmap })); let list = [...decodeMsgs, ...this.messagesRendered.slice(0,80)] - // this.messagesRendered = [...decodeMsgs, ...this.messagesRendered.slice(0,80)].sort(function (a, b) { - // return a.timestamp - // - b.timestamp - // }) + await new Promise((res, rej) => { this.webWorkerSortMessages.postMessage({list}); @@ -2911,18 +2905,37 @@ viewElement.scrollTop = originalScrollTop + heightDifference; async processMessages(messages, isInitial) { const isReceipient = this.chatId.includes('direct') - const decodedMessages = messages.map((eachMessage) => { + // const decodedMessages = messages.map((eachMessage) => { - if (eachMessage.isText === true) { - this.messageSignature = eachMessage.signature - let _eachMessage = this.decodeMessage(eachMessage) - return _eachMessage - } else { - this.messageSignature = eachMessage.signature - let _eachMessage = this.decodeMessage(eachMessage) - return _eachMessage + // if (eachMessage.isText === true) { + // this.messageSignature = eachMessage.signature + // let _eachMessage = this.decodeMessage(eachMessage) + // return _eachMessage + // } else { + // this.messageSignature = eachMessage.signature + // let _eachMessage = this.decodeMessage(eachMessage) + // return _eachMessage + // } + // }) + + let decodedMessages = [] + console.log({messages: messages, isReceipient: this.isReceipient, _publicKey: this._publicKey, privateKey: window.parent.reduxStore.getState().app.selectedAddress.keyPair.privateKey}) + + await new Promise((res, rej) => { + this.webWorkerDecodeMessages.postMessage({messages: messages, isReceipient: this.isReceipient, _publicKey: this._publicKey, privateKey: window.parent.reduxStore.getState().app.selectedAddress.keyPair.privateKey }); + + this.webWorkerDecodeMessages.onmessage = e => { + decodedMessages = e.data + res() + } - }) + this.webWorkerDecodeMessages.onerror = e => { + console.log('e',e) + rej() + + } + }) + console.log('process', decodedMessages) if (isInitial) { this.chatEditorPlaceholder = await this.renderPlaceholder() diff --git a/plugins/plugins/core/components/webworkerDecodeMessages.js b/plugins/plugins/core/components/webworkerDecodeMessages.js index 36883f9f..794be632 100644 --- a/plugins/plugins/core/components/webworkerDecodeMessages.js +++ b/plugins/plugins/core/components/webworkerDecodeMessages.js @@ -2465,6 +2465,7 @@ class Base58 { } decode(string) { + console.log({string}) if (string.length === 0) { return new Uint8Array(0); } @@ -2644,6 +2645,16 @@ class Curve25519 { this.pack25519(d, b); return this.crypto_verify_32(c, 0, d, 0); } + par25519(a) { + var d = new Uint8Array(32); + this.pack25519(d, a); + return d[0] & 1; + } + unpack25519(o, n) { + var i; + for (i = 0; i < 16; i++) o[i] = n[2*i] + (n[2*i+1] << 8); + o[15] &= 0x7fff; + } crypto_verify_32(x, xi, y, yi) { return this.vn(x, xi, y, yi, 32); @@ -2757,6 +2768,12 @@ export const decryptChatMessageBase64 = ( recipientPublicKey, lastReference ) => { + console.log('1', { + encryptedMessage, + privateKey, + recipientPublicKey, + lastReference + }) let _encryptedMessage = atob(encryptedMessage); const binaryLength = _encryptedMessage.length; const bytes = new Uint8Array(binaryLength); @@ -2765,12 +2782,21 @@ export const decryptChatMessageBase64 = ( bytes[i] = _encryptedMessage.charCodeAt(i); } - const _base58RecipientPublicKey = - recipientPublicKey instanceof Uint8Array - ? base58Instant.encode(recipientPublicKey) - : recipientPublicKey; - const _recipientPublicKey = base58Instant.decode(_base58RecipientPublicKey); + let _base58RecipientPublic = recipientPublicKey + try { + _base58RecipientPublic = recipientPublicKey.key + } catch (error) { + _base58RecipientPublic = recipientPublicKey + } + + const _base58RecipientPublicKey = + _base58RecipientPublic instanceof Uint8Array + ? base58Instant.encode(_base58RecipientPublic) + : _base58RecipientPublic; + console.log({_base58RecipientPublicKey}) + const _recipientPublicKey = base58Instant.decode(_base58RecipientPublicKey); + console.log({_recipientPublicKey}) const _lastReference = lastReference instanceof Uint8Array ? lastReference @@ -2825,6 +2851,7 @@ const decodeMessage = ( _publicKeyVar.hasPubKey === true && encodedMessageObj.data ) { + console.log('hello encrypt') let decodedMessage = decryptChatMessageBase64( encodedMessageObj.data, privateKey, From 451fdd4f763cd4f9c43a0ad7f61090746407a700 Mon Sep 17 00:00:00 2001 From: PhilReact Date: Tue, 5 Sep 2023 23:16:44 -0500 Subject: [PATCH 07/57] switch all msg processing to webworkers --- plugins/plugins/core/components/ChatPage.js | 343 +++++++++++++++----- 1 file changed, 266 insertions(+), 77 deletions(-) diff --git a/plugins/plugins/core/components/ChatPage.js b/plugins/plugins/core/components/ChatPage.js index f831a60e..9836b3e7 100644 --- a/plugins/plugins/core/components/ChatPage.js +++ b/plugins/plugins/core/components/ChatPage.js @@ -2578,9 +2578,21 @@ class ChatPage extends LitElement { url: `/chat/messages?involving=${window.parent.reduxStore.getState().app.selectedAddress.address}&involving=${this._chatId}&limit=${limit}&reverse=true&before=${before}&after=${after}&haschatreference=false&encoding=BASE64` }) - const decodeMsgs = getInitialMessages.map((eachMessage) => { - return this.decodeMessage(eachMessage) - }) + let decodeMsgs = [] + await new Promise((res, rej) => { + this.webWorkerDecodeMessages.postMessage({messages: getInitialMessages, isReceipient: this.isReceipient, _publicKey: this._publicKey, privateKey: window.parent.reduxStore.getState().app.selectedAddress.keyPair.privateKey }); + + this.webWorkerDecodeMessages.onmessage = e => { + decodeMsgs = e.data + res() + + } + this.webWorkerDecodeMessages.onerror = e => { + console.log('e',e) + rej() + + } + }) queue.push(() => replaceMessagesEdited({ @@ -2592,10 +2604,24 @@ class ChatPage extends LitElement { addToUpdateMessageHashmap: this.addToUpdateMessageHashmap })); - this.messagesRendered = [...decodeMsgs, ...this.messagesRendered].sort(function (a, b) { - return a.timestamp - - b.timestamp - }) + + let list = [...decodeMsgs, ...this.messagesRendered.slice(0,80)] + + await new Promise((res, rej) => { + + this.webWorkerSortMessages.postMessage({list}); + + this.webWorkerSortMessages.onmessage = e => { + console.log('e',e) + + list = e.data + res() + + } + }) + + this.messagesRendered = list + this.isLoadingOldMessages = false await this.getUpdateComplete() @@ -2610,9 +2636,21 @@ class ChatPage extends LitElement { url: `/chat/messages?txGroupId=${Number(this._chatId)}&limit=${limit}&reverse=true&before=${before}&after=${after}&haschatreference=false&encoding=BASE64` }) - const decodeMsgs = getInitialMessages.map((eachMessage) => { - return this.decodeMessage(eachMessage) - }) + let decodeMsgs = [] + await new Promise((res, rej) => { + this.webWorkerDecodeMessages.postMessage({messages: getInitialMessages, isReceipient: this.isReceipient, _publicKey: this._publicKey, privateKey: window.parent.reduxStore.getState().app.selectedAddress.keyPair.privateKey }); + + this.webWorkerDecodeMessages.onmessage = e => { + decodeMsgs = e.data + res() + + } + this.webWorkerDecodeMessages.onerror = e => { + console.log('e',e) + rej() + + } + }) queue.push(() => replaceMessagesEdited({ decodedMessages: decodeMsgs, @@ -2624,10 +2662,24 @@ class ChatPage extends LitElement { })); - this.messagesRendered = [...decodeMsgs, ...this.messagesRendered].sort(function (a, b) { - return a.timestamp - - b.timestamp - }) + + let list = [...decodeMsgs, ...this.messagesRendered.slice(0,80)] + + await new Promise((res, rej) => { + + this.webWorkerSortMessages.postMessage({list}); + + this.webWorkerSortMessages.onmessage = e => { + console.log('e',e) + + list = e.data + res() + + } + }) + + this.messagesRendered = list + this.isLoadingOldMessages = false await this.getUpdateComplete() @@ -2645,11 +2697,21 @@ class ChatPage extends LitElement { type: 'api', url: `/chat/messages?involving=${window.parent.reduxStore.getState().app.selectedAddress.address}&involving=${this._chatId}&limit=20&reverse=true&before=${scrollElement.messageObj.timestamp}&haschatreference=false&encoding=BASE64` }) - - const decodeMsgs = getInitialMessages.map((eachMessage) => { - return this.decodeMessage(eachMessage) - }) - + let decodeMsgs = [] + await new Promise((res, rej) => { + this.webWorkerDecodeMessages.postMessage({messages: getInitialMessages, isReceipient: this.isReceipient, _publicKey: this._publicKey, privateKey: window.parent.reduxStore.getState().app.selectedAddress.keyPair.privateKey }); + + this.webWorkerDecodeMessages.onmessage = e => { + decodeMsgs = e.data + res() + + } + this.webWorkerDecodeMessages.onerror = e => { + console.log('e',e) + rej() + + } + }) queue.push(() => replaceMessagesEdited({ decodedMessages: decodeMsgs, @@ -2659,11 +2721,23 @@ class ChatPage extends LitElement { _publicKey: this._publicKey, addToUpdateMessageHashmap: this.addToUpdateMessageHashmap })); - const lengthOfExistingMsgs = this.messagesRendered - this.messagesRendered = [...decodeMsgs, ...this.messagesRendered.slice(0, 80)].sort(function (a, b) { - return a.timestamp - - b.timestamp - }) + + let list = [...decodeMsgs, ...this.messagesRendered.slice(0,80)] + + await new Promise((res, rej) => { + + this.webWorkerSortMessages.postMessage({list}); + + this.webWorkerSortMessages.onmessage = e => { + console.log('e',e) + + list = e.data + res() + + } + }) + ) + this.messagesRendered = list this.isLoadingOldMessages = false await this.getUpdateComplete() @@ -2684,7 +2758,6 @@ class ChatPage extends LitElement { await new Promise((res, rej) => { - console.log('this.webWorkerDecodeMessages2.', this.webWorkerDecodeMessages) this.webWorkerDecodeMessages.postMessage({messages: getInitialMessages, isReceipient: this.isReceipient, _publicKey: this._publicKey, privateKey: window.parent.reduxStore.getState().app.selectedAddress.keyPair.privateKey }); this.webWorkerDecodeMessages.onmessage = e => { @@ -2723,7 +2796,7 @@ class ChatPage extends LitElement { } }) - console.log({list}) + ) this.messagesRendered = list this.isLoadingOldMessages = false await this.getUpdateComplete() @@ -2746,9 +2819,21 @@ class ChatPage extends LitElement { url: `/chat/messages?involving=${window.parent.reduxStore.getState().app.selectedAddress.address}&involving=${this._chatId}&limit=20&reverse=true&after=${timestamp}&haschatreference=false&encoding=BASE64` }) - const decodeMsgs = getInitialMessages.map((eachMessage) => { - return this.decodeMessage(eachMessage) - }) + let decodeMsgs = [] + await new Promise((res, rej) => { + this.webWorkerDecodeMessages.postMessage({messages: getInitialMessages, isReceipient: this.isReceipient, _publicKey: this._publicKey, privateKey: window.parent.reduxStore.getState().app.selectedAddress.keyPair.privateKey }); + + this.webWorkerDecodeMessages.onmessage = e => { + decodeMsgs = e.data + res() + + } + this.webWorkerDecodeMessages.onerror = e => { + console.log('e',e) + rej() + + } + }) queue.push(() => replaceMessagesEdited({ @@ -2759,10 +2844,23 @@ class ChatPage extends LitElement { _publicKey: this._publicKey, addToUpdateMessageHashmap: this.addToUpdateMessageHashmap })); - this.messagesRendered = [ ...this.messagesRendered.slice(-80), ...decodeMsgs].sort(function (a, b) { - return a.timestamp - - b.timestamp - }) + + let list = [...this.messagesRendered.slice(-80), ...decodeMsgs] + + await new Promise((res, rej) => { + + this.webWorkerSortMessages.postMessage({list}); + + this.webWorkerSortMessages.onmessage = e => { + console.log('e',e) + + list = e.data + res() + + } + }) + + this.messagesRendered = list this.isLoadingOldMessages = false await this.getUpdateComplete() @@ -2778,10 +2876,21 @@ class ChatPage extends LitElement { type: 'api', url: `/chat/messages?txGroupId=${Number(this._chatId)}&limit=20&reverse=true&after=${timestamp}&haschatreference=false&encoding=BASE64` }) - - const decodeMsgs = getInitialMessages.map((eachMessage) => { - return this.decodeMessage(eachMessage) - }) + let decodeMsgs = [] + await new Promise((res, rej) => { + this.webWorkerDecodeMessages.postMessage({messages: getInitialMessages, isReceipient: this.isReceipient, _publicKey: this._publicKey, privateKey: window.parent.reduxStore.getState().app.selectedAddress.keyPair.privateKey }); + + this.webWorkerDecodeMessages.onmessage = e => { + decodeMsgs = e.data + res() + + } + this.webWorkerDecodeMessages.onerror = e => { + console.log('e',e) + rej() + + } + }) queue.push(() => replaceMessagesEdited({ @@ -2793,10 +2902,25 @@ class ChatPage extends LitElement { addToUpdateMessageHashmap: this.addToUpdateMessageHashmap })); - this.messagesRendered = [...this.messagesRendered.slice(-80), ...decodeMsgs].sort(function (a, b) { - return a.timestamp - - b.timestamp - }) + + + let list = [...this.messagesRendered.slice(-80), ...decodeMsgs] + + await new Promise((res, rej) => { + + this.webWorkerSortMessages.postMessage({list}); + + this.webWorkerSortMessages.onmessage = e => { + console.log('e',e) + + list = e.data + res() + + } + }) + + this.messagesRendered = list + this.isLoadingOldMessages = false await this.getUpdateComplete() @@ -2839,9 +2963,21 @@ viewElement.scrollTop = originalScrollTop + heightDifference; url: `/chat/messages?involving=${window.parent.reduxStore.getState().app.selectedAddress.address}&involving=${this._chatId}&limit=20&reverse=true&afer=${scrollElement.messageObj.timestamp}&haschatreference=false&encoding=BASE64` }) - const decodeMsgs = getInitialMessages.map((eachMessage) => { - return this.decodeMessage(eachMessage) - }) + let decodeMsgs = [] + await new Promise((res, rej) => { + this.webWorkerDecodeMessages.postMessage({messages: getInitialMessages, isReceipient: this.isReceipient, _publicKey: this._publicKey, privateKey: window.parent.reduxStore.getState().app.selectedAddress.keyPair.privateKey }); + + this.webWorkerDecodeMessages.onmessage = e => { + decodeMsgs = e.data + res() + + } + this.webWorkerDecodeMessages.onerror = e => { + console.log('e',e) + rej() + + } + }) queue.push(() => replaceMessagesEdited({ @@ -2853,10 +2989,24 @@ viewElement.scrollTop = originalScrollTop + heightDifference; addToUpdateMessageHashmap: this.addToUpdateMessageHashmap })); - this.messagesRendered = [...this.messagesRendered, ...decodeMsgs].sort(function (a, b) { - return a.timestamp - - b.timestamp - }) + + let list = [...this.messagesRendered.slice(-80), ...decodeMsgs] + + await new Promise((res, rej) => { + + this.webWorkerSortMessages.postMessage({list}); + + this.webWorkerSortMessages.onmessage = e => { + console.log('e',e) + + list = e.data + res() + + } + }) + + this.messagesRendered = list + this.isLoadingOldMessages = false await this.getUpdateComplete() @@ -2873,9 +3023,21 @@ viewElement.scrollTop = originalScrollTop + heightDifference; url: `/chat/messages?txGroupId=${Number(this._chatId)}&limit=20&reverse=true&after=${scrollElement.messageObj.timestamp}&haschatreference=false&encoding=BASE64` }) - const decodeMsgs = getInitialMessages.map((eachMessage) => { - return this.decodeMessage(eachMessage) - }) + let decodeMsgs = [] + await new Promise((res, rej) => { + this.webWorkerDecodeMessages.postMessage({messages: getInitialMessages, isReceipient: this.isReceipient, _publicKey: this._publicKey, privateKey: window.parent.reduxStore.getState().app.selectedAddress.keyPair.privateKey }); + + this.webWorkerDecodeMessages.onmessage = e => { + decodeMsgs = e.data + res() + + } + this.webWorkerDecodeMessages.onerror = e => { + console.log('e',e) + rej() + + } + }) queue.push(() => replaceMessagesEdited({ @@ -2887,10 +3049,23 @@ viewElement.scrollTop = originalScrollTop + heightDifference; addToUpdateMessageHashmap: this.addToUpdateMessageHashmap })); - this.messagesRendered = [...this.messagesRendered, ...decodeMsgs].sort(function (a, b) { - return a.timestamp - - b.timestamp - }) + + let list = [...this.messagesRendered.slice(-80), ...decodeMsgs] + + await new Promise((res, rej) => { + + this.webWorkerSortMessages.postMessage({list}); + + this.webWorkerSortMessages.onmessage = e => { + console.log('e',e) + + list = e.data + res() + + } + }) + + this.messagesRendered = list this.isLoadingOldMessages = false await this.getUpdateComplete() @@ -2905,19 +3080,6 @@ viewElement.scrollTop = originalScrollTop + heightDifference; async processMessages(messages, isInitial) { const isReceipient = this.chatId.includes('direct') - // const decodedMessages = messages.map((eachMessage) => { - - // if (eachMessage.isText === true) { - // this.messageSignature = eachMessage.signature - // let _eachMessage = this.decodeMessage(eachMessage) - // return _eachMessage - // } else { - // this.messageSignature = eachMessage.signature - // let _eachMessage = this.decodeMessage(eachMessage) - // return _eachMessage - // } - // }) - let decodedMessages = [] console.log({messages: messages, isReceipient: this.isReceipient, _publicKey: this._publicKey, privateKey: window.parent.reduxStore.getState().app.selectedAddress.keyPair.privateKey}) @@ -2954,10 +3116,24 @@ viewElement.scrollTop = originalScrollTop + heightDifference; } - this._messages = decodedMessages.sort(function (a, b) { - return a.timestamp - - b.timestamp - }) + + + let list = decodedMessages + + await new Promise((res, rej) => { + + this.webWorkerSortMessages.postMessage({list}); + + this.webWorkerSortMessages.onmessage = e => { + console.log('e',e) + + list = e.data + res() + + } + }) + + this.messagesRendered = list // TODO: Determine number of initial messages by screen height... // this.messagesRendered = this._messages @@ -2991,11 +3167,24 @@ viewElement.scrollTop = originalScrollTop + heightDifference; })) } - // this.newMessages = this.newMessages.concat(_newMessages) - this.messagesRendered = [...this.messagesRendered].sort(function (a, b) { - return a.timestamp - - b.timestamp - }) + + + let list = [...this.messagesRendered] + + await new Promise((res, rej) => { + + this.webWorkerSortMessages.postMessage({list}); + + this.webWorkerSortMessages.onmessage = e => { + console.log('e',e) + + list = e.data + res() + + } + }) + + this.messagesRendered = list } } From e63798e58d83fe8206d388d7a211de5c583a6a0d Mon Sep 17 00:00:00 2001 From: PhilReact Date: Tue, 5 Sep 2023 23:20:58 -0500 Subject: [PATCH 08/57] fix error --- plugins/plugins/core/components/ChatPage.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/plugins/core/components/ChatPage.js b/plugins/plugins/core/components/ChatPage.js index 9836b3e7..5146a22a 100644 --- a/plugins/plugins/core/components/ChatPage.js +++ b/plugins/plugins/core/components/ChatPage.js @@ -2736,7 +2736,7 @@ class ChatPage extends LitElement { } }) - ) + this.messagesRendered = list this.isLoadingOldMessages = false @@ -2796,7 +2796,7 @@ class ChatPage extends LitElement { } }) - ) + this.messagesRendered = list this.isLoadingOldMessages = false await this.getUpdateComplete() From 4824bc185a0713473cd85e2db2cee5b0c2811b19 Mon Sep 17 00:00:00 2001 From: PhilReact Date: Tue, 5 Sep 2023 23:32:49 -0500 Subject: [PATCH 09/57] fix initial message error --- plugins/plugins/core/components/ChatPage.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/plugins/core/components/ChatPage.js b/plugins/plugins/core/components/ChatPage.js index 5146a22a..cef89daa 100644 --- a/plugins/plugins/core/components/ChatPage.js +++ b/plugins/plugins/core/components/ChatPage.js @@ -3133,7 +3133,7 @@ viewElement.scrollTop = originalScrollTop + heightDifference; } }) - this.messagesRendered = list + this._messages = list // TODO: Determine number of initial messages by screen height... // this.messagesRendered = this._messages From 772c5f689a9ded7998031a06e3cc805320ecb802 Mon Sep 17 00:00:00 2001 From: PhilReact Date: Wed, 6 Sep 2023 00:43:16 -0500 Subject: [PATCH 10/57] fix sending message --- plugins/plugins/core/components/ChatPage.js | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/plugins/plugins/core/components/ChatPage.js b/plugins/plugins/core/components/ChatPage.js index cef89daa..b3d65f65 100644 --- a/plugins/plugins/core/components/ChatPage.js +++ b/plugins/plugins/core/components/ChatPage.js @@ -1291,7 +1291,7 @@ class ChatPage extends LitElement { this.setUserName = this.setUserName.bind(this) this.setSelectedHead = this.setSelectedHead.bind(this) this.setGifsLoading = this.setGifsLoading.bind(this) - this.selectedAddress = {} + this.selectedAddress = window.parent.reduxStore.getState().app.selectedAddress this.userName = "" this.chatId = '' this.myAddress = '' @@ -1406,7 +1406,6 @@ class ChatPage extends LitElement { } render() { - console.log('chatpage') return html`
replaceMessagesEdited({ decodedMessages: decodeMsgs, @@ -2811,7 +2806,6 @@ class ChatPage extends LitElement { async getAfterMessages(scrollElement) { const firstMsg = this.messagesRendered.at(-1) const timestamp = scrollElement.messageObj.timestamp - console.log('getAfterMessages') if (this.isReceipient) { const getInitialMessages = await parentEpml.request('apiCall', { @@ -2934,7 +2928,6 @@ class ChatPage extends LitElement { } async addToUpdateMessageHashmap(array){ - console.log({array}) const chatscrollerEl = this.shadowRoot.querySelector('chat-scroller') if(!chatscrollerEl) return const viewElement = this.shadowRoot.querySelector('chat-scroller').shadowRoot.getElementById('viewElement') @@ -3081,8 +3074,12 @@ viewElement.scrollTop = originalScrollTop + heightDifference; async processMessages(messages, isInitial) { const isReceipient = this.chatId.includes('direct') let decodedMessages = [] - console.log({messages: messages, isReceipient: this.isReceipient, _publicKey: this._publicKey, privateKey: window.parent.reduxStore.getState().app.selectedAddress.keyPair.privateKey}) - + if(!this.webWorkerDecodeMessages){ + this.webWorkerDecodeMessages = new WebWorkerDecodeMessages() + } + if(!this.webWorkerSortMessages){ + this.webWorkerSortMessages = new WebWorkerSortMessages() + } await new Promise((res, rej) => { this.webWorkerDecodeMessages.postMessage({messages: messages, isReceipient: this.isReceipient, _publicKey: this._publicKey, privateKey: window.parent.reduxStore.getState().app.selectedAddress.keyPair.privateKey }); @@ -3097,7 +3094,6 @@ viewElement.scrollTop = originalScrollTop + heightDifference; } }) - console.log('process', decodedMessages) if (isInitial) { this.chatEditorPlaceholder = await this.renderPlaceholder() @@ -3278,7 +3274,6 @@ viewElement.scrollTop = originalScrollTop + heightDifference; isReceipientVar = isReceipient _publicKeyVar = _publicKey } - console.log({_publicKeyVar}) let decodedMessageObj = {} @@ -4066,7 +4061,6 @@ viewElement.scrollTop = originalScrollTop + heightDifference; async sendMessage(messageText, typeMessage, chatReference, isForward) { this.isLoading = true - let _reference = new Uint8Array(64) window.crypto.getRandomValues(_reference) let reference = window.parent.Base58.encode(_reference) From 48bf6b3099659da481f10521273d77b5d9d6837d Mon Sep 17 00:00:00 2001 From: PhilReact Date: Wed, 6 Sep 2023 23:56:08 -0500 Subject: [PATCH 11/57] started mutable change --- plugins/plugins/core/components/ChatPage.js | 229 +++++---------- .../plugins/core/components/ChatScroller.js | 278 ++++++++++++++---- 2 files changed, 291 insertions(+), 216 deletions(-) diff --git a/plugins/plugins/core/components/ChatPage.js b/plugins/plugins/core/components/ChatPage.js index b3d65f65..e14fe629 100644 --- a/plugins/plugins/core/components/ChatPage.js +++ b/plugins/plugins/core/components/ChatPage.js @@ -55,7 +55,7 @@ const parentEpml = new Epml({ type: 'WINDOW', source: window.parent }) export const queue = new RequestQueue(); - +export const chatLimit = 40 class ChatPage extends LitElement { static get properties() { return { @@ -81,7 +81,7 @@ class ChatPage extends LitElement { hideNewMessageBar: { attribute: false }, setOpenPrivateMessage: { attribute: false }, chatEditorPlaceholder: { type: String }, - messagesRendered: { type: Array }, + messagesRendered: { type: Object }, repliedToMessageObj: { type: Object }, editedMessageObj: { type: Object }, iframeHeight: { type: Number }, @@ -120,7 +120,8 @@ class ChatPage extends LitElement { gifsLoading: { type: Boolean }, goToRepliedMessage: { attribute: false }, isLoadingGoToRepliedMessage: { type: Object }, - updateMessageHash: { type: Object} + updateMessageHash: { type: Object}, + oldMessages: {type: Array} } } @@ -1309,7 +1310,10 @@ class ChatPage extends LitElement { this.isUserDown = false this.isPasteMenuOpen = false this.chatEditorPlaceholder = "" - this.messagesRendered = [] + this.messagesRendered = { + messages: [], + type: '' + } this.repliedToMessageObj = null this.editedMessageObj = null this.iframeHeight = 42 @@ -1360,6 +1364,7 @@ class ChatPage extends LitElement { this.updateMessageHash = {} this.addToUpdateMessageHashmap = this.addToUpdateMessageHashmap.bind(this) this.getAfterMessages = this.getAfterMessages.bind(this) + this.oldMessages = [] } setOpenGifModal(value) { @@ -2510,6 +2515,7 @@ class ChatPage extends LitElement { this.goToRepliedMessage(val, val2)} - .getOldMessageAfter=${(val) => this.getOldMessageAfter(val)} .updateMessageHash=${this.updateMessageHash} > @@ -2691,7 +2696,7 @@ class ChatPage extends LitElement { if (this.isReceipient) { const getInitialMessages = await parentEpml.request('apiCall', { type: 'api', - url: `/chat/messages?involving=${window.parent.reduxStore.getState().app.selectedAddress.address}&involving=${this._chatId}&limit=20&reverse=true&before=${scrollElement.messageObj.timestamp}&haschatreference=false&encoding=BASE64` + url: `/chat/messages?involving=${window.parent.reduxStore.getState().app.selectedAddress.address}&involving=${this._chatId}&limit=${chatLimit}&reverse=true&before=${scrollElement.messageObj.timestamp}&haschatreference=false&encoding=BASE64` }) let decodeMsgs = [] await new Promise((res, rej) => { @@ -2718,22 +2723,26 @@ class ChatPage extends LitElement { addToUpdateMessageHashmap: this.addToUpdateMessageHashmap })); - let list = [...decodeMsgs, ...this.messagesRendered.slice(0,80)] + let list = [...decodeMsgs] - await new Promise((res, rej) => { + // await new Promise((res, rej) => { - this.webWorkerSortMessages.postMessage({list}); + // this.webWorkerSortMessages.postMessage({list}); - this.webWorkerSortMessages.onmessage = e => { - console.log('e',e) + // this.webWorkerSortMessages.onmessage = e => { + // console.log('e',e) - list = e.data - res() + // list = e.data + // res() - } - }) + // } + // }) - this.messagesRendered = list + this.messagesRendered = { + messages: list, + type: 'old', + el: scrollElement + } this.isLoadingOldMessages = false await this.getUpdateComplete() @@ -2747,7 +2756,7 @@ class ChatPage extends LitElement { } else { const getInitialMessages = await parentEpml.request('apiCall', { type: 'api', - url: `/chat/messages?txGroupId=${Number(this._chatId)}&limit=20&reverse=true&before=${scrollElement.messageObj.timestamp}&haschatreference=false&encoding=BASE64` + url: `/chat/messages?txGroupId=${Number(this._chatId)}&limit=${chatLimit}&reverse=true&before=${scrollElement.messageObj.timestamp}&haschatreference=false&encoding=BASE64` }) let decodeMsgs = [] @@ -2777,23 +2786,27 @@ class ChatPage extends LitElement { _publicKey: this._publicKey, addToUpdateMessageHashmap: this.addToUpdateMessageHashmap })); - let list = [...decodeMsgs, ...this.messagesRendered.slice(0,80)] + let list = [...decodeMsgs] - await new Promise((res, rej) => { + // await new Promise((res, rej) => { - this.webWorkerSortMessages.postMessage({list}); + // this.webWorkerSortMessages.postMessage({list}); - this.webWorkerSortMessages.onmessage = e => { - console.log('e',e) + // this.webWorkerSortMessages.onmessage = e => { + // console.log('e',e) - list = e.data - res() + // list = e.data + // res() - } - }) + // } + // }) - this.messagesRendered = list - this.isLoadingOldMessages = false + this.messagesRendered = { + messages: list, + type: 'old', + el: scrollElement + } + // this.isLoadingOldMessages = false await this.getUpdateComplete() const marginElements = Array.from(this.shadowRoot.querySelector('chat-scroller').shadowRoot.querySelectorAll('message-template')) const findElement = marginElements.find((item) => item.messageObj.signature === scrollElement.messageObj.signature) @@ -2804,7 +2817,6 @@ class ChatPage extends LitElement { } } async getAfterMessages(scrollElement) { - const firstMsg = this.messagesRendered.at(-1) const timestamp = scrollElement.messageObj.timestamp if (this.isReceipient) { @@ -2839,7 +2851,7 @@ class ChatPage extends LitElement { addToUpdateMessageHashmap: this.addToUpdateMessageHashmap })); - let list = [...this.messagesRendered.slice(-80), ...decodeMsgs] + let list = [ ...decodeMsgs] await new Promise((res, rej) => { @@ -2854,7 +2866,11 @@ class ChatPage extends LitElement { } }) - this.messagesRendered = list + this.messagesRendered = { + messages: list, + type: 'new' + } + this.isLoadingOldMessages = false await this.getUpdateComplete() @@ -2898,7 +2914,7 @@ class ChatPage extends LitElement { - let list = [...this.messagesRendered.slice(-80), ...decodeMsgs] + let list = [...decodeMsgs] await new Promise((res, rej) => { @@ -2913,7 +2929,10 @@ class ChatPage extends LitElement { } }) - this.messagesRendered = list + this.messagesRendered = { + messages: list, + type: 'new' + } this.isLoadingOldMessages = false @@ -2949,127 +2968,7 @@ const originalScrollHeight = viewElement.scrollHeight; viewElement.scrollTop = originalScrollTop + heightDifference; } - async getOldMessageAfter(scrollElement) { - if (this.isReceipient) { - const getInitialMessages = await parentEpml.request('apiCall', { - type: 'api', - url: `/chat/messages?involving=${window.parent.reduxStore.getState().app.selectedAddress.address}&involving=${this._chatId}&limit=20&reverse=true&afer=${scrollElement.messageObj.timestamp}&haschatreference=false&encoding=BASE64` - }) - - let decodeMsgs = [] - await new Promise((res, rej) => { - this.webWorkerDecodeMessages.postMessage({messages: getInitialMessages, isReceipient: this.isReceipient, _publicKey: this._publicKey, privateKey: window.parent.reduxStore.getState().app.selectedAddress.keyPair.privateKey }); - - this.webWorkerDecodeMessages.onmessage = e => { - decodeMsgs = e.data - res() - - } - this.webWorkerDecodeMessages.onerror = e => { - console.log('e',e) - rej() - - } - }) - - - queue.push(() => replaceMessagesEdited({ - decodedMessages: decodeMsgs, - parentEpml, - isReceipient: this.isReceipient, - decodeMessageFunc: this.decodeMessage, - _publicKey: this._publicKey, - addToUpdateMessageHashmap: this.addToUpdateMessageHashmap - })); - - - let list = [...this.messagesRendered.slice(-80), ...decodeMsgs] - - await new Promise((res, rej) => { - - this.webWorkerSortMessages.postMessage({list}); - - this.webWorkerSortMessages.onmessage = e => { - console.log('e',e) - - list = e.data - res() - - } - }) - - this.messagesRendered = list - - - this.isLoadingOldMessages = false - await this.getUpdateComplete() - const marginElements = Array.from(this.shadowRoot.querySelector('chat-scroller').shadowRoot.querySelectorAll('message-template')) - - const findElement = marginElements.find((item) => item.messageObj.signature === scrollElement.messageObj.signature) - - if (findElement) { - findElement.scrollIntoView({ behavior: 'auto', block: 'center' }) - } - } else { - const getInitialMessages = await parentEpml.request('apiCall', { - type: 'api', - url: `/chat/messages?txGroupId=${Number(this._chatId)}&limit=20&reverse=true&after=${scrollElement.messageObj.timestamp}&haschatreference=false&encoding=BASE64` - }) - - let decodeMsgs = [] - await new Promise((res, rej) => { - this.webWorkerDecodeMessages.postMessage({messages: getInitialMessages, isReceipient: this.isReceipient, _publicKey: this._publicKey, privateKey: window.parent.reduxStore.getState().app.selectedAddress.keyPair.privateKey }); - - this.webWorkerDecodeMessages.onmessage = e => { - decodeMsgs = e.data - res() - - } - this.webWorkerDecodeMessages.onerror = e => { - console.log('e',e) - rej() - - } - }) - - - queue.push(() => replaceMessagesEdited({ - decodedMessages: decodeMsgs, - parentEpml, - isReceipient: this.isReceipient, - decodeMessageFunc: this.decodeMessage, - _publicKey: this._publicKey, - addToUpdateMessageHashmap: this.addToUpdateMessageHashmap - })); - - - let list = [...this.messagesRendered.slice(-80), ...decodeMsgs] - - await new Promise((res, rej) => { - - this.webWorkerSortMessages.postMessage({list}); - - this.webWorkerSortMessages.onmessage = e => { - console.log('e',e) - - list = e.data - res() - - } - }) - - this.messagesRendered = list - - this.isLoadingOldMessages = false - await this.getUpdateComplete() - const marginElements = Array.from(this.shadowRoot.querySelector('chat-scroller').shadowRoot.querySelectorAll('message-template')) - const findElement = marginElements.find((item) => item.messageObj.signature === scrollElement.messageObj.signature) - - if (findElement) { - findElement.scrollIntoView({ behavior: 'auto', block: 'center' }) - } - } - } + async processMessages(messages, isInitial) { const isReceipient = this.chatId.includes('direct') @@ -3133,7 +3032,10 @@ viewElement.scrollTop = originalScrollTop + heightDifference; // TODO: Determine number of initial messages by screen height... // this.messagesRendered = this._messages - this.messagesRendered = this._messages + this.messagesRendered = { + messages: this._messages, + type: 'initial' + } this.isLoadingMessages = false setTimeout(() => this.downElementObserver(), 500) @@ -3337,13 +3239,13 @@ viewElement.scrollTop = originalScrollTop + heightDifference; const lastMessage = cachedData[cachedData.length - 1] const newMessages = await parentEpml.request('apiCall', { type: 'api', - url: `/chat/messages?involving=${window.parent.reduxStore.getState().app.selectedAddress.address}&involving=${cid}&limit=20&reverse=true&after=${lastMessage.timestamp}&haschatreference=false&encoding=BASE64` + url: `/chat/messages?involving=${window.parent.reduxStore.getState().app.selectedAddress.address}&involving=${cid}&limit=${chatLimit}&reverse=true&after=${lastMessage.timestamp}&haschatreference=false&encoding=BASE64` }) - getInitialMessages = [...cachedData, ...newMessages].slice(-20) + getInitialMessages = [...newMessages] } else { getInitialMessages = await parentEpml.request('apiCall', { type: 'api', - url: `/chat/messages?involving=${window.parent.reduxStore.getState().app.selectedAddress.address}&involving=${cid}&limit=20&reverse=true&haschatreference=false&encoding=BASE64` + url: `/chat/messages?involving=${window.parent.reduxStore.getState().app.selectedAddress.address}&involving=${cid}&limit=${chatLimit}&reverse=true&haschatreference=false&encoding=BASE64` }) } @@ -3437,15 +3339,16 @@ viewElement.scrollTop = originalScrollTop + heightDifference; const newMessages = await parentEpml.request('apiCall', { type: 'api', - url: `/chat/messages?txGroupId=${groupId}&limit=20&reverse=true&after=${lastMessage.timestamp}&haschatreference=false&encoding=BASE64` + url: `/chat/messages?txGroupId=${groupId}&limit=${chatLimit}&reverse=true&after=${lastMessage.timestamp}&haschatreference=false&encoding=BASE64` }) - getInitialMessages = [...cachedData, ...newMessages].slice(-20) - } else { + getInitialMessages = [...newMessages] + }else { getInitialMessages = await parentEpml.request('apiCall', { type: 'api', - url: `/chat/messages?txGroupId=${groupId}&limit=20&reverse=true&haschatreference=false&encoding=BASE64` + url: `/chat/messages?txGroupId=${groupId}&limit=${chatLimit}&reverse=true&haschatreference=false&encoding=BASE64` }) - } + + } this.processMessages(getInitialMessages, true) diff --git a/plugins/plugins/core/components/ChatScroller.js b/plugins/plugins/core/components/ChatScroller.js index a3afe95d..9ef56e2e 100644 --- a/plugins/plugins/core/components/ChatScroller.js +++ b/plugins/plugins/core/components/ChatScroller.js @@ -166,15 +166,53 @@ function processText(input) { return wrapper } +const formatMessages = (messages) => { + const formattedMessages = messages.reduce((messageArray, message) => { + const currentMessage = message; + const lastGroupedMessage = messageArray[messageArray.length - 1]; + + currentMessage.firstMessageInChat = messageArray.length === 0; + + let timestamp, sender, repliedToData; + + if (lastGroupedMessage) { + timestamp = lastGroupedMessage.timestamp; + sender = lastGroupedMessage.sender; + repliedToData = lastGroupedMessage.repliedToData; + } else { + timestamp = currentMessage.timestamp; + sender = currentMessage.sender; + repliedToData = currentMessage.repliedToData; + } + + const isSameGroup = Math.abs(timestamp - currentMessage.timestamp) < 600000 && + sender === currentMessage.sender && + !repliedToData; + + if (isSameGroup && lastGroupedMessage) { + lastGroupedMessage.messages.push(currentMessage); + } else { + messageArray.push({ + messages: [currentMessage], + ...currentMessage + }); + } + + return messageArray; + }, []); + + return formattedMessages +} + class ChatScroller extends LitElement { static get properties() { return { theme: { type: String, reflect: true }, getNewMessage: { attribute: false }, getOldMessage: { attribute: false }, - getAfterMessages: {attribute: false}, + getAfterMessages: { attribute: false }, escapeHTML: { attribute: false }, - messages: { type: Array }, + messages: { type: Object }, hideMessages: { type: Array }, setRepliedToMessageObj: { attribute: false }, setEditedMessageObj: { attribute: false }, @@ -196,9 +234,10 @@ class ChatScroller extends LitElement { userName: { type: String }, selectedHead: { type: Object }, goToRepliedMessage: { attribute: false }, - getOldMessageAfter: { attribute: false }, listSeenMessages: { type: Array }, - updateMessageHash: {type: Object} + updateMessageHash: { type: Object }, + messagesToRender: { type: Array }, + oldMessages: { type: Array } } } @@ -208,7 +247,11 @@ class ChatScroller extends LitElement { constructor() { super() - this.messages = [] + this.messages = { + messages: [], + type: '' + } + this.oldMessages = [] this._upObserverhandler = this._upObserverhandler.bind(this) this._downObserverHandler = this._downObserverHandler.bind(this) this.__bottomObserverForFetchingMessagesHandler = this.__bottomObserverForFetchingMessagesHandler.bind(this) @@ -218,52 +261,176 @@ class ChatScroller extends LitElement { this.openUserInfo = false this.listSeenMessages = [] this.theme = localStorage.getItem('qortalTheme') ? localStorage.getItem('qortalTheme') : 'light' + this.messagesToRender = [] } addSeenMessage(val) { this.listSeenMessages.push(val) } - render() { - let formattedMessages = this.messages.reduce((messageArray, message, index) => { - if(this.updateMessageHash[message.signature]){ - - message = this.updateMessageHash[message.signature] - } - const lastGroupedMessage = messageArray[messageArray.length - 1] - let timestamp - let sender - let repliedToData - let firstMessageInChat + shouldGroupWithLastMessage(newMessage, lastGroupedMessage) { + if (!lastGroupedMessage) return false; - if (index === 0) { - firstMessageInChat = true + return Math.abs(lastGroupedMessage.timestamp - newMessage.timestamp) < 600000 && + lastGroupedMessage.sender === newMessage.sender && + !lastGroupedMessage.repliedToData; + } + addNewMessage(newMessage) { + const lastGroupedMessage = this.messagesToRender[this.messagesToRender.length - 1]; + + if (this.shouldGroupWithLastMessage(newMessage, lastGroupedMessage)) { + lastGroupedMessage.messages.push(newMessage); + } else { + this.messagesToRender.push({ + messages: [newMessage], + ...newMessage + }); + } + + this.requestUpdate(); + } + + async addNewMessages(newMessages, type) { + console.log('sup') + newMessages.forEach(newMessage => { + const lastGroupedMessage = this.messagesToRender[this.messagesToRender.length - 1]; + + if (this.shouldGroupWithLastMessage(newMessage, lastGroupedMessage)) { + lastGroupedMessage.messages.push(newMessage); } else { - firstMessageInChat = false + this.messagesToRender.push({ + messages: [newMessage], + ...newMessage + }); } + }); - message = { ...message, firstMessageInChat } + this.requestUpdate(); + if(type === 'initial'){ + await this.getUpdateComplete(); + setTimeout(() => { + this.viewElement.scrollTop = this.viewElement.scrollHeight + 50; + this.setIsLoadingMessages(false) + }, 50) + } + - if (lastGroupedMessage) { - timestamp = lastGroupedMessage.timestamp - sender = lastGroupedMessage.sender - repliedToData = lastGroupedMessage.repliedToData - } - const isSameGroup = Math.abs(timestamp - message.timestamp) < 600000 && sender === message.sender && !repliedToData + } - if (isSameGroup) { - messageArray[messageArray.length - 1].messages = [ - ...(messageArray[messageArray.length - 1].messages || []), - message - ] - } else { - messageArray.push({ + async prependOldMessages(oldMessages) { + + console.log('2', { oldMessages }) + if (!this.messagesToRender) this.messagesToRender = []; // Ensure it's initialized + + let currentMessageGroup = null; + let previousMessage = null; + + for (const message of oldMessages) { + if (!previousMessage || !this.shouldGroupWithLastMessage(message, previousMessage)) { + // If no previous message, or if the current message shouldn't be grouped with the previous, + // push the current group to the front of the formatted messages (since these are older messages) + if (currentMessageGroup) { + this.messagesToRender.unshift(currentMessageGroup); + } + currentMessageGroup = { + id: message.signature, messages: [message], ...message - }) + }; + } else { + // Add to the current group + currentMessageGroup.messages.push(message); } - return messageArray - }, []) + previousMessage = message; + } + + // After processing all old messages, add the last group + if (currentMessageGroup) { + this.messagesToRender.unshift(currentMessageGroup); + } + + this.requestUpdate(); + this.setIsLoadingMessages(false) + // await this.getUpdateComplete(); + // setTimeout(()=> { + // this.viewElement.scrollTop = this.viewElement.scrollHeight + 50; + // this.setIsLoadingMessages(false) + // },50) + + + + } + + async replaceMessagesWithUpdate(updatedMessages) { + for (let group of this.messagesToRender) { + for (let i = 0; i < group.messages.length; i++) { + if (updatedMessages[group.messages[i].signature]) { + Object.assign(group.messages[i], updatedMessages[group.messages[i].signature]); + } + } + } + this.requestUpdate(); + } + + + + async updated(changedProperties) { + if (changedProperties && changedProperties.has('messages')) { + console.log('this.messages', this.messages) + if (this.messages.type === 'initial') { + this.addNewMessages(this.messages.messages, 'initial') + + + + } else if (this.messages.type === 'new') this.addNewMessages(this.messages.messages) + else if (this.messages.type === 'old') this.prependOldMessages(this.messages.messages) + + + } + if (changedProperties && changedProperties.has('updateMessageHash')) { + this.replaceMessagesWithUpdate(this.updateMessageHash) + } + + } + + render() { + // let formattedMessages = this.messages.reduce((messageArray, message) => { + // const currentMessage = this.updateMessageHash[message.signature] || message; + // const lastGroupedMessage = messageArray[messageArray.length - 1]; + + // currentMessage.firstMessageInChat = messageArray.length === 0; + + // let timestamp, sender, repliedToData; + + // if (lastGroupedMessage) { + // timestamp = lastGroupedMessage.timestamp; + // sender = lastGroupedMessage.sender; + // repliedToData = lastGroupedMessage.repliedToData; + // } else { + // timestamp = currentMessage.timestamp; + // sender = currentMessage.sender; + // repliedToData = currentMessage.repliedToData; + // } + + // const isSameGroup = Math.abs(timestamp - currentMessage.timestamp) < 600000 && + // sender === currentMessage.sender && + // !repliedToData; + + // if (isSameGroup && lastGroupedMessage) { + // lastGroupedMessage.messages.push(currentMessage); + // } else { + // messageArray.push({ + // messages: [currentMessage], + // ...currentMessage + // }); + // } + + // return messageArray; + // }, []); + + let formattedMessages = this.messagesToRender + + console.log('this.messagesToRender', this.messagesToRender) return html` ${this.isLoadingMessages ? html` @@ -273,12 +440,15 @@ class ChatScroller extends LitElement { ` : ''}
    - ${formattedMessages.map((formattedMessage) => { - return repeat( + ${repeat( + formattedMessages, + (formattedMessage) => formattedMessage.id, // Use .id as the unique key for formattedMessage. + (formattedMessage) => html` + ${repeat( formattedMessage.messages, (message) => message.signature, (message, indexMessage) => html` - this.addSeenMessage(val)} .listSeenMessages=${this.listSeenMessages} chatId=${this.chatId} - > - ` - ) - })} + >` + )} + ` + )}
    @@ -339,6 +509,7 @@ class ChatScroller extends LitElement { async getUpdateComplete() { await super.getUpdateComplete() const marginElements = Array.from(this.shadowRoot.querySelectorAll('message-template')) + console.log({ marginElements }) await Promise.all(marginElements.map(el => el.updateComplete)) return true } @@ -376,8 +547,7 @@ class ChatScroller extends LitElement { this.upElementObserver() this.downElementObserver() this.bottomObserver() - await this.getUpdateComplete() - this.viewElement.scrollTop = this.viewElement.scrollHeight + 50 + this.clearConsole() setInterval(() => { @@ -410,13 +580,12 @@ class ChatScroller extends LitElement { this.getAfterMessages(_scrollElement) } - _getOldMessageAfter(_scrollElement) { - this.getOldMessageAfter(_scrollElement) - } + _upObserverhandler(entries) { + if (entries[0].isIntersecting) { - if (this.messages.length < 20) { + if (this.isLoadingMessages) { return } this.setIsLoadingMessages(true) @@ -433,7 +602,10 @@ class ChatScroller extends LitElement { } } - __bottomObserverForFetchingMessagesHandler(entries){ + __bottomObserverForFetchingMessagesHandler(entries) { + if (this.messagesToRender.length === 0) { + return + } if (!entries[0].isIntersecting) { } else { let _scrollElement = entries[0].target.previousElementSibling @@ -453,7 +625,7 @@ class ChatScroller extends LitElement { downElementObserver() { const options = { - + } // identify an element to observe const elementToObserve = this.downObserverElement @@ -465,7 +637,7 @@ class ChatScroller extends LitElement { } bottomObserver() { const options = { - + } // identify an element to observe const elementToObserve = this.bottomObserverForFetchingMessages @@ -631,7 +803,7 @@ class MessageTemplate extends LitElement { } render() { - + const hidemsg = this.hideMessages let message = "" let messageVersion2 = "" From 63d65c6773946d105bdf5c4b7b5c008deac2044e Mon Sep 17 00:00:00 2001 From: PhilReact Date: Thu, 7 Sep 2023 19:29:12 -0500 Subject: [PATCH 12/57] fix newest messages and dynamic old --- plugins/plugins/core/components/ChatPage.js | 144 +++++++++++------- .../plugins/core/components/ChatScroller.js | 52 +++++-- 2 files changed, 130 insertions(+), 66 deletions(-) diff --git a/plugins/plugins/core/components/ChatPage.js b/plugins/plugins/core/components/ChatPage.js index e14fe629..dbd11b11 100644 --- a/plugins/plugins/core/components/ChatPage.js +++ b/plugins/plugins/core/components/ChatPage.js @@ -55,7 +55,7 @@ const parentEpml = new Epml({ type: 'WINDOW', source: window.parent }) export const queue = new RequestQueue(); -export const chatLimit = 40 +export const chatLimit = 10 class ChatPage extends LitElement { static get properties() { return { @@ -2152,13 +2152,7 @@ class ChatPage extends LitElement { return } - if ((message.timestamp - this.messagesRendered[0].timestamp) > 86400000) { - let errorMsg = get("chatpage.cchange66") - parentEpml.request('showSnackBar', `${errorMsg}`) - return - } - - if ((message.timestamp - this.messagesRendered[0].timestamp) < 86400000) { + const findOriginalMessage = this.shadowRoot.querySelector('chat-scroller').shadowRoot.getElementById(clickedOnMessage.signature) if (findOriginalMessage) { const messageClientRect = findOriginalMessage.getBoundingClientRect() @@ -2169,15 +2163,25 @@ class ChatPage extends LitElement { top: messageClientRect.top, offsetHeight: findOriginalMessage.offsetHeight } + + await this.getOldMessageDynamic(0, clickedOnMessage.timestamp, message) + await new Promise((res)=> { + setTimeout(()=> { + res() + },1000) + }) + await this.getUpdateCompleteMessages() + + const marginElements = Array.from(this.shadowRoot.querySelector('chat-scroller').shadowRoot.querySelectorAll('message-template')) + const findMessage = marginElements.find((item) => item.messageObj.signature === message.signature) + if (findMessage) { + findMessage.scrollIntoView({ behavior: 'auto', block: 'center' }) } - await this.getOldMessageDynamic(0, this.messagesRendered[0].timestamp, message.timestamp - 7200000) - const findMessage = this.shadowRoot.querySelector('chat-scroller').shadowRoot.getElementById(message.signature) if (findMessage) { this.isLoadingGoToRepliedMessage = { ...this.isLoadingGoToRepliedMessage, loading: false } - findMessage.scrollIntoView({ block: 'center' }) const findElement = findMessage.shadowRoot.querySelector('.message-parent') if (findElement) { findElement.classList.add('blink-bg') @@ -2553,6 +2557,13 @@ class ChatPage extends LitElement { return true } + async getUpdateCompleteMessages() { + await super.getUpdateComplete() + const marginElements = Array.from(this.shadowRoot.querySelector('chat-scroller').shadowRoot.querySelectorAll('message-template')) + await Promise.all(marginElements.map(el => el.updateComplete)) + return true + } + async getUpdateCompleteTextEditor() { await super.getUpdateComplete() const marginElements = Array.from(this.shadowRoot.querySelectorAll('chat-text-editor')) @@ -2571,14 +2582,22 @@ class ChatPage extends LitElement { }) } - async getOldMessageDynamic(limit, before, after) { - + async getOldMessageDynamic(limit, timestampClickedOnMessage, messageToGoTo) { + const findMsg = await parentEpml.request("apiCall", { + type: "api", + url: `/chat/message/${messageToGoTo.signature}?encoding=BASE64`, + }) + if(!findMsg) return null if (this.isReceipient) { - const getInitialMessages = await parentEpml.request('apiCall', { + const getInitialMessagesBefore = await parentEpml.request('apiCall', { type: 'api', - url: `/chat/messages?involving=${window.parent.reduxStore.getState().app.selectedAddress.address}&involving=${this._chatId}&limit=${limit}&reverse=true&before=${before}&after=${after}&haschatreference=false&encoding=BASE64` + url: `/chat/messages?involving=${window.parent.reduxStore.getState().app.selectedAddress.address}&involving=${this._chatId}&limit=${20}&reverse=true&before=${findMsg.timestamp}&haschatreference=false&encoding=BASE64` }) - + const getInitialMessagesAfter = await parentEpml.request('apiCall', { + type: 'api', + url: `/chat/messages?involving=${window.parent.reduxStore.getState().app.selectedAddress.address}&involving=${this._chatId}&limit=${20}&reverse=true&after=${findMsg.timestamp - 100}&haschatreference=false&encoding=BASE64` + }) + const getInitialMessages = [...getInitialMessagesBefore, ...getInitialMessagesAfter] let decodeMsgs = [] await new Promise((res, rej) => { this.webWorkerDecodeMessages.postMessage({messages: getInitialMessages, isReceipient: this.isReceipient, _publicKey: this._publicKey, privateKey: window.parent.reduxStore.getState().app.selectedAddress.keyPair.privateKey }); @@ -2606,7 +2625,7 @@ class ChatPage extends LitElement { })); - let list = [...decodeMsgs, ...this.messagesRendered.slice(0,80)] + let list = [...decodeMsgs] await new Promise((res, rej) => { @@ -2621,21 +2640,23 @@ class ChatPage extends LitElement { } }) - this.messagesRendered = list - + this.messagesRendered = { + messages: list, + type: 'inBetween', + } this.isLoadingOldMessages = false - await this.getUpdateComplete() - const viewElement = this.shadowRoot.querySelector('chat-scroller').shadowRoot.getElementById('viewElement') - - if (viewElement) { - viewElement.scrollTop = 200 - } + } else { - const getInitialMessages = await parentEpml.request('apiCall', { + const getInitialMessagesBefore = await parentEpml.request('apiCall', { type: 'api', - url: `/chat/messages?txGroupId=${Number(this._chatId)}&limit=${limit}&reverse=true&before=${before}&after=${after}&haschatreference=false&encoding=BASE64` + url: `/chat/messages?txGroupId=${Number(this._chatId)}&limit=${20}&reverse=true&before=${findMsg.timestamp}&haschatreference=false&encoding=BASE64` }) + const getInitialMessagesAfter = await parentEpml.request('apiCall', { + type: 'api', + url: `/chat/messages?txGroupId=${Number(this._chatId)}&limit=${20}&reverse=true&after=${findMsg.timestamp - 100}&haschatreference=false&encoding=BASE64` + }) + const getInitialMessages = [...getInitialMessagesBefore, ...getInitialMessagesAfter] let decodeMsgs = [] await new Promise((res, rej) => { @@ -2664,7 +2685,7 @@ class ChatPage extends LitElement { - let list = [...decodeMsgs, ...this.messagesRendered.slice(0,80)] + let list = [...decodeMsgs] await new Promise((res, rej) => { @@ -2679,16 +2700,15 @@ class ChatPage extends LitElement { } }) - this.messagesRendered = list + this.messagesRendered = { + messages: list, + type: 'inBetween', + signature: messageToGoTo.signature + } this.isLoadingOldMessages = false - await this.getUpdateComplete() - const viewElement = this.shadowRoot.querySelector('chat-scroller').shadowRoot.getElementById('viewElement') - - if (viewElement) { - viewElement.scrollTop = 200 - } + } } @@ -3067,22 +3087,22 @@ viewElement.scrollTop = originalScrollTop + heightDifference; } - let list = [...this.messagesRendered] + // let list = [...this.messagesRendered] - await new Promise((res, rej) => { + // await new Promise((res, rej) => { - this.webWorkerSortMessages.postMessage({list}); + // this.webWorkerSortMessages.postMessage({list}); - this.webWorkerSortMessages.onmessage = e => { - console.log('e',e) + // this.webWorkerSortMessages.onmessage = e => { + // console.log('e',e) - list = e.data - res() + // list = e.data + // res() - } - }) + // } + // }) - this.messagesRendered = list + // this.messagesRendered = list } } @@ -3124,15 +3144,19 @@ viewElement.scrollTop = originalScrollTop + heightDifference; async renderNewMessage(newMessage) { if (newMessage.chatReference) { - const findOriginalMessageIndex = this.messagesRendered.findIndex(msg => msg.signature === newMessage.chatReference || (msg.chatReference && msg.chatReference === newMessage.chatReference)) - if (findOriginalMessageIndex !== -1 && this.messagesRendered[findOriginalMessageIndex].sender === newMessage.sender) { - const newMessagesRendered = [...this.messagesRendered] - newMessagesRendered[findOriginalMessageIndex] = { - ...newMessage, timestamp: newMessagesRendered[findOriginalMessageIndex].timestamp, senderName: newMessagesRendered[findOriginalMessageIndex].senderName, - sender: newMessagesRendered[findOriginalMessageIndex].sender, editedTimestamp: newMessage.timestamp - } - this.messagesRendered = newMessagesRendered - await this.getUpdateComplete() + // const findOriginalMessageIndex = this.messagesRendered.findIndex(msg => msg.signature === newMessage.chatReference || (msg.chatReference && msg.chatReference === newMessage.chatReference)) + // if (findOriginalMessageIndex !== -1 && this.messagesRendered[findOriginalMessageIndex].sender === newMessage.sender) { + // const newMessagesRendered = [...this.messagesRendered] + // newMessagesRendered[findOriginalMessageIndex] = { + // ...newMessage, timestamp: newMessagesRendered[findOriginalMessageIndex].timestamp, senderName: newMessagesRendered[findOriginalMessageIndex].senderName, + // sender: newMessagesRendered[findOriginalMessageIndex].sender, editedTimestamp: newMessage.timestamp + // } + // this.messagesRendered = newMessagesRendered + // await this.getUpdateComplete() + // } + this.messagesRendered = { + messages: [newMessage], + type: 'update', } return } @@ -3141,14 +3165,22 @@ viewElement.scrollTop = originalScrollTop + heightDifference; if (newMessage.sender === this.selectedAddress.address) { - this.messagesRendered = [...this.messagesRendered, newMessage] + + this.messagesRendered = { + messages: [newMessage], + type: 'new', + } await this.getUpdateComplete() viewElement.scrollTop = viewElement.scrollHeight } else if (this.isUserDown) { + this.messagesRendered = { + messages: [newMessage], + type: 'new', + } // Append the message and scroll to the bottom if user is down the page - this.messagesRendered = [...this.messagesRendered, newMessage] + // this.messagesRendered = [...this.messagesRendered, newMessage] await this.getUpdateComplete() viewElement.scrollTop = viewElement.scrollHeight diff --git a/plugins/plugins/core/components/ChatScroller.js b/plugins/plugins/core/components/ChatScroller.js index 9ef56e2e..6239db60 100644 --- a/plugins/plugins/core/components/ChatScroller.js +++ b/plugins/plugins/core/components/ChatScroller.js @@ -253,6 +253,7 @@ class ChatScroller extends LitElement { } this.oldMessages = [] this._upObserverhandler = this._upObserverhandler.bind(this) + this.newListMessages = this.newListMessages.bind(this) this._downObserverHandler = this._downObserverHandler.bind(this) this.__bottomObserverForFetchingMessagesHandler = this.__bottomObserverForFetchingMessagesHandler.bind(this) this.myAddress = window.parent.reduxStore.getState().app.selectedAddress.address @@ -290,6 +291,30 @@ class ChatScroller extends LitElement { this.requestUpdate(); } + async newListMessages(newMessages, signature) { + console.log('sup') + let data = [] + newMessages.forEach(newMessage => { + const lastGroupedMessage = data[data.length - 1]; + + if (this.shouldGroupWithLastMessage(newMessage, lastGroupedMessage)) { + lastGroupedMessage.messages.push(newMessage); + } else { + data.push({ + messages: [newMessage], + ...newMessage + }); + } + }); + + + + this.messagesToRender = data + this.requestUpdate() + + + + } async addNewMessages(newMessages, type) { console.log('sup') newMessages.forEach(newMessage => { @@ -351,14 +376,6 @@ class ChatScroller extends LitElement { this.requestUpdate(); this.setIsLoadingMessages(false) - // await this.getUpdateComplete(); - // setTimeout(()=> { - // this.viewElement.scrollTop = this.viewElement.scrollHeight + 50; - // this.setIsLoadingMessages(false) - // },50) - - - } async replaceMessagesWithUpdate(updatedMessages) { @@ -372,11 +389,25 @@ class ChatScroller extends LitElement { this.requestUpdate(); } + async replaceMessagesWithUpdateByArray(updatedMessagesArray) { + console.log({updatedMessagesArray}, this.messagesToRender) + for (let group of this.messagesToRender) { + for (let i = 0; i < group.messages.length; i++) { + const update = updatedMessagesArray.find(updatedMessage => ((updatedMessage.chatReference === group.messages[i].signature) || (updatedMessage.chatReference === group.messages[i].originalSignature))); + if (update) { + Object.assign(group.messages[i], update); + } + } + } + this.requestUpdate(); + } + + async updated(changedProperties) { if (changedProperties && changedProperties.has('messages')) { - console.log('this.messages', this.messages) + if (this.messages.type === 'initial') { this.addNewMessages(this.messages.messages, 'initial') @@ -384,6 +415,8 @@ class ChatScroller extends LitElement { } else if (this.messages.type === 'new') this.addNewMessages(this.messages.messages) else if (this.messages.type === 'old') this.prependOldMessages(this.messages.messages) + else if (this.messages.type === 'inBetween') this.newListMessages(this.messages.messages, this.messages.signature) + else if (this.messages.type === 'update') this.replaceMessagesWithUpdateByArray(this.messages.messages) } @@ -430,7 +463,6 @@ class ChatScroller extends LitElement { let formattedMessages = this.messagesToRender - console.log('this.messagesToRender', this.messagesToRender) return html` ${this.isLoadingMessages ? html` From 9f0e4821833d8b864aacbf909c3920e1f18c2fa5 Mon Sep 17 00:00:00 2001 From: PhilReact Date: Fri, 8 Sep 2023 02:00:56 -0500 Subject: [PATCH 13/57] fix immutable issues --- plugins/plugins/core/components/ChatPage.js | 52 +++-- .../plugins/core/components/ChatScroller.js | 198 ++++++++++++++---- 2 files changed, 187 insertions(+), 63 deletions(-) diff --git a/plugins/plugins/core/components/ChatPage.js b/plugins/plugins/core/components/ChatPage.js index dbd11b11..1d256e69 100644 --- a/plugins/plugins/core/components/ChatPage.js +++ b/plugins/plugins/core/components/ChatPage.js @@ -55,7 +55,7 @@ const parentEpml = new Epml({ type: 'WINDOW', source: window.parent }) export const queue = new RequestQueue(); -export const chatLimit = 10 +export const chatLimit = 40 class ChatPage extends LitElement { static get properties() { return { @@ -1282,6 +1282,7 @@ class ChatPage extends LitElement { constructor() { super() this.getOldMessage = this.getOldMessage.bind(this) + this.clearUpdateMessageHashmap = this.clearUpdateMessageHashmap.bind(this) this._sendMessage = this._sendMessage.bind(this) this.insertFile = this.insertFile.bind(this) this.pasteImage = this.pasteImage.bind(this) @@ -2138,6 +2139,7 @@ class ChatPage extends LitElement { } async goToRepliedMessage(message, clickedOnMessage) { + console.log({message}) const findMessage = this.shadowRoot.querySelector('chat-scroller').shadowRoot.getElementById(message.signature) if (findMessage) { @@ -2165,24 +2167,21 @@ class ChatPage extends LitElement { } await this.getOldMessageDynamic(0, clickedOnMessage.timestamp, message) - await new Promise((res)=> { - setTimeout(()=> { - res() - },1000) - }) - await this.getUpdateCompleteMessages() + await this.getUpdateComplete() const marginElements = Array.from(this.shadowRoot.querySelector('chat-scroller').shadowRoot.querySelectorAll('message-template')) - const findMessage = marginElements.find((item) => item.messageObj.signature === message.signature) - if (findMessage) { - findMessage.scrollIntoView({ behavior: 'auto', block: 'center' }) + console.log({marginElements}) + const findMessage2 = marginElements.find((item) => item.messageObj.signature === message.signature) || marginElements.find((item) => item.messageObj.originalSignature === message.signature) || marginElements.find((item) => item.messageObj.signature === message.originalSignature) || marginElements.find((item) => item.messageObj.originalSignature === message.originalSignature) + console.log({findMessage2}, message.signature) + if (findMessage2) { + findMessage2.scrollIntoView({ block: 'center' }) } - if (findMessage) { + if (findMessage2) { this.isLoadingGoToRepliedMessage = { ...this.isLoadingGoToRepliedMessage, loading: false } - const findElement = findMessage.shadowRoot.querySelector('.message-parent') + const findElement = findMessage2.shadowRoot.querySelector('.message-parent') if (findElement) { findElement.classList.add('blink-bg') setTimeout(() => { @@ -2515,6 +2514,7 @@ class ChatPage extends LitElement { } renderChatScroller() { + console.log('clearUpdateMessageHashmap', Object.keys(this.updateMessageHash).length) return html` this.goToRepliedMessage(val, val2)} .updateMessageHash=${this.updateMessageHash} + .clearUpdateMessageHashmap=${this.clearUpdateMessageHashmap} > ` @@ -2585,7 +2586,7 @@ class ChatPage extends LitElement { async getOldMessageDynamic(limit, timestampClickedOnMessage, messageToGoTo) { const findMsg = await parentEpml.request("apiCall", { type: "api", - url: `/chat/message/${messageToGoTo.signature}?encoding=BASE64`, + url: `/chat/message/${messageToGoTo.originalSignature || messageToGoTo.signature}?encoding=BASE64`, }) if(!findMsg) return null if (this.isReceipient) { @@ -2595,7 +2596,7 @@ class ChatPage extends LitElement { }) const getInitialMessagesAfter = await parentEpml.request('apiCall', { type: 'api', - url: `/chat/messages?involving=${window.parent.reduxStore.getState().app.selectedAddress.address}&involving=${this._chatId}&limit=${20}&reverse=true&after=${findMsg.timestamp - 100}&haschatreference=false&encoding=BASE64` + url: `/chat/messages?involving=${window.parent.reduxStore.getState().app.selectedAddress.address}&involving=${this._chatId}&limit=${20}&reverse=false&after=${findMsg.timestamp - 1000}&haschatreference=false&encoding=BASE64` }) const getInitialMessages = [...getInitialMessagesBefore, ...getInitialMessagesAfter] let decodeMsgs = [] @@ -2624,7 +2625,8 @@ class ChatPage extends LitElement { addToUpdateMessageHashmap: this.addToUpdateMessageHashmap })); - + console.log({decodeMsgs}) + let list = [...decodeMsgs] await new Promise((res, rej) => { @@ -2643,6 +2645,7 @@ class ChatPage extends LitElement { this.messagesRendered = { messages: list, type: 'inBetween', + message: messageToGoTo } this.isLoadingOldMessages = false @@ -2654,7 +2657,7 @@ class ChatPage extends LitElement { }) const getInitialMessagesAfter = await parentEpml.request('apiCall', { type: 'api', - url: `/chat/messages?txGroupId=${Number(this._chatId)}&limit=${20}&reverse=true&after=${findMsg.timestamp - 100}&haschatreference=false&encoding=BASE64` + url: `/chat/messages?txGroupId=${Number(this._chatId)}&limit=${20}&reverse=false&after=${findMsg.timestamp - 1000}&haschatreference=false&encoding=BASE64` }) const getInitialMessages = [...getInitialMessagesBefore, ...getInitialMessagesAfter] @@ -2683,7 +2686,7 @@ class ChatPage extends LitElement { addToUpdateMessageHashmap: this.addToUpdateMessageHashmap })); - + console.log({decodeMsgs}) let list = [...decodeMsgs] @@ -2842,7 +2845,7 @@ class ChatPage extends LitElement { if (this.isReceipient) { const getInitialMessages = await parentEpml.request('apiCall', { type: 'api', - url: `/chat/messages?involving=${window.parent.reduxStore.getState().app.selectedAddress.address}&involving=${this._chatId}&limit=20&reverse=true&after=${timestamp}&haschatreference=false&encoding=BASE64` + url: `/chat/messages?involving=${window.parent.reduxStore.getState().app.selectedAddress.address}&involving=${this._chatId}&limit=${chatLimit}&reverse=false&after=${timestamp}&haschatreference=false&encoding=BASE64` }) let decodeMsgs = [] @@ -2904,7 +2907,7 @@ class ChatPage extends LitElement { } else { const getInitialMessages = await parentEpml.request('apiCall', { type: 'api', - url: `/chat/messages?txGroupId=${Number(this._chatId)}&limit=20&reverse=true&after=${timestamp}&haschatreference=false&encoding=BASE64` + url: `/chat/messages?txGroupId=${Number(this._chatId)}&limit=${chatLimit}&reverse=false&after=${timestamp}&haschatreference=false&encoding=BASE64` }) let decodeMsgs = [] await new Promise((res, rej) => { @@ -2959,7 +2962,6 @@ class ChatPage extends LitElement { await this.getUpdateComplete() const marginElements = Array.from(this.shadowRoot.querySelector('chat-scroller').shadowRoot.querySelectorAll('message-template')) const findElement = marginElements.find((item) => item.messageObj.signature === scrollElement.messageObj.signature) - if (findElement) { findElement.scrollIntoView({ behavior: 'auto', block: 'center' }) } @@ -2984,8 +2986,14 @@ const originalScrollHeight = viewElement.scrollHeight; ...newObj } await this.getUpdateComplete() - const heightDifference = viewElement.scrollHeight - originalScrollHeight; -viewElement.scrollTop = originalScrollTop + heightDifference; +// const heightDifference = viewElement.scrollHeight - originalScrollHeight; +// viewElement.scrollTop = originalScrollTop + heightDifference; + } + + async clearUpdateMessageHashmap(){ + console.log('hello clear') + this.updateMessageHash = {} + this.requestUpdate() } diff --git a/plugins/plugins/core/components/ChatScroller.js b/plugins/plugins/core/components/ChatScroller.js index 6239db60..8ea34681 100644 --- a/plugins/plugins/core/components/ChatScroller.js +++ b/plugins/plugins/core/components/ChatScroller.js @@ -237,7 +237,8 @@ class ChatScroller extends LitElement { listSeenMessages: { type: Array }, updateMessageHash: { type: Object }, messagesToRender: { type: Array }, - oldMessages: { type: Array } + oldMessages: { type: Array }, + clearUpdateMessageHashmap: { attribute: false} } } @@ -263,11 +264,16 @@ class ChatScroller extends LitElement { this.listSeenMessages = [] this.theme = localStorage.getItem('qortalTheme') ? localStorage.getItem('qortalTheme') : 'light' this.messagesToRender = [] + this.disableFetching = false } addSeenMessage(val) { this.listSeenMessages.push(val) } + goToRepliedMessageFunc(val, val2){ + this.disableFetching = true + this.goToRepliedMessage(val, val2) + } shouldGroupWithLastMessage(newMessage, lastGroupedMessage) { if (!lastGroupedMessage) return false; @@ -289,12 +295,15 @@ class ChatScroller extends LitElement { } this.requestUpdate(); + this.disableFetching = false } - async newListMessages(newMessages, signature) { + async newListMessages(newMessages, message) { + this.disableFetching = true console.log('sup') let data = [] - newMessages.forEach(newMessage => { + const copy = [...newMessages] + copy.forEach(newMessage => { const lastGroupedMessage = data[data.length - 1]; if (this.shouldGroupWithLastMessage(newMessage, lastGroupedMessage)) { @@ -307,49 +316,95 @@ class ChatScroller extends LitElement { } }); - + console.log({data}) this.messagesToRender = data + this.disableFetching = false this.requestUpdate() - + await this.updateComplete() + + this.setIsLoadingMessages(false); + + + + + } - async addNewMessages(newMessages, type) { - console.log('sup') - newMessages.forEach(newMessage => { - const lastGroupedMessage = this.messagesToRender[this.messagesToRender.length - 1]; + + + async addNewMessages(newMessages, type) { + let previousScrollTop; + let previousScrollHeight; + + const viewElement = this.shadowRoot.querySelector("#viewElement"); + previousScrollTop = viewElement.scrollTop; + previousScrollHeight = viewElement.scrollHeight; + this.disableFetching = true + + console.log('sup', type); + const copy = [...this.messagesToRender] + + for (const newMessage of newMessages) { + const lastGroupedMessage = copy[copy.length - 1]; + if (this.shouldGroupWithLastMessage(newMessage, lastGroupedMessage)) { lastGroupedMessage.messages.push(newMessage); } else { - this.messagesToRender.push({ + copy.push({ messages: [newMessage], ...newMessage }); } - }); - - this.requestUpdate(); - if(type === 'initial'){ - await this.getUpdateComplete(); - setTimeout(() => { - this.viewElement.scrollTop = this.viewElement.scrollHeight + 50; - this.setIsLoadingMessages(false) - }, 50) } - + + + // Ensure that the total number of individual messages doesn't exceed 80 + let totalMessagesCount = copy.reduce((acc, group) => acc + group.messages.length, 0); + while (totalMessagesCount > 80 && copy.length) { + const firstGroup = copy[0]; + if (firstGroup.messages.length <= (totalMessagesCount - 80)) { + // If removing the whole first group achieves the goal, remove it + totalMessagesCount -= firstGroup.messages.length; + copy.shift(); + } else { + // Otherwise, trim individual messages from the first group + const messagesToRemove = totalMessagesCount - 80; + firstGroup.messages.splice(0, messagesToRemove); + totalMessagesCount = 80; + } + } + this.messagesToRender = copy + this.requestUpdate(); + console.log("Before waiting for updateComplete"); + await this.updateComplete; + console.log("After waiting for updateComplete"); + + if (type === 'initial') { + + this.viewElement.scrollTop = this.viewElement.scrollHeight + this.setIsLoadingMessages(false); + + + } else { + this.setIsLoadingMessages(false); + + } + this.disableFetching = false } + async prependOldMessages(oldMessages) { - - console.log('2', { oldMessages }) + console.log('2', { oldMessages }); + this.disableFetching = true if (!this.messagesToRender) this.messagesToRender = []; // Ensure it's initialized - + let currentMessageGroup = null; let previousMessage = null; - + for (const message of oldMessages) { if (!previousMessage || !this.shouldGroupWithLastMessage(message, previousMessage)) { // If no previous message, or if the current message shouldn't be grouped with the previous, @@ -368,28 +423,75 @@ class ChatScroller extends LitElement { } previousMessage = message; } - + // After processing all old messages, add the last group if (currentMessageGroup) { this.messagesToRender.unshift(currentMessageGroup); } - - this.requestUpdate(); - this.setIsLoadingMessages(false) - } - - async replaceMessagesWithUpdate(updatedMessages) { - for (let group of this.messagesToRender) { - for (let i = 0; i < group.messages.length; i++) { - if (updatedMessages[group.messages[i].signature]) { - Object.assign(group.messages[i], updatedMessages[group.messages[i].signature]); - } + + // Ensure that the total number of individual messages doesn't exceed 80 + let totalMessagesCount = this.messagesToRender.reduce((acc, group) => acc + group.messages.length, 0); + while (totalMessagesCount > 80 && this.messagesToRender.length) { + const lastGroup = this.messagesToRender[this.messagesToRender.length - 1]; + if (lastGroup.messages.length <= (totalMessagesCount - 80)) { + // If removing the whole last group achieves the goal, remove it + totalMessagesCount -= lastGroup.messages.length; + this.messagesToRender.pop(); + } else { + // Otherwise, trim individual messages from the last group + const messagesToRemove = totalMessagesCount - 80; + lastGroup.messages.splice(-messagesToRemove, messagesToRemove); + totalMessagesCount = 80; } } + this.requestUpdate(); + this.setIsLoadingMessages(false); + this.disableFetching = false } + + + async replaceMessagesWithUpdate(updatedMessages) { + const viewElement = this.shadowRoot.querySelector("#viewElement"); + if (!viewElement) return; // Ensure the element exists + + const previousScrollTop = viewElement.scrollTop; + const previousScrollHeight = viewElement.scrollHeight; + + // Using map to return a new array, rather than mutating the old one + const newMessagesToRender = this.messagesToRender.map(group => { + // For each message, return the updated message if it exists, otherwise return the original message + const updatedGroupMessages = group.messages.map(message => { + return updatedMessages[message.signature] ? {...message, ...updatedMessages[message.signature]} : message; + }); + + // Return a new group object with updated messages + return { + ...group, + messages: updatedGroupMessages + }; + }); + + this.messagesToRender = newMessagesToRender; + + await this.updateComplete; + + // Adjust scroll position based on the difference in scroll heights + const newScrollHeight = viewElement.scrollHeight; + viewElement.scrollTop = previousScrollTop + (newScrollHeight - previousScrollHeight); + + this.clearUpdateMessageHashmap(); + this.disableFetching = false; + } + async replaceMessagesWithUpdateByArray(updatedMessagesArray) { + let previousScrollTop; + let previousScrollHeight; + + const viewElement = this.shadowRoot.querySelector("#viewElement"); + previousScrollTop = viewElement.scrollTop; + previousScrollHeight = viewElement.scrollHeight; console.log({updatedMessagesArray}, this.messagesToRender) for (let group of this.messagesToRender) { for (let i = 0; i < group.messages.length; i++) { @@ -400,6 +502,10 @@ class ChatScroller extends LitElement { } } this.requestUpdate(); + const newScrollHeight = viewElement.scrollHeight; + viewElement.scrollTop = previousScrollTop + (newScrollHeight - previousScrollHeight); + this.clearUpdateMessageHashmap() + this.disableFetching = false } @@ -420,7 +526,7 @@ class ChatScroller extends LitElement { } - if (changedProperties && changedProperties.has('updateMessageHash')) { + if (changedProperties && changedProperties.has('updateMessageHash') && Object.keys(this.updateMessageHash).length > 0) { this.replaceMessagesWithUpdate(this.updateMessageHash) } @@ -499,7 +605,7 @@ class ChatScroller extends LitElement { .setOpenUserInfo=${(val) => this.setOpenUserInfo(val)} .setUserName=${(val) => this.setUserName(val)} id=${message.signature} - .goToRepliedMessage=${this.goToRepliedMessage} + .goToRepliedMessage=${(val, val2)=> this.goToRepliedMessageFunc(val, val2)} .addSeenMessage=${(val) => this.addSeenMessage(val)} .listSeenMessages=${this.listSeenMessages} chatId=${this.chatId} @@ -510,12 +616,17 @@ class ChatScroller extends LitElement {
    - + ${this.isLoadingMessages ? html` +
    + +
    + ` : ''}
` } shouldUpdate(changedProperties) { + console.log({changedProperties}) if (changedProperties.has('isLoadingMessages')) { return true } @@ -534,6 +645,10 @@ class ChatScroller extends LitElement { if (changedProperties.has('updateMessageHash')) { return true } + if(changedProperties.has('messagesToRender')){ + console.log('true', this.messagesToRender) + return true + } // Only update element if prop1 changed. return changedProperties.has('messages') } @@ -617,7 +732,7 @@ class ChatScroller extends LitElement { _upObserverhandler(entries) { if (entries[0].isIntersecting) { - if (this.isLoadingMessages) { + if (this.isLoadingMessages || this.disableFetching) { return } this.setIsLoadingMessages(true) @@ -635,11 +750,12 @@ class ChatScroller extends LitElement { } __bottomObserverForFetchingMessagesHandler(entries) { - if (this.messagesToRender.length === 0) { + if (this.messagesToRender.length === 0 || this.disableFetching) { return } if (!entries[0].isIntersecting) { } else { + this.setIsLoadingMessages(true) let _scrollElement = entries[0].target.previousElementSibling this._getAfterMessages(_scrollElement) } From ad716b528644a10537a5a542fcfab6542f1fdec2 Mon Sep 17 00:00:00 2001 From: PhilReact Date: Fri, 8 Sep 2023 18:59:29 -0500 Subject: [PATCH 14/57] fix some bugs --- plugins/plugins/core/components/ChatPage.js | 18 ++-- .../plugins/core/components/ChatScroller.js | 99 +++++++++++++------ 2 files changed, 78 insertions(+), 39 deletions(-) diff --git a/plugins/plugins/core/components/ChatPage.js b/plugins/plugins/core/components/ChatPage.js index 1d256e69..2fce10fd 100644 --- a/plugins/plugins/core/components/ChatPage.js +++ b/plugins/plugins/core/components/ChatPage.js @@ -2143,7 +2143,7 @@ class ChatPage extends LitElement { const findMessage = this.shadowRoot.querySelector('chat-scroller').shadowRoot.getElementById(message.signature) if (findMessage) { - findMessage.scrollIntoView({ behavior: 'smooth', block: 'center' }) + findMessage.scrollIntoView({ behavior: 'smooth', block: 'nearest' }) const findElement = findMessage.shadowRoot.querySelector('.message-parent') if (findElement) { findElement.classList.add('blink-bg') @@ -2969,11 +2969,8 @@ class ChatPage extends LitElement { } async addToUpdateMessageHashmap(array){ - const chatscrollerEl = this.shadowRoot.querySelector('chat-scroller') - if(!chatscrollerEl) return - const viewElement = this.shadowRoot.querySelector('chat-scroller').shadowRoot.getElementById('viewElement') - const originalScrollTop = viewElement.scrollTop; -const originalScrollHeight = viewElement.scrollHeight; + + const newObj = {} @@ -2985,9 +2982,9 @@ const originalScrollHeight = viewElement.scrollHeight; ...this.updateMessageHash, ...newObj } + this.requestUpdate() await this.getUpdateComplete() -// const heightDifference = viewElement.scrollHeight - originalScrollHeight; -// viewElement.scrollTop = originalScrollTop + heightDifference; + } async clearUpdateMessageHashmap(){ @@ -3194,7 +3191,10 @@ const originalScrollHeight = viewElement.scrollHeight; viewElement.scrollTop = viewElement.scrollHeight } else { - this.messagesRendered = [...this.messagesRendered, newMessage] + this.messagesRendered = { + messages: [newMessage], + type: 'new', + } await this.getUpdateComplete() this.showNewMessageBar() diff --git a/plugins/plugins/core/components/ChatScroller.js b/plugins/plugins/core/components/ChatScroller.js index 8ea34681..dc614d95 100644 --- a/plugins/plugins/core/components/ChatScroller.js +++ b/plugins/plugins/core/components/ChatScroller.js @@ -238,7 +238,10 @@ class ChatScroller extends LitElement { updateMessageHash: { type: Object }, messagesToRender: { type: Array }, oldMessages: { type: Array }, - clearUpdateMessageHashmap: { attribute: false} + clearUpdateMessageHashmap: { attribute: false}, + disableFetching: {type: Boolean}, + isLoadingBefore: {type: Boolean}, + isLoadingAfter: {type: Boolean} } } @@ -256,6 +259,7 @@ class ChatScroller extends LitElement { this._upObserverhandler = this._upObserverhandler.bind(this) this.newListMessages = this.newListMessages.bind(this) this._downObserverHandler = this._downObserverHandler.bind(this) + this.replaceMessagesWithUpdate = this.replaceMessagesWithUpdate.bind(this) this.__bottomObserverForFetchingMessagesHandler = this.__bottomObserverForFetchingMessagesHandler.bind(this) this.myAddress = window.parent.reduxStore.getState().app.selectedAddress.address this.hideMessages = JSON.parse(localStorage.getItem("MessageBlockedAddresses") || "[]") @@ -265,6 +269,8 @@ class ChatScroller extends LitElement { this.theme = localStorage.getItem('qortalTheme') ? localStorage.getItem('qortalTheme') : 'light' this.messagesToRender = [] this.disableFetching = false + this.isLoadingBefore = false + this.isLoadingAfter = false } addSeenMessage(val) { @@ -282,6 +288,12 @@ class ChatScroller extends LitElement { lastGroupedMessage.sender === newMessage.sender && !lastGroupedMessage.repliedToData; } + + clearLoaders(){ + this.isLoadingBefore = false + this.isLoadingAfter = false + this.disableFetching = false + } addNewMessage(newMessage) { const lastGroupedMessage = this.messagesToRender[this.messagesToRender.length - 1]; @@ -293,13 +305,13 @@ class ChatScroller extends LitElement { ...newMessage }); } - + this.clearLoaders() this.requestUpdate(); - this.disableFetching = false + } async newListMessages(newMessages, message) { - this.disableFetching = true + console.log('sup') let data = [] const copy = [...newMessages] @@ -319,11 +331,10 @@ class ChatScroller extends LitElement { console.log({data}) this.messagesToRender = data - this.disableFetching = false + this.clearLoaders() this.requestUpdate() - await this.updateComplete() + await this.updateComplete - this.setIsLoadingMessages(false); @@ -342,7 +353,7 @@ class ChatScroller extends LitElement { const viewElement = this.shadowRoot.querySelector("#viewElement"); previousScrollTop = viewElement.scrollTop; previousScrollHeight = viewElement.scrollHeight; - this.disableFetching = true + console.log('sup', type); const copy = [...this.messagesToRender] @@ -386,20 +397,16 @@ class ChatScroller extends LitElement { if (type === 'initial') { this.viewElement.scrollTop = this.viewElement.scrollHeight - this.setIsLoadingMessages(false); + - } else { - this.setIsLoadingMessages(false); - - } - this.disableFetching = false + } + this.clearLoaders() } async prependOldMessages(oldMessages) { console.log('2', { oldMessages }); - this.disableFetching = true if (!this.messagesToRender) this.messagesToRender = []; // Ensure it's initialized let currentMessageGroup = null; @@ -444,17 +451,22 @@ class ChatScroller extends LitElement { totalMessagesCount = 80; } } - - this.requestUpdate(); - this.setIsLoadingMessages(false); - this.disableFetching = false + this.clearLoaders() + this.requestUpdate(); // await new Promise((res)=> { + // setTimeout(()=> { + // res() + // }, 5000) + // }) + } async replaceMessagesWithUpdate(updatedMessages) { + const viewElement = this.shadowRoot.querySelector("#viewElement"); if (!viewElement) return; // Ensure the element exists - + const isUserAtBottom = (viewElement.scrollTop + viewElement.clientHeight) === viewElement.scrollHeight; + const previousScrollTop = viewElement.scrollTop; const previousScrollHeight = viewElement.scrollHeight; @@ -473,15 +485,34 @@ class ChatScroller extends LitElement { }); this.messagesToRender = newMessagesToRender; - + this.requestUpdate(); + console.log('await this.updateComplete 1') await this.updateComplete; - + console.log('await this.updateComplete 2') + + // Adjust scroll position based on the difference in scroll heights + // await new Promise((res)=> { + // setTimeout(()=> { + // res() + // }, 5000) + // }) + // const newScrollHeight = viewElement.scrollHeight; + // viewElement.scrollTop = previousScrollTop + (newScrollHeight - previousScrollHeight); + // viewElement.scrollTop = viewElement.scrollHeight - viewElement.clientHeight; + // const newScrollHeight = viewElement.scrollHeight; + // viewElement.scrollTop = viewElement.scrollTop + (newScrollHeight - viewElement.scrollHeight); + // If the user was at the bottom before the update, keep them at the bottom + if (isUserAtBottom) { + viewElement.scrollTop = viewElement.scrollHeight - viewElement.clientHeight; + } else { // Adjust scroll position based on the difference in scroll heights const newScrollHeight = viewElement.scrollHeight; - viewElement.scrollTop = previousScrollTop + (newScrollHeight - previousScrollHeight); + viewElement.scrollTop = viewElement.scrollTop + (newScrollHeight - viewElement.scrollHeight); + } + this.clearUpdateMessageHashmap(); - this.disableFetching = false; + this.clearLoaders() } @@ -505,7 +536,7 @@ class ChatScroller extends LitElement { const newScrollHeight = viewElement.scrollHeight; viewElement.scrollTop = previousScrollTop + (newScrollHeight - previousScrollHeight); this.clearUpdateMessageHashmap() - this.disableFetching = false + this.clearLoaders() } @@ -571,7 +602,7 @@ class ChatScroller extends LitElement { return html` - ${this.isLoadingMessages ? html` + ${this.isLoadingBefore ? html`
@@ -616,7 +647,7 @@ class ChatScroller extends LitElement {
- ${this.isLoadingMessages ? html` + ${this.isLoadingAfter ? html`
@@ -649,6 +680,12 @@ class ChatScroller extends LitElement { console.log('true', this.messagesToRender) return true } + if(changedProperties.has('isLoadingBefore')){ + return true + } + if(changedProperties.has('isLoadingAfter')){ + return true + } // Only update element if prop1 changed. return changedProperties.has('messages') } @@ -732,10 +769,11 @@ class ChatScroller extends LitElement { _upObserverhandler(entries) { if (entries[0].isIntersecting) { - if (this.isLoadingMessages || this.disableFetching) { + if (this.disableFetching) { return } - this.setIsLoadingMessages(true) + this.disableFetching = true + this.isLoadingBefore = true let _scrollElement = entries[0].target.nextElementSibling this._getOldMessage(_scrollElement) } @@ -755,7 +793,8 @@ class ChatScroller extends LitElement { } if (!entries[0].isIntersecting) { } else { - this.setIsLoadingMessages(true) + this.disableFetching = true + this.isLoadingAfter = true let _scrollElement = entries[0].target.previousElementSibling this._getAfterMessages(_scrollElement) } From f8477c0307ccebbc47fc0da4cd8ba44dfb2805cf Mon Sep 17 00:00:00 2001 From: PhilReact Date: Fri, 8 Sep 2023 21:35:08 -0500 Subject: [PATCH 15/57] don't add new msg if passed max chat length --- plugins/plugins/core/components/ChatPage.js | 44 ++++++++++--------- .../plugins/core/components/ChatScroller.js | 38 +++++++++------- 2 files changed, 44 insertions(+), 38 deletions(-) diff --git a/plugins/plugins/core/components/ChatPage.js b/plugins/plugins/core/components/ChatPage.js index 2fce10fd..0f1be491 100644 --- a/plugins/plugins/core/components/ChatPage.js +++ b/plugins/plugins/core/components/ChatPage.js @@ -55,7 +55,8 @@ const parentEpml = new Epml({ type: 'WINDOW', source: window.parent }) export const queue = new RequestQueue(); -export const chatLimit = 40 +export const chatLimit = 10 +export const totalMsgCount = 20 class ChatPage extends LitElement { static get properties() { return { @@ -2748,18 +2749,18 @@ class ChatPage extends LitElement { let list = [...decodeMsgs] - // await new Promise((res, rej) => { + await new Promise((res, rej) => { - // this.webWorkerSortMessages.postMessage({list}); + this.webWorkerSortMessages.postMessage({list}); - // this.webWorkerSortMessages.onmessage = e => { - // console.log('e',e) + this.webWorkerSortMessages.onmessage = e => { + console.log('e',e) - // list = e.data - // res() + list = e.data + res() - // } - // }) + } + }) this.messagesRendered = { messages: list, @@ -2811,18 +2812,18 @@ class ChatPage extends LitElement { })); let list = [...decodeMsgs] - // await new Promise((res, rej) => { + await new Promise((res, rej) => { - // this.webWorkerSortMessages.postMessage({list}); + this.webWorkerSortMessages.postMessage({list}); - // this.webWorkerSortMessages.onmessage = e => { - // console.log('e',e) + this.webWorkerSortMessages.onmessage = e => { + console.log('e',e) - // list = e.data - // res() + list = e.data + res() - // } - // }) + } + }) this.messagesRendered = { messages: list, @@ -3173,16 +3174,16 @@ class ChatPage extends LitElement { this.messagesRendered = { messages: [newMessage], - type: 'new', + type: 'newComingInAuto', } await this.getUpdateComplete() - viewElement.scrollTop = viewElement.scrollHeight + // viewElement.scrollTop = viewElement.scrollHeight } else if (this.isUserDown) { this.messagesRendered = { messages: [newMessage], - type: 'new', + type: 'newComingInAuto', } // Append the message and scroll to the bottom if user is down the page // this.messagesRendered = [...this.messagesRendered, newMessage] @@ -3193,7 +3194,7 @@ class ChatPage extends LitElement { this.messagesRendered = { messages: [newMessage], - type: 'new', + type: 'newComingInAuto', } await this.getUpdateComplete() @@ -4202,6 +4203,7 @@ class ChatPage extends LitElement { const _computePow = async (chatBytes, isForward) => { const difficulty = this.balance < 4 ? 18 : 8 + console.log({difficulty}) const path = window.parent.location.origin + '/memory-pow/memory-pow.wasm.full' let worker diff --git a/plugins/plugins/core/components/ChatScroller.js b/plugins/plugins/core/components/ChatScroller.js index dc614d95..9b7cce0d 100644 --- a/plugins/plugins/core/components/ChatScroller.js +++ b/plugins/plugins/core/components/ChatScroller.js @@ -29,6 +29,7 @@ import '@material/mwc-icon' import '@vaadin/icon' import '@vaadin/icons' import '@vaadin/tooltip' +import { chatLimit, totalMsgCount } from './ChatPage.js' const parentEpml = new Epml({ type: 'WINDOW', source: window.parent }) @@ -271,6 +272,7 @@ class ChatScroller extends LitElement { this.disableFetching = false this.isLoadingBefore = false this.isLoadingAfter = false + this.disableAddingNewMessages = false } addSeenMessage(val) { @@ -347,6 +349,7 @@ class ChatScroller extends LitElement { async addNewMessages(newMessages, type) { + if(this.disableAddingNewMessages && type === 'newComingInAuto') return let previousScrollTop; let previousScrollHeight; @@ -373,19 +376,22 @@ class ChatScroller extends LitElement { - // Ensure that the total number of individual messages doesn't exceed 80 + // Ensure that the total number of individual messages doesn't exceed totalMsgCount let totalMessagesCount = copy.reduce((acc, group) => acc + group.messages.length, 0); - while (totalMessagesCount > 80 && copy.length) { + while (totalMessagesCount > totalMsgCount && copy.length) { + if(newMessages.length < chatLimit && type !== 'newComingInAuto' && type !== 'initial'){ + this.disableAddingNewMessages = false + } const firstGroup = copy[0]; - if (firstGroup.messages.length <= (totalMessagesCount - 80)) { + if (firstGroup.messages.length <= (totalMessagesCount - totalMsgCount)) { // If removing the whole first group achieves the goal, remove it totalMessagesCount -= firstGroup.messages.length; copy.shift(); } else { // Otherwise, trim individual messages from the first group - const messagesToRemove = totalMessagesCount - 80; + const messagesToRemove = totalMessagesCount - totalMsgCount; firstGroup.messages.splice(0, messagesToRemove); - totalMessagesCount = 80; + totalMessagesCount = totalMsgCount; } } this.messagesToRender = copy @@ -436,27 +442,24 @@ class ChatScroller extends LitElement { this.messagesToRender.unshift(currentMessageGroup); } - // Ensure that the total number of individual messages doesn't exceed 80 + // Ensure that the total number of individual messages doesn't exceed totalMsgCount let totalMessagesCount = this.messagesToRender.reduce((acc, group) => acc + group.messages.length, 0); - while (totalMessagesCount > 80 && this.messagesToRender.length) { + while (totalMessagesCount > totalMsgCount && this.messagesToRender.length) { + this.disableAddingNewMessages = true const lastGroup = this.messagesToRender[this.messagesToRender.length - 1]; - if (lastGroup.messages.length <= (totalMessagesCount - 80)) { + if (lastGroup.messages.length <= (totalMessagesCount - totalMsgCount)) { // If removing the whole last group achieves the goal, remove it totalMessagesCount -= lastGroup.messages.length; this.messagesToRender.pop(); } else { // Otherwise, trim individual messages from the last group - const messagesToRemove = totalMessagesCount - 80; + const messagesToRemove = totalMessagesCount - totalMsgCount; lastGroup.messages.splice(-messagesToRemove, messagesToRemove); - totalMessagesCount = 80; + totalMessagesCount = totalMsgCount; } } this.clearLoaders() - this.requestUpdate(); // await new Promise((res)=> { - // setTimeout(()=> { - // res() - // }, 5000) - // }) + this.requestUpdate(); } @@ -548,9 +551,10 @@ class ChatScroller extends LitElement { if (this.messages.type === 'initial') { this.addNewMessages(this.messages.messages, 'initial') + - - } else if (this.messages.type === 'new') this.addNewMessages(this.messages.messages) + } else if (this.messages.type === 'new') this.addNewMessages(this.messages.messages) + else if(this.messages.type === 'newComingInAuto') this.addNewMessages(this.messages.messages, 'newComingInAuto') else if (this.messages.type === 'old') this.prependOldMessages(this.messages.messages) else if (this.messages.type === 'inBetween') this.newListMessages(this.messages.messages, this.messages.signature) else if (this.messages.type === 'update') this.replaceMessagesWithUpdateByArray(this.messages.messages) From 8dfab316a7dc36639cb7f5e4aaa753968ef5b4cd Mon Sep 17 00:00:00 2001 From: PhilReact Date: Sun, 10 Sep 2023 01:25:14 -0500 Subject: [PATCH 16/57] added unread message label --- plugins/plugins/core/components/ChatPage.js | 208 +++++++++++++----- .../core/components/ChatScroller-css.js | 11 + .../plugins/core/components/ChatScroller.js | 88 +++++++- 3 files changed, 243 insertions(+), 64 deletions(-) diff --git a/plugins/plugins/core/components/ChatPage.js b/plugins/plugins/core/components/ChatPage.js index 0f1be491..dee271f1 100644 --- a/plugins/plugins/core/components/ChatPage.js +++ b/plugins/plugins/core/components/ChatPage.js @@ -55,8 +55,8 @@ const parentEpml = new Epml({ type: 'WINDOW', source: window.parent }) export const queue = new RequestQueue(); -export const chatLimit = 10 -export const totalMsgCount = 20 +export const chatLimit = 40 +export const totalMsgCount = 120 class ChatPage extends LitElement { static get properties() { return { @@ -1367,6 +1367,8 @@ class ChatPage extends LitElement { this.addToUpdateMessageHashmap = this.addToUpdateMessageHashmap.bind(this) this.getAfterMessages = this.getAfterMessages.bind(this) this.oldMessages = [] + this.lastReadMessageTimestamp = 0 + this.initUpdate = this.initUpdate.bind(this) } setOpenGifModal(value) { @@ -2006,42 +2008,42 @@ class ChatPage extends LitElement { document.addEventListener('keydown', this.initialChat) document.addEventListener('paste', this.pasteImage) - if (this.chatId) { - window.parent.reduxStore.dispatch(window.parent.reduxAction.addChatLastSeen({ - key: this.chatId, - timestamp: Date.now() - })) - } + // if (this.chatId) { + // window.parent.reduxStore.dispatch(window.parent.reduxAction.addChatLastSeen({ + // key: this.chatId, + // timestamp: Date.now() + // })) + // } - let callback = (entries, observer) => { - entries.forEach(entry => { - if (entry.isIntersecting) { + // let callback = (entries, observer) => { + // entries.forEach(entry => { + // if (entry.isIntersecting) { - this.isPageVisible = true - if (this.chatId) { - window.parent.reduxStore.dispatch(window.parent.reduxAction.addChatLastSeen({ - key: this.chatId, - timestamp: Date.now() - })) + // this.isPageVisible = true + // if (this.chatId) { + // window.parent.reduxStore.dispatch(window.parent.reduxAction.addChatLastSeen({ + // key: this.chatId, + // timestamp: Date.now() + // })) - } - } else { - this.isPageVisible = false - } - }) - } + // } + // } else { + // this.isPageVisible = false + // } + // }) + // } - let options = { - root: null, - rootMargin: '0px', - threshold: 0.5 - } + // let options = { + // root: null, + // rootMargin: '0px', + // threshold: 0.5 + // } - // Create the observer with the callback function and options - this.observer = new IntersectionObserver(callback, options) - const mainContainer = this.shadowRoot.querySelector('.main-container') + // // Create the observer with the callback function and options + // this.observer = new IntersectionObserver(callback, options) + // const mainContainer = this.shadowRoot.querySelector('.main-container') - this.observer.observe(mainContainer) + // this.observer.observe(mainContainer) } disconnectedCallback() { @@ -2416,6 +2418,17 @@ class ChatPage extends LitElement { // this.selectedAddress = selectedAddress // }) + // }) + this.lastReadMessageTimestamp = await chatLastSeen.getItem(this.chatId) || 0 + // parentEpml.subscribe('chat_last_seen', async chatList => { + // const parsedChatList = JSON.parse(chatList) + // console.log({parsedChatList}, this.chatId) + // const findChatSeen = parsedChatList.find(chat=> chat.key === this.chatId) + // console.log({findChatSeen}) + // if(findChatSeen && this.lastReadMessageTimestamp !== findChatSeen.timestamp){ + // this.lastReadMessageTimestamp = findChatSeen.timestamp + + // } // }) parentEpml.imReady() @@ -2994,9 +3007,28 @@ class ChatPage extends LitElement { this.requestUpdate() } + findContent(identifier, data) { + const [type, id] = identifier.split('/'); + + if (type === 'group') { + for (let group of data.groups) { + if (group.groupId === parseInt(id, 10)) { + return group; + } + } + } else if (type === 'direct') { + for (let direct of data.direct) { + if (direct.address === id) { + return direct; + } + } + } + return null; + } + - async processMessages(messages, isInitial) { + async processMessages(messages, isInitial, isUnread) { const isReceipient = this.chatId.includes('direct') let decodedMessages = [] if(!this.webWorkerDecodeMessages){ @@ -3058,10 +3090,27 @@ class ChatPage extends LitElement { // TODO: Determine number of initial messages by screen height... // this.messagesRendered = this._messages - this.messagesRendered = { - messages: this._messages, - type: 'initial' + const lastReadMessageTimestamp = this.lastReadMessageTimestamp + + + if(isUnread){ + this.messagesRendered = { + messages: this._messages, + type: 'initialLastSeen', + lastReadMessageTimestamp + } + + window.parent.reduxStore.dispatch(window.parent.reduxAction.addChatLastSeen({ + key: this.chatId, + timestamp: Date.now() + })) + } else { + this.messagesRendered = { + messages: this._messages, + type: 'initial' + } } + this.isLoadingMessages = false setTimeout(() => this.downElementObserver(), 500) @@ -3272,25 +3321,47 @@ class ChatPage extends LitElement { directSocketTimeout = setTimeout(pingDirectSocket, 45000) return } + if (initial === 0) { + this.lastReadMessageTimestamp = await chatLastSeen.getItem(this.chatId) || 0 if (noInitial) return - const cachedData = null let getInitialMessages = [] - if (cachedData && cachedData.length !== 0) { - const lastMessage = cachedData[cachedData.length - 1] - const newMessages = await parentEpml.request('apiCall', { + let isUnread = false + + const chatId = this.chatId + console.log('this.chatHeads', this.chatHeads) + const findContent = this.chatHeads.find((item)=> item.url === chatId) + const chatInfoTimestamp = findContent.timestamp || 0 + const lastReadMessageTimestamp = this.lastReadMessageTimestamp + + console.log({lastReadMessageTimestamp, chatInfoTimestamp}) + + if(lastReadMessageTimestamp && chatInfoTimestamp){ + if(lastReadMessageTimestamp < chatInfoTimestamp){ + isUnread = true + } + } + console.log({isUnread}) + if(isUnread){ + const getInitialMessagesBefore = await parentEpml.request('apiCall', { type: 'api', - url: `/chat/messages?involving=${window.parent.reduxStore.getState().app.selectedAddress.address}&involving=${cid}&limit=${chatLimit}&reverse=true&after=${lastMessage.timestamp}&haschatreference=false&encoding=BASE64` + url: `/chat/messages?involving=${window.parent.reduxStore.getState().app.selectedAddress.address}&involving=${cid}&limit=${20}&reverse=true&before=${lastReadMessageTimestamp}&haschatreference=false&encoding=BASE64` }) - getInitialMessages = [...newMessages] + const getInitialMessagesAfter = await parentEpml.request('apiCall', { + type: 'api', + url: `/chat/messages?involving=${window.parent.reduxStore.getState().app.selectedAddress.address}&involving=${cid}&limit=${20}&reverse=false&after=${lastReadMessageTimestamp - 1000}&haschatreference=false&encoding=BASE64` + }) + getInitialMessages = [...getInitialMessagesBefore, ...getInitialMessagesAfter] } else { getInitialMessages = await parentEpml.request('apiCall', { type: 'api', url: `/chat/messages?involving=${window.parent.reduxStore.getState().app.selectedAddress.address}&involving=${cid}&limit=${chatLimit}&reverse=true&haschatreference=false&encoding=BASE64` }) } + + - this.processMessages(getInitialMessages, true) + this.processMessages(getInitialMessages, true, isUnread) initial = initial + 1 @@ -3371,27 +3442,48 @@ class ChatPage extends LitElement { return } if (initial === 0) { + this.lastReadMessageTimestamp = await chatLastSeen.getItem(this.chatId) || 0 if (noInitial) return - const cachedData = null let getInitialMessages = [] - if (cachedData && cachedData.length !== 0) { + const lastReadMessageTimestamp = this.lastReadMessageTimestamp - const lastMessage = cachedData[cachedData.length - 1] + let isUnread = false + + const chatId = this.chatId + console.log('this.chatHeads', this.chatHeads) + const findContent = this.chatHeads.find((item)=> item.url === chatId) + const chatInfoTimestamp = findContent.timestamp || 0 + console.log({lastReadMessageTimestamp, chatInfoTimestamp}) - const newMessages = await parentEpml.request('apiCall', { - type: 'api', - url: `/chat/messages?txGroupId=${groupId}&limit=${chatLimit}&reverse=true&after=${lastMessage.timestamp}&haschatreference=false&encoding=BASE64` - }) - getInitialMessages = [...newMessages] - }else { - getInitialMessages = await parentEpml.request('apiCall', { - type: 'api', - url: `/chat/messages?txGroupId=${groupId}&limit=${chatLimit}&reverse=true&haschatreference=false&encoding=BASE64` - }) + if(lastReadMessageTimestamp && chatInfoTimestamp){ + if(lastReadMessageTimestamp < chatInfoTimestamp){ + isUnread = true + } + } + console.log({isUnread}, '2') + if(isUnread){ + + + const getInitialMessagesBefore = await parentEpml.request('apiCall', { + type: 'api', + url: `/chat/messages?txGroupId=${groupId}&limit=${20}&reverse=true&before=${lastReadMessageTimestamp}&haschatreference=false&encoding=BASE64` + }) + const getInitialMessagesAfter = await parentEpml.request('apiCall', { + type: 'api', + url: `/chat/messages?txGroupId=${groupId}&limit=${20}&reverse=false&after=${lastReadMessageTimestamp - 1000}&haschatreference=false&encoding=BASE64` + }) + getInitialMessages = [...getInitialMessagesBefore, ...getInitialMessagesAfter] + } else { + getInitialMessages = await parentEpml.request('apiCall', { + type: 'api', + url: `/chat/messages?txGroupId=${groupId}&limit=${chatLimit}&reverse=true&haschatreference=false&encoding=BASE64` + }) + } + + - } - this.processMessages(getInitialMessages, true) + this.processMessages(getInitialMessages, true, isUnread) initial = initial + 1 } else { diff --git a/plugins/plugins/core/components/ChatScroller-css.js b/plugins/plugins/core/components/ChatScroller-css.js index 028b0a79..d2ac074a 100644 --- a/plugins/plugins/core/components/ChatScroller-css.js +++ b/plugins/plugins/core/components/ChatScroller-css.js @@ -753,6 +753,17 @@ export const chatStyles = css` visibility: visible; } + .unread-divider { + width: 100%; + background: #9B111E; + padding: 5px; + color: #FAEBD7; + display: flex; + justify-content: center; + border-radius: 2px; + margin-top: 5px; + } + .blink-bg{ border-radius: 8px; animation: blinkingBackground 3s; diff --git a/plugins/plugins/core/components/ChatScroller.js b/plugins/plugins/core/components/ChatScroller.js index 9b7cce0d..33b4ffc6 100644 --- a/plugins/plugins/core/components/ChatScroller.js +++ b/plugins/plugins/core/components/ChatScroller.js @@ -10,6 +10,7 @@ import { roundToNearestDecimal } from '../../utils/roundToNearestDecimal.js' import { EmojiPicker } from 'emoji-picker-js' import { generateHTML } from '@tiptap/core' import isElectron from 'is-electron' +import localForage from 'localforage' import axios from 'axios' import Highlight from '@tiptap/extension-highlight' @@ -32,7 +33,9 @@ import '@vaadin/tooltip' import { chatLimit, totalMsgCount } from './ChatPage.js' const parentEpml = new Epml({ type: 'WINDOW', source: window.parent }) - +const chatLastSeen = localForage.createInstance({ + name: "chat-last-seen", +}) let toggledMessage = {} const uid = new ShortUniqueId() @@ -259,7 +262,9 @@ class ChatScroller extends LitElement { this.oldMessages = [] this._upObserverhandler = this._upObserverhandler.bind(this) this.newListMessages = this.newListMessages.bind(this) + this.newListMessagesUnreadMessages = this.newListMessagesUnreadMessages.bind(this) this._downObserverHandler = this._downObserverHandler.bind(this) + this.isLastMessageBeforeUnread = this.isLastMessageBeforeUnread.bind(this) this.replaceMessagesWithUpdate = this.replaceMessagesWithUpdate.bind(this) this.__bottomObserverForFetchingMessagesHandler = this.__bottomObserverForFetchingMessagesHandler.bind(this) this.myAddress = window.parent.reduxStore.getState().app.selectedAddress.address @@ -273,6 +278,7 @@ class ChatScroller extends LitElement { this.isLoadingBefore = false this.isLoadingAfter = false this.disableAddingNewMessages = false + this.lastReadMessageTimestamp = null } addSeenMessage(val) { @@ -346,6 +352,57 @@ class ChatScroller extends LitElement { } + async newListMessagesUnreadMessages(newMessages, message, lastReadMessageTimestamp) { + const viewElement = this.shadowRoot.querySelector("#viewElement"); + + console.log('sup', lastReadMessageTimestamp); + let data = []; + const copy = [...newMessages]; + + let dividerPlaced = false; // To ensure the divider is added only once + + // Start from the end of the list (newest messages) + for (let i = copy.length - 1; i >= 0; i--) { + let newMessage = copy[i]; + + // Initialize a property for the divider + newMessage.isDivider = false; + + // Check if this is the message before which the divider should be placed + if (!dividerPlaced && newMessage.timestamp <= lastReadMessageTimestamp) { + console.log('true true') + newMessage.isDivider = true; + dividerPlaced = true; // Ensure the divider is only added once + break; // Exit once the divider is placed + } + } + + copy.forEach((newMessage, groupIndex) => { + const lastGroupedMessage = data[data.length - 1]; + + if (this.shouldGroupWithLastMessage(newMessage, lastGroupedMessage)) { + lastGroupedMessage.messages.push(newMessage); + } else { + data.push({ + messages: [newMessage], + ...newMessage + }); + } + }); + + console.log({ data }); + this.messagesToRender = data; + this.clearLoaders(); + this.requestUpdate(); + await this.updateComplete; + const findElement = this.shadowRoot.getElementById('unread-divider-id') + if (findElement) { + findElement.scrollIntoView({ behavior: 'auto', block: 'center' }) + } + } + + + async addNewMessages(newMessages, type) { @@ -402,7 +459,7 @@ class ChatScroller extends LitElement { if (type === 'initial') { - this.viewElement.scrollTop = this.viewElement.scrollHeight + viewElement.scrollTop = viewElement.scrollHeight @@ -547,13 +604,17 @@ class ChatScroller extends LitElement { async updated(changedProperties) { if (changedProperties && changedProperties.has('messages')) { - + console.log({changedProperties}, this.messages) if (this.messages.type === 'initial') { this.addNewMessages(this.messages.messages, 'initial') - } else if (this.messages.type === 'new') this.addNewMessages(this.messages.messages) + } else if (this.messages.type === 'initialLastSeen') { + this.newListMessagesUnreadMessages(this.messages.messages, 'initialLastSeen', this.messages.lastReadMessageTimestamp) + + } + else if (this.messages.type === 'new') this.addNewMessages(this.messages.messages) else if(this.messages.type === 'newComingInAuto') this.addNewMessages(this.messages.messages, 'newComingInAuto') else if (this.messages.type === 'old') this.prependOldMessages(this.messages.messages) else if (this.messages.type === 'inBetween') this.newListMessages(this.messages.messages, this.messages.signature) @@ -567,6 +628,15 @@ class ChatScroller extends LitElement { } + isLastMessageBeforeUnread(message, formattedMessages) { + // if the message is the last one in the older messages list and its timestamp is before the user's last seen timestamp + if (message.timestamp < this.lastReadMessageTimestamp && formattedMessages.indexOf(message) === (formattedMessages.length - 21)) { + return true; + } + return false; + } + + render() { // let formattedMessages = this.messages.reduce((messageArray, message) => { // const currentMessage = this.updateMessageHash[message.signature] || message; @@ -617,10 +687,12 @@ class ChatScroller extends LitElement { formattedMessages, (formattedMessage) => formattedMessage.id, // Use .id as the unique key for formattedMessage. (formattedMessage) => html` + ${repeat( formattedMessage.messages, (message) => message.signature, (message, indexMessage) => html` + this.addSeenMessage(val)} .listSeenMessages=${this.listSeenMessages} chatId=${this.chatId} - >` + > + ${message.isDivider ? html`
Unread Messages Below
` : null} + + ` + )} + ` )}
@@ -708,7 +785,6 @@ class ChatScroller extends LitElement { async firstUpdated() { this.changeTheme() - window.addEventListener('storage', () => { const checkTheme = localStorage.getItem('qortalTheme') From f5a9fe46c41579e8cd566d0931a245950b54ef9f Mon Sep 17 00:00:00 2001 From: PhilReact Date: Sun, 10 Sep 2023 11:37:01 -0500 Subject: [PATCH 17/57] remove logs , imporve replace msgs efficiency --- plugins/plugins/core/components/ChatPage.js | 80 ++-------- .../plugins/core/components/ChatScroller.js | 29 +--- .../core/messaging/q-chat/q-chat.src.js | 6 +- .../plugins/utils/replace-messages-edited.js | 150 +++++++----------- 4 files changed, 73 insertions(+), 192 deletions(-) diff --git a/plugins/plugins/core/components/ChatPage.js b/plugins/plugins/core/components/ChatPage.js index dee271f1..85948ccd 100644 --- a/plugins/plugins/core/components/ChatPage.js +++ b/plugins/plugins/core/components/ChatPage.js @@ -55,8 +55,10 @@ const parentEpml = new Epml({ type: 'WINDOW', source: window.parent }) export const queue = new RequestQueue(); -export const chatLimit = 40 -export const totalMsgCount = 120 +export const chatLimit = 20 +export const chatLimitHalf = 10 + +export const totalMsgCount = 60 class ChatPage extends LitElement { static get properties() { return { @@ -2142,7 +2144,6 @@ class ChatPage extends LitElement { } async goToRepliedMessage(message, clickedOnMessage) { - console.log({message}) const findMessage = this.shadowRoot.querySelector('chat-scroller').shadowRoot.getElementById(message.signature) if (findMessage) { @@ -2173,9 +2174,7 @@ class ChatPage extends LitElement { await this.getUpdateComplete() const marginElements = Array.from(this.shadowRoot.querySelector('chat-scroller').shadowRoot.querySelectorAll('message-template')) - console.log({marginElements}) const findMessage2 = marginElements.find((item) => item.messageObj.signature === message.signature) || marginElements.find((item) => item.messageObj.originalSignature === message.signature) || marginElements.find((item) => item.messageObj.signature === message.originalSignature) || marginElements.find((item) => item.messageObj.originalSignature === message.originalSignature) - console.log({findMessage2}, message.signature) if (findMessage2) { findMessage2.scrollIntoView({ block: 'center' }) } @@ -2410,26 +2409,9 @@ class ChatPage extends LitElement { document.querySelector('html').setAttribute('theme', this.theme) }) - // parentEpml.ready().then(() => { - // parentEpml.subscribe('selected_address', async selectedAddress => { - // this.selectedAddress = {} - // selectedAddress = JSON.parse(selectedAddress) - // if (!selectedAddress || Object.entries(selectedAddress).length === 0) return - // this.selectedAddress = selectedAddress - // }) - - // }) + this.lastReadMessageTimestamp = await chatLastSeen.getItem(this.chatId) || 0 - // parentEpml.subscribe('chat_last_seen', async chatList => { - // const parsedChatList = JSON.parse(chatList) - // console.log({parsedChatList}, this.chatId) - // const findChatSeen = parsedChatList.find(chat=> chat.key === this.chatId) - // console.log({findChatSeen}) - // if(findChatSeen && this.lastReadMessageTimestamp !== findChatSeen.timestamp){ - // this.lastReadMessageTimestamp = findChatSeen.timestamp - - // } - // }) + parentEpml.imReady() const isEnabledChatEnter = localStorage.getItem('isEnabledChatEnter') @@ -2528,7 +2510,6 @@ class ChatPage extends LitElement { } renderChatScroller() { - console.log('clearUpdateMessageHashmap', Object.keys(this.updateMessageHash).length) return html` { - console.log('e',e) rej() } @@ -2639,7 +2619,6 @@ class ChatPage extends LitElement { addToUpdateMessageHashmap: this.addToUpdateMessageHashmap })); - console.log({decodeMsgs}) let list = [...decodeMsgs] @@ -2648,7 +2627,6 @@ class ChatPage extends LitElement { this.webWorkerSortMessages.postMessage({list}); this.webWorkerSortMessages.onmessage = e => { - console.log('e',e) list = e.data res() @@ -2685,7 +2663,6 @@ class ChatPage extends LitElement { } this.webWorkerDecodeMessages.onerror = e => { - console.log('e',e) rej() } @@ -2700,7 +2677,6 @@ class ChatPage extends LitElement { addToUpdateMessageHashmap: this.addToUpdateMessageHashmap })); - console.log({decodeMsgs}) let list = [...decodeMsgs] @@ -2709,7 +2685,6 @@ class ChatPage extends LitElement { this.webWorkerSortMessages.postMessage({list}); this.webWorkerSortMessages.onmessage = e => { - console.log('e',e) list = e.data res() @@ -2745,7 +2720,6 @@ class ChatPage extends LitElement { } this.webWorkerDecodeMessages.onerror = e => { - console.log('e',e) rej() } @@ -2767,7 +2741,6 @@ class ChatPage extends LitElement { this.webWorkerSortMessages.postMessage({list}); this.webWorkerSortMessages.onmessage = e => { - console.log('e',e) list = e.data res() @@ -2808,7 +2781,6 @@ class ChatPage extends LitElement { } this.webWorkerDecodeMessages.onerror = e => { - console.log('e',e) rej() } @@ -2830,7 +2802,6 @@ class ChatPage extends LitElement { this.webWorkerSortMessages.postMessage({list}); this.webWorkerSortMessages.onmessage = e => { - console.log('e',e) list = e.data res() @@ -2872,7 +2843,6 @@ class ChatPage extends LitElement { } this.webWorkerDecodeMessages.onerror = e => { - console.log('e',e) rej() } @@ -2895,7 +2865,6 @@ class ChatPage extends LitElement { this.webWorkerSortMessages.postMessage({list}); this.webWorkerSortMessages.onmessage = e => { - console.log('e',e) list = e.data res() @@ -2933,7 +2902,6 @@ class ChatPage extends LitElement { } this.webWorkerDecodeMessages.onerror = e => { - console.log('e',e) rej() } @@ -2958,7 +2926,6 @@ class ChatPage extends LitElement { this.webWorkerSortMessages.postMessage({list}); this.webWorkerSortMessages.onmessage = e => { - console.log('e',e) list = e.data res() @@ -3002,7 +2969,6 @@ class ChatPage extends LitElement { } async clearUpdateMessageHashmap(){ - console.log('hello clear') this.updateMessageHash = {} this.requestUpdate() } @@ -3046,7 +3012,6 @@ class ChatPage extends LitElement { } this.webWorkerDecodeMessages.onerror = e => { - console.log('e',e) rej() } @@ -3078,7 +3043,6 @@ class ChatPage extends LitElement { this.webWorkerSortMessages.postMessage({list}); this.webWorkerSortMessages.onmessage = e => { - console.log('e',e) list = e.data res() @@ -3141,23 +3105,6 @@ class ChatPage extends LitElement { } - - // let list = [...this.messagesRendered] - - // await new Promise((res, rej) => { - - // this.webWorkerSortMessages.postMessage({list}); - - // this.webWorkerSortMessages.onmessage = e => { - // console.log('e',e) - - // list = e.data - // res() - - // } - // }) - - // this.messagesRendered = list } } @@ -3329,27 +3276,24 @@ class ChatPage extends LitElement { let isUnread = false const chatId = this.chatId - console.log('this.chatHeads', this.chatHeads) const findContent = this.chatHeads.find((item)=> item.url === chatId) const chatInfoTimestamp = findContent.timestamp || 0 const lastReadMessageTimestamp = this.lastReadMessageTimestamp - console.log({lastReadMessageTimestamp, chatInfoTimestamp}) if(lastReadMessageTimestamp && chatInfoTimestamp){ if(lastReadMessageTimestamp < chatInfoTimestamp){ isUnread = true } } - console.log({isUnread}) if(isUnread){ const getInitialMessagesBefore = await parentEpml.request('apiCall', { type: 'api', - url: `/chat/messages?involving=${window.parent.reduxStore.getState().app.selectedAddress.address}&involving=${cid}&limit=${20}&reverse=true&before=${lastReadMessageTimestamp}&haschatreference=false&encoding=BASE64` + url: `/chat/messages?involving=${window.parent.reduxStore.getState().app.selectedAddress.address}&involving=${cid}&limit=${chatLimitHalf}&reverse=true&before=${lastReadMessageTimestamp}&haschatreference=false&encoding=BASE64` }) const getInitialMessagesAfter = await parentEpml.request('apiCall', { type: 'api', - url: `/chat/messages?involving=${window.parent.reduxStore.getState().app.selectedAddress.address}&involving=${cid}&limit=${20}&reverse=false&after=${lastReadMessageTimestamp - 1000}&haschatreference=false&encoding=BASE64` + url: `/chat/messages?involving=${window.parent.reduxStore.getState().app.selectedAddress.address}&involving=${cid}&limit=${chatLimitHalf}&reverse=false&after=${lastReadMessageTimestamp - 1000}&haschatreference=false&encoding=BASE64` }) getInitialMessages = [...getInitialMessagesBefore, ...getInitialMessagesAfter] } else { @@ -3450,27 +3394,24 @@ class ChatPage extends LitElement { let isUnread = false const chatId = this.chatId - console.log('this.chatHeads', this.chatHeads) const findContent = this.chatHeads.find((item)=> item.url === chatId) const chatInfoTimestamp = findContent.timestamp || 0 - console.log({lastReadMessageTimestamp, chatInfoTimestamp}) if(lastReadMessageTimestamp && chatInfoTimestamp){ if(lastReadMessageTimestamp < chatInfoTimestamp){ isUnread = true } } - console.log({isUnread}, '2') if(isUnread){ const getInitialMessagesBefore = await parentEpml.request('apiCall', { type: 'api', - url: `/chat/messages?txGroupId=${groupId}&limit=${20}&reverse=true&before=${lastReadMessageTimestamp}&haschatreference=false&encoding=BASE64` + url: `/chat/messages?txGroupId=${groupId}&limit=${chatLimitHalf}&reverse=true&before=${lastReadMessageTimestamp}&haschatreference=false&encoding=BASE64` }) const getInitialMessagesAfter = await parentEpml.request('apiCall', { type: 'api', - url: `/chat/messages?txGroupId=${groupId}&limit=${20}&reverse=false&after=${lastReadMessageTimestamp - 1000}&haschatreference=false&encoding=BASE64` + url: `/chat/messages?txGroupId=${groupId}&limit=${chatLimitHalf}&reverse=false&after=${lastReadMessageTimestamp - 1000}&haschatreference=false&encoding=BASE64` }) getInitialMessages = [...getInitialMessagesBefore, ...getInitialMessagesAfter] } else { @@ -4295,7 +4236,6 @@ class ChatPage extends LitElement { const _computePow = async (chatBytes, isForward) => { const difficulty = this.balance < 4 ? 18 : 8 - console.log({difficulty}) const path = window.parent.location.origin + '/memory-pow/memory-pow.wasm.full' let worker diff --git a/plugins/plugins/core/components/ChatScroller.js b/plugins/plugins/core/components/ChatScroller.js index 33b4ffc6..d5a78398 100644 --- a/plugins/plugins/core/components/ChatScroller.js +++ b/plugins/plugins/core/components/ChatScroller.js @@ -320,7 +320,6 @@ class ChatScroller extends LitElement { async newListMessages(newMessages, message) { - console.log('sup') let data = [] const copy = [...newMessages] copy.forEach(newMessage => { @@ -337,7 +336,6 @@ class ChatScroller extends LitElement { }); - console.log({data}) this.messagesToRender = data this.clearLoaders() this.requestUpdate() @@ -355,7 +353,6 @@ class ChatScroller extends LitElement { async newListMessagesUnreadMessages(newMessages, message, lastReadMessageTimestamp) { const viewElement = this.shadowRoot.querySelector("#viewElement"); - console.log('sup', lastReadMessageTimestamp); let data = []; const copy = [...newMessages]; @@ -370,7 +367,6 @@ class ChatScroller extends LitElement { // Check if this is the message before which the divider should be placed if (!dividerPlaced && newMessage.timestamp <= lastReadMessageTimestamp) { - console.log('true true') newMessage.isDivider = true; dividerPlaced = true; // Ensure the divider is only added once break; // Exit once the divider is placed @@ -390,7 +386,6 @@ class ChatScroller extends LitElement { } }); - console.log({ data }); this.messagesToRender = data; this.clearLoaders(); this.requestUpdate(); @@ -415,7 +410,6 @@ class ChatScroller extends LitElement { previousScrollHeight = viewElement.scrollHeight; - console.log('sup', type); const copy = [...this.messagesToRender] for (const newMessage of newMessages) { @@ -453,9 +447,7 @@ class ChatScroller extends LitElement { } this.messagesToRender = copy this.requestUpdate(); - console.log("Before waiting for updateComplete"); await this.updateComplete; - console.log("After waiting for updateComplete"); if (type === 'initial') { @@ -469,7 +461,6 @@ class ChatScroller extends LitElement { async prependOldMessages(oldMessages) { - console.log('2', { oldMessages }); if (!this.messagesToRender) this.messagesToRender = []; // Ensure it's initialized let currentMessageGroup = null; @@ -546,22 +537,9 @@ class ChatScroller extends LitElement { this.messagesToRender = newMessagesToRender; this.requestUpdate(); - console.log('await this.updateComplete 1') await this.updateComplete; - console.log('await this.updateComplete 2') - // Adjust scroll position based on the difference in scroll heights - // await new Promise((res)=> { - // setTimeout(()=> { - // res() - // }, 5000) - // }) - // const newScrollHeight = viewElement.scrollHeight; - // viewElement.scrollTop = previousScrollTop + (newScrollHeight - previousScrollHeight); - // viewElement.scrollTop = viewElement.scrollHeight - viewElement.clientHeight; - // const newScrollHeight = viewElement.scrollHeight; - // viewElement.scrollTop = viewElement.scrollTop + (newScrollHeight - viewElement.scrollHeight); - // If the user was at the bottom before the update, keep them at the bottom + if (isUserAtBottom) { viewElement.scrollTop = viewElement.scrollHeight - viewElement.clientHeight; } else { @@ -583,7 +561,6 @@ class ChatScroller extends LitElement { const viewElement = this.shadowRoot.querySelector("#viewElement"); previousScrollTop = viewElement.scrollTop; previousScrollHeight = viewElement.scrollHeight; - console.log({updatedMessagesArray}, this.messagesToRender) for (let group of this.messagesToRender) { for (let i = 0; i < group.messages.length; i++) { const update = updatedMessagesArray.find(updatedMessage => ((updatedMessage.chatReference === group.messages[i].signature) || (updatedMessage.chatReference === group.messages[i].originalSignature))); @@ -604,7 +581,6 @@ class ChatScroller extends LitElement { async updated(changedProperties) { if (changedProperties && changedProperties.has('messages')) { - console.log({changedProperties}, this.messages) if (this.messages.type === 'initial') { this.addNewMessages(this.messages.messages, 'initial') @@ -738,7 +714,6 @@ class ChatScroller extends LitElement { } shouldUpdate(changedProperties) { - console.log({changedProperties}) if (changedProperties.has('isLoadingMessages')) { return true } @@ -758,7 +733,6 @@ class ChatScroller extends LitElement { return true } if(changedProperties.has('messagesToRender')){ - console.log('true', this.messagesToRender) return true } if(changedProperties.has('isLoadingBefore')){ @@ -774,7 +748,6 @@ class ChatScroller extends LitElement { async getUpdateComplete() { await super.getUpdateComplete() const marginElements = Array.from(this.shadowRoot.querySelectorAll('message-template')) - console.log({ marginElements }) await Promise.all(marginElements.map(el => el.updateComplete)) return true } diff --git a/plugins/plugins/core/messaging/q-chat/q-chat.src.js b/plugins/plugins/core/messaging/q-chat/q-chat.src.js index 5e784bf3..95370b95 100644 --- a/plugins/plugins/core/messaging/q-chat/q-chat.src.js +++ b/plugins/plugins/core/messaging/q-chat/q-chat.src.js @@ -179,7 +179,6 @@ class Chat extends LitElement { } render() { - console.log('q-chat') return html`
@@ -470,10 +469,7 @@ class Chat extends LitElement { }, 60000) } - async updated(changedProperties) { - console.log({changedProperties}) - - } + clearConsole() { if (!isElectron()) { diff --git a/plugins/plugins/utils/replace-messages-edited.js b/plugins/plugins/utils/replace-messages-edited.js index d27364d4..129a4a8b 100644 --- a/plugins/plugins/utils/replace-messages-edited.js +++ b/plugins/plugins/utils/replace-messages-edited.js @@ -155,125 +155,97 @@ export const replaceMessagesEdited = async ({ return results; }; - const findNewMessages = async (msg) => { - let msgItem = msg + const findUpdatedMessage = async (msg) => { + let msgItem = { ...msg }; + try { - let msgQuery = `&involving=${msg.recipient}&involving=${msg.sender}` + let msgQuery = `&involving=${msg.recipient}&involving=${msg.sender}`; if (!isReceipient) { - msgQuery = `&txGroupId=${msg.txGroupId}` + msgQuery = `&txGroupId=${msg.txGroupId}`; } - const response = await parentEpml.request("apiCall", { + + // Find new messages first + const newMsgResponse = await parentEpml.request("apiCall", { type: "api", url: `/chat/messages?chatreference=${msg.signature}&reverse=true${msgQuery}&limit=1&sender=${msg.sender}&encoding=BASE64`, - }) - - if (response && Array.isArray(response) && response.length !== 0) { - let responseItem = { ...response[0] } - const decodeResponseItem = decodeMessageFunc(responseItem, isReceipient, _publicKey) - delete decodeResponseItem.timestamp - + }); + + if (Array.isArray(newMsgResponse) && newMsgResponse.length > 0) { + const decodeResponseItem = decodeMessageFunc(newMsgResponse[0], isReceipient, _publicKey); + delete decodeResponseItem.timestamp; + msgItem = { - ...msg, + ...msgItem, ...decodeResponseItem, senderName: msg.senderName, sender: msg.sender, - editedTimestamp: response[0].timestamp, + editedTimestamp: newMsgResponse[0].timestamp, originalSignature: msg.signature - } + }; } - } catch (error) { - } - - return msgItem - }; - - const findNewMessages2 = async (msg) => { - let parsedMessageObj = msg - try { - parsedMessageObj = JSON.parse(msg.decodedMessage) - } catch (error) { - return msg - } - let msgItem = msg - try { - let msgQuery = `&involving=${msg.recipient}&involving=${msg.sender}` - if (!isReceipient) { - msgQuery = `&txGroupId=${msg.txGroupId}` + + // Then check and find replies in the same iteration + let parsedMessageObj; + try { + parsedMessageObj = JSON.parse(msg.decodedMessage); + } catch (error) { + // If parsing fails, return the msgItem as is + return msgItem; } + if (parsedMessageObj.repliedTo) { - let originalReply + let originalReply; if(+parsedMessageObj.version > 2){ originalReply = await parentEpml.request("apiCall", { type: "api", - url: `/chat/message/${parsedMessageObj.repliedTo}?encoding=BASE64`, - }) - } - if(+parsedMessageObj.version < 3){ + url: `/chat/message/${parsedMessageObj.repliedTo}?encoding=BASE64`, + }); + } else { originalReply = await parentEpml.request("apiCall", { type: "api", - url: `/chat/messages?reference=${parsedMessageObj.repliedTo}&reverse=true${msgQuery}&encoding=BASE64`, - }) - } - - - - - const originalReplyMessage = originalReply.timestamp ? originalReply : originalReply.length !== 0 ? originalReply[0] : null - - const response = await parentEpml.request("apiCall", { + url: `/chat/messages?reference=${parsedMessageObj.repliedTo}&reverse=true${msgQuery}&encoding=BASE64`, + }); + } + + const originalReplyMessage = originalReply.timestamp ? originalReply : originalReply.length > 0 ? originalReply[0] : null; + + const replyResponse = await parentEpml.request("apiCall", { type: "api", url: `/chat/messages?chatreference=${parsedMessageObj.repliedTo}&reverse=true${msgQuery}&limit=1&sender=${originalReplyMessage.sender}&encoding=BASE64`, - }) - - + }); + if ( originalReplyMessage && - response && - Array.isArray(response) && - response.length !== 0 + Array.isArray(replyResponse) && + replyResponse.length !== 0 ) { - const decodeOriginalReply = decodeMessageFunc(originalReplyMessage, isReceipient, _publicKey) - - const decodeUpdatedReply = decodeMessageFunc(response[0], isReceipient, _publicKey) - const formattedRepliedToData = { + const decodeOriginalReply = decodeMessageFunc(originalReplyMessage, isReceipient, _publicKey); + const decodeUpdatedReply = decodeMessageFunc(replyResponse[0], isReceipient, _publicKey); + + msgItem.repliedToData = { ...decodeUpdatedReply, senderName: decodeOriginalReply.senderName, sender: decodeOriginalReply.sender, - } - msgItem = { - ...msg, - repliedToData: formattedRepliedToData, - } - } else { - - - if ( - originalReplyMessage - ) { - msgItem = { - ...msg, - repliedToData: decodeMessageFunc(originalReplyMessage, isReceipient, _publicKey), - } - } + }; + } else if (originalReplyMessage) { + msgItem.repliedToData = decodeMessageFunc(originalReplyMessage, isReceipient, _publicKey); } } + } catch (error) { + // Handle or log the error gracefully + console.error(error); } - - - return msgItem + + return msgItem; }; - - - + const sortedMessages = decodedMessages.sort((a, b) => b.timestamp - a.timestamp); - - // Execute the functions with concurrency limit - const updateMessages = await executeWithConcurrencyLimit(sortedMessages, findNewMessages); - const updateMessages2 = await executeWithConcurrencyLimit(updateMessages.filter(item => item !== 'null' && item !== null), findNewMessages2); - - addToUpdateMessageHashmap(updateMessages2) - - - return updateMessages2; + + // Execute the functions with concurrency limit + const updatedMessages = await executeWithConcurrencyLimit(sortedMessages, findUpdatedMessage); + addToUpdateMessageHashmap(updatedMessages); + + return updatedMessages; + }; From 79fa08a099074fa1c375bec5d72daa7d3f978f5b Mon Sep 17 00:00:00 2001 From: PhilReact Date: Tue, 12 Sep 2023 21:49:01 -0500 Subject: [PATCH 18/57] added pending messages --- plugins/plugins/core/components/ChatPage.js | 174 ++++++++++++++---- .../core/components/ChatScroller-css.js | 4 + .../plugins/core/components/ChatScroller.js | 88 +++++++-- .../core/components/ChatTextEditor copy.js | 1 + .../plugins/core/components/ChatTextEditor.js | 16 +- 5 files changed, 235 insertions(+), 48 deletions(-) diff --git a/plugins/plugins/core/components/ChatPage.js b/plugins/plugins/core/components/ChatPage.js index 85948ccd..66f1ada6 100644 --- a/plugins/plugins/core/components/ChatPage.js +++ b/plugins/plugins/core/components/ChatPage.js @@ -124,7 +124,9 @@ class ChatPage extends LitElement { goToRepliedMessage: { attribute: false }, isLoadingGoToRepliedMessage: { type: Object }, updateMessageHash: { type: Object}, - oldMessages: {type: Array} + oldMessages: {type: Array}, + messageQueue: {type: Array}, + isInProcessQueue: {type: Boolean} } } @@ -1371,6 +1373,10 @@ class ChatPage extends LitElement { this.oldMessages = [] this.lastReadMessageTimestamp = 0 this.initUpdate = this.initUpdate.bind(this) + this.messageQueue = [] + this.addToQueue = this.addToQueue.bind(this) + this.processQueue = this.processQueue.bind(this) + this.isInProcessQueue = false } setOpenGifModal(value) { @@ -1416,7 +1422,84 @@ class ChatPage extends LitElement { this.gifsLoading = props } + addToQueue(outSideMsg, messageQueue) { + console.log('added to queue', messageQueue, this.messageQueue) + // Push the new message object to the queue + + this.messageQueue = [...messageQueue, { ...outSideMsg, timestamp: Date.now()}]; + console.log('Current Queue after adding:', [...this.messageQueue]); + + // Start processing the queue only if the message we just added is the only one in the queue + // This ensures that the queue processing starts only once, even if this method is called multiple times + if (this.messageQueue.length === 1) { + this.processQueue(); + } + + // Notify Lit to update/render due to the property change + this.requestUpdate(); + } + + async processQueue() { + if (this.messageQueue.length === 0) return; + const currentMessage = this.messageQueue[0]; + console.log({currentMessage}) + try { + const res = await this.sendMessage(currentMessage); + console.log({res}) + if(res === true) { + this.messageQueue = this.messageQueue.slice(1); + } else { + throw new Error('failed') + } + + if (this.messageQueue.length > 0) { + setTimeout(() => this.processQueue(), 2000); // Wait for 10 seconds before retrying + // setTimeout(() => this.processQueue(), 0); // Process the next message immediately + } + } catch (error) { + console.error("Failed to send message:", error); + setTimeout(() => this.processQueue(), 10000); // Wait for 10 seconds before retrying + } + } + // async processQueue() { + // let inProcess = false; // to indicate if we're currently sending a message + + // while (true) { // Infinite loop + // const currentMessage = this.messageQueue[0]; + + // // If there's no current message, you could wait a while (e.g., a second) before the next iteration. + // // This prevents the loop from consuming resources unnecessarily when the queue is empty. + // if (!currentMessage) { + // await new Promise(res => setTimeout(res, 1000)); // Wait for 1 second + // continue; // go to the next iteration of the loop + // } + + // // If not currently processing another message, attempt to send the current one + // if (!inProcess) { + // inProcess = true; // indicate that we're starting to process a message + + // try { + // await this._sendMessage(currentMessage.outSideMsg, currentMessage.msg, true, currentMessage.chatId, currentMessage.isReceipient, currentMessage._publicKey, currentMessage.attachment); + + // // If successful, remove the processed message from the queue + // this.messageQueue = this.messageQueue.slice(1); + // inProcess = false; // set inProcess back to false since we're done with this message + // } catch (error) { + // console.error("Failed to send message:", error); + // inProcess = false; // set inProcess back to false since an error occurred + // await new Promise(res => setTimeout(res, 10000)); // Wait for 10 seconds before retrying + // } + // } else { + // await new Promise(res => setTimeout(res, 1000)); // Wait for 1 second before checking again if inProcess is false + // } + // } + // } + + + render() { + console.log('this.messageQueue', this.messageQueue.length) + return html`
this.setOpenGifModal(val)} chatId=${this.chatId} + .messageQueue=${this.messageQueue} >
@@ -2394,7 +2478,9 @@ class ChatPage extends LitElement { async firstUpdated() { this.changeTheme() - + + // this.processQueue(); + window.addEventListener('storage', () => { const checkLanguage = localStorage.getItem('qortalLanguage') const checkTheme = localStorage.getItem('qortalTheme') @@ -2453,6 +2539,7 @@ class ChatPage extends LitElement { shouldUpdate(changedProperties) { + console.log({changedProperties}) if (changedProperties.has('setActiveChatHeadUrl')) { return false } @@ -2537,6 +2624,7 @@ class ChatPage extends LitElement { .goToRepliedMessage=${(val, val2) => this.goToRepliedMessage(val, val2)} .updateMessageHash=${this.updateMessageHash} .clearUpdateMessageHashmap=${this.clearUpdateMessageHashmap} + .messageQueue=${this.messageQueue} > ` @@ -3484,11 +3572,25 @@ class ChatPage extends LitElement { } } - async _sendMessage(outSideMsg, msg) { + async _sendMessage(outSideMsg, msg, messageQueue) { + const _chatId= this._chatId + const isReceipient= this.isReceipient + const _publicKey= this._publicKey + const attachment= this.attachment + console.log({ + _chatId, isReceipient, _publicKey, attachment + }) + // if(messageQueue){ + // console.log('is queueCurrent Queue after before:', [...this.messageQueue]); + // this.addToQueue(outSideMsg, msg, messageQueue); + + // return + // } + try { if (this.isReceipient) { let hasPublicKey = true - if (!this._publicKey.hasPubKey) { + if (!publicKey.hasPubKey) { hasPublicKey = false try { const res = await parentEpml.request('apiCall', { @@ -3496,20 +3598,20 @@ class ChatPage extends LitElement { url: `/addresses/publickey/${this.selectedAddress.address}` }) if (res.error === 102) { - this._publicKey.key = '' - this._publicKey.hasPubKey = false + publicKey.key = '' + publicKey.hasPubKey = false } else if (res !== false) { - this._publicKey.key = res - this._publicKey.hasPubKey = true + publicKey.key = res + publicKey.hasPubKey = true hasPublicKey = true } else { - this._publicKey.key = '' - this._publicKey.hasPubKey = false + publicKey.key = '' + publicKey.hasPubKey = false } } catch (error) { } - if (!hasPublicKey || !this._publicKey.hasPubKey) { + if (!hasPublicKey || !publicKey.hasPubKey) { let err4string = get("chatpage.cchange39") parentEpml.request('showSnackBar', `${err4string}`) return @@ -3524,7 +3626,7 @@ class ChatPage extends LitElement { // create new var called repliedToData and use that to modify the UI // find specific object property in local let typeMessage = 'regular' - this.isLoading = true + // this.isLoading = true const trimmedMessage = msg const getName = async (recipient) => { @@ -3640,7 +3742,7 @@ class ChatPage extends LitElement { isImageDeleted: true } const stringifyMessageObject = JSON.stringify(messageObject) - this.sendMessage(stringifyMessageObject, typeMessage, chatReference) + return this.sendMessage({messageText: stringifyMessageObject, typeMessage, chatReference, isForward: false, isReceipient, _chatId, _publicKey, messageQueue}) } else if (outSideMsg && outSideMsg.type === 'deleteAttachment') { this.isDeletingAttachment = true let compressedFile = '' @@ -3738,7 +3840,7 @@ class ChatPage extends LitElement { isAttachmentDeleted: true } const stringifyMessageObject = JSON.stringify(messageObject) - this.sendMessage(stringifyMessageObject, typeMessage, chatReference) + return this.sendMessage({messageText: stringifyMessageObject, typeMessage, chatReference, isForward: false, isReceipient, _chatId, _publicKey, messageQueue}) } else if (outSideMsg && outSideMsg.type === 'image') { this.isUploadingImage = true const userName = await getName(this.selectedAddress.address) @@ -3826,7 +3928,7 @@ class ChatPage extends LitElement { version: 3 } const stringifyMessageObject = JSON.stringify(messageObject) - this.sendMessage(stringifyMessageObject, typeMessage) + return this.sendMessage({messageText: stringifyMessageObject, typeMessage, chatReference: undefined, isForward: false, isReceipient, _chatId, _publicKey, messageQueue}) } else if (outSideMsg && outSideMsg.type === 'gif') { const userName = await getName(this.selectedAddress.address) if (!userName) { @@ -3847,7 +3949,7 @@ class ChatPage extends LitElement { version: 3 } const stringifyMessageObject = JSON.stringify(messageObject) - this.sendMessage(stringifyMessageObject, typeMessage) + return this.sendMessage({messageText: stringifyMessageObject, typeMessage, chatReference: undefined, isForward: false, isReceipient, _chatId, _publicKey, messageQueue}) } else if (outSideMsg && outSideMsg.type === 'attachment') { this.isUploadingAttachment = true const userName = await getName(this.selectedAddress.address) @@ -3864,7 +3966,7 @@ class ChatPage extends LitElement { this.webWorkerFile = new WebWorkerFile() - const attachment = this.attachment + // const attachment = attachment const id = this.uid() const identifier = `qchat_${id}` const fileSize = attachment.size @@ -3916,7 +4018,7 @@ class ChatPage extends LitElement { version: 3 } const stringifyMessageObject = JSON.stringify(messageObject) - this.sendMessage(stringifyMessageObject, typeMessage) + return this.sendMessage({messageText: stringifyMessageObject, typeMessage, chatReference: undefined, isForward: false, isReceipient, _chatId, _publicKey, messageQueue}) } else if (outSideMsg && outSideMsg.type === 'reaction') { const userName = await getName(this.selectedAddress.address) typeMessage = 'edit' @@ -3971,7 +4073,7 @@ class ChatPage extends LitElement { reactions } const stringifyMessageObject = JSON.stringify(messageObject) - this.sendMessage(stringifyMessageObject, typeMessage, chatReference) + return this.sendMessage({messageText: stringifyMessageObject, typeMessage, chatReference, isForward: false, isReceipient, _chatId, _publicKey, messageQueue}) } else if (/^\s*$/.test(trimmedMessage)) { this.isLoading = false } else if (this.repliedToMessageObj) { @@ -3987,7 +4089,7 @@ class ChatPage extends LitElement { version: 3 } const stringifyMessageObject = JSON.stringify(messageObject) - this.sendMessage(stringifyMessageObject, typeMessage) + return this.sendMessage({messageText: stringifyMessageObject, typeMessage, chatReference: undefined, isForward: false, isReceipient, _chatId, _publicKey, messageQueue}) } else if (this.editedMessageObj) { typeMessage = 'edit' let chatReference = this.editedMessageObj.signature @@ -4010,7 +4112,7 @@ class ChatPage extends LitElement { isEdited: true } const stringifyMessageObject = JSON.stringify(messageObject) - this.sendMessage(stringifyMessageObject, typeMessage, chatReference) + return this.sendMessage({messageText: stringifyMessageObject, typeMessage, chatReference, isForward: false, isReceipient, _chatId, _publicKey, messageQueue}) } else { const messageObject = { messageText: trimmedMessage, @@ -4025,7 +4127,7 @@ class ChatPage extends LitElement { this.myTrimmedMeassage = stringifyMessageObject this.shadowRoot.getElementById('confirmDialog').open() } else { - this.sendMessage(stringifyMessageObject, typeMessage) + return this.sendMessage({messageText: stringifyMessageObject, typeMessage, chatReference: undefined, isForward: false, isReceipient, _chatId, _publicKey, messageQueue}) } } } catch (error) { @@ -4036,20 +4138,27 @@ class ChatPage extends LitElement { } - async sendMessage(messageText, typeMessage, chatReference, isForward) { - this.isLoading = true + async sendMessage({messageText, typeMessage, chatReference, isForward,isReceipient, _chatId, _publicKey, messageQueue}) { + console.log('2', {messageText, typeMessage, chatReference, isForward,isReceipient, _chatId, _publicKey}) + if(messageQueue){ + console.log('is queueCurrent Queue after before:', [...this.messageQueue]); + this.addToQueue({messageText, typeMessage, chatReference, isForward, isReceipient, _chatId, _publicKey}, messageQueue); + this.resetChatEditor() + return + } + // this.isLoading = true let _reference = new Uint8Array(64) window.crypto.getRandomValues(_reference) let reference = window.parent.Base58.encode(_reference) const sendMessageRequest = async () => { - if (this.isReceipient === true) { + if (isReceipient === true) { let chatResponse = await parentEpml.request('chat', { type: 18, nonce: this.selectedAddress.nonce, params: { timestamp: Date.now(), - recipient: this._chatId, - recipientPublicKey: this._publicKey.key, + recipient: _chatId, + recipientPublicKey: _publicKey.key, hasChatReference: typeMessage === 'edit' ? 1 : 0, chatReference: chatReference, message: messageText, @@ -4059,14 +4168,14 @@ class ChatPage extends LitElement { isText: 1 } }) - _computePow(chatResponse) + return _computePow(chatResponse) } else { let groupResponse = await parentEpml.request('chat', { type: 181, nonce: this.selectedAddress.nonce, params: { timestamp: Date.now(), - groupID: Number(this._chatId), + groupID: Number(_chatId), hasReceipient: 0, hasChatReference: typeMessage === 'edit' ? 1 : 0, chatReference: chatReference, @@ -4077,7 +4186,7 @@ class ChatPage extends LitElement { isText: 1 } }) - _computePow(groupResponse) + return _computePow(groupResponse) } } @@ -4266,11 +4375,12 @@ class ChatPage extends LitElement { }) getSendChatResponse(_response, isForward) + return _response } const getSendChatResponse = (response, isForward, customErrorMessage) => { if (response === true) { - this.resetChatEditor() + // this.resetChatEditor() if (isForward) { let successString = get("blockpage.bcchange15") parentEpml.request('showSnackBar', `${successString}`) @@ -4302,7 +4412,7 @@ class ChatPage extends LitElement { sendForwardRequest() return } - sendMessageRequest() + return sendMessageRequest() } /** diff --git a/plugins/plugins/core/components/ChatScroller-css.js b/plugins/plugins/core/components/ChatScroller-css.js index d2ac074a..378ccac8 100644 --- a/plugins/plugins/core/components/ChatScroller-css.js +++ b/plugins/plugins/core/components/ChatScroller-css.js @@ -43,6 +43,10 @@ export const chatStyles = css` margin: 0; padding: 20px 17px; } + .message-sending { + opacity: 0.5; + cursor: progress; +} .chat-list { overflow-y: auto; diff --git a/plugins/plugins/core/components/ChatScroller.js b/plugins/plugins/core/components/ChatScroller.js index d5a78398..a206d633 100644 --- a/plugins/plugins/core/components/ChatScroller.js +++ b/plugins/plugins/core/components/ChatScroller.js @@ -245,7 +245,8 @@ class ChatScroller extends LitElement { clearUpdateMessageHashmap: { attribute: false}, disableFetching: {type: Boolean}, isLoadingBefore: {type: Boolean}, - isLoadingAfter: {type: Boolean} + isLoadingAfter: {type: Boolean}, + messageQueue: {type: Array} } } @@ -279,6 +280,7 @@ class ChatScroller extends LitElement { this.isLoadingAfter = false this.disableAddingNewMessages = false this.lastReadMessageTimestamp = null + this.messageQueue = [] } addSeenMessage(val) { @@ -614,6 +616,7 @@ class ChatScroller extends LitElement { render() { + console.log('this.chatId', this.chatId) // let formattedMessages = this.messages.reduce((messageArray, message) => { // const currentMessage = this.updateMessageHash[message.signature] || message; // const lastGroupedMessage = messageArray[messageArray.length - 1]; @@ -698,17 +701,67 @@ class ChatScroller extends LitElement { ` )} + ` )} + + ${this.messageQueue.filter((item )=> this.chatId.includes(item._chatId)).length > 0 ? html` +
+ ` : html`
+ `} +
+ ${this.isLoadingAfter ? html`
` : ''} + ${repeat( + this.messageQueue.filter((item )=> this.chatId.includes(item._chatId)), + (message) => message.messageText, + (message, indexMessage) => html` + + this.setOpenPrivateMessage(val)} + .setOpenTipUser=${(val) => this.setOpenTipUser(val)} + .setOpenUserInfo=${(val) => this.setOpenUserInfo(val)} + .setUserName=${(val) => this.setUserName(val)} + id=${message.signature} + .goToRepliedMessage=${(val, val2)=> this.goToRepliedMessageFunc(val, val2)} + .addSeenMessage=${(val) => this.addSeenMessage(val)} + .listSeenMessages=${this.listSeenMessages} + chatId=${this.chatId} + ?isInProgress=${true} + > + + ` + + )} ` } @@ -741,6 +794,9 @@ class ChatScroller extends LitElement { if(changedProperties.has('isLoadingAfter')){ return true } + if(changedProperties.has('messageQueue')){ + return true + } // Only update element if prop1 changed. return changedProperties.has('messages') } @@ -820,7 +876,7 @@ class ChatScroller extends LitElement { _upObserverhandler(entries) { - + if(!entries[0].target || !entries[0].target.nextElementSibling) return if (entries[0].isIntersecting) { if (this.disableFetching) { return @@ -844,7 +900,7 @@ class ChatScroller extends LitElement { if (this.messagesToRender.length === 0 || this.disableFetching) { return } - if (!entries[0].isIntersecting) { + if (!entries[0].isIntersecting || !entries[0].target || !entries[0].target.previousElementSibling) { } else { this.disableFetching = true this.isLoadingAfter = true @@ -926,7 +982,8 @@ class MessageTemplate extends LitElement { goToRepliedMessage: { attribute: false }, listSeenMessages: { type: Array }, addSeenMessage: { attribute: false }, - chatId: { type: String } + chatId: { type: String }, + isInProgress: {type: Boolean} } } @@ -947,6 +1004,7 @@ class MessageTemplate extends LitElement { this.isSingleMessageInGroup = false this.isLastMessageInGroup = false this.viewImage = false + this.isInProgress = false } static get styles() { @@ -1059,6 +1117,7 @@ class MessageTemplate extends LitElement { let isEdited = false let attachment = null try { + console.log('this.messageOb', this.messageObj ) const parsedMessageObj = JSON.parse(this.messageObj.decodedMessage) if (+parsedMessageObj.version > 1 && parsedMessageObj.messageText) { messageVersion2 = generateHTML(parsedMessageObj.messageText, [ @@ -1250,7 +1309,7 @@ class MessageTemplate extends LitElement {
-
+
${(this.isSingleMessageInGroup === false || (this.isSingleMessageInGroup === true && this.isLastMessageInGroup === true)) ? ( @@ -1476,7 +1535,12 @@ class MessageTemplate extends LitElement { ` : '' } + ${this.isInProgress ? html` +

Sending...

+ ` : html` + `} +
@@ -1518,7 +1582,7 @@ class MessageTemplate extends LitElement { editedMessageObj: this.messageObj, reaction: reaction.type, })} - id=${`reactions-${index}`} + id=${`reactions-${indexMessageTemplate}`} class="reactions-bg"> ${reaction.type} ${reaction.qty} @@ -1530,7 +1594,7 @@ class MessageTemplate extends LitElement { text=${reaction.users.length > 3 ? ( `${reaction.users[0].name - ? reaction.users[0].name + ? reaction.users[0].nameMessageTemplate : cropAddress(reaction.users[0].address)}, ${reaction.users[1].name ? reaction.users[1].name @@ -1558,7 +1622,7 @@ class MessageTemplate extends LitElement { : cropAddress(reaction.users[0].address)} ${get("chatpage.cchange71")} ${reaction.users[1].name - ? reaction.users[1].name + ? reaction.users[1].namMessageTemplatee : cropAddress(reaction.users[1].address)} ${get("chatpage.cchange74")} ${reaction.type}` ) : reaction.users.length === 1 ? ( @@ -1585,7 +1649,7 @@ class MessageTemplate extends LitElement { toblockaddress=${this.messageObj.sender} > - { @@ -1600,7 +1664,7 @@ class MessageTemplate extends LitElement { dialogAction="cancel" class="red" @click=${() => { - +MessageTemplate this.openDialogImage = false }} > @@ -1612,7 +1676,7 @@ class MessageTemplate extends LitElement { ?open=${this.openDialogGif} @closed=${() => { this.openDialogGif = false - }}> + }}>MessageTemplate
${gifHTMLDialog} @@ -1627,7 +1691,7 @@ class MessageTemplate extends LitElement { }} > ${translate("general.close")} - + MessageTemplate 60 && this.shadowRoot.querySelector(".chat-editor").contentDocument.body.querySelector("#chatbarId").innerHTML.trim() !== "") { diff --git a/plugins/plugins/core/components/ChatTextEditor.js b/plugins/plugins/core/components/ChatTextEditor.js index b13dc72f..ba2b4c67 100644 --- a/plugins/plugins/core/components/ChatTextEditor.js +++ b/plugins/plugins/core/components/ChatTextEditor.js @@ -36,7 +36,8 @@ class ChatTextEditor extends LitElement { isEnabledChatEnter: {type: Boolean}, openGifModal: { type: Boolean }, setOpenGifModal: { attribute: false }, - chatId: {type: String} + chatId: {type: String}, + messageQueue: {type: Array} } } @@ -376,9 +377,12 @@ mwc-checkbox::shadow .mdc-checkbox::after, mwc-checkbox::shadow .mdc-checkbox::b this.userName = window.parent.reduxStore.getState().app.accountInfo.names[0] this.theme = localStorage.getItem('qortalTheme') ? localStorage.getItem('qortalTheme') : 'light' this.editor = null + this.messageQueue = [] + } render() { + console.log('at render', this.messageQueue) return html`
{ - this.sendMessageFunc(); + console.log('onclick', this.messageQueue) + this.sendMessageFunc(this.messageQueue); }} > @@ -538,7 +543,8 @@ mwc-checkbox::shadow .mdc-checkbox::after, mwc-checkbox::shadow .mdc-checkbox::b alt="send-icon" class="send-icon" @click=${() => { - this.sendMessageFunc(); + console.log('onclick', this.messageQueue) + this.sendMessageFunc(this.messageQueue); }} /> ` : @@ -641,6 +647,7 @@ mwc-checkbox::shadow .mdc-checkbox::after, mwc-checkbox::shadow .mdc-checkbox::b } sendMessageFunc(props) { + console.log({props}) if(this.editor.isEmpty && (this.iframeId !== 'newChat' && this.iframeId !== 'newAttachmentChat')) return this.getMessageSize(this.editor.getJSON()) if (this.chatMessageSize > 4000 ) { @@ -648,7 +655,8 @@ mwc-checkbox::shadow .mdc-checkbox::after, mwc-checkbox::shadow .mdc-checkbox::b return; } this.chatMessageSize = 0; - this._sendMessage(props, this.editor.getJSON()); + console.log('messageQueue', this.messageQueue) + this._sendMessage(props, this.editor.getJSON(), this.messageQueue); } getMessageSize(message){ From 60251f9f72e24438d912055645fe1eac1fe30c01 Mon Sep 17 00:00:00 2001 From: PhilReact Date: Wed, 13 Sep 2023 23:56:05 -0500 Subject: [PATCH 19/57] finished pending messages --- core/language/us.json | 4 +- plugins/plugins/core/components/ChatPage.js | 216 ++++++++---------- .../plugins/core/components/ChatScroller.js | 22 +- .../plugins/core/components/ChatTextEditor.js | 5 - .../components/webworkerDecodeMessages.js | 11 +- .../chain-messaging/chain-messaging.js | 6 +- .../core/messaging/q-chat/q-chat.src.js | 11 +- 7 files changed, 131 insertions(+), 144 deletions(-) diff --git a/core/language/us.json b/core/language/us.json index 64afa585..8a272e57 100644 --- a/core/language/us.json +++ b/core/language/us.json @@ -828,7 +828,9 @@ "cchange80": "This image has been deleted", "cchange81": "This image type is not supported", "cchange82": "This attachment has been deleted", - "cchange90": "No messages" + "cchange90": "No messages", + "cchange91": "Sending...", + "cchange92": "Unread messages below" }, "welcomepage": { "wcchange1": "Welcome to Q-Chat", diff --git a/plugins/plugins/core/components/ChatPage.js b/plugins/plugins/core/components/ChatPage.js index 66f1ada6..45d9867c 100644 --- a/plugins/plugins/core/components/ChatPage.js +++ b/plugins/plugins/core/components/ChatPage.js @@ -1423,11 +1423,9 @@ class ChatPage extends LitElement { } addToQueue(outSideMsg, messageQueue) { - console.log('added to queue', messageQueue, this.messageQueue) // Push the new message object to the queue this.messageQueue = [...messageQueue, { ...outSideMsg, timestamp: Date.now()}]; - console.log('Current Queue after adding:', [...this.messageQueue]); // Start processing the queue only if the message we just added is the only one in the queue // This ensures that the queue processing starts only once, even if this method is called multiple times @@ -1442,10 +1440,8 @@ class ChatPage extends LitElement { async processQueue() { if (this.messageQueue.length === 0) return; const currentMessage = this.messageQueue[0]; - console.log({currentMessage}) try { const res = await this.sendMessage(currentMessage); - console.log({res}) if(res === true) { this.messageQueue = this.messageQueue.slice(1); } else { @@ -1461,44 +1457,36 @@ class ChatPage extends LitElement { setTimeout(() => this.processQueue(), 10000); // Wait for 10 seconds before retrying } } - // async processQueue() { - // let inProcess = false; // to indicate if we're currently sending a message - - // while (true) { // Infinite loop - // const currentMessage = this.messageQueue[0]; - - // // If there's no current message, you could wait a while (e.g., a second) before the next iteration. - // // This prevents the loop from consuming resources unnecessarily when the queue is empty. - // if (!currentMessage) { - // await new Promise(res => setTimeout(res, 1000)); // Wait for 1 second - // continue; // go to the next iteration of the loop - // } - - // // If not currently processing another message, attempt to send the current one - // if (!inProcess) { - // inProcess = true; // indicate that we're starting to process a message - - // try { - // await this._sendMessage(currentMessage.outSideMsg, currentMessage.msg, true, currentMessage.chatId, currentMessage.isReceipient, currentMessage._publicKey, currentMessage.attachment); - - // // If successful, remove the processed message from the queue - // this.messageQueue = this.messageQueue.slice(1); - // inProcess = false; // set inProcess back to false since we're done with this message - // } catch (error) { - // console.error("Failed to send message:", error); - // inProcess = false; // set inProcess back to false since an error occurred - // await new Promise(res => setTimeout(res, 10000)); // Wait for 10 seconds before retrying - // } - // } else { - // await new Promise(res => setTimeout(res, 1000)); // Wait for 1 second before checking again if inProcess is false - // } - // } - // } - + + async getLastestMessages(){ + try { + let getInitialMessages = [] + if (this.isReceipient) { + + + + getInitialMessages = await parentEpml.request('apiCall', { + type: 'api', + url: `/chat/messages?involving=${window.parent.reduxStore.getState().app.selectedAddress.address}&involving=${this._chatId}&limit=${chatLimit}&reverse=true&haschatreference=false&encoding=BASE64` + }) + + this.processMessages(getInitialMessages, true, isUnread) + + } else { + getInitialMessages = await parentEpml.request('apiCall', { + type: 'api', + url: `/chat/messages?txGroupId=${Number(this._chatId)}&limit=${chatLimit}&reverse=true&haschatreference=false&encoding=BASE64` + }) + + } + this.processMessages(getInitialMessages, true, false) + } catch (error) { + console.log + } + } render() { - console.log('this.messageQueue', this.messageQueue.length) return html`
@@ -1552,10 +1540,16 @@ class ChatPage extends LitElement { class='last-message-ref' style=${(this.lastMessageRefVisible && !this.imageFile && !this.openGifModal) ? 'opacity: 1;' : 'opacity: 0;'}> { - this.shadowRoot.querySelector("chat-scroller").shadowRoot.getElementById("downObserver") + const chatScrollerElement = this.shadowRoot.querySelector('chat-scroller'); + if (chatScrollerElement && chatScrollerElement.disableAddingNewMessages) { + this.getLastestMessages() + } else { + this.shadowRoot.querySelector("chat-scroller").shadowRoot.getElementById("downObserver") .scrollIntoView({ behavior: 'smooth', }); + } + }}>
@@ -1987,10 +1981,19 @@ class ChatPage extends LitElement { async connectedCallback() { super.connectedCallback() await this.initUpdate() - this.webWorker = new WebWorker() - this.webWorkerFile = new WebWorkerFile() - this.webWorkerSortMessages = new WebWorkerSortMessages() - this.webWorkerDecodeMessages = new WebWorkerDecodeMessages() + if(!this.webWorker){ + this.webWorker = new WebWorker() + } + if(!this.webWorkerFile){ + this.webWorkerFile = new WebWorkerFile() + } + if(!this.webWorkerSortMessages){ + this.webWorkerSortMessages = new WebWorkerSortMessages() + + } + if(!this.webWorkerDecodeMessages){ + this.webWorkerDecodeMessages = new WebWorkerDecodeMessages() + } await this.getUpdateCompleteTextEditor() const elementChatId = this.shadowRoot.getElementById('_chatEditorDOM').shadowRoot.getElementById('_chatEditorDOM') @@ -2093,43 +2096,6 @@ class ChatPage extends LitElement { document.addEventListener('keydown', this.initialChat) document.addEventListener('paste', this.pasteImage) - - // if (this.chatId) { - // window.parent.reduxStore.dispatch(window.parent.reduxAction.addChatLastSeen({ - // key: this.chatId, - // timestamp: Date.now() - // })) - // } - - // let callback = (entries, observer) => { - // entries.forEach(entry => { - // if (entry.isIntersecting) { - - // this.isPageVisible = true - // if (this.chatId) { - // window.parent.reduxStore.dispatch(window.parent.reduxAction.addChatLastSeen({ - // key: this.chatId, - // timestamp: Date.now() - // })) - - // } - // } else { - // this.isPageVisible = false - // } - // }) - // } - - // let options = { - // root: null, - // rootMargin: '0px', - // threshold: 0.5 - // } - - // // Create the observer with the callback function and options - // this.observer = new IntersectionObserver(callback, options) - // const mainContainer = this.shadowRoot.querySelector('.main-container') - - // this.observer.observe(mainContainer) } disconnectedCallback() { @@ -2338,7 +2304,7 @@ class ChatPage extends LitElement { } delete message.reactions const stringifyMessageObject = JSON.stringify(message) - this.sendMessage(stringifyMessageObject, undefined, '', true) + this.sendMessage({messageText: stringifyMessageObject, chatReference: undefined, isForward: true}) } catch (error) { } } @@ -2377,6 +2343,10 @@ class ChatPage extends LitElement { } async initUpdate() { + if (this.webSocket) { + this.webSocket.close(1000, 'switch chat') + this.webSocket = '' + } this.pageNumber = 1 const getAddressPublicKey = () => { @@ -2534,12 +2504,21 @@ class ChatPage extends LitElement { if (this.isLoading === false && this.currentEditor === '_chatEditorDOM' && this.editor && this.editor.setEditable) { this.editor.setEditable(true) } + } + if(changedProperties && changedProperties.has('chatId')){ + this.isLoadingMessages = true + this.initUpdate() + + + } } shouldUpdate(changedProperties) { - console.log({changedProperties}) + if(changedProperties.has('chatId')){ + return true + } if (changedProperties.has('setActiveChatHeadUrl')) { return false } @@ -3575,22 +3554,13 @@ class ChatPage extends LitElement { async _sendMessage(outSideMsg, msg, messageQueue) { const _chatId= this._chatId const isReceipient= this.isReceipient - const _publicKey= this._publicKey + let _publicKey= this._publicKey const attachment= this.attachment - console.log({ - _chatId, isReceipient, _publicKey, attachment - }) - // if(messageQueue){ - // console.log('is queueCurrent Queue after before:', [...this.messageQueue]); - // this.addToQueue(outSideMsg, msg, messageQueue); - - // return - // } - + try { if (this.isReceipient) { let hasPublicKey = true - if (!publicKey.hasPubKey) { + if (!_publicKey.hasPubKey) { hasPublicKey = false try { const res = await parentEpml.request('apiCall', { @@ -3598,20 +3568,20 @@ class ChatPage extends LitElement { url: `/addresses/publickey/${this.selectedAddress.address}` }) if (res.error === 102) { - publicKey.key = '' - publicKey.hasPubKey = false + _publicKey.key = '' + _publicKey.hasPubKey = false } else if (res !== false) { - publicKey.key = res - publicKey.hasPubKey = true + _publicKey.key = res + _publicKey.hasPubKey = true hasPublicKey = true } else { - publicKey.key = '' - publicKey.hasPubKey = false + _publicKey.key = '' + _publicKey.hasPubKey = false } } catch (error) { } - if (!hasPublicKey || !publicKey.hasPubKey) { + if (!hasPublicKey || !_publicKey.hasPubKey) { let err4string = get("chatpage.cchange39") parentEpml.request('showSnackBar', `${err4string}`) return @@ -4139,14 +4109,18 @@ class ChatPage extends LitElement { } async sendMessage({messageText, typeMessage, chatReference, isForward,isReceipient, _chatId, _publicKey, messageQueue}) { - console.log('2', {messageText, typeMessage, chatReference, isForward,isReceipient, _chatId, _publicKey}) + if(messageQueue){ - console.log('is queueCurrent Queue after before:', [...this.messageQueue]); this.addToQueue({messageText, typeMessage, chatReference, isForward, isReceipient, _chatId, _publicKey}, messageQueue); this.resetChatEditor() + this.closeEditMessageContainer() + this.closeRepliedToContainer() return } - // this.isLoading = true + if(isForward){ + this.isLoading = true + } + let _reference = new Uint8Array(64) window.crypto.getRandomValues(_reference) let reference = window.parent.Base58.encode(_reference) @@ -4384,21 +4358,31 @@ class ChatPage extends LitElement { if (isForward) { let successString = get("blockpage.bcchange15") parentEpml.request('showSnackBar', `${successString}`) + this.resetChatEditor() + this.closeEditMessageContainer() + this.closeRepliedToContainer() + this.openForwardOpen = false + this.forwardActiveChatHeadUrl = { + url: "", + name: "", + selected: false + } + this.isLoading = false } - this.closeEditMessageContainer() - this.closeRepliedToContainer() - this.openForwardOpen = false - this.forwardActiveChatHeadUrl = { - url: "", - name: "", - selected: false - } + // this.closeEditMessageContainer() + // this.closeRepliedToContainer() + // this.openForwardOpen = false + // this.forwardActiveChatHeadUrl = { + // url: "", + // name: "", + // selected: false + // } } else if (response.error) { - parentEpml.request('showSnackBar', response.message) + // parentEpml.request('showSnackBar', response.message) } else { - let err2string = get("chatpage.cchange21") - parentEpml.request('showSnackBar', `${customErrorMessage || err2string}`) + // let err2string = get("chatpage.cchange21") + // parentEpml.request('showSnackBar', `${customErrorMessage || err2string}`) } if (isForward && response !== true) { this.isLoading = false diff --git a/plugins/plugins/core/components/ChatScroller.js b/plugins/plugins/core/components/ChatScroller.js index a206d633..169e5137 100644 --- a/plugins/plugins/core/components/ChatScroller.js +++ b/plugins/plugins/core/components/ChatScroller.js @@ -412,7 +412,7 @@ class ChatScroller extends LitElement { previousScrollHeight = viewElement.scrollHeight; - const copy = [...this.messagesToRender] + const copy = type === 'initial' ? [] : [...this.messagesToRender] for (const newMessage of newMessages) { const lastGroupedMessage = copy[copy.length - 1]; @@ -458,6 +458,8 @@ class ChatScroller extends LitElement { } + + this.clearLoaders() } @@ -603,6 +605,18 @@ class ChatScroller extends LitElement { if (changedProperties && changedProperties.has('updateMessageHash') && Object.keys(this.updateMessageHash).length > 0) { this.replaceMessagesWithUpdate(this.updateMessageHash) } + if (changedProperties && changedProperties.has('messageQueue') && Object.keys(this.messageQueue).length > 0) { + if(!this.disableAddingNewMessages){ + await new Promise((res)=> { + setTimeout(()=> { + res() + }, 200) + }) + const viewElement = this.shadowRoot.querySelector("#viewElement"); + viewElement.scrollTop = viewElement.scrollHeight + 200 + } + } + } @@ -616,7 +630,6 @@ class ChatScroller extends LitElement { render() { - console.log('this.chatId', this.chatId) // let formattedMessages = this.messages.reduce((messageArray, message) => { // const currentMessage = this.updateMessageHash[message.signature] || message; // const lastGroupedMessage = messageArray[messageArray.length - 1]; @@ -696,7 +709,7 @@ class ChatScroller extends LitElement { .listSeenMessages=${this.listSeenMessages} chatId=${this.chatId} > - ${message.isDivider ? html`
Unread Messages Below
` : null} + ${message.isDivider ? html`
${translate('chatpage.cchange92')}
` : null} ` @@ -1117,7 +1130,6 @@ class MessageTemplate extends LitElement { let isEdited = false let attachment = null try { - console.log('this.messageOb', this.messageObj ) const parsedMessageObj = JSON.parse(this.messageObj.decodedMessage) if (+parsedMessageObj.version > 1 && parsedMessageObj.messageText) { messageVersion2 = generateHTML(parsedMessageObj.messageText, [ @@ -1536,7 +1548,7 @@ class MessageTemplate extends LitElement { : '' } ${this.isInProgress ? html` -

Sending...

+

${translate('chatpage.cchange91')}

` : html` `} diff --git a/plugins/plugins/core/components/ChatTextEditor.js b/plugins/plugins/core/components/ChatTextEditor.js index ba2b4c67..fac14a5a 100644 --- a/plugins/plugins/core/components/ChatTextEditor.js +++ b/plugins/plugins/core/components/ChatTextEditor.js @@ -382,7 +382,6 @@ mwc-checkbox::shadow .mdc-checkbox::after, mwc-checkbox::shadow .mdc-checkbox::b } render() { - console.log('at render', this.messageQueue) return html`
{ - console.log('onclick', this.messageQueue) this.sendMessageFunc(this.messageQueue); }} > @@ -543,7 +541,6 @@ mwc-checkbox::shadow .mdc-checkbox::after, mwc-checkbox::shadow .mdc-checkbox::b alt="send-icon" class="send-icon" @click=${() => { - console.log('onclick', this.messageQueue) this.sendMessageFunc(this.messageQueue); }} /> @@ -647,7 +644,6 @@ mwc-checkbox::shadow .mdc-checkbox::after, mwc-checkbox::shadow .mdc-checkbox::b } sendMessageFunc(props) { - console.log({props}) if(this.editor.isEmpty && (this.iframeId !== 'newChat' && this.iframeId !== 'newAttachmentChat')) return this.getMessageSize(this.editor.getJSON()) if (this.chatMessageSize > 4000 ) { @@ -655,7 +651,6 @@ mwc-checkbox::shadow .mdc-checkbox::after, mwc-checkbox::shadow .mdc-checkbox::b return; } this.chatMessageSize = 0; - console.log('messageQueue', this.messageQueue) this._sendMessage(props, this.editor.getJSON(), this.messageQueue); } diff --git a/plugins/plugins/core/components/webworkerDecodeMessages.js b/plugins/plugins/core/components/webworkerDecodeMessages.js index 794be632..c1fb5cb7 100644 --- a/plugins/plugins/core/components/webworkerDecodeMessages.js +++ b/plugins/plugins/core/components/webworkerDecodeMessages.js @@ -2465,7 +2465,6 @@ class Base58 { } decode(string) { - console.log({string}) if (string.length === 0) { return new Uint8Array(0); } @@ -2768,12 +2767,7 @@ export const decryptChatMessageBase64 = ( recipientPublicKey, lastReference ) => { - console.log('1', { - encryptedMessage, - privateKey, - recipientPublicKey, - lastReference - }) + let _encryptedMessage = atob(encryptedMessage); const binaryLength = _encryptedMessage.length; const bytes = new Uint8Array(binaryLength); @@ -2794,9 +2788,7 @@ export const decryptChatMessageBase64 = ( _base58RecipientPublic instanceof Uint8Array ? base58Instant.encode(_base58RecipientPublic) : _base58RecipientPublic; - console.log({_base58RecipientPublicKey}) const _recipientPublicKey = base58Instant.decode(_base58RecipientPublicKey); - console.log({_recipientPublicKey}) const _lastReference = lastReference instanceof Uint8Array ? lastReference @@ -2851,7 +2843,6 @@ const decodeMessage = ( _publicKeyVar.hasPubKey === true && encodedMessageObj.data ) { - console.log('hello encrypt') let decodedMessage = decryptChatMessageBase64( encodedMessageObj.data, privateKey, diff --git a/plugins/plugins/core/messaging/chain-messaging/chain-messaging.js b/plugins/plugins/core/messaging/chain-messaging/chain-messaging.js index ab09e3f6..69d972b6 100644 --- a/plugins/plugins/core/messaging/chain-messaging/chain-messaging.js +++ b/plugins/plugins/core/messaging/chain-messaging/chain-messaging.js @@ -1,4 +1,4 @@ -!function(t){"function"==typeof define&&define.amd?define(t):t()}((function(){"use strict";const t=window,e=t.ShadowRoot&&(void 0===t.ShadyCSS||t.ShadyCSS.nativeShadow)&&"adoptedStyleSheets"in Document.prototype&&"replace"in CSSStyleSheet.prototype,i=Symbol(),s=new WeakMap;let n=class{constructor(t,e,s){if(this._$cssResult$=!0,s!==i)throw Error("CSSResult is not constructable. Use `unsafeCSS` or `css` instead.");this.cssText=t,this.t=e}get styleSheet(){let t=this.o;const i=this.t;if(e&&void 0===t){const e=void 0!==i&&1===i.length;e&&(t=s.get(i)),void 0===t&&((this.o=t=new CSSStyleSheet).replaceSync(this.cssText),e&&s.set(i,t))}return t}toString(){return this.cssText}};const r=(t,...e)=>{const s=1===t.length?t[0]:e.reduce(((e,i,s)=>e+(t=>{if(!0===t._$cssResult$)return t.cssText;if("number"==typeof t)return t;throw Error("Value passed to 'css' function must be a 'css' function result: "+t+". Use 'unsafeCSS' to pass non-literal values, but take care to ensure page security.")})(i)+t[s+1]),t[0]);return new n(s,t,i)},o=e?t=>t:t=>t instanceof CSSStyleSheet?(t=>{let e="";for(const i of t.cssRules)e+=i.cssText;return(t=>new n("string"==typeof t?t:t+"",void 0,i))(e)})(t):t;var l;const h=window,a=h.trustedTypes,d=a?a.emptyScript:"",c=h.reactiveElementPolyfillSupport,u={toAttribute(t,e){switch(e){case Boolean:t=t?d:null;break;case Object:case Array:t=null==t?t:JSON.stringify(t)}return t},fromAttribute(t,e){let i=t;switch(e){case Boolean:i=null!==t;break;case Number:i=null===t?null:Number(t);break;case Object:case Array:try{i=JSON.parse(t)}catch(t){i=null}}return i}},p=(t,e)=>e!==t&&(e==e||t==t),v={attribute:!0,type:String,converter:u,reflect:!1,hasChanged:p};let $=class extends HTMLElement{constructor(){super(),this._$Ei=new Map,this.isUpdatePending=!1,this.hasUpdated=!1,this._$El=null,this.u()}static addInitializer(t){var e;this.finalize(),(null!==(e=this.h)&&void 0!==e?e:this.h=[]).push(t)}static get observedAttributes(){this.finalize();const t=[];return this.elementProperties.forEach(((e,i)=>{const s=this._$Ep(i,e);void 0!==s&&(this._$Ev.set(s,i),t.push(s))})),t}static createProperty(t,e=v){if(e.state&&(e.attribute=!1),this.finalize(),this.elementProperties.set(t,e),!e.noAccessor&&!this.prototype.hasOwnProperty(t)){const i="symbol"==typeof t?Symbol():"__"+t,s=this.getPropertyDescriptor(t,i,e);void 0!==s&&Object.defineProperty(this.prototype,t,s)}}static getPropertyDescriptor(t,e,i){return{get(){return this[e]},set(s){const n=this[t];this[e]=s,this.requestUpdate(t,n,i)},configurable:!0,enumerable:!0}}static getPropertyOptions(t){return this.elementProperties.get(t)||v}static finalize(){if(this.hasOwnProperty("finalized"))return!1;this.finalized=!0;const t=Object.getPrototypeOf(this);if(t.finalize(),void 0!==t.h&&(this.h=[...t.h]),this.elementProperties=new Map(t.elementProperties),this._$Ev=new Map,this.hasOwnProperty("properties")){const t=this.properties,e=[...Object.getOwnPropertyNames(t),...Object.getOwnPropertySymbols(t)];for(const i of e)this.createProperty(i,t[i])}return this.elementStyles=this.finalizeStyles(this.styles),!0}static finalizeStyles(t){const e=[];if(Array.isArray(t)){const i=new Set(t.flat(1/0).reverse());for(const t of i)e.unshift(o(t))}else void 0!==t&&e.push(o(t));return e}static _$Ep(t,e){const i=e.attribute;return!1===i?void 0:"string"==typeof i?i:"string"==typeof t?t.toLowerCase():void 0}u(){var t;this._$E_=new Promise((t=>this.enableUpdating=t)),this._$AL=new Map,this._$Eg(),this.requestUpdate(),null===(t=this.constructor.h)||void 0===t||t.forEach((t=>t(this)))}addController(t){var e,i;(null!==(e=this._$ES)&&void 0!==e?e:this._$ES=[]).push(t),void 0!==this.renderRoot&&this.isConnected&&(null===(i=t.hostConnected)||void 0===i||i.call(t))}removeController(t){var e;null===(e=this._$ES)||void 0===e||e.splice(this._$ES.indexOf(t)>>>0,1)}_$Eg(){this.constructor.elementProperties.forEach(((t,e)=>{this.hasOwnProperty(e)&&(this._$Ei.set(e,this[e]),delete this[e])}))}createRenderRoot(){var i;const s=null!==(i=this.shadowRoot)&&void 0!==i?i:this.attachShadow(this.constructor.shadowRootOptions);return((i,s)=>{e?i.adoptedStyleSheets=s.map((t=>t instanceof CSSStyleSheet?t:t.styleSheet)):s.forEach((e=>{const s=document.createElement("style"),n=t.litNonce;void 0!==n&&s.setAttribute("nonce",n),s.textContent=e.cssText,i.appendChild(s)}))})(s,this.constructor.elementStyles),s}connectedCallback(){var t;void 0===this.renderRoot&&(this.renderRoot=this.createRenderRoot()),this.enableUpdating(!0),null===(t=this._$ES)||void 0===t||t.forEach((t=>{var e;return null===(e=t.hostConnected)||void 0===e?void 0:e.call(t)}))}enableUpdating(t){}disconnectedCallback(){var t;null===(t=this._$ES)||void 0===t||t.forEach((t=>{var e;return null===(e=t.hostDisconnected)||void 0===e?void 0:e.call(t)}))}attributeChangedCallback(t,e,i){this._$AK(t,i)}_$EO(t,e,i=v){var s;const n=this.constructor._$Ep(t,i);if(void 0!==n&&!0===i.reflect){const r=(void 0!==(null===(s=i.converter)||void 0===s?void 0:s.toAttribute)?i.converter:u).toAttribute(e,i.type);this._$El=t,null==r?this.removeAttribute(n):this.setAttribute(n,r),this._$El=null}}_$AK(t,e){var i;const s=this.constructor,n=s._$Ev.get(t);if(void 0!==n&&this._$El!==n){const t=s.getPropertyOptions(n),r="function"==typeof t.converter?{fromAttribute:t.converter}:void 0!==(null===(i=t.converter)||void 0===i?void 0:i.fromAttribute)?t.converter:u;this._$El=n,this[n]=r.fromAttribute(e,t.type),this._$El=null}}requestUpdate(t,e,i){let s=!0;void 0!==t&&(((i=i||this.constructor.getPropertyOptions(t)).hasChanged||p)(this[t],e)?(this._$AL.has(t)||this._$AL.set(t,e),!0===i.reflect&&this._$El!==t&&(void 0===this._$EC&&(this._$EC=new Map),this._$EC.set(t,i))):s=!1),!this.isUpdatePending&&s&&(this._$E_=this._$Ej())}async _$Ej(){this.isUpdatePending=!0;try{await this._$E_}catch(t){Promise.reject(t)}const t=this.scheduleUpdate();return null!=t&&await t,!this.isUpdatePending}scheduleUpdate(){return this.performUpdate()}performUpdate(){var t;if(!this.isUpdatePending)return;this.hasUpdated,this._$Ei&&(this._$Ei.forEach(((t,e)=>this[e]=t)),this._$Ei=void 0);let e=!1;const i=this._$AL;try{e=this.shouldUpdate(i),e?(this.willUpdate(i),null===(t=this._$ES)||void 0===t||t.forEach((t=>{var e;return null===(e=t.hostUpdate)||void 0===e?void 0:e.call(t)})),this.update(i)):this._$Ek()}catch(t){throw e=!1,this._$Ek(),t}e&&this._$AE(i)}willUpdate(t){}_$AE(t){var e;null===(e=this._$ES)||void 0===e||e.forEach((t=>{var e;return null===(e=t.hostUpdated)||void 0===e?void 0:e.call(t)})),this.hasUpdated||(this.hasUpdated=!0,this.firstUpdated(t)),this.updated(t)}_$Ek(){this._$AL=new Map,this.isUpdatePending=!1}get updateComplete(){return this.getUpdateComplete()}getUpdateComplete(){return this._$E_}shouldUpdate(t){return!0}update(t){void 0!==this._$EC&&(this._$EC.forEach(((t,e)=>this._$EO(e,this[e],t))),this._$EC=void 0),this._$Ek()}updated(t){}firstUpdated(t){}};var _;$.finalized=!0,$.elementProperties=new Map,$.elementStyles=[],$.shadowRootOptions={mode:"open"},null==c||c({ReactiveElement:$}),(null!==(l=h.reactiveElementVersions)&&void 0!==l?l:h.reactiveElementVersions=[]).push("1.6.1");const f=window,m=f.trustedTypes,g=m?m.createPolicy("lit-html",{createHTML:t=>t}):void 0,A="$lit$",y=`lit$${(Math.random()+"").slice(9)}$`,E="?"+y,S=`<${E}>`,b=document,w=()=>b.createComment(""),C=t=>null===t||"object"!=typeof t&&"function"!=typeof t,x=Array.isArray,U="[ \t\n\f\r]",P=/<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g,H=/-->/g,T=/>/g,N=RegExp(`>|${U}(?:([^\\s"'>=/]+)(${U}*=${U}*(?:[^ \t\n\f\r"'\`<>=]|("|')|))|$)`,"g"),k=/'/g,O=/"/g,R=/^(?:script|style|textarea|title)$/i,M=(t=>(e,...i)=>({_$litType$:t,strings:e,values:i}))(1),L=Symbol.for("lit-noChange"),I=Symbol.for("lit-nothing"),j=new WeakMap,z=b.createTreeWalker(b,129,null,!1),B=(t,e)=>{const i=t.length-1,s=[];let n,r=2===e?"":"",o=P;for(let e=0;e"===h[0]?(o=null!=n?n:P,a=-1):void 0===h[1]?a=-2:(a=o.lastIndex-h[2].length,l=h[1],o=void 0===h[3]?N:'"'===h[3]?O:k):o===O||o===k?o=N:o===H||o===T?o=P:(o=N,n=void 0);const c=o===N&&t[e+1].startsWith("/>")?" ":"";r+=o===P?i+S:a>=0?(s.push(l),i.slice(0,a)+A+i.slice(a)+y+c):i+y+(-2===a?(s.push(void 0),e):c)}const l=r+(t[i]||"")+(2===e?"":"");if(!Array.isArray(t)||!t.hasOwnProperty("raw"))throw Error("invalid template strings array");return[void 0!==g?g.createHTML(l):l,s]};class D{constructor({strings:t,_$litType$:e},i){let s;this.parts=[];let n=0,r=0;const o=t.length-1,l=this.parts,[h,a]=B(t,e);if(this.el=D.createElement(h,i),z.currentNode=this.el.content,2===e){const t=this.el.content,e=t.firstChild;e.remove(),t.append(...e.childNodes)}for(;null!==(s=z.nextNode())&&l.length0){s.textContent=m?m.emptyScript:"";for(let i=0;ix(t)||"function"==typeof(null==t?void 0:t[Symbol.iterator]))(t)?this.T(t):this._(t)}k(t){return this._$AA.parentNode.insertBefore(t,this._$AB)}$(t){this._$AH!==t&&(this._$AR(),this._$AH=this.k(t))}_(t){this._$AH!==I&&C(this._$AH)?this._$AA.nextSibling.data=t:this.$(b.createTextNode(t)),this._$AH=t}g(t){var e;const{values:i,_$litType$:s}=t,n="number"==typeof s?this._$AC(t):(void 0===s.el&&(s.el=D.createElement(s.h,this.options)),s);if((null===(e=this._$AH)||void 0===e?void 0:e._$AD)===n)this._$AH.v(i);else{const t=new q(n,this),e=t.u(this.options);t.v(i),this.$(e),this._$AH=t}}_$AC(t){let e=j.get(t.strings);return void 0===e&&j.set(t.strings,e=new D(t)),e}T(t){x(this._$AH)||(this._$AH=[],this._$AR());const e=this._$AH;let i,s=0;for(const n of t)s===e.length?e.push(i=new W(this.k(w()),this.k(w()),this,this.options)):i=e[s],i._$AI(n),s++;s2||""!==i[0]||""!==i[1]?(this._$AH=Array(i.length-1).fill(new String),this.strings=i):this._$AH=I}get tagName(){return this.element.tagName}get _$AU(){return this._$AM._$AU}_$AI(t,e=this,i,s){const n=this.strings;let r=!1;if(void 0===n)t=V(this,t,e,0),r=!C(t)||t!==this._$AH&&t!==L,r&&(this._$AH=t);else{const s=t;let o,l;for(t=n[0],o=0;o{var s,n;const r=null!==(s=null==i?void 0:i.renderBefore)&&void 0!==s?s:e;let o=r._$litPart$;if(void 0===o){const t=null!==(n=null==i?void 0:i.renderBefore)&&void 0!==n?n:null;r._$litPart$=o=new W(e.insertBefore(w(),t),t,void 0,null!=i?i:{})}return o._$AI(t),o})(e,this.renderRoot,this.renderOptions)}connectedCallback(){var t;super.connectedCallback(),null===(t=this._$Do)||void 0===t||t.setConnected(!0)}disconnectedCallback(){var t;super.disconnectedCallback(),null===(t=this._$Do)||void 0===t||t.setConnected(!1)}render(){return L}}et.finalized=!0,et._$litElement$=!0,null===(Y=globalThis.litElementHydrateSupport)||void 0===Y||Y.call(globalThis,{LitElement:et});const it=globalThis.litElementPolyfillSupport;null==it||it({LitElement:et}),(null!==(tt=globalThis.litElementVersions)&&void 0!==tt?tt:globalThis.litElementVersions=[]).push("3.3.2");window.customElements.define("chain-messaging",class extends et{static get properties(){return{loading:{type:Boolean},theme:{type:String,reflect:!0}}}static get styles(){return r` +!function(t){"function"==typeof define&&define.amd?define(t):t()}((function(){"use strict";const t=window,e=t.ShadowRoot&&(void 0===t.ShadyCSS||t.ShadyCSS.nativeShadow)&&"adoptedStyleSheets"in Document.prototype&&"replace"in CSSStyleSheet.prototype,s=Symbol(),r=new WeakMap;let i=class{constructor(t,e,r){if(this._$cssResult$=!0,r!==s)throw Error("CSSResult is not constructable. Use `unsafeCSS` or `css` instead.");this.cssText=t,this.t=e}get styleSheet(){let t=this.o;const s=this.t;if(e&&void 0===t){const e=void 0!==s&&1===s.length;e&&(t=r.get(s)),void 0===t&&((this.o=t=new CSSStyleSheet).replaceSync(this.cssText),e&&r.set(s,t))}return t}toString(){return this.cssText}};const n=(t,...e)=>{const r=1===t.length?t[0]:e.reduce(((e,s,r)=>e+(t=>{if(!0===t._$cssResult$)return t.cssText;if("number"==typeof t)return t;throw Error("Value passed to 'css' function must be a 'css' function result: "+t+". Use 'unsafeCSS' to pass non-literal values, but take care to ensure page security.")})(s)+t[r+1]),t[0]);return new i(r,t,s)},o=e?t=>t:t=>t instanceof CSSStyleSheet?(t=>{let e="";for(const s of t.cssRules)e+=s.cssText;return(t=>new i("string"==typeof t?t:t+"",void 0,s))(e)})(t):t;var a;const l=window,h=l.trustedTypes,c=h?h.emptyScript:"",u=l.reactiveElementPolyfillSupport,d={toAttribute(t,e){switch(e){case Boolean:t=t?c:null;break;case Object:case Array:t=null==t?t:JSON.stringify(t)}return t},fromAttribute(t,e){let s=t;switch(e){case Boolean:s=null!==t;break;case Number:s=null===t?null:Number(t);break;case Object:case Array:try{s=JSON.parse(t)}catch(t){s=null}}return s}},p=(t,e)=>e!==t&&(e==e||t==t),g={attribute:!0,type:String,converter:d,reflect:!1,hasChanged:p},f="finalized";let y=class extends HTMLElement{constructor(){super(),this._$Ei=new Map,this.isUpdatePending=!1,this.hasUpdated=!1,this._$El=null,this._$Eu()}static addInitializer(t){var e;this.finalize(),(null!==(e=this.h)&&void 0!==e?e:this.h=[]).push(t)}static get observedAttributes(){this.finalize();const t=[];return this.elementProperties.forEach(((e,s)=>{const r=this._$Ep(s,e);void 0!==r&&(this._$Ev.set(r,s),t.push(r))})),t}static createProperty(t,e=g){if(e.state&&(e.attribute=!1),this.finalize(),this.elementProperties.set(t,e),!e.noAccessor&&!this.prototype.hasOwnProperty(t)){const s="symbol"==typeof t?Symbol():"__"+t,r=this.getPropertyDescriptor(t,s,e);void 0!==r&&Object.defineProperty(this.prototype,t,r)}}static getPropertyDescriptor(t,e,s){return{get(){return this[e]},set(r){const i=this[t];this[e]=r,this.requestUpdate(t,i,s)},configurable:!0,enumerable:!0}}static getPropertyOptions(t){return this.elementProperties.get(t)||g}static finalize(){if(this.hasOwnProperty(f))return!1;this[f]=!0;const t=Object.getPrototypeOf(this);if(t.finalize(),void 0!==t.h&&(this.h=[...t.h]),this.elementProperties=new Map(t.elementProperties),this._$Ev=new Map,this.hasOwnProperty("properties")){const t=this.properties,e=[...Object.getOwnPropertyNames(t),...Object.getOwnPropertySymbols(t)];for(const s of e)this.createProperty(s,t[s])}return this.elementStyles=this.finalizeStyles(this.styles),!0}static finalizeStyles(t){const e=[];if(Array.isArray(t)){const s=new Set(t.flat(1/0).reverse());for(const t of s)e.unshift(o(t))}else void 0!==t&&e.push(o(t));return e}static _$Ep(t,e){const s=e.attribute;return!1===s?void 0:"string"==typeof s?s:"string"==typeof t?t.toLowerCase():void 0}_$Eu(){var t;this._$E_=new Promise((t=>this.enableUpdating=t)),this._$AL=new Map,this._$Eg(),this.requestUpdate(),null===(t=this.constructor.h)||void 0===t||t.forEach((t=>t(this)))}addController(t){var e,s;(null!==(e=this._$ES)&&void 0!==e?e:this._$ES=[]).push(t),void 0!==this.renderRoot&&this.isConnected&&(null===(s=t.hostConnected)||void 0===s||s.call(t))}removeController(t){var e;null===(e=this._$ES)||void 0===e||e.splice(this._$ES.indexOf(t)>>>0,1)}_$Eg(){this.constructor.elementProperties.forEach(((t,e)=>{this.hasOwnProperty(e)&&(this._$Ei.set(e,this[e]),delete this[e])}))}createRenderRoot(){var s;const r=null!==(s=this.shadowRoot)&&void 0!==s?s:this.attachShadow(this.constructor.shadowRootOptions);return((s,r)=>{e?s.adoptedStyleSheets=r.map((t=>t instanceof CSSStyleSheet?t:t.styleSheet)):r.forEach((e=>{const r=document.createElement("style"),i=t.litNonce;void 0!==i&&r.setAttribute("nonce",i),r.textContent=e.cssText,s.appendChild(r)}))})(r,this.constructor.elementStyles),r}connectedCallback(){var t;void 0===this.renderRoot&&(this.renderRoot=this.createRenderRoot()),this.enableUpdating(!0),null===(t=this._$ES)||void 0===t||t.forEach((t=>{var e;return null===(e=t.hostConnected)||void 0===e?void 0:e.call(t)}))}enableUpdating(t){}disconnectedCallback(){var t;null===(t=this._$ES)||void 0===t||t.forEach((t=>{var e;return null===(e=t.hostDisconnected)||void 0===e?void 0:e.call(t)}))}attributeChangedCallback(t,e,s){this._$AK(t,s)}_$EO(t,e,s=g){var r;const i=this.constructor._$Ep(t,s);if(void 0!==i&&!0===s.reflect){const n=(void 0!==(null===(r=s.converter)||void 0===r?void 0:r.toAttribute)?s.converter:d).toAttribute(e,s.type);this._$El=t,null==n?this.removeAttribute(i):this.setAttribute(i,n),this._$El=null}}_$AK(t,e){var s;const r=this.constructor,i=r._$Ev.get(t);if(void 0!==i&&this._$El!==i){const t=r.getPropertyOptions(i),n="function"==typeof t.converter?{fromAttribute:t.converter}:void 0!==(null===(s=t.converter)||void 0===s?void 0:s.fromAttribute)?t.converter:d;this._$El=i,this[i]=n.fromAttribute(e,t.type),this._$El=null}}requestUpdate(t,e,s){let r=!0;void 0!==t&&(((s=s||this.constructor.getPropertyOptions(t)).hasChanged||p)(this[t],e)?(this._$AL.has(t)||this._$AL.set(t,e),!0===s.reflect&&this._$El!==t&&(void 0===this._$EC&&(this._$EC=new Map),this._$EC.set(t,s))):r=!1),!this.isUpdatePending&&r&&(this._$E_=this._$Ej())}async _$Ej(){this.isUpdatePending=!0;try{await this._$E_}catch(t){Promise.reject(t)}const t=this.scheduleUpdate();return null!=t&&await t,!this.isUpdatePending}scheduleUpdate(){return this.performUpdate()}performUpdate(){var t;if(!this.isUpdatePending)return;this.hasUpdated,this._$Ei&&(this._$Ei.forEach(((t,e)=>this[e]=t)),this._$Ei=void 0);let e=!1;const s=this._$AL;try{e=this.shouldUpdate(s),e?(this.willUpdate(s),null===(t=this._$ES)||void 0===t||t.forEach((t=>{var e;return null===(e=t.hostUpdate)||void 0===e?void 0:e.call(t)})),this.update(s)):this._$Ek()}catch(t){throw e=!1,this._$Ek(),t}e&&this._$AE(s)}willUpdate(t){}_$AE(t){var e;null===(e=this._$ES)||void 0===e||e.forEach((t=>{var e;return null===(e=t.hostUpdated)||void 0===e?void 0:e.call(t)})),this.hasUpdated||(this.hasUpdated=!0,this.firstUpdated(t)),this.updated(t)}_$Ek(){this._$AL=new Map,this.isUpdatePending=!1}get updateComplete(){return this.getUpdateComplete()}getUpdateComplete(){return this._$E_}shouldUpdate(t){return!0}update(t){void 0!==this._$EC&&(this._$EC.forEach(((t,e)=>this._$EO(e,this[e],t))),this._$EC=void 0),this._$Ek()}updated(t){}firstUpdated(t){}};var m;y[f]=!0,y.elementProperties=new Map,y.elementStyles=[],y.shadowRootOptions={mode:"open"},null==u||u({ReactiveElement:y}),(null!==(a=l.reactiveElementVersions)&&void 0!==a?a:l.reactiveElementVersions=[]).push("1.6.3");const _=window,v=_.trustedTypes,$=v?v.createPolicy("lit-html",{createHTML:t=>t}):void 0,w="$lit$",E=`lit$${(Math.random()+"").slice(9)}$`,A="?"+E,b=`<${A}>`,S=document,T=()=>S.createComment(""),M=t=>null===t||"object"!=typeof t&&"function"!=typeof t,C=Array.isArray,R="[ \t\n\f\r]",P=/<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g,x=/-->/g,O=/>/g,N=RegExp(`>|${R}(?:([^\\s"'>=/]+)(${R}*=${R}*(?:[^ \t\n\f\r"'\`<>=]|("|')|))|$)`,"g"),U=/'/g,I=/"/g,D=/^(?:script|style|textarea|title)$/i,k=(t=>(e,...s)=>({_$litType$:t,strings:e,values:s}))(1),H=Symbol.for("lit-noChange"),q=Symbol.for("lit-nothing"),L=new WeakMap,j=S.createTreeWalker(S,129,null,!1);function B(t,e){if(!Array.isArray(t)||!t.hasOwnProperty("raw"))throw Error("invalid template strings array");return void 0!==$?$.createHTML(e):e}const V=(t,e)=>{const s=t.length-1,r=[];let i,n=2===e?"":"",o=P;for(let e=0;e"===l[0]?(o=null!=i?i:P,h=-1):void 0===l[1]?h=-2:(h=o.lastIndex-l[2].length,a=l[1],o=void 0===l[3]?N:'"'===l[3]?I:U):o===I||o===U?o=N:o===x||o===O?o=P:(o=N,i=void 0);const u=o===N&&t[e+1].startsWith("/>")?" ":"";n+=o===P?s+b:h>=0?(r.push(a),s.slice(0,h)+w+s.slice(h)+E+u):s+E+(-2===h?(r.push(void 0),e):u)}return[B(t,n+(t[s]||"")+(2===e?"":"")),r]};class z{constructor({strings:t,_$litType$:e},s){let r;this.parts=[];let i=0,n=0;const o=t.length-1,a=this.parts,[l,h]=V(t,e);if(this.el=z.createElement(l,s),j.currentNode=this.el.content,2===e){const t=this.el.content,e=t.firstChild;e.remove(),t.append(...e.childNodes)}for(;null!==(r=j.nextNode())&&a.length0){r.textContent=v?v.emptyScript:"";for(let s=0;sC(t)||"function"==typeof(null==t?void 0:t[Symbol.iterator]))(t)?this.T(t):this._(t)}k(t){return this._$AA.parentNode.insertBefore(t,this._$AB)}$(t){this._$AH!==t&&(this._$AR(),this._$AH=this.k(t))}_(t){this._$AH!==q&&M(this._$AH)?this._$AA.nextSibling.data=t:this.$(S.createTextNode(t)),this._$AH=t}g(t){var e;const{values:s,_$litType$:r}=t,i="number"==typeof r?this._$AC(t):(void 0===r.el&&(r.el=z.createElement(B(r.h,r.h[0]),this.options)),r);if((null===(e=this._$AH)||void 0===e?void 0:e._$AD)===i)this._$AH.v(s);else{const t=new W(i,this),e=t.u(this.options);t.v(s),this.$(e),this._$AH=t}}_$AC(t){let e=L.get(t.strings);return void 0===e&&L.set(t.strings,e=new z(t)),e}T(t){C(this._$AH)||(this._$AH=[],this._$AR());const e=this._$AH;let s,r=0;for(const i of t)r===e.length?e.push(s=new F(this.k(T()),this.k(T()),this,this.options)):s=e[r],s._$AI(i),r++;r2||""!==s[0]||""!==s[1]?(this._$AH=Array(s.length-1).fill(new String),this.strings=s):this._$AH=q}get tagName(){return this.element.tagName}get _$AU(){return this._$AM._$AU}_$AI(t,e=this,s,r){const i=this.strings;let n=!1;if(void 0===i)t=K(this,t,e,0),n=!M(t)||t!==this._$AH&&t!==H,n&&(this._$AH=t);else{const r=t;let o,a;for(t=i[0],o=0;o{var r,i;const n=null!==(r=null==s?void 0:s.renderBefore)&&void 0!==r?r:e;let o=n._$litPart$;if(void 0===o){const t=null!==(i=null==s?void 0:s.renderBefore)&&void 0!==i?i:null;n._$litPart$=o=new F(e.insertBefore(T(),t),t,void 0,null!=s?s:{})}return o._$AI(t),o})(e,this.renderRoot,this.renderOptions)}connectedCallback(){var t;super.connectedCallback(),null===(t=this._$Do)||void 0===t||t.setConnected(!0)}disconnectedCallback(){var t;super.disconnectedCallback(),null===(t=this._$Do)||void 0===t||t.setConnected(!1)}render(){return H}}rt.finalized=!0,rt._$litElement$=!0,null===(et=globalThis.litElementHydrateSupport)||void 0===et||et.call(globalThis,{LitElement:rt});const it=globalThis.litElementPolyfillSupport;null==it||it({LitElement:rt}),(null!==(st=globalThis.litElementVersions)&&void 0!==st?st:globalThis.litElementVersions=[]).push("3.3.3");class nt{static prepareOutgoingData(t){return JSON.stringify(t)}constructor(t){if(!t)throw new Error("Source must be spcified");if(!this.constructor.type)throw new Error("Type not defined");if(this.constructor.name||console.warn("No name provided"),this.constructor.description||console.warn("No description provided"),!this.sendMessage)throw new Error("A new target requires a sendMessage method")}}const ot={},at={};class lt{static registerPlugin(t,e){return t.init(lt,e),lt}static registerTargetType(t,e){if(t in at)throw new Error("Target type has already been registered");if(!(e.prototype instanceof nt))throw new Error("Target constructors must inherit from the Target base class");return at[t]=e,lt}static registerEpmlMessageType(t,e){return ot[t]=e,lt}registerPlugin(t){return t.init(this),this}static handleMessage(t,e){const s=lt.prepareIncomingData(t);"EpmlMessageType"in s&&ot[s.EpmlMessageType](s,e,this)}static prepareIncomingData(t){return"string"!=typeof t?t:JSON.parse(t)}static createTargets(t){Array.isArray(t)||(t=[t]);const e=[];for(const s of t)void 0===s.allowObjects&&(s.allowObjects=!1),e.push(...lt.createTarget(s));return e}static createTarget(t){if(!at[t.type])throw new Error(`Target type '${t.type}' not registered`);let e=new at[t.type](t.source);Array.isArray(e)||(e=[e]);for(const s of e)s.allowObjects=t.allowObjects;return e}constructor(t){this.targets=this.constructor.createTargets(t)}}var ht=(t,e)=>{for(e=t="";t++<36;e+=51*t&52?(15^t?8^Math.random()*(20^t?16:4):4).toString(16):"-");return e};const ct=15,ut="EPML_READY_STATE_CHECK",dt="EPML_READY_STATE_CHECK_RESPONSE",pt={},gt={init:(t,e)=>{if(t.prototype.ready)throw new Error("Epml.prototype.ready is already defined");if(t.prototype.imReady)throw new Error("Epml.prototype.imReady is already defined");t.prototype.ready=mt,t.prototype.resetReadyCheck=_t,t.prototype.imReady=yt,t.registerEpmlMessageType(ut,ft),t.registerEpmlMessageType(dt,$t)}};function ft(t,e){e._i_am_ready&&e.sendMessage({EpmlMessageType:dt,requestID:t.requestID})}function yt(){for(const t of this.targets)t._i_am_ready=!0}function mt(){return this._ready_plugin=this._ready_plugin||{},this._ready_plugin.pendingReadyResolves=this._ready_plugin.pendingReadyResolves?this._ready_plugin.pendingReadyResolves:[],this._pending_ready_checking||(this._pending_ready_checking=!0,vt.call(this,this.targets).then((()=>{this._ready_plugin.pendingReadyResolves.forEach((t=>t()))}))),new Promise((t=>{this._ready_plugin.isReady?t():this._ready_plugin.pendingReadyResolves.push(t)}))}function _t(){this._ready_plugin=this._ready_plugin||{},this._ready_plugin.isReady=!1}function vt(t){return this._ready_plugin=this._ready_plugin||{},this._ready_plugin.pendingReadyResolves=[],Promise.all(t.map((t=>new Promise(((e,s)=>{const r=ht(),i=setInterval((()=>{t.sendMessage({EpmlMessageType:ut,requestID:r})}),ct);pt[r]=()=>{clearInterval(i),e()}}))))).then((()=>{this._ready_plugin.isReady=!0}))}function $t(t,e){e._ready_plugin=e._ready_plugin||{},e._ready_plugin._is_ready=!0,pt[t.requestID]()}const wt=new Map;class Et extends nt{static get sources(){return Array.from(wt.keys())}static get targets(){return Array.from(wt.values())}static getTargetFromSource(t){return wt.get(t)}static hasTarget(t){return wt.has(t)}static get type(){return"WINDOW"}static get name(){return"Content window plugin"}static get description(){return"Allows Epml to communicate with iframes and popup windows."}static test(t){return"object"==typeof t&&t===t.self}isFrom(t){}constructor(t){if(super(t),wt.has(t))return wt.get(t);if(!this.constructor.test(t))throw new Error("Source can not be used with target");this._source=t,this._sourceOrigin="*",wt.set(t,this)}get source(){return this._source}sendMessage(t){t=nt.prepareOutgoingData(t),this._source.postMessage(t,this._sourceOrigin)}}var At={init:function(t){!function(t,e,s){if(t.addEventListener)t.addEventListener(e,s,!1);else{if(!t.attachEvent)throw new Error("Could not bind event.");t.attachEvent("on"+e,s)}}(window,"message",(e=>{Et.hasTarget(e.source)&&t.handleMessage(e.data,Et.getTargetFromSource(e.source))})),t.registerTargetType(Et.type,Et)}};const bt="REQUEST",St="REQUEST_RESPONSE",Tt=new Map,Mt={},Ct={init:(t,e)=>{if(t.prototype.request)throw new Error("Epml.prototype.request is already defined");if(t.prototype.route)throw new Error("Empl.prototype.route is already defined");t.prototype.request=Rt,t.prototype.route=Ot,t.registerEpmlMessageType(bt,xt),t.registerEpmlMessageType(St,Pt)}},Rt=function(t,e,s){return Promise.all(this.targets.map((r=>{const i=ht(),n={EpmlMessageType:bt,requestOrResponse:"request",requestID:i,requestType:t,data:e};return r.sendMessage(n),new Promise(((t,e)=>{let r;s&&(r=setTimeout((()=>{delete Mt[i],e(new Error("Request timed out"))}),s)),Mt[i]=(...e)=>{r&&clearTimeout(r),t(...e)}}))}))).then((t=>{if(1===this.targets.length)return t[0]}))};function Pt(t,e,s){if(t.requestID in Mt){const e=t.data;Mt[t.requestID](e)}else console.warn("requestID not found in pendingRequests")}function xt(t,e){if(!Tt.has(e))return void console.warn("Route does not exist - missing target");const s=Tt.get(e)[t.requestType];s?s(t,e):console.warn("Route does not exist")}function Ot(t,e){if(this.routes||(this.routes={}),!this.routes[t])for(const s of this.targets){Tt.has(s)||Tt.set(s,{});Tt.get(s)[t]=(t,s)=>{Promise.resolve(e(t)).catch((t=>t instanceof Error?t.message:t)).then((e=>{s.sendMessage({data:e,EpmlMessageType:St,requestOrResponse:"request",requestID:t.requestID})}))}}}const Nt="PROXY_MESSAGE",Ut=new class{constructor(t){this._map=t||new Map,this._revMap=new Map,this._map.forEach(((t,e)=>{this._revMap.set(e,t)}))}values(){return this._map.values()}entries(){return this._map.entries()}push(t,e){this._map.set(t,e),this._revMap.set(e,t)}getByKey(t){return this._map.get(t)}getByValue(t){return this._revMap.get(t)}hasKey(t){return this._map.has(t)}hasValue(t){return this._revMap.has(t)}deleteByKey(t){const e=this._map.get(t);this._map.delete(t),this._revMap.delete(e)}deleteByValue(t){const e=this._revMap.get(t);this._map.delete(e),this._revMap.delete(t)}};class It extends nt{static get proxySources(){return Ut}static get sources(){for(const[t,e]of Ut)for(const[t]of e);Array.from(Ut.entries()).map(((t,e)=>({proxy:t,target:Array.from(e.keys())[0]})))}static get targets(){return Array.from(Ut.values())}static getTargetFromSource(t){return Ut.getByValue(t)}static hasTarget(t){return Ut.hasValue(t)}static get type(){return"PROXY"}static get name(){return"Proxy target"}static get description(){return"Uses other target, and proxies requests, allowing things like iframes to communicate through their host"}static test(t){return"object"==typeof t&&t.proxy instanceof this.Epml}isFrom(t){}constructor(t){if(super(t),this.constructor.proxySources.push(t.id,this),!this.constructor.test(t))throw new Error("Source can not be used with target");this._source=t}get source(){return this._source}sendMessage(t){const e=ht();t=nt.prepareOutgoingData(t),t={EpmlMessageType:Nt,state:"TRANSIT",requestID:e,target:this._source.target,message:t,id:this._source.id},this._source.proxy.targets[0].sendMessage(t)}}const Dt=It.proxySources;let kt;var Ht={init:function(t){Object.defineProperty(It,"Epml",{get:()=>t}),kt=t,t.registerTargetType(It.type,It),t.registerProxyInstance=Lt,t.registerEpmlMessageType(Nt,qt)}};function qt(t,e){if("TRANSIT"===t.state){const e=Dt.getByKey(t.target);if(!e)return void console.warn(`Target ${t.target} not registered.`);t.state="DELIVERY",e.targets.forEach((e=>e.sendMessage(t)))}else if("DELIVERY"===t.state){if(!Dt.getByKey(t.target))return void console.warn(`Target ${t.target} not registered.`);const e=Dt.getByKey(t.target);kt.handleMessage(t.message,e)}}function Lt(t,e){Dt.hasKey(t)&&console.warn(`${t} is already defined. Overwriting...`),Dt.push(t,e)}const jt="STREAM_UPDATE",Bt={};class Vt{static get streams(){return Bt}constructor(t,e=(()=>{})){if(this._name=t,this.targets=[],this._subscriptionFn=e,t in Bt)return console.warn(`Stream with name ${t} already exists! Returning it instead`),Bt[t];Bt[t]=this}async subscribe(t){t in this.targets&&console.info("Target is already subscribed to this stream");const e=await this._subscriptionFn();this._sendMessage(e,t),this.targets.push(t)}_sendMessage(t,e){e.sendMessage({data:nt.prepareOutgoingData(t),EpmlMessageType:jt,streamName:this._name})}emit(t){this.targets.forEach((e=>this._sendMessage(t,e)))}}const zt="JOIN_STREAM",Kt={},Wt={init:(t,e)=>{if(t.prototype.subscribe)throw new Error("Epml.prototype.subscribe is already defined");if(t.prototype.createStream)throw new Error("Empl.prototype.createStream is already defined");t.prototype.subscribe=Yt,t.registerEpmlMessageType(zt,Ft),t.registerEpmlMessageType(jt,Jt)}},Ft=function(t,e){const s=t.data.name,r=Vt.streams[s];r?r.subscribe(e):console.warn(`No stream with name ${s}`,this)},Yt=function(t,e){this.targets.forEach((e=>{e.sendMessage({EpmlMessageType:zt,data:{name:t}})})),Kt[t]=Kt[t]||[],Kt[t].push(e)},Jt=function(t,e){Kt[t.streamName].forEach((e=>e(t.data)))};lt.registerPlugin(Ct),lt.registerPlugin(gt),lt.registerPlugin(At),lt.registerPlugin(Wt),lt.registerPlugin(Ht),lt.allowProxying=!0;var Qt="undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{};function Xt(){throw new Error("setTimeout has not been defined")}function Zt(){throw new Error("clearTimeout has not been defined")}var Gt=Xt,te=Zt;function ee(t){if(Gt===setTimeout)return setTimeout(t,0);if((Gt===Xt||!Gt)&&setTimeout)return Gt=setTimeout,setTimeout(t,0);try{return Gt(t,0)}catch(e){try{return Gt.call(null,t,0)}catch(e){return Gt.call(this,t,0)}}}"function"==typeof Qt.setTimeout&&(Gt=setTimeout),"function"==typeof Qt.clearTimeout&&(te=clearTimeout);var se,re=[],ie=!1,ne=-1;function oe(){ie&&se&&(ie=!1,se.length?re=se.concat(re):ne=-1,re.length&&ae())}function ae(){if(!ie){var t=ee(oe);ie=!0;for(var e=re.length;e;){for(se=re,re=[];++ne1)for(var s=1;s=0)}));new lt({type:"WINDOW",source:window.parent});window.customElements.define("chain-messaging",class extends rt{static get properties(){return{loading:{type:Boolean},theme:{type:String,reflect:!0}}}static get styles(){return n` * { --mdc-theme-primary: rgb(3, 169, 244); --paper-input-container-focus-color: var(--mdc-theme-primary); @@ -15,8 +15,8 @@ background: var(--white); } - `}constructor(){super(),this.theme=localStorage.getItem("qortalTheme")?localStorage.getItem("qortalTheme"):"light"}render(){return M` + `}constructor(){super(),this.theme=localStorage.getItem("qortalTheme")?localStorage.getItem("qortalTheme"):"light"}render(){return k`

Coming Soon!

- `}firstUpdated(){this.changeTheme(),setInterval((()=>{this.changeTheme()}),100),window.addEventListener("contextmenu",(t=>{t.preventDefault()})),window.addEventListener("click",(()=>{})),window.onkeyup=t=>{t.keyCode}}changeTheme(){const t=localStorage.getItem("qortalTheme");this.theme="dark"===t?"dark":"light",document.querySelector("html").setAttribute("theme",this.theme)}isEmptyArray(t){return!t||0===t.length}})})); + `}firstUpdated(){this.changeTheme(),window.addEventListener("storage",(()=>{const t=localStorage.getItem("qortalTheme");this.theme="dark"===t?"dark":"light",document.querySelector("html").setAttribute("theme",this.theme)})),Ee()&&window.addEventListener("contextmenu",(t=>{t.preventDefault(),window.parent.electronAPI.showMyMenu()}))}clearConsole(){Ee()&&(console.clear(),window.parent.electronAPI.clearCache())}changeTheme(){const t=localStorage.getItem("qortalTheme");this.theme="dark"===t?"dark":"light",document.querySelector("html").setAttribute("theme",this.theme)}isEmptyArray(t){return!t||0===t.length}})})); diff --git a/plugins/plugins/core/messaging/q-chat/q-chat.src.js b/plugins/plugins/core/messaging/q-chat/q-chat.src.js index 95370b95..7d5390cc 100644 --- a/plugins/plugins/core/messaging/q-chat/q-chat.src.js +++ b/plugins/plugins/core/messaging/q-chat/q-chat.src.js @@ -95,9 +95,8 @@ class Chat extends LitElement { } async setActiveChatHeadUrl(url) { - this.activeChatHeadUrl = '' - await this.updateComplete; this.activeChatHeadUrl = url + this.requestUpdate() } resetChatEditor(){ @@ -223,8 +222,8 @@ class Chat extends LitElement { ${translate("chatpage.cchange5")} keyboard_arrow_down
- - ${this.activeChatHeadUrl ? html`${this.renderChatPage()}` : html`${this.renderChatWelcomePage()}`} + ${this.isEmptyArray(this.chatHeads) ? html`${this.renderChatWelcomePage()}` : html`${this.renderChatPage()}`} +
@@ -818,6 +817,7 @@ class Chat extends LitElement { } renderChatPage() { + console.log('this.chatHeads', this.chatHeads, this.activeChatHeadUrl) // Check for the chat ID from and render chat messages // Else render Welcome to Q-CHat @@ -864,6 +864,9 @@ class Chat extends LitElement { } this.chatHeads = chatHeadMasterList.sort(compareArgs) + if(!this.activeChatHeadUrl && this.chatHeads.length > 0){ + this.activeChatHeadUrl = this.chatHeads[0].url + } } getChatHeadFromState(chatObj) { From 5a9b92744ada4d7d71fad9f7ba25db316fea29d5 Mon Sep 17 00:00:00 2001 From: PhilReact Date: Thu, 14 Sep 2023 00:58:02 -0500 Subject: [PATCH 20/57] fix double websocket --- plugins/plugins/core/components/ChatPage.js | 28 ++++++++++++++----- .../plugins/core/components/ChatScroller.js | 3 +- 2 files changed, 23 insertions(+), 8 deletions(-) diff --git a/plugins/plugins/core/components/ChatPage.js b/plugins/plugins/core/components/ChatPage.js index 45d9867c..e05c91a9 100644 --- a/plugins/plugins/core/components/ChatPage.js +++ b/plugins/plugins/core/components/ChatPage.js @@ -2347,6 +2347,7 @@ class ChatPage extends LitElement { this.webSocket.close(1000, 'switch chat') this.webSocket = '' } + console.log('this.webSocket', this.webSocket) this.pageNumber = 1 const getAddressPublicKey = () => { @@ -2489,6 +2490,7 @@ class ChatPage extends LitElement { } async updated(changedProperties) { + console.log({changedProperties}) if (changedProperties && changedProperties.has('userLanguage')) { const userLang = changedProperties.get('userLanguage') if (userLang) { @@ -2505,7 +2507,7 @@ class ChatPage extends LitElement { this.editor.setEditable(true) } } - if(changedProperties && changedProperties.has('chatId')){ + if(changedProperties && changedProperties.has('chatId') && this.webSocket){ this.isLoadingMessages = true this.initUpdate() @@ -3146,7 +3148,7 @@ class ChatPage extends LitElement { setTimeout(() => this.downElementObserver(), 500) } else { - + console.log('decodedmsg', decodedMessages) queue.push(() => replaceMessagesEdited({ decodedMessages: decodedMessages, @@ -3212,6 +3214,7 @@ class ChatPage extends LitElement { */ async renderNewMessage(newMessage) { + console.log('newMessage', newMessage) if (newMessage.chatReference) { // const findOriginalMessageIndex = this.messagesRendered.findIndex(msg => msg.signature === newMessage.chatReference || (msg.chatReference && msg.chatReference === newMessage.chatReference)) // if (findOriginalMessageIndex !== -1 && this.messagesRendered[findOriginalMessageIndex].sender === newMessage.sender) { @@ -3230,7 +3233,12 @@ class ChatPage extends LitElement { return } - const viewElement = this.shadowRoot.querySelector('chat-scroller').shadowRoot.getElementById('viewElement') + let viewElement = this.shadowRoot.querySelector('chat-scroller') + if(viewElement){ + viewElement = viewElement.shadowRoot.getElementById('viewElement') + } else { + viewElement = null + } if (newMessage.sender === this.selectedAddress.address) { @@ -3251,8 +3259,10 @@ class ChatPage extends LitElement { // Append the message and scroll to the bottom if user is down the page // this.messagesRendered = [...this.messagesRendered, newMessage] await this.getUpdateComplete() - - viewElement.scrollTop = viewElement.scrollHeight + if(viewElement){ + viewElement.scrollTop = viewElement.scrollHeight + } + } else { this.messagesRendered = { @@ -3320,7 +3330,9 @@ class ChatPage extends LitElement { } else { // Fallback to http directSocketLink = `ws://${nodeUrl}/websockets/chat/messages?involving=${window.parent.reduxStore.getState().app.selectedAddress.address}&involving=${cid}&encoding=BASE64&limit=1` - } + } + + this.webSocket = new WebSocket(directSocketLink) // Open Connection @@ -3389,7 +3401,9 @@ class ChatPage extends LitElement { // Closed Event this.webSocket.onclose = (e) => { clearTimeout(directSocketTimeout) + console.log('e', e) if (e.reason === 'switch chat') return + console.log('not coming in') restartDirectWebSocket() } @@ -3437,7 +3451,7 @@ class ChatPage extends LitElement { // Fallback to http groupSocketLink = `ws://${nodeUrl}/websockets/chat/messages?txGroupId=${groupId}&encoding=BASE64&limit=1` } - + this.webSocket = new WebSocket(groupSocketLink) // Open Connection diff --git a/plugins/plugins/core/components/ChatScroller.js b/plugins/plugins/core/components/ChatScroller.js index 169e5137..bd224124 100644 --- a/plugins/plugins/core/components/ChatScroller.js +++ b/plugins/plugins/core/components/ChatScroller.js @@ -559,6 +559,7 @@ class ChatScroller extends LitElement { async replaceMessagesWithUpdateByArray(updatedMessagesArray) { + console.log({updatedMessagesArray, messages: this.messagesToRender}) let previousScrollTop; let previousScrollHeight; @@ -567,7 +568,7 @@ class ChatScroller extends LitElement { previousScrollHeight = viewElement.scrollHeight; for (let group of this.messagesToRender) { for (let i = 0; i < group.messages.length; i++) { - const update = updatedMessagesArray.find(updatedMessage => ((updatedMessage.chatReference === group.messages[i].signature) || (updatedMessage.chatReference === group.messages[i].originalSignature))); + const update = updatedMessagesArray.find(updatedMessage => ((updatedMessage.chatReference === group.messages[i].signature) || (updatedMessage.chatReference === group.messages[i].originalSignature) || (updatedMessage.chatReference === group.messages[i].chatReference))); if (update) { Object.assign(group.messages[i], update); } From 8fa64e793331534806960ea4efaee050106d828d Mon Sep 17 00:00:00 2001 From: PhilReact Date: Thu, 14 Sep 2023 01:23:34 -0500 Subject: [PATCH 21/57] fix var --- plugins/plugins/core/components/ChatPage.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/plugins/core/components/ChatPage.js b/plugins/plugins/core/components/ChatPage.js index ebb7517b..07bf667a 100644 --- a/plugins/plugins/core/components/ChatPage.js +++ b/plugins/plugins/core/components/ChatPage.js @@ -3905,7 +3905,7 @@ class ChatPage extends LitElement { images: [{ service: "QCHAT_IMAGE", name: userName, - identifier: identifieroh it's p + identifier: identifier, }], isImageDeleted: false, repliedTo: '', From 355e102a020d63526645d3bc5f6948769796d5b2 Mon Sep 17 00:00:00 2001 From: PhilReact Date: Thu, 14 Sep 2023 01:41:28 -0500 Subject: [PATCH 22/57] fix reply error --- plugins/plugins/core/components/ChatPage.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/plugins/plugins/core/components/ChatPage.js b/plugins/plugins/core/components/ChatPage.js index 07bf667a..c28565a8 100644 --- a/plugins/plugins/core/components/ChatPage.js +++ b/plugins/plugins/core/components/ChatPage.js @@ -55,10 +55,10 @@ const parentEpml = new Epml({ type: 'WINDOW', source: window.parent }) export const queue = new RequestQueue(); -export const chatLimit = 20 -export const chatLimitHalf = 10 +export const chatLimit = 40 +export const chatLimitHalf = 20 -export const totalMsgCount = 60 +export const totalMsgCount = 120 class ChatPage extends LitElement { static get properties() { return { @@ -2205,6 +2205,10 @@ class ChatPage extends LitElement { findElement.classList.remove('blink-bg') }, 2000) } + const chatScrollerElement = this.shadowRoot.querySelector('chat-scroller'); + if (chatScrollerElement && chatScrollerElement.disableFetching) { + chatScrollerElement.disableFetching = false + } return } From b2fca5a6c6ed7a2709c6709c130cc7e012db23a5 Mon Sep 17 00:00:00 2001 From: PhilReact Date: Thu, 14 Sep 2023 21:36:55 -0500 Subject: [PATCH 23/57] revert back to welcome page in chat --- plugins/plugins/core/messaging/q-chat/q-chat.src.js | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/plugins/plugins/core/messaging/q-chat/q-chat.src.js b/plugins/plugins/core/messaging/q-chat/q-chat.src.js index 7d5390cc..7db50437 100644 --- a/plugins/plugins/core/messaging/q-chat/q-chat.src.js +++ b/plugins/plugins/core/messaging/q-chat/q-chat.src.js @@ -222,8 +222,8 @@ class Chat extends LitElement { ${translate("chatpage.cchange5")} keyboard_arrow_down
- ${this.isEmptyArray(this.chatHeads) ? html`${this.renderChatWelcomePage()}` : html`${this.renderChatPage()}`} - + + ${this.activeChatHeadUrl ? html`${this.renderChatPage()}` : html`${this.renderChatWelcomePage()}`}
@@ -817,7 +817,6 @@ class Chat extends LitElement { } renderChatPage() { - console.log('this.chatHeads', this.chatHeads, this.activeChatHeadUrl) // Check for the chat ID from and render chat messages // Else render Welcome to Q-CHat @@ -864,9 +863,6 @@ class Chat extends LitElement { } this.chatHeads = chatHeadMasterList.sort(compareArgs) - if(!this.activeChatHeadUrl && this.chatHeads.length > 0){ - this.activeChatHeadUrl = this.chatHeads[0].url - } } getChatHeadFromState(chatObj) { From 063b4649b257430dc2ecd0b9cd62c4ab413af206 Mon Sep 17 00:00:00 2001 From: PhilReact Date: Thu, 14 Sep 2023 22:22:40 -0500 Subject: [PATCH 24/57] remove fixed username --- plugins/plugins/core/components/ChatPage.js | 6 ++++- .../plugins/core/components/ChatScroller.js | 26 ++++++++++++------- .../core/messaging/q-chat/q-chat.src.js | 15 ++++++++++- 3 files changed, 36 insertions(+), 11 deletions(-) diff --git a/plugins/plugins/core/components/ChatPage.js b/plugins/plugins/core/components/ChatPage.js index c28565a8..3211d601 100644 --- a/plugins/plugins/core/components/ChatPage.js +++ b/plugins/plugins/core/components/ChatPage.js @@ -126,7 +126,9 @@ class ChatPage extends LitElement { updateMessageHash: { type: Object}, oldMessages: {type: Array}, messageQueue: {type: Array}, - isInProcessQueue: {type: Boolean} + isInProcessQueue: {type: Boolean}, + loggedInUserName: {type: String}, + loggedInUserAddress: {type: String} } } @@ -2610,6 +2612,8 @@ class ChatPage extends LitElement { .updateMessageHash=${this.updateMessageHash} .clearUpdateMessageHashmap=${this.clearUpdateMessageHashmap} .messageQueue=${this.messageQueue} + loggedInUserName=${this.loggedInUserName} + loggedInUserAddress=${this.loggedInUserAddress} > ` diff --git a/plugins/plugins/core/components/ChatScroller.js b/plugins/plugins/core/components/ChatScroller.js index a88d1eab..31077de1 100644 --- a/plugins/plugins/core/components/ChatScroller.js +++ b/plugins/plugins/core/components/ChatScroller.js @@ -46,6 +46,8 @@ const getApiKey = () => { return apiKey } + + const extractComponents = async (url) => { if (!url.startsWith("qortal://")) { return null @@ -246,7 +248,9 @@ class ChatScroller extends LitElement { disableFetching: {type: Boolean}, isLoadingBefore: {type: Boolean}, isLoadingAfter: {type: Boolean}, - messageQueue: {type: Array} + messageQueue: {type: Array}, + loggedInUserName: {type: String}, + loggedInUserAddress: {type: String} } } @@ -743,13 +747,11 @@ class ChatScroller extends LitElement { .emojiPicker=${this.emojiPicker} .escapeHTML=${this.escapeHTML} .messageObj=${{ - decodedMessage: message.messageText, - - "timestamp": message.timestamp, - "sender": "QWxEcmZxnM8yb1p92C1YKKRsp8svSVbFEs", - "senderName": "palmas", - "signature": "4B6hHMHTnSvXTMmQb73P4Yr2o772zu7XxiTiRQv8GsgysNaoc9UCUqb9x7ihz2Su6xCREZUvgACmFpHY2gzUbYHf", - + decodedMessage: message.messageText, + "timestamp": message.timestamp, + "sender": this.loggedInUserAddress, + "senderName": this.loggedInUserName, + "signature": "", }} .hideMessages=${this.hideMessages} .setRepliedToMessageObj=${this.setRepliedToMessageObj} @@ -796,6 +798,9 @@ class ChatScroller extends LitElement { if (changedProperties.has('userName')) { return true } + if(changedProperties.has('loggedInUserName')){ + return true + } if (changedProperties.has('updateMessageHash')) { return true } @@ -1557,7 +1562,8 @@ class MessageTemplate extends LitElement {
- + `} +
diff --git a/plugins/plugins/core/messaging/q-chat/q-chat.src.js b/plugins/plugins/core/messaging/q-chat/q-chat.src.js index 7db50437..6a85446c 100644 --- a/plugins/plugins/core/messaging/q-chat/q-chat.src.js +++ b/plugins/plugins/core/messaging/q-chat/q-chat.src.js @@ -53,7 +53,9 @@ class Chat extends LitElement { userFoundModalOpen: { type: Boolean }, userSelected: { type: Object }, editor: {type: Object}, - groupInvites: { type: Array } + groupInvites: { type: Array }, + loggedInUserName: {type: String}, + loggedInUserAddress: {type: String}, } } @@ -92,6 +94,7 @@ class Chat extends LitElement { this.userFoundModalOpen = false this.userSelected = {} this.groupInvites = [] + this.loggedInUserName = "" } async setActiveChatHeadUrl(url) { @@ -145,6 +148,8 @@ class Chat extends LitElement { this.unsubscribeStore = window.parent.reduxStore.subscribe(() => { try { + const currentState = window.parent.reduxStore.getState(); + if(window.parent.location && window.parent.location.search) { const queryString = window.parent.location.search const params = new URLSearchParams(queryString) @@ -156,6 +161,12 @@ class Chat extends LitElement { this.setActiveChatHeadUrl(chat) } } + if(currentState.app.accountInfo && currentState.app.accountInfo.names && currentState.app.accountInfo.names.length > 0 && this.loggedInUserName !== currentState.app.accountInfo.names[0].name){ + this.loggedInUserName = currentState.app.accountInfo.names[0].name + } + if(currentState.app.accountInfo && currentState.app.accountInfo.addressInfo && currentState.app.accountInfo.addressInfo.address && this.loggedInUserAddress !== currentState.app.accountInfo.addressInfo.address){ + this.loggedInUserAddress = currentState.app.accountInfo.addressInfo.address + } } catch (error) { } }) @@ -831,6 +842,8 @@ class Chat extends LitElement { .setOpenPrivateMessage=${(val) => this.setOpenPrivateMessage(val)} .setActiveChatHeadUrl=${(val)=> this.setActiveChatHeadUrl(val)} balance=${this.balance} + loggedInUserName=${this.loggedInUserName} + loggedInUserAddress=${this.loggedInUserAddress} > ` From 47d2332fa613e269c1680473532203d419d9760c Mon Sep 17 00:00:00 2001 From: PhilReact Date: Thu, 14 Sep 2023 23:42:21 -0500 Subject: [PATCH 25/57] fix scrolling down when in old messages --- plugins/plugins/core/components/ChatPage.js | 94 +++++++++++++------ .../plugins/core/components/ChatScroller.js | 18 ++-- .../core/components/ChatTextEditor copy.js | 1 - 3 files changed, 74 insertions(+), 39 deletions(-) diff --git a/plugins/plugins/core/components/ChatPage.js b/plugins/plugins/core/components/ChatPage.js index 3211d601..3a3c59c0 100644 --- a/plugins/plugins/core/components/ChatPage.js +++ b/plugins/plugins/core/components/ChatPage.js @@ -1472,7 +1472,7 @@ class ChatPage extends LitElement { url: `/chat/messages?involving=${window.parent.reduxStore.getState().app.selectedAddress.address}&involving=${this._chatId}&limit=${chatLimit}&reverse=true&haschatreference=false&encoding=BASE64` }) - this.processMessages(getInitialMessages, true, isUnread) + } else { getInitialMessages = await parentEpml.request('apiCall', { @@ -2098,6 +2098,36 @@ class ChatPage extends LitElement { document.addEventListener('keydown', this.initialChat) document.addEventListener('paste', this.pasteImage) + + let callback = (entries, observer) => { + entries.forEach(entry => { + if (entry.isIntersecting) { + + this.isPageVisible = true + if (this.chatId) { + window.parent.reduxStore.dispatch(window.parent.reduxAction.addChatLastSeen({ + key: this.chatId, + timestamp: Date.now() + })) + + } + } else { + this.isPageVisible = false + } + }) + } + + let options = { + root: null, + rootMargin: '0px', + threshold: 0.5 + } + + // Create the observer with the callback function and options + this.observer = new IntersectionObserver(callback, options) + const mainContainer = this.shadowRoot.querySelector('.main-container') + + this.observer.observe(mainContainer) } disconnectedCallback() { @@ -2127,13 +2157,6 @@ class ChatPage extends LitElement { document.removeEventListener('keydown', this.initialChat) document.removeEventListener('paste', this.pasteImage) - - if (this.chatId) { - window.parent.reduxStore.dispatch(window.parent.reduxAction.addChatLastSeen({ - key: this.chatId, - timestamp: Date.now() - })) - } } initialChat(e) { @@ -2353,7 +2376,6 @@ class ChatPage extends LitElement { this.webSocket.close(1000, 'switch chat') this.webSocket = '' } - console.log('this.webSocket', this.webSocket) this.pageNumber = 1 const getAddressPublicKey = () => { @@ -2496,7 +2518,6 @@ class ChatPage extends LitElement { } async updated(changedProperties) { - console.log({changedProperties}) if (changedProperties && changedProperties.has('userLanguage')) { const userLang = changedProperties.get('userLanguage') if (userLang) { @@ -2514,11 +2535,18 @@ class ChatPage extends LitElement { } } if(changedProperties && changedProperties.has('chatId') && this.webSocket){ + const previousChatId = changedProperties.get('chatId'); + this.isLoadingMessages = true this.initUpdate() - + if (previousChatId) { + window.parent.reduxStore.dispatch(window.parent.reduxAction.addChatLastSeen({ + key: previousChatId, + timestamp: Date.now() + })) + } } } @@ -3071,7 +3099,7 @@ class ChatPage extends LitElement { - async processMessages(messages, isInitial, isUnread) { + async processMessages(messages, isInitial, isUnread, count) { const isReceipient = this.chatId.includes('direct') let decodedMessages = [] if(!this.webWorkerDecodeMessages){ @@ -3135,10 +3163,12 @@ class ChatPage extends LitElement { if(isUnread){ + this.messagesRendered = { messages: this._messages, type: 'initialLastSeen', - lastReadMessageTimestamp + lastReadMessageTimestamp, + count } window.parent.reduxStore.dispatch(window.parent.reduxAction.addChatLastSeen({ @@ -3156,7 +3186,6 @@ class ChatPage extends LitElement { setTimeout(() => this.downElementObserver(), 500) } else { - console.log('decodedmsg', decodedMessages) queue.push(() => replaceMessagesEdited({ decodedMessages: decodedMessages, @@ -3222,18 +3251,7 @@ class ChatPage extends LitElement { */ async renderNewMessage(newMessage) { - console.log('newMessage', newMessage) if (newMessage.chatReference) { - // const findOriginalMessageIndex = this.messagesRendered.findIndex(msg => msg.signature === newMessage.chatReference || (msg.chatReference && msg.chatReference === newMessage.chatReference)) - // if (findOriginalMessageIndex !== -1 && this.messagesRendered[findOriginalMessageIndex].sender === newMessage.sender) { - // const newMessagesRendered = [...this.messagesRendered] - // newMessagesRendered[findOriginalMessageIndex] = { - // ...newMessage, timestamp: newMessagesRendered[findOriginalMessageIndex].timestamp, senderName: newMessagesRendered[findOriginalMessageIndex].senderName, - // sender: newMessagesRendered[findOriginalMessageIndex].sender, editedTimestamp: newMessage.timestamp - // } - // this.messagesRendered = newMessagesRendered - // await this.getUpdateComplete() - // } this.messagesRendered = { messages: [newMessage], type: 'update', @@ -3360,6 +3378,7 @@ class ChatPage extends LitElement { this.lastReadMessageTimestamp = await chatLastSeen.getItem(this.chatId) || 0 if (noInitial) return let getInitialMessages = [] + let count = 0 let isUnread = false const chatId = this.chatId @@ -3383,6 +3402,13 @@ class ChatPage extends LitElement { url: `/chat/messages?involving=${window.parent.reduxStore.getState().app.selectedAddress.address}&involving=${cid}&limit=${chatLimitHalf}&reverse=false&after=${lastReadMessageTimestamp - 1000}&haschatreference=false&encoding=BASE64` }) getInitialMessages = [...getInitialMessagesBefore, ...getInitialMessagesAfter] + const lastMessage = getInitialMessagesAfter.at(-1) + if(lastMessage){ + count = await parentEpml.request('apiCall', { + type: 'api', + url: `/chat/messages/count?after=${lastMessage.timestamp}&involving=${window.parent.reduxStore.getState().app.selectedAddress.address}&involving=${cid}&limit=20&reverse=false` + }) + } } else { getInitialMessages = await parentEpml.request('apiCall', { type: 'api', @@ -3392,7 +3418,7 @@ class ChatPage extends LitElement { - this.processMessages(getInitialMessages, true, isUnread) + this.processMessages(getInitialMessages, true, isUnread, count) initial = initial + 1 @@ -3409,9 +3435,9 @@ class ChatPage extends LitElement { // Closed Event this.webSocket.onclose = (e) => { clearTimeout(directSocketTimeout) - console.log('e', e) + if (e.reason === 'switch chat') return - console.log('not coming in') + restartDirectWebSocket() } @@ -3445,7 +3471,7 @@ class ChatPage extends LitElement { let groupId = Number(gId) let initial = 0 - + let count = 0 let groupSocketTimeout let myNode = window.parent.reduxStore.getState().app.nodeConfig.knownNodes[window.parent.reduxStore.getState().app.nodeConfig.node] @@ -3503,6 +3529,14 @@ class ChatPage extends LitElement { url: `/chat/messages?txGroupId=${groupId}&limit=${chatLimitHalf}&reverse=false&after=${lastReadMessageTimestamp - 1000}&haschatreference=false&encoding=BASE64` }) getInitialMessages = [...getInitialMessagesBefore, ...getInitialMessagesAfter] + const lastMessage = getInitialMessagesAfter.at(-1) + if(lastMessage){ + count = await parentEpml.request('apiCall', { + type: 'api', + url: `/chat/messages/count?after=${lastMessage.timestamp}&txGroupId=${groupId}&limit=20&reverse=false` + }) + } + } else { getInitialMessages = await parentEpml.request('apiCall', { type: 'api', @@ -3513,7 +3547,7 @@ class ChatPage extends LitElement { - this.processMessages(getInitialMessages, true, isUnread) + this.processMessages(getInitialMessages, true, isUnread, count) initial = initial + 1 } else { diff --git a/plugins/plugins/core/components/ChatScroller.js b/plugins/plugins/core/components/ChatScroller.js index 31077de1..c24ba911 100644 --- a/plugins/plugins/core/components/ChatScroller.js +++ b/plugins/plugins/core/components/ChatScroller.js @@ -325,7 +325,6 @@ class ChatScroller extends LitElement { } async newListMessages(newMessages, message) { - let data = [] const copy = [...newMessages] copy.forEach(newMessage => { @@ -341,7 +340,10 @@ class ChatScroller extends LitElement { } }); - + // const getCount = await parentEpml.request('apiCall', { + // type: 'api', + // url: `/chat/messages?involving=${window.parent.reduxStore.getState().app.selectedAddress.address}&involving=${this._chatId}&limit=${chatLimit}&reverse=true&before=${scrollElement.messageObj.timestamp}&haschatreference=false&encoding=BASE64` + // }) this.messagesToRender = data this.clearLoaders() this.requestUpdate() @@ -356,9 +358,8 @@ class ChatScroller extends LitElement { } - async newListMessagesUnreadMessages(newMessages, message, lastReadMessageTimestamp) { - const viewElement = this.shadowRoot.querySelector("#viewElement"); - + async newListMessagesUnreadMessages(newMessages, message, lastReadMessageTimestamp, count) { + let data = []; const copy = [...newMessages]; @@ -391,7 +392,9 @@ class ChatScroller extends LitElement { }); } }); - + if(count > 0){ + this.disableAddingNewMessages = true + } this.messagesToRender = data; this.clearLoaders(); this.requestUpdate(); @@ -563,7 +566,6 @@ class ChatScroller extends LitElement { async replaceMessagesWithUpdateByArray(updatedMessagesArray) { - console.log({updatedMessagesArray, messages: this.messagesToRender}) let previousScrollTop; let previousScrollHeight; @@ -596,7 +598,7 @@ class ChatScroller extends LitElement { } else if (this.messages.type === 'initialLastSeen') { - this.newListMessagesUnreadMessages(this.messages.messages, 'initialLastSeen', this.messages.lastReadMessageTimestamp) + this.newListMessagesUnreadMessages(this.messages.messages, 'initialLastSeen', this.messages.lastReadMessageTimestamp, this.messages.count) } else if (this.messages.type === 'new') this.addNewMessages(this.messages.messages) diff --git a/plugins/plugins/core/components/ChatTextEditor copy.js b/plugins/plugins/core/components/ChatTextEditor copy.js index 15b32d8f..c7a3ecdc 100644 --- a/plugins/plugins/core/components/ChatTextEditor copy.js +++ b/plugins/plugins/core/components/ChatTextEditor copy.js @@ -163,7 +163,6 @@ class ChatTextEditor extends LitElement { } render() { - console.log('here here') let scrollHeightBool = false; try { if (this.chatMessageInput && this.chatMessageInput.contentDocument.body.scrollHeight > 60 && this.shadowRoot.querySelector(".chat-editor").contentDocument.body.querySelector("#chatbarId").innerHTML.trim() !== "") { From cdcacaf4cffb187eab450f0904ec5c90a6851b0d Mon Sep 17 00:00:00 2001 From: PhilReact Date: Fri, 15 Sep 2023 00:00:58 -0500 Subject: [PATCH 26/57] fix old message sorting --- plugins/plugins/core/components/ChatPage.js | 24 ++------------------- 1 file changed, 2 insertions(+), 22 deletions(-) diff --git a/plugins/plugins/core/components/ChatPage.js b/plugins/plugins/core/components/ChatPage.js index 3a3c59c0..a6731607 100644 --- a/plugins/plugins/core/components/ChatPage.js +++ b/plugins/plugins/core/components/ChatPage.js @@ -2841,17 +2841,7 @@ class ChatPage extends LitElement { let list = [...decodeMsgs] - await new Promise((res, rej) => { - - this.webWorkerSortMessages.postMessage({list}); - - this.webWorkerSortMessages.onmessage = e => { - - list = e.data - res() - - } - }) + this.messagesRendered = { messages: list, @@ -2902,17 +2892,7 @@ class ChatPage extends LitElement { })); let list = [...decodeMsgs] - await new Promise((res, rej) => { - - this.webWorkerSortMessages.postMessage({list}); - - this.webWorkerSortMessages.onmessage = e => { - - list = e.data - res() - - } - }) + this.messagesRendered = { messages: list, From 3ef035249bf00adb5ace8b40dbe17b2662b36586 Mon Sep 17 00:00:00 2001 From: PhilReact Date: Fri, 15 Sep 2023 00:10:40 -0500 Subject: [PATCH 27/57] added scroll bottom condition to blue message bar --- plugins/plugins/core/messaging/q-chat/q-chat.src.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/plugins/plugins/core/messaging/q-chat/q-chat.src.js b/plugins/plugins/core/messaging/q-chat/q-chat.src.js index 6a85446c..9edcaa76 100644 --- a/plugins/plugins/core/messaging/q-chat/q-chat.src.js +++ b/plugins/plugins/core/messaging/q-chat/q-chat.src.js @@ -908,7 +908,15 @@ class Chat extends LitElement { scrollToBottom() { const viewElement = this.shadowRoot.querySelector('chat-page').shadowRoot.querySelector('chat-scroller').shadowRoot.getElementById('viewElement') - viewElement.scroll({ top: viewElement.scrollHeight, left: 0, behavior: 'smooth' }) + + const chatScrollerElement = this.shadowRoot.querySelector('chat-page').shadowRoot.querySelector('chat-scroller') + if (chatScrollerElement && chatScrollerElement.disableAddingNewMessages) { + const chatPageElement = this.shadowRoot.querySelector('chat-page') + if(chatPageElement && chatPageElement.getLastestMessages) + chatPageElement.getLastestMessages() + } else { + viewElement.scroll({ top: viewElement.scrollHeight, left: 0, behavior: 'smooth' }) + } } showNewMessageBar() { From 0b613beed6523502c578ea5f7c34805e3f679b60 Mon Sep 17 00:00:00 2001 From: PhilReact Date: Fri, 15 Sep 2023 11:10:52 -0500 Subject: [PATCH 28/57] reduce message re-renders --- plugins/plugins/core/components/ChatScroller.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/plugins/plugins/core/components/ChatScroller.js b/plugins/plugins/core/components/ChatScroller.js index c24ba911..4b2b728f 100644 --- a/plugins/plugins/core/components/ChatScroller.js +++ b/plugins/plugins/core/components/ChatScroller.js @@ -684,7 +684,7 @@ class ChatScroller extends LitElement {
${repeat( formattedMessages, - (formattedMessage) => formattedMessage.id, // Use .id as the unique key for formattedMessage. + (formattedMessage) => formattedMessage.reference, // Use .id as the unique key for formattedMessage. (formattedMessage) => html` ${repeat( @@ -1004,7 +1004,8 @@ class MessageTemplate extends LitElement { listSeenMessages: { type: Array }, addSeenMessage: { attribute: false }, chatId: { type: String }, - isInProgress: {type: Boolean} + isInProgress: {type: Boolean}, + id: {type: String} } } @@ -1113,6 +1114,13 @@ class MessageTemplate extends LitElement { }, 60000) } + shouldUpdate(changedProperties){ + if (changedProperties.has('messageObj')) { + return true + } + return false + } + clearConsole() { if (!isElectron()) { } else { From 78942e6bd9a3428f962ef5170ff8b5f8e9defc00 Mon Sep 17 00:00:00 2001 From: PhilReact Date: Fri, 15 Sep 2023 21:51:35 -0500 Subject: [PATCH 29/57] fixed a couple bugs --- plugins/plugins/core/components/ChatPage.js | 21 +- .../core/components/ChatScroller-css.js | 3 - .../plugins/core/components/ChatScroller.js | 8 +- plugins/plugins/core/components/TipUser.js | 285 +++++++----------- 4 files changed, 130 insertions(+), 187 deletions(-) diff --git a/plugins/plugins/core/components/ChatPage.js b/plugins/plugins/core/components/ChatPage.js index a6731607..7f8a5f8d 100644 --- a/plugins/plugins/core/components/ChatPage.js +++ b/plugins/plugins/core/components/ChatPage.js @@ -55,10 +55,10 @@ const parentEpml = new Epml({ type: 'WINDOW', source: window.parent }) export const queue = new RequestQueue(); -export const chatLimit = 40 -export const chatLimitHalf = 20 +export const chatLimit = 20 +export const chatLimitHalf = 10 -export const totalMsgCount = 120 +export const totalMsgCount = 60 class ChatPage extends LitElement { static get properties() { return { @@ -2810,6 +2810,14 @@ class ChatPage extends LitElement { } async getOldMessage(scrollElement) { + if(!scrollElement || !scrollElement.messageObj || !scrollElement.messageObj.timestamp){ + this.messagesRendered = { + messages: [], + type: 'old', + el: scrollElement + } + return + } if (this.isReceipient) { const getInitialMessages = await parentEpml.request('apiCall', { type: 'api', @@ -2910,6 +2918,13 @@ class ChatPage extends LitElement { } } async getAfterMessages(scrollElement) { + if(!scrollElement || !scrollElement.messageObj || !scrollElement.messageObj.timestamp){ + this.messagesRendered = { + messages: [], + type: 'new', + } + return + } const timestamp = scrollElement.messageObj.timestamp if (this.isReceipient) { diff --git a/plugins/plugins/core/components/ChatScroller-css.js b/plugins/plugins/core/components/ChatScroller-css.js index 378ccac8..7c337d70 100644 --- a/plugins/plugins/core/components/ChatScroller-css.js +++ b/plugins/plugins/core/components/ChatScroller-css.js @@ -260,7 +260,6 @@ export const chatStyles = css` .message-parent { padding: 3px; background: rgba(245, 245, 245, 0); - transition: all 0.1s ease-in-out; } .message-parent:hover { @@ -368,7 +367,6 @@ export const chatStyles = css` background:#fff; color: #000; text-align: center; - box-shadow: rgba(149, 157, 165, 0.2) 0px 8px 24px; font-size: 12px; z-index: 5; white-space: nowrap; @@ -414,7 +412,6 @@ export const chatStyles = css` width: 150px; height: 32px; padding: 3px 8px; - box-shadow: rgba(77, 77, 82, 0.2) 0px 7px 29px 0px; } .block-user:hover { diff --git a/plugins/plugins/core/components/ChatScroller.js b/plugins/plugins/core/components/ChatScroller.js index 4b2b728f..b44b4a2e 100644 --- a/plugins/plugins/core/components/ChatScroller.js +++ b/plugins/plugins/core/components/ChatScroller.js @@ -593,6 +593,7 @@ class ChatScroller extends LitElement { async updated(changedProperties) { if (changedProperties && changedProperties.has('messages')) { if (this.messages.type === 'initial') { + this.addNewMessages(this.messages.messages, 'initial') @@ -1118,6 +1119,12 @@ class MessageTemplate extends LitElement { if (changedProperties.has('messageObj')) { return true } + if(changedProperties.has('showBlockAddressIcon')){ + return true + } + if(changedProperties.has('openDialogBlockUser')){ + return true + } return false } @@ -1130,7 +1137,6 @@ class MessageTemplate extends LitElement { } render() { - const hidemsg = this.hideMessages let message = "" let messageVersion2 = "" diff --git a/plugins/plugins/core/components/TipUser.js b/plugins/plugins/core/components/TipUser.js index 28220b18..51434378 100644 --- a/plugins/plugins/core/components/TipUser.js +++ b/plugins/plugins/core/components/TipUser.js @@ -99,211 +99,136 @@ export class TipUser extends LitElement { } async sendQort() { - const amount = this.shadowRoot.getElementById("amountInput").value - let recipient = this.userName - this.sendMoneyLoading = true - this.btnDisable = true - - 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 + const amount = this.shadowRoot.getElementById("amountInput").value; + const recipient = this.userName; + + this.sendMoneyLoading = true; + this.btnDisable = true; + + // Helper function to reset loading and button state + const resetState = () => { + this.sendMoneyLoading = false; + this.btnDisable = false; } - 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) + parseFloat(0.011) > parseFloat(this.walletBalance)) { + resetState(); + const snack1string = get("chatpage.cchange51"); + parentEpml.request('showSnackBar', `${snack1string}`); + return false; } - - 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) - if (myNameRes !== false) { - let myNameAddress = myNameRes.owner - let myTransaction = await makeTransactionRequest(myNameAddress, lastRef) - getTxnRequestResponse(myTransaction) - } else { - console.error(this.renderReceiverText()) - this.errorMessage = this.renderReceiverText() - this.sendMoneyLoading = false - this.btnDisable = false - } - } + + if (parseFloat(amount) <= 0) { + resetState(); + const snack2string = get("chatpage.cchange52"); + parentEpml.request('showSnackBar', `${snack2string}`); + return false; } - + + if (recipient.length === 0) { + resetState(); + const snack3string = get("chatpage.cchange53"); + parentEpml.request('showSnackBar', `${snack3string}`); + return false; + } + + const validateName = async (receiverName) => { + const myNameRes = await parentEpml.request('apiCall', { + type: 'api', + url: `/names/${receiverName}` + }); + return myNameRes.error === 401 ? false : myNameRes; + }; + + const validateAddress = async (receiverAddress) => { + return await window.parent.validateAddress(receiverAddress); + }; + const getName = async (recipient) => { try { const getNames = await parentEpml.request("apiCall", { type: "api", - url: `/names/address/${recipient}`, + url: `/names/address/${recipient}` }); - - if (getNames?.length > 0) { - return getNames[0].name - } else { - return '' - } + return getNames?.length > 0 ? getNames[0].name : ''; } catch (error) { - return "" + return ""; } - } - + }; + const makeTransactionRequest = async (receiver, lastRef) => { - let myReceiver = receiver - let mylastRef = lastRef - 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', { + const dialogAmount = get("transactions.amount"); + const dialogAddress = get("login.address"); + const dialogName = get("login.name"); + const dialogTo = get("transactions.to"); + const recipientName = await getName(receiver); + + return await parentEpml.request('transaction', { type: 2, nonce: this.myAddress.nonce, params: { - recipient: myReceiver, + recipient: receiver, recipientName: recipientName, amount: amount, - lastReference: mylastRef, - fee: 0.001, - dialogamount: dialogamount, - dialogto: dialogto, + lastReference: lastRef, + fee: this.qortPaymentFee, + dialogAmount, + 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) + this.errorMessage = txnResponse.message; + resetState(); + 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 + this.shadowRoot.getElementById('amountInput').value = ''; + this.errorMessage = ''; + this.successMessage = this.renderSuccessText(); + resetState(); setTimeout(() => { - this.setOpenTipUser(false) - this.successMessage = "" - }, 3000) + this.setOpenTipUser(false); + this.successMessage = ""; + }, 3000); } else { - this.errorMessage = txnResponse.data.message - this.sendMoneyLoading = false - this.btnDisable = false - throw new Error(txnResponse) + this.errorMessage = txnResponse.data.message; + resetState(); + throw new Error(txnResponse); } - } - 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) + }; + + const validateReceiver = async (recipient) => { + let lastRef = await this.getLastRef(); + let isAddress; + + try { + isAddress = await validateAddress(recipient); + } catch (err) { + isAddress = false; + } + + if (isAddress) { + const myTransaction = await makeTransactionRequest(recipient, lastRef); + getTxnRequestResponse(myTransaction); + } else { + const myNameRes = await validateName(recipient); + if (myNameRes !== false) { + const myTransaction = await makeTransactionRequest(myNameRes.owner, lastRef); + getTxnRequestResponse(myTransaction); + } else { + this.errorMessage = this.renderReceiverText(); + resetState(); + } + } + }; + + await validateReceiver(recipient); } + render() { return html` From 94e9e8157019f15dc11e590dd2e4731d17fd337e Mon Sep 17 00:00:00 2001 From: PhilReact Date: Fri, 15 Sep 2023 23:42:42 -0500 Subject: [PATCH 30/57] fix shouldupdated for messages --- .../plugins/core/components/ChatScroller.js | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/plugins/plugins/core/components/ChatScroller.js b/plugins/plugins/core/components/ChatScroller.js index b44b4a2e..da55aaa1 100644 --- a/plugins/plugins/core/components/ChatScroller.js +++ b/plugins/plugins/core/components/ChatScroller.js @@ -1125,6 +1125,24 @@ class MessageTemplate extends LitElement { if(changedProperties.has('openDialogBlockUser')){ return true } + if(changedProperties.has('viewImage')){ + return true + } + if(changedProperties.has('isImageLoaded')){ + return true + } + if(changedProperties.has('openDialogImage')){ + return true + } + if(changedProperties.has('openDialogPrivateMessage')){ + return true + } + if(changedProperties.has('openDialogGif')){ + return true + } + if(changedProperties.has('isGifLoaded')){ + return true + } return false } @@ -1437,8 +1455,9 @@ class MessageTemplate extends LitElement { ${image && !isImageDeleted && !this.viewImage && this.myAddress !== this.messageObj.sender ? html`
{ + console.log('clicked') this.viewImage = true - this.addSeenMessage(this.messageObj.signature) + // this.addSeenMessage(this.messageObj.signature) }} class=${[`image-container`, !this.isImageLoaded ? 'defaultSize' : ''].join(' ')} style=${this.isFirstMessage && "margin-top: 10px;"}> From 1b80a6447ce52ec694bdf4454f3f35cf61279f9c Mon Sep 17 00:00:00 2001 From: PhilReact Date: Sat, 16 Sep 2023 00:50:25 -0500 Subject: [PATCH 31/57] tab parent to flex from block --- core/src/components/show-plugin.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/components/show-plugin.js b/core/src/components/show-plugin.js index 00cd7882..836ec4c3 100644 --- a/core/src/components/show-plugin.js +++ b/core/src/components/show-plugin.js @@ -101,7 +101,7 @@ class ShowPlugin extends connect(store)(LitElement) { } .showIframe { - display: block; + display: flex; position: relative; zIndex: 1; } From 3dd81dffc1ecb4528bd832cfa6871611701eaae6 Mon Sep 17 00:00:00 2001 From: PhilReact Date: Sat, 16 Sep 2023 01:29:47 -0500 Subject: [PATCH 32/57] fix z index --- core/src/components/show-plugin.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/components/show-plugin.js b/core/src/components/show-plugin.js index 836ec4c3..ab3b46da 100644 --- a/core/src/components/show-plugin.js +++ b/core/src/components/show-plugin.js @@ -97,13 +97,13 @@ class ShowPlugin extends connect(store)(LitElement) { .hideIframe { display: none; position: absolute; - zIndex: -10; + z-Index: -10; } .showIframe { display: flex; position: relative; - zIndex: 1; + z-Index: 1; } .tabs { From 6dde64f804755078cc0ab4cde1a8b93fb248333a Mon Sep 17 00:00:00 2001 From: PhilReact Date: Sat, 16 Sep 2023 22:07:34 -0500 Subject: [PATCH 33/57] fix levels --- .../plugins/core/components/ChatScroller.js | 1 - .../plugins/core/components/LevelFounder.js | 34 +++++++++---------- 2 files changed, 16 insertions(+), 19 deletions(-) diff --git a/plugins/plugins/core/components/ChatScroller.js b/plugins/plugins/core/components/ChatScroller.js index da55aaa1..2c66f5ea 100644 --- a/plugins/plugins/core/components/ChatScroller.js +++ b/plugins/plugins/core/components/ChatScroller.js @@ -1455,7 +1455,6 @@ class MessageTemplate extends LitElement { ${image && !isImageDeleted && !this.viewImage && this.myAddress !== this.messageObj.sender ? html`
{ - console.log('clicked') this.viewImage = true // this.addSeenMessage(this.messageObj.signature) }} diff --git a/plugins/plugins/core/components/LevelFounder.js b/plugins/plugins/core/components/LevelFounder.js index 3c8224e9..635dda40 100644 --- a/plugins/plugins/core/components/LevelFounder.js +++ b/plugins/plugins/core/components/LevelFounder.js @@ -4,14 +4,17 @@ import { Epml } from '../../../epml.js' import snackbar from './snackbar.js' import { use, get, translate, translateUnsafeHTML, registerTranslateConfig } from 'lit-translate' import '@polymer/paper-tooltip/paper-tooltip.js' +import { RequestQueue } from '../../utils/queue.js' const parentEpml = new Epml({ type: 'WINDOW', source: window.parent }) + const queue = new RequestQueue(3); + + class LevelFounder extends LitElement { static get properties() { return { checkleveladdress: { type: String }, - selectedAddress: { type: String }, config: { type: Object }, memberInfo: { type: Array } } @@ -39,7 +42,7 @@ class LevelFounder extends LitElement { } h2, h3, h4, h5 { - color:# var(--black); + color: var(--black); font-weight: 400; } @@ -88,7 +91,6 @@ class LevelFounder extends LitElement { constructor() { super() this.memberInfo = [] - this.selectedAddress = window.parent.reduxStore.getState().app.selectedAddress.address } render() { @@ -101,24 +103,20 @@ class LevelFounder extends LitElement { } firstUpdated() { - - parentEpml.ready().then(() => { - parentEpml.subscribe('selected_address', async selectedAddress => { - this.selectedAddress = {} - selectedAddress = JSON.parse(selectedAddress) - if (!selectedAddress || Object.entries(selectedAddress).length === 0) return - this.selectedAddress = selectedAddress - }) - }) - parentEpml.imReady() + queue.push(() => this.checkAddressInfo()); } async checkAddressInfo() { - // let toCheck = this.checkleveladdress - // const memberInfo = await parentEpml.request('apiCall', { - // url: `/addresses/${toCheck}` - // }) - // this.memberInfo = memberInfo + try { + let toCheck = this.checkleveladdress + const memberInfo = await parentEpml.request('apiCall', { + url: `/addresses/${toCheck}` + }) + this.memberInfo = memberInfo + } catch (error) { + console.error(error) + } + } renderFounder() { From cb886e29e15893eef505cd23ad6b655f555a6482 Mon Sep 17 00:00:00 2001 From: PhilReact Date: Sat, 16 Sep 2023 23:12:08 -0500 Subject: [PATCH 34/57] fix q-mail tab label when clicking icon in ui --- core/src/components/notification-view/notification-bell.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/components/notification-view/notification-bell.js b/core/src/components/notification-view/notification-bell.js index bb3913c5..cf78f907 100644 --- a/core/src/components/notification-view/notification-bell.js +++ b/core/src/components/notification-view/notification-bell.js @@ -158,7 +158,7 @@ class NotificationBell extends connect(store)(LitElement) { url: `qdn/browser/index.html${query}`, id: 'q-mail-notification', myPlugObj: { - "url": "qapps", + "url": "myapp", "domain": "core", "page": `qdn/browser/index.html${query}`, "title": "Q-Mail", @@ -196,7 +196,7 @@ class NotificationBell extends connect(store)(LitElement) { url: `qdn/browser/index.html${query}`, id: 'q-mail-notification', myPlugObj: { - "url": "qapps", + "url": "myapp", "domain": "core", "page": `qdn/browser/index.html${query}`, "title": "Q-Mail", From 28c58cb2496b46754dc9422c3c210c3ddcb9e71b Mon Sep 17 00:00:00 2001 From: PhilReact Date: Sun, 17 Sep 2023 22:57:25 -0500 Subject: [PATCH 35/57] added custom select input for port selection --- core/language/us.json | 2 + .../functional-components/settings-page.js | 1132 +++++++++++------ core/src/redux/app/actions/node-config.js | 24 +- core/src/redux/app/app-action-types.js | 2 + core/src/redux/app/app-reducer.js | 8 +- core/src/redux/app/reducers/manage-node.js | 23 + 6 files changed, 801 insertions(+), 390 deletions(-) diff --git a/core/language/us.json b/core/language/us.json index c2c7157c..128d1c66 100644 --- a/core/language/us.json +++ b/core/language/us.json @@ -200,6 +200,8 @@ "snack3": "Successfully added and saved custom node", "snack4": "Nodes successfully saved as", "snack5": "Nodes successfully imported", + "snack6": "Successfully removed custom node", + "snack7": "Successfully edited custom node", "exp1": "Export Private Master Key", "exp2": "Export Master Key", "exp3": "Export", diff --git a/core/src/functional-components/settings-page.js b/core/src/functional-components/settings-page.js index 31b3b330..d414fd1e 100644 --- a/core/src/functional-components/settings-page.js +++ b/core/src/functional-components/settings-page.js @@ -1,419 +1,777 @@ -import { LitElement, html, css } from 'lit' -import { connect } from 'pwa-helpers' -import { store } from '../store.js' -import { doAddNode, doSetNode, doLoadNodeConfig } from '../redux/app/app-actions.js' -import { use, get, translate, translateUnsafeHTML, registerTranslateConfig } from 'lit-translate' -import snackbar from './snackbar.js' -import '../components/language-selector.js' -import '../custom-elements/frag-file-input.js' -import FileSaver from 'file-saver' +import { LitElement, html, css } from 'lit'; +import { connect } from 'pwa-helpers'; +import { store } from '../store.js'; +import { + doAddNode, + doSetNode, + doLoadNodeConfig, + doRemoveNode, + doEditNode, +} from '../redux/app/app-actions.js'; +import { + use, + get, + translate, + translateUnsafeHTML, + registerTranslateConfig, +} from 'lit-translate'; +import snackbar from './snackbar.js'; +import '../components/language-selector.js'; +import '../custom-elements/frag-file-input.js'; +import FileSaver from 'file-saver'; -import '@material/mwc-dialog' -import '@material/mwc-button' -import '@material/mwc-select' -import '@material/mwc-textfield' -import '@material/mwc-icon' -import '@material/mwc-list/mwc-list-item.js' +import '@material/mwc-dialog'; +import '@material/mwc-button'; +import '@material/mwc-select'; +import '@material/mwc-textfield'; +import '@material/mwc-icon'; +import '@material/mwc-list/mwc-list-item.js'; registerTranslateConfig({ - loader: lang => fetch(`/language/${lang}.json`).then(res => res.json()) -}) + loader: (lang) => fetch(`/language/${lang}.json`).then((res) => res.json()), +}); -const checkLanguage = localStorage.getItem('qortalLanguage') +const checkLanguage = localStorage.getItem('qortalLanguage'); if (checkLanguage === null || checkLanguage.length === 0) { - localStorage.setItem('qortalLanguage', 'us') - use('us') + localStorage.setItem('qortalLanguage', 'us'); + use('us'); } else { - use(checkLanguage) + use(checkLanguage); } -let settingsDialog +let settingsDialog; class SettingsPage extends connect(store)(LitElement) { - static get properties() { - return { - lastSelected: { type: Number }, - nodeConfig: { type: Object }, - theme: { type: String, reflect: true }, - nodeIndex: { type: Number } - } - } + static get properties() { + return { + lastSelected: { type: Number }, + nodeConfig: { type: Object }, + theme: { type: String, reflect: true }, + nodeIndex: { type: Number },e + isBeingEdited: { type: Boolean }, + dropdownOpen: { type: Boolean }, + }; + } - static get styles() { - return css` - * { - --mdc-theme-primary: rgb(3, 169, 244); - --mdc-theme-secondary: var(--mdc-theme-primary); - --mdc-dialog-content-ink-color: var(--black); - --mdc-theme-surface: var(--white); - --mdc-theme-text-primary-on-background: var(--black); - --mdc-dialog-min-width: 300px; - --mdc-dialog-max-width: 650px; - --mdc-dialog-max-height: 700px; + static get styles() { + return css` + * { + --mdc-theme-primary: rgb(3, 169, 244); + --mdc-theme-secondary: var(--mdc-theme-primary); + --mdc-dialog-content-ink-color: var(--black); + --mdc-theme-surface: var(--white); + --mdc-theme-text-primary-on-background: var(--black); + --mdc-dialog-min-width: 300px; + --mdc-dialog-max-width: 650px; + --mdc-dialog-max-height: 700px; + --mdc-list-item-text-width: 100%; + } + + #main { + width: 210px; + display: flex; + align-items: center; + } + + .globe { + color: var(--black); + --mdc-icon-size: 36px; + } + + span.name { + display: inline-block; + width: 150px; + font-weight: 600; + color: #03a9f4; + border: 1px solid transparent; + } + + .red { + --mdc-theme-primary: red; + } + + .buttonred { + color: #f44336; + } + + .buttongreen { + color: #03c851; + } + .buttonBlue { + color: #03a9f4; } + .floatleft { + float: left; + } - #main { - width: 210px; + .floatright { + float: right; + } + .list-parent { + display: flex; + align-items: center; + justify-content: space-between; + width: 100%; + } + #customSelect { + position: relative; + border: 1px solid #ccc; + cursor: pointer; + background: var(--plugback); + } + + #customSelect .selected { + padding: 10px; + display: flex; + align-items: center; + justify-content: space-between; + } + + #customSelect ul { + position: absolute; + top: 100%; + left: 0; + list-style: none; + margin: 0; + padding: 0; + border: 1px solid #ccc; + display: none; + background: var(--plugback); + width: 100%; + box-sizing: border-box; + z-index: 10; + } + + #customSelect ul.open { + display: block; + } + + #customSelect ul li { + padding: 10px; + transition: 0.2s all; + } + + #customSelect ul li:hover { + background-color: var(--graylight); + } + .selected-left-side{ display: flex; align-items: center; } + `; + } - .globe { - color: var(--black); - --mdc-icon-size: 36px; - } + constructor() { + super(); + this.nodeConfig = {}; + this.nodeIndex = localStorage.getItem('mySelectedNode'); + this.theme = localStorage.getItem('qortalTheme') + ? localStorage.getItem('qortalTheme') + : 'light'; + this.isBeingEdited = false; + this.isBeingEditedIndex = null; + this.dropdownOpen = false; + } - span.name { - display: inline-block; - width: 150px; - font-weight: 600; - color: #03a9f4; - border: 1px solid transparent; - } + render() { + console.log('this.dropdownOpen', this.dropdownOpen); + return html` + +
+

${translate('settings.settings')}

+
+
+
+
+
+
+
+ link + + ${ + this.selectedItem + ? html` +
+ + ${this.selectedItem + .name} + ${this.selectedItem + .protocol + + '://' + + this.selectedItem.domain + + ':' + + this.selectedItem + .port} +
+ ` + : 'Please select an option' + } +
+ expand_more +
+
    + ${this.nodeConfig.knownNodes.map( + (n, index) => html` +
  • +
    +
    + ${n.name} + ${n.protocol + + '://' + + n.domain + + ':' + + n.port} +
    +
    + edit + remove +
    +
    +
  • + ` + )} +
+
- .floatright { - float: right; - } - ` - } + - constructor() { - super() - this.nodeConfig = {} - this.nodeIndex = localStorage.getItem('mySelectedNode') - this.theme = localStorage.getItem('qortalTheme') ? localStorage.getItem('qortalTheme') : 'light' - } +

+ ${translate('settings.nodehint')} +

+
+ add${translate('settings.addcustomnode')} +
+
+ remove${translate('settings.deletecustomnode')} +
+
+
+ ${this.renderExportNodesListButton()} +
+
+ ${this.renderImportNodesListButton()} +
+

+
+
+
+
+
+
+ language  +
+
+
+ + ${translate('general.close')} + +
- render() { - return html` - -
-

${translate("settings.settings")}

-
-
-
-
- - ${this.nodeConfig.knownNodes.map((n, index) => html` - - ${n.name} - ${n.protocol + '://' + n.domain + ':' + n.port} - - `)} - -

${translate("settings.nodehint")}

-
- add${translate("settings.addcustomnode")} -
-
- remove${translate("settings.deletecustomnode")} -
-
-
${this.renderExportNodesListButton()}
${this.renderImportNodesListButton()}
-

-
-
-

-
-
- language  -
-
-
- - ${translate("general.close")} - -
+ +
+

${translate('settings.addcustomnode')}

+
+
+
+ +
+ + http + https + +
+ + + + ${translate('general.close')} + + + ${translate('settings.addandsave')} + +
- -
-

${translate("settings.addcustomnode")}

-
-
-
- -
- - http - https - -
- - - - ${translate("general.close")} - - - ${translate("settings.addandsave")} - -
- - -
-

${translate("settings.import")}

-
-
-
-
- -

${translate("walletpage.wchange56")}

-
${translate("settings.warning")}
-
- - ${translate("general.close")} - -
- ` - } - - firstUpdated() { - const checkNode = localStorage.getItem('mySelectedNode') - if (checkNode === null || checkNode.length === 0) { - localStorage.setItem('mySelectedNode', 0) - } else { - } - } - - show() { - this.shadowRoot.getElementById('settingsDialog').show() - } - - close() { - this.shadowRoot.getElementById('settingsDialog').close() - } - - removeList() { - localStorage.removeItem("myQortalNodes") - - const obj1 = { - name: 'Local Node', - protocol: 'http', - domain: '127.0.0.1', - port: 12391, - enableManagement: true - } - - const obj2 = { - name: 'Local Testnet', - protocol: 'http', - domain: '127.0.0.1', - port: 62391, - enableManagement: true - } - - var renewNodes = []; - renewNodes.push(obj1,obj2) - localStorage.setItem('myQortalNodes', JSON.stringify(renewNodes)) - - let snack1string = get("settings.snack1") - snackbar.add({ - labelText: `${snack1string}`, - dismiss: true - }) - - localStorage.removeItem('mySelectedNode') - localStorage.setItem('mySelectedNode', 0) - - store.dispatch(doLoadNodeConfig()) - } - - nodeSelected(e) { - const selectedNodeIndex = this.shadowRoot.getElementById('nodeSelect').value - const selectedNode = this.nodeConfig.knownNodes[selectedNodeIndex] - const selectedNodeUrl = `${selectedNode.protocol + '://' + selectedNode.domain + ':' + selectedNode.port}` - - const index = parseInt(selectedNodeIndex) - if (isNaN(index)) return - - store.dispatch(doSetNode(selectedNodeIndex)) - - localStorage.removeItem('mySelectedNode') - localStorage.setItem('mySelectedNode', selectedNodeIndex) - - let snack2string = get("settings.snack2") - snackbar.add({ - labelText: `${snack2string} : ${selectedNodeUrl}`, - dismiss: true - }) - - this.shadowRoot.querySelector('#settingsDialog').close() - } - - addNode() { - const nameInput = this.shadowRoot.getElementById('nameInput').value - const protocolList = this.shadowRoot.getElementById('protocolList').value - const domainInput = this.shadowRoot.getElementById('domainInput').value - const portInput = this.shadowRoot.getElementById('portInput').value - - if (protocolList.length >= 4 && domainInput.length >= 3 && portInput.length >= 2) { - const nodeObject = { - name: nameInput, - protocol: protocolList, - domain: domainInput, - port: portInput, - enableManagement: true - } - - store.dispatch(doAddNode(nodeObject)) - - const haveNodes = JSON.parse(localStorage.getItem('myQortalNodes')) - - if (haveNodes === null || haveNodes.length === 0) { - - var savedNodes = []; - savedNodes.push(nodeObject); - localStorage.setItem('myQortalNodes', JSON.stringify(savedNodes)) - - let snack3string = get("settings.snack3") - snackbar.add({ - labelText: `${snack3string}`, - dismiss: true - }) - - this.shadowRoot.getElementById('nameInput').value = '' - this.shadowRoot.getElementById('protocolList').value = '' - this.shadowRoot.getElementById('domainInput').value = '' - this.shadowRoot.getElementById('portInput').value = '' - - this.shadowRoot.querySelector('#addNodeDialog').close() - - } else { - - var stored = JSON.parse(localStorage.getItem('myQortalNodes')); - stored.push(nodeObject); - localStorage.setItem('myQortalNodes', JSON.stringify(stored)); - - let snack3string = get("settings.snack3") - snackbar.add({ - labelText: `${snack3string}`, - dismiss: true - }) - - this.shadowRoot.getElementById('nameInput').value = '' - this.shadowRoot.getElementById('protocolList').value = '' - this.shadowRoot.getElementById('domainInput').value = '' - this.shadowRoot.getElementById('portInput').value = '' - - this.shadowRoot.querySelector('#addNodeDialog').close() - } - } - } - - openImportNodesDialog() { - this.shadowRoot.querySelector("#importQortalNodesListDialog").show() - } - - closeImportNodesDialog() { - this.shadowRoot.querySelector("#importQortalNodesListDialog").close() - } - - renderExportNodesListButton() { - return html` - - ` - } - - exportQortalNodesList() { - let nodelist = "" - const qortalNodesList = JSON.stringify(localStorage.getItem("myQortalNodes")) - const qortalNodesListSave = JSON.parse((qortalNodesList) || "[]") - const blob = new Blob([qortalNodesListSave], { type: 'text/plain;charset=utf-8' }) - nodelist = "qortal.nodes" - this.saveFileToDisk(blob, nodelist) - } - - async saveFileToDisk(blob, fileName) { - try { - const fileHandle = await self.showSaveFilePicker({ - suggestedName: fileName, - types: [{ - description: "File", - }] - }) - const writeFile = async (fileHandle, contents) => { - const writable = await fileHandle.createWritable() - await writable.write(contents) - await writable.close() - } - writeFile(fileHandle, blob).then(() => console.log("FILE SAVED")) - let snack4string = get("settings.snack4") - snackbar.add({ - labelText: `${snack4string} qortal.nodes`, - dismiss: true - }) - } catch (error) { - if (error.name === 'AbortError') { - return - } - FileSaver.saveAs(blob, fileName) - } - } - - renderImportNodesListButton() { - return html` - - ` - } - - async importQortalNodesList(file) { - localStorage.removeItem("myQortalNodes") - const newItems = JSON.parse((file) || "[]") - localStorage.setItem("myQortalNodes", JSON.stringify(newItems)) - this.shadowRoot.querySelector('#importQortalNodesListDialog').close() - - let snack5string = get("settings.snack5") - snackbar.add({ - labelText: `${snack5string}`, - dismiss: true - }) - - localStorage.removeItem('mySelectedNode') - localStorage.setItem('mySelectedNode', 0) - - store.dispatch(doLoadNodeConfig()) - } - - stateChanged(state) { - this.config = state.config - this.nodeConfig = state.app.nodeConfig + +
+

${translate('settings.import')}

+
+
+
+
+ +

+ ${translate('walletpage.wchange56')} +

+
+ ${translate('settings.warning')} +
+
+ + ${translate('general.close')} + +
+ `; + } -window.customElements.define('settings-page', SettingsPage) + firstUpdated() { + const checkNode = localStorage.getItem('mySelectedNode'); + if (checkNode === null || checkNode.length === 0) { + localStorage.setItem('mySelectedNode', 0); + } else { + } + } -const settings = document.createElement('settings-page') -settingsDialog = document.body.appendChild(settings) + toggleDropdown() { + this.dropdownOpen = !this.dropdownOpen; + } -export default settingsDialog + handleBlur(event) { + if ( + !this.shadowRoot + .querySelector('#customSelect') + .contains(event.relatedTarget) + ) { + this.dropdownOpen = false; + } + } + focusOnCustomSelect() { + const customSelect = this.shadowRoot.querySelector('#customSelect'); + if (customSelect) { + customSelect.focus(); + } + } + + handleSelection(event, node, index) { + event.stopPropagation(); + + this.selectedItem = node; + this.dropdownOpen = false; + this.requestUpdate(); + this.nodeSelected(index); + } + + show() { + this.shadowRoot.getElementById('settingsDialog').show(); + } + + close() { + this.shadowRoot.getElementById('settingsDialog').close(); + } + + removeList() { + localStorage.removeItem('myQortalNodes'); + + const obj1 = { + name: 'Local Node', + protocol: 'http', + domain: '127.0.0.1', + port: 12391, + enableManagement: true, + }; + + const obj2 = { + name: 'Local Testnet', + protocol: 'http', + domain: '127.0.0.1', + port: 62391, + enableManagement: true, + }; + + var renewNodes = []; + renewNodes.push(obj1, obj2); + localStorage.setItem('myQortalNodes', JSON.stringify(renewNodes)); + + let snack1string = get('settings.snack1'); + snackbar.add({ + labelText: `${snack1string}`, + dismiss: true, + }); + + localStorage.removeItem('mySelectedNode'); + localStorage.setItem('mySelectedNode', 0); + + store.dispatch(doLoadNodeConfig()); + } + + nodeSelected(selectedNodeIndex) { + const selectedNode = this.nodeConfig.knownNodes[selectedNodeIndex]; + const selectedNodeUrl = `${ + selectedNode.protocol + + '://' + + selectedNode.domain + + ':' + + selectedNode.port + }`; + + const index = parseInt(selectedNodeIndex); + if (isNaN(index)) return; + + store.dispatch(doSetNode(selectedNodeIndex)); + + localStorage.removeItem('mySelectedNode'); + localStorage.setItem('mySelectedNode', selectedNodeIndex); + + let snack2string = get('settings.snack2'); + snackbar.add({ + labelText: `${snack2string} : ${selectedNodeUrl}`, + dismiss: true, + }); + + // this.shadowRoot.querySelector('#settingsDialog').close(); + } + + addNode(e) { + e.stopPropagation(); + if (this.isBeingEdited) { + this.editNode(this.isBeingEditedIndex); + return; + } + const nameInput = this.shadowRoot.getElementById('nameInput').value; + const protocolList = + this.shadowRoot.getElementById('protocolList').value; + const domainInput = this.shadowRoot.getElementById('domainInput').value; + const portInput = this.shadowRoot.getElementById('portInput').value; + + if ( + protocolList.length >= 4 && + domainInput.length >= 3 && + portInput.length >= 2 + ) { + const nodeObject = { + name: nameInput, + protocol: protocolList, + domain: domainInput, + port: portInput, + enableManagement: true, + }; + + store.dispatch(doAddNode(nodeObject)); + + const haveNodes = JSON.parse(localStorage.getItem('myQortalNodes')); + + if (haveNodes === null || haveNodes.length === 0) { + var savedNodes = []; + savedNodes.push(nodeObject); + localStorage.setItem( + 'myQortalNodes', + JSON.stringify(savedNodes) + ); + + let snack3string = get('settings.snack3'); + snackbar.add({ + labelText: `${snack3string}`, + dismiss: true, + }); + + this.shadowRoot.getElementById('nameInput').value = ''; + this.shadowRoot.getElementById('protocolList').value = ''; + this.shadowRoot.getElementById('domainInput').value = ''; + this.shadowRoot.getElementById('portInput').value = ''; + + this.shadowRoot.querySelector('#addNodeDialog').close(); + } else { + var stored = JSON.parse(localStorage.getItem('myQortalNodes')); + stored.push(nodeObject); + localStorage.setItem('myQortalNodes', JSON.stringify(stored)); + + let snack3string = get('settings.snack3'); + snackbar.add({ + labelText: `${snack3string}`, + dismiss: true, + }); + + this.shadowRoot.getElementById('nameInput').value = ''; + this.shadowRoot.getElementById('protocolList').value = ''; + this.shadowRoot.getElementById('domainInput').value = ''; + this.shadowRoot.getElementById('portInput').value = ''; + + this.shadowRoot.querySelector('#addNodeDialog').close(); + } + } + } + + removeNode(event, index) { + event.stopPropagation(); + let stored = JSON.parse(localStorage.getItem('myQortalNodes')); + stored.splice(index, 1); + localStorage.setItem('myQortalNodes', JSON.stringify(stored)); + store.dispatch(doRemoveNode(index)); + let snack3string = get('settings.snack6'); + snackbar.add({ + labelText: `${snack3string}`, + dismiss: true, + }); + + this.shadowRoot.querySelector('#addNodeDialog').close(); + } + editNode(index) { + const nameInput = this.shadowRoot.getElementById('nameInput').value; + const protocolList = + this.shadowRoot.getElementById('protocolList').value; + const domainInput = this.shadowRoot.getElementById('domainInput').value; + const portInput = this.shadowRoot.getElementById('portInput').value; + + if ( + protocolList.length >= 4 && + domainInput.length >= 3 && + portInput.length >= 2 + ) { + const nodeObject = { + name: nameInput, + protocol: protocolList, + domain: domainInput, + port: portInput, + enableManagement: true, + }; + + let stored = JSON.parse(localStorage.getItem('myQortalNodes')); + const copyStored = [...stored]; + copyStored[index] = nodeObject; + localStorage.setItem('myQortalNodes', JSON.stringify(copyStored)); + store.dispatch(doEditNode(index, nodeObject)); + let snack3string = get('settings.snack7'); + snackbar.add({ + labelText: `${snack3string}`, + dismiss: true, + }); + this.shadowRoot.getElementById('nameInput').value = ''; + this.shadowRoot.getElementById('protocolList').value = ''; + this.shadowRoot.getElementById('domainInput').value = ''; + this.shadowRoot.getElementById('portInput').value = ''; + this.isBeingEdited = false; + this.isBeingEditedIndex = null; + + this.shadowRoot.querySelector('#addNodeDialog').close(); + } + } + + openImportNodesDialog() { + this.shadowRoot.querySelector('#importQortalNodesListDialog').show(); + } + + closeImportNodesDialog() { + this.shadowRoot.querySelector('#importQortalNodesListDialog').close(); + } + + renderExportNodesListButton() { + return html` + + `; + } + + exportQortalNodesList() { + let nodelist = ''; + const qortalNodesList = JSON.stringify( + localStorage.getItem('myQortalNodes') + ); + const qortalNodesListSave = JSON.parse(qortalNodesList || '[]'); + const blob = new Blob([qortalNodesListSave], { + type: 'text/plain;charset=utf-8', + }); + nodelist = 'qortal.nodes'; + this.saveFileToDisk(blob, nodelist); + } + + async saveFileToDisk(blob, fileName) { + try { + const fileHandle = await self.showSaveFilePicker({ + suggestedName: fileName, + types: [ + { + description: 'File', + }, + ], + }); + const writeFile = async (fileHandle, contents) => { + const writable = await fileHandle.createWritable(); + await writable.write(contents); + await writable.close(); + }; + writeFile(fileHandle, blob).then(() => console.log('FILE SAVED')); + let snack4string = get('settings.snack4'); + snackbar.add({ + labelText: `${snack4string} qortal.nodes`, + dismiss: true, + }); + } catch (error) { + if (error.name === 'AbortError') { + return; + } + FileSaver.saveAs(blob, fileName); + } + } + + renderImportNodesListButton() { + return html` + + `; + } + + async importQortalNodesList(file) { + localStorage.removeItem('myQortalNodes'); + const newItems = JSON.parse(file || '[]'); + localStorage.setItem('myQortalNodes', JSON.stringify(newItems)); + this.shadowRoot.querySelector('#importQortalNodesListDialog').close(); + + let snack5string = get('settings.snack5'); + snackbar.add({ + labelText: `${snack5string}`, + dismiss: true, + }); + + localStorage.removeItem('mySelectedNode'); + localStorage.setItem('mySelectedNode', 0); + + store.dispatch(doLoadNodeConfig()); + } + + stateChanged(state) { + this.config = state.config; + this.nodeConfig = state.app.nodeConfig; + } +} + +window.customElements.define('settings-page', SettingsPage); + +const settings = document.createElement('settings-page'); +settingsDialog = document.body.appendChild(settings); + +export default settingsDialog; diff --git a/core/src/redux/app/actions/node-config.js b/core/src/redux/app/actions/node-config.js index 2cf5eca1..700e0c5f 100644 --- a/core/src/redux/app/actions/node-config.js +++ b/core/src/redux/app/actions/node-config.js @@ -1,5 +1,5 @@ // Node Config Actions here... -import { LOAD_NODE_CONFIG, SET_NODE, ADD_NODE } from '../app-action-types.js' +import { LOAD_NODE_CONFIG, SET_NODE, ADD_NODE, REMOVE_NODE, EDIT_NODE } from '../app-action-types.js' import { UI_VERSION } from '../version.js' const nodeConfigUrl = '/getConfig' @@ -72,6 +72,16 @@ export const doAddNode = (nodeObject) => { return dispatch(addNode(nodeObject)) } } +export const doRemoveNode = (index) => { + return (dispatch, getState) => { + return dispatch(removeNode(index)) + } +} +export const doEditNode = (index, nodeObject) => { + return (dispatch, getState) => { + return dispatch(editNode({index, nodeObject})) + } +} const addNode = (payload) => { return { @@ -80,6 +90,18 @@ const addNode = (payload) => { } } +const editNode = (payload) => { + return { + type: EDIT_NODE, + payload + } +} +const removeNode = (payload) => { + return { + type: REMOVE_NODE, + payload + } +} const obj1 = { name: 'Local Node', protocol: 'http', diff --git a/core/src/redux/app/app-action-types.js b/core/src/redux/app/app-action-types.js index ba2cde2a..d98ff2c6 100644 --- a/core/src/redux/app/app-action-types.js +++ b/core/src/redux/app/app-action-types.js @@ -12,6 +12,8 @@ export const UPDATE_NODE_STATUS = 'UPDATE_NODE_STATUS' export const UPDATE_NODE_INFO = 'UPDATE_NODE_INFO' export const SET_NODE = 'SET_NODE' export const ADD_NODE = 'ADD_NODE' +export const EDIT_NODE = 'EDIT_NODE' +export const REMOVE_NODE = 'REMOVE_NODE' export const LOAD_NODE_CONFIG = 'LOAD_NODE_CONFIG' export const PAGE_URL = 'PAGE_URL' export const CHAT_HEADS = 'CHAT_HEADS' diff --git a/core/src/redux/app/app-reducer.js b/core/src/redux/app/app-reducer.js index 11e77486..d8d8abd2 100644 --- a/core/src/redux/app/app-reducer.js +++ b/core/src/redux/app/app-reducer.js @@ -1,9 +1,9 @@ // Loading state, login state, isNavDrawOpen state etc. None of this needs to be saved to localstorage. 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, IS_OPEN_DEV_DIALOG } 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, REMOVE_NODE, EDIT_NODE } from './app-action-types.js' import { initWorkersReducer } from './reducers/init-workers.js' import { loginReducer } from './reducers/login-reducer.js' -import { setNode, addNode } from './reducers/manage-node.js' +import { setNode, addNode, removeNode, editNode } from './reducers/manage-node.js' import localForage from "localforage"; const chatLastSeen = localForage.createInstance({ name: "chat-last-seen", @@ -120,6 +120,10 @@ export default (state = INITIAL_STATE, action) => { return setNode(state, action) case ADD_NODE: return addNode(state, action) + case EDIT_NODE: + return editNode(state, action) + case REMOVE_NODE: + return removeNode(state, action) case PAGE_URL: return { ...state, diff --git a/core/src/redux/app/reducers/manage-node.js b/core/src/redux/app/reducers/manage-node.js index ad284452..4e42f01f 100644 --- a/core/src/redux/app/reducers/manage-node.js +++ b/core/src/redux/app/reducers/manage-node.js @@ -20,3 +20,26 @@ export const addNode = (state, action) => { } } } + +export const editNode = (state, action) => { + const copyKnownNodes = [...state.nodeConfig.knownNodes] + copyKnownNodes[action.payload.index] = action.payload.nodeObject + return { + ...state, + nodeConfig: { + ...state.nodeConfig, + knownNodes: copyKnownNodes + } + } +} +export const removeNode = (state, action) => { + const copyKnownNodes = [...state.nodeConfig.knownNodes] + copyKnownNodes.splice(action.payload, 1); + return { + ...state, + nodeConfig: { + ...state.nodeConfig, + knownNodes: copyKnownNodes + } + } +} From a6a77aa0266512b8d81e671fc02c8ed3109ddb50 Mon Sep 17 00:00:00 2001 From: PhilReact Date: Mon, 18 Sep 2023 20:45:39 -0500 Subject: [PATCH 36/57] fix scroll in new tab page --- core/src/components/show-plugin.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/components/show-plugin.js b/core/src/components/show-plugin.js index ab3b46da..953302bc 100644 --- a/core/src/components/show-plugin.js +++ b/core/src/components/show-plugin.js @@ -893,7 +893,7 @@ class NavBar extends connect(store)(LitElement) { flex-flow: column; align-items: center; padding: 20px; - height: 100vh; + height: calc(100vh - 120px); overflow-y: auto; } From 62b249c0823db32d3dd30824c7462e236718c2fa Mon Sep 17 00:00:00 2001 From: PhilReact Date: Mon, 18 Sep 2023 21:02:32 -0500 Subject: [PATCH 37/57] fix error --- core/src/functional-components/settings-page.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/functional-components/settings-page.js b/core/src/functional-components/settings-page.js index d414fd1e..f7a908fb 100644 --- a/core/src/functional-components/settings-page.js +++ b/core/src/functional-components/settings-page.js @@ -48,7 +48,7 @@ class SettingsPage extends connect(store)(LitElement) { lastSelected: { type: Number }, nodeConfig: { type: Object }, theme: { type: String, reflect: true }, - nodeIndex: { type: Number },e + nodeIndex: { type: Number }, isBeingEdited: { type: Boolean }, dropdownOpen: { type: Boolean }, }; From e3d227ca254652c6a28d73ce8ff41b7fd83df9fc Mon Sep 17 00:00:00 2001 From: PhilReact Date: Tue, 19 Sep 2023 01:07:17 -0500 Subject: [PATCH 38/57] fix name tab when using url input --- core/src/components/show-plugin.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/core/src/components/show-plugin.js b/core/src/components/show-plugin.js index 953302bc..ad0430bb 100644 --- a/core/src/components/show-plugin.js +++ b/core/src/components/show-plugin.js @@ -2276,10 +2276,10 @@ class NavBar extends connect(store)(LitElement) { if (service === "APP") { this.changePage({ - "url": "qapp", + "url": "myapp", "domain": "core", "page": `qdn/browser/index.html${query}`, - "title": "Q-App", + "title": name || "Q-App", "icon": "vaadin:external-browser", "mwcicon": "open_in_browser", "menus": [], @@ -2287,10 +2287,10 @@ class NavBar extends connect(store)(LitElement) { }) } else if (service === "WEBSITE") { this.changePage({ - "url": "websites", + "url": "myapp", "domain": "core", "page": `qdn/browser/index.html${query}`, - "title": "Website", + "title": name || "Website", "icon": "vaadin:desktop", "mwcicon": "desktop_mac", "menus": [], From 2de6f4d25b31948cd91175364c1a7658be059780 Mon Sep 17 00:00:00 2001 From: PhilReact Date: Tue, 19 Sep 2023 11:14:53 -0500 Subject: [PATCH 39/57] fix css --- core/src/components/show-plugin.js | 6 +++--- core/src/functional-components/settings-page.js | 1 - 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/core/src/components/show-plugin.js b/core/src/components/show-plugin.js index ad0430bb..fddcd127 100644 --- a/core/src/components/show-plugin.js +++ b/core/src/components/show-plugin.js @@ -135,7 +135,7 @@ class ShowPlugin extends connect(store)(LitElement) { min-width: 110px; max-width: 220px; overflow: hidden; - zIndex: 2; + z-index: 2; } .tabCard { @@ -171,7 +171,7 @@ class ShowPlugin extends connect(store)(LitElement) { border-left: 1px solid var(--black); border-right: 1px solid var(--black); border-bottom: 1px solid var(--white); - zIndex: 1; + z-index: 1; } .close { @@ -901,7 +901,7 @@ class NavBar extends connect(store)(LitElement) { display: flex; justify-content: space-between; align-items: center; - background-color: color: var(--white); + background-color: var(--white); padding: 10px 20px; max-width: 750px; width: 80%; diff --git a/core/src/functional-components/settings-page.js b/core/src/functional-components/settings-page.js index f7a908fb..76ed6811 100644 --- a/core/src/functional-components/settings-page.js +++ b/core/src/functional-components/settings-page.js @@ -175,7 +175,6 @@ class SettingsPage extends connect(store)(LitElement) { } render() { - console.log('this.dropdownOpen', this.dropdownOpen); return html`
From 5c6529d269bb2c3575c71be1d05cb2a57ab562d5 Mon Sep 17 00:00:00 2001 From: PhilReact Date: Wed, 20 Sep 2023 00:30:12 -0500 Subject: [PATCH 40/57] improve loading of img in chat --- plugins/plugins/core/components/ChatImage.js | 289 ++ plugins/plugins/core/components/ChatPage.js | 3 +- .../plugins/core/components/ChatScroller.js | 4291 ++++++++++------- .../plugins/core/components/ImageComponent.js | 245 +- plugins/plugins/utils/queue.js | 37 + 5 files changed, 2899 insertions(+), 1966 deletions(-) create mode 100644 plugins/plugins/core/components/ChatImage.js diff --git a/plugins/plugins/core/components/ChatImage.js b/plugins/plugins/core/components/ChatImage.js new file mode 100644 index 00000000..a0f2abb0 --- /dev/null +++ b/plugins/plugins/core/components/ChatImage.js @@ -0,0 +1,289 @@ +import { LitElement, html, css } from 'lit'; +import { render } from 'lit/html.js'; +import { + use, + get, + translate, + translateUnsafeHTML, + registerTranslateConfig, +} from 'lit-translate'; +import axios from 'axios' +import { RequestQueueWithPromise } from '../../utils/queue'; + +const requestQueue = new RequestQueueWithPromise(5); + +export class ChatImage extends LitElement { + static get properties() { + return { + resource: { type: Object }, + isReady: { type: Boolean}, + status: {type: Object}, + setOpenDialogImage: { attribute: false} + }; + } + + static get styles() { + return css` + img { + max-width:45vh; + max-height:40vh; + border-radius: 5px; + cursor: pointer; + } + .smallLoading, + .smallLoading:after { + border-radius: 50%; + width: 2px; + height: 2px; + } + + .smallLoading { + border-width: 0.8em; + border-style: solid; + border-color: rgba(3, 169, 244, 0.2) rgba(3, 169, 244, 0.2) + rgba(3, 169, 244, 0.2) rgb(3, 169, 244); + font-size: 30px; + position: relative; + text-indent: -9999em; + transform: translateZ(0px); + animation: 1.1s linear 0s infinite normal none running loadingAnimation; + } + + .defaultSize { + width: 45vh; + height: 40vh; + } + + + + + @-webkit-keyframes loadingAnimation { + 0% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); + } + 100% { + -webkit-transform: rotate(360deg); + transform: rotate(360deg); + } + } + + @keyframes loadingAnimation { + 0% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); + } + 100% { + -webkit-transform: rotate(360deg); + transform: rotate(360deg); + } + } + `; + } + + constructor() { + super(); + this.resource = { + identifier: "", + name: "", + service: "" + } + this.status = { + status: '' + } + this.url = "" + this.isReady = false + this.nodeUrl = this.getNodeUrl() + this.myNode = this.getMyNode() + this.hasCalledWhenDownloaded = false + + this.observer = new IntersectionObserver(entries => { + for (const entry of entries) { + if (entry.isIntersecting && this.status.status !== 'READY') { + this._fetchImage(); + // Stop observing after the image has started loading + this.observer.unobserve(this); + } + } + }); + } + getNodeUrl(){ + const myNode = window.parent.reduxStore.getState().app.nodeConfig.knownNodes[window.parent.reduxStore.getState().app.nodeConfig.node] + + const nodeUrl = myNode.protocol + '://' + myNode.domain + ':' + myNode.port + return nodeUrl +} +getMyNode(){ + const myNode = window.parent.reduxStore.getState().app.nodeConfig.knownNodes[window.parent.reduxStore.getState().app.nodeConfig.node] + + return myNode +} + + getApiKey() { + const myNode = + window.parent.reduxStore.getState().app.nodeConfig.knownNodes[ + window.parent.reduxStore.getState().app.nodeConfig.node + ]; + let apiKey = myNode.apiKey; + return apiKey; + } + + async fetchResource() { + try { + // await qortalRequest({ + // action: 'GET_QDN_RESOURCE_PROPERTIES', + // name, + // service, + // identifier + // }) + await axios.get(`${this.nodeUrl}/arbitrary/resource/properties/${this.resource.service}/${this.resource.name}/${this.resource.identifier}?apiKey=${this.myNode.apiKey}`) + + + } catch (error) {} + } + + async fetchVideoUrl() { + + this.fetchResource() + this.url = `${this.nodeUrl}/arbitrary/${this.resource.service}/${this.resource.name}/${this.resource.identifier}?async=true&apiKey=${this.myNode.apiKey}` + + } + + async fetchStatus(){ + let isCalling = false + let percentLoaded = 0 + let timer = 24 + const response = await axios.get(`${this.nodeUrl}/arbitrary/resource/status/${this.resource.service}/${this.resource.name}/${this.resource.identifier}?apiKey=${this.myNode.apiKey}`) + if(response && response.data && response.data.status === 'READY'){ + this.status = response.data + return + } + const intervalId = setInterval(async () => { + if (isCalling) return + isCalling = true + + const data = await requestQueue.enqueue(() => { + return axios.get(`${this.nodeUrl}/arbitrary/resource/status/${this.resource.service}/${this.resource.name}/${this.resource.identifier}?apiKey=${this.myNode.apiKey}`) + }); + const res = data.data + + isCalling = false + if (res.localChunkCount) { + if (res.percentLoaded) { + if ( + res.percentLoaded === percentLoaded && + res.percentLoaded !== 100 + ) { + timer = timer - 5 + } else { + timer = 24 + } + if (timer < 0) { + timer = 24 + isCalling = true + this.status = { + ...res, + status: 'REFETCHING' + } + + setTimeout(() => { + isCalling = false + this.fetchResource({ + name, + service, + identifier + }) + }, 25000) + return + } + percentLoaded = res.percentLoaded + } + + this.status = res + + } + + // check if progress is 100% and clear interval if true + if (res?.status === 'READY') { + clearInterval(intervalId) + this.status = res + this.isReady = true + } + }, 5000) // 1 second interval + } + + async _fetchImage() { + try { + this.fetchVideoUrl({ + name: this.resource.name, + service: this.resource.service, + identifier: this.resource.identifier + }) + this.fetchStatus() + } catch (error) { + + } + } + + firstUpdated(){ + this.observer.observe(this); + + } + + shouldUpdate(changedProperties) { + if (changedProperties.has('setOpenDialogImage') && changedProperties.size === 1) { + return false; + } + + return true + } + async updated(changedProperties) { + if (changedProperties && changedProperties.has('status')) { + if(this.hasCalledWhenDownloaded === false && this.status.status === 'DOWNLOADED'){ + this.fetchResource() + this.hasCalledWhenDownloaded = true + } + } + } + + render() { + + return html` +
+ ${ + this.status.status !== 'READY' + ? html` +
+
+

${`${Math.round(this.status.percentLoaded || 0 + ).toFixed(0)}% loaded`}

+
+ ` + : '' + } + ${this.status.status === 'READY' ? html` + this.setOpenDialogImage(true)} src=${this.url} /> + ` : ''} + +
+ ` + + + } +} + +customElements.define('chat-image', ChatImage); diff --git a/plugins/plugins/core/components/ChatPage.js b/plugins/plugins/core/components/ChatPage.js index 7f8a5f8d..8ccd94f0 100644 --- a/plugins/plugins/core/components/ChatPage.js +++ b/plugins/plugins/core/components/ChatPage.js @@ -3895,9 +3895,10 @@ class ChatPage extends LitElement { new Compressor(image, { quality: .6, maxWidth: 1200, + mimeType: 'image/webp', success(result) { const file = new File([result], "name", { - type: image.type + type: 'image/webp' }) compressedFile = file resolve() diff --git a/plugins/plugins/core/components/ChatScroller.js b/plugins/plugins/core/components/ChatScroller.js index 2c66f5ea..acc3faf7 100644 --- a/plugins/plugins/core/components/ChatScroller.js +++ b/plugins/plugins/core/components/ChatScroller.js @@ -1,1695 +1,2238 @@ -import { LitElement, html, css } from 'lit' -import { render } from 'lit/html.js' -import { repeat } from 'lit/directives/repeat.js' -import { use, get, translate, translateUnsafeHTML, registerTranslateConfig } from 'lit-translate' -import { unsafeHTML } from 'lit/directives/unsafe-html.js' -import { chatStyles } from './ChatScroller-css.js' -import { Epml } from '../../../epml' -import { cropAddress } from "../../utils/cropAddress" -import { roundToNearestDecimal } from '../../utils/roundToNearestDecimal.js' -import { EmojiPicker } from 'emoji-picker-js' -import { generateHTML } from '@tiptap/core' -import isElectron from 'is-electron' -import localForage from 'localforage' +import { LitElement, html, css } from 'lit'; +import { render } from 'lit/html.js'; +import { repeat } from 'lit/directives/repeat.js'; +import { + use, + get, + translate, + translateUnsafeHTML, + registerTranslateConfig, +} from 'lit-translate'; +import { unsafeHTML } from 'lit/directives/unsafe-html.js'; +import { chatStyles } from './ChatScroller-css.js'; +import { Epml } from '../../../epml'; +import { cropAddress } from '../../utils/cropAddress'; +import { roundToNearestDecimal } from '../../utils/roundToNearestDecimal.js'; +import { EmojiPicker } from 'emoji-picker-js'; +import { generateHTML } from '@tiptap/core'; +import isElectron from 'is-electron'; +import localForage from 'localforage'; -import axios from 'axios' -import Highlight from '@tiptap/extension-highlight' -import ShortUniqueId from 'short-unique-id' -import StarterKit from '@tiptap/starter-kit' -import Underline from '@tiptap/extension-underline' +import axios from 'axios'; +import Highlight from '@tiptap/extension-highlight'; +import ShortUniqueId from 'short-unique-id'; +import StarterKit from '@tiptap/starter-kit'; +import Underline from '@tiptap/extension-underline'; -import './ChatModals.js' -import './LevelFounder.js' -import './NameMenu.js' -import './UserInfo/UserInfo.js' -import './WrapperModal' +import './ChatModals.js'; +import './LevelFounder.js'; +import './NameMenu.js'; +import './UserInfo/UserInfo.js'; +import './WrapperModal'; +import './ChatImage'; -import '@material/mwc-button' -import '@material/mwc-dialog' -import '@material/mwc-icon' -import '@vaadin/icon' -import '@vaadin/icons' -import '@vaadin/tooltip' -import { chatLimit, totalMsgCount } from './ChatPage.js' +import '@material/mwc-button'; +import '@material/mwc-dialog'; +import '@material/mwc-icon'; +import '@vaadin/icon'; +import '@vaadin/icons'; +import '@vaadin/tooltip'; +import { chatLimit, totalMsgCount } from './ChatPage.js'; -const parentEpml = new Epml({ type: 'WINDOW', source: window.parent }) +const parentEpml = new Epml({ type: 'WINDOW', source: window.parent }); const chatLastSeen = localForage.createInstance({ - name: "chat-last-seen", -}) -let toggledMessage = {} + name: 'chat-last-seen', +}); +let toggledMessage = {}; -const uid = new ShortUniqueId() +const uid = new ShortUniqueId(); const getApiKey = () => { - const myNode = window.parent.reduxStore.getState().app.nodeConfig.knownNodes[window.parent.reduxStore.getState().app.nodeConfig.node] - let apiKey = myNode.apiKey - return apiKey -} - - + const myNode = + window.parent.reduxStore.getState().app.nodeConfig.knownNodes[ + window.parent.reduxStore.getState().app.nodeConfig.node + ]; + let apiKey = myNode.apiKey; + return apiKey; +}; const extractComponents = async (url) => { - if (!url.startsWith("qortal://")) { - return null - } + 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 + 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=${getApiKey()}` - }) + 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=${getApiKey()}`, + }); - if (responseObj.totalChunkCount > 0) { - // Identifier exists, so don't include it in the path - parts.shift() - } - else { - identifier = null - } - } + if (responseObj.totalChunkCount > 0) { + // Identifier exists, so don't include it in the path + parts.shift(); + } else { + identifier = null; + } + } - const path = parts.join("/") + const path = parts.join('/'); - const components = {} - components["service"] = service - components["name"] = name - components["identifier"] = identifier - components["path"] = path - return components - } + const components = {}; + components['service'] = service; + components['name'] = name; + components['identifier'] = identifier; + components['path'] = path; + return components; + } - return null -} + return null; +}; function processText(input) { - const linkRegex = /(qortal:\/\/\S+)/g + const linkRegex = /(qortal:\/\/\S+)/g; - function processNode(node) { - if (node.nodeType === Node.TEXT_NODE) { - const parts = node.textContent.split(linkRegex) + function processNode(node) { + if (node.nodeType === Node.TEXT_NODE) { + const parts = node.textContent.split(linkRegex); - if (parts.length > 1) { - const fragment = document.createDocumentFragment() + if (parts.length > 1) { + const fragment = document.createDocumentFragment(); - parts.forEach((part) => { - if (part.startsWith('qortal://')) { - const link = document.createElement('span') - // Store the URL in a data attribute - link.setAttribute('data-url', part) - link.textContent = part - link.style.color = 'var(--code-block-text-color)' - link.style.textDecoration = 'underline' - link.style.cursor = 'pointer' + parts.forEach((part) => { + if (part.startsWith('qortal://')) { + const link = document.createElement('span'); + // Store the URL in a data attribute + link.setAttribute('data-url', part); + link.textContent = part; + link.style.color = 'var(--code-block-text-color)'; + link.style.textDecoration = 'underline'; + link.style.cursor = 'pointer'; - link.addEventListener('click', async (e) => { - e.preventDefault() - try { - const res = await extractComponents(part) - 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: uid.rnd(), - myPlugObj: { - "url": "myapp", - "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 - } - })) + link.addEventListener('click', async (e) => { + e.preventDefault(); + try { + const res = await extractComponents(part); + 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: uid.rnd(), + myPlugObj: { + url: 'myapp', + 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, + }, + }) + ); + } catch (error) { + console.log({ error }); + } + }); - } catch (error) { - console.log({ error }) - } + fragment.appendChild(link); + } else { + const textNode = document.createTextNode(part); + fragment.appendChild(textNode); + } + }); - }) + node.replaceWith(fragment); + } + } else { + for (const childNode of Array.from(node.childNodes)) { + processNode(childNode); + } + } + } - fragment.appendChild(link) - } else { - const textNode = document.createTextNode(part) - fragment.appendChild(textNode) - } - }) + const wrapper = document.createElement('div'); + wrapper.innerHTML = input; - node.replaceWith(fragment) - } - } else { - for (const childNode of Array.from(node.childNodes)) { - processNode(childNode) - } - } - } + processNode(wrapper); - const wrapper = document.createElement('div') - wrapper.innerHTML = input - - processNode(wrapper) - - return wrapper + return wrapper; } const formatMessages = (messages) => { - const formattedMessages = messages.reduce((messageArray, message) => { - const currentMessage = message; - const lastGroupedMessage = messageArray[messageArray.length - 1]; + const formattedMessages = messages.reduce((messageArray, message) => { + const currentMessage = message; + const lastGroupedMessage = messageArray[messageArray.length - 1]; - currentMessage.firstMessageInChat = messageArray.length === 0; + currentMessage.firstMessageInChat = messageArray.length === 0; - let timestamp, sender, repliedToData; + let timestamp, sender, repliedToData; - if (lastGroupedMessage) { - timestamp = lastGroupedMessage.timestamp; - sender = lastGroupedMessage.sender; - repliedToData = lastGroupedMessage.repliedToData; - } else { - timestamp = currentMessage.timestamp; - sender = currentMessage.sender; - repliedToData = currentMessage.repliedToData; - } + if (lastGroupedMessage) { + timestamp = lastGroupedMessage.timestamp; + sender = lastGroupedMessage.sender; + repliedToData = lastGroupedMessage.repliedToData; + } else { + timestamp = currentMessage.timestamp; + sender = currentMessage.sender; + repliedToData = currentMessage.repliedToData; + } - const isSameGroup = Math.abs(timestamp - currentMessage.timestamp) < 600000 && - sender === currentMessage.sender && - !repliedToData; + const isSameGroup = + Math.abs(timestamp - currentMessage.timestamp) < 600000 && + sender === currentMessage.sender && + !repliedToData; - if (isSameGroup && lastGroupedMessage) { - lastGroupedMessage.messages.push(currentMessage); - } else { - messageArray.push({ - messages: [currentMessage], - ...currentMessage - }); - } + if (isSameGroup && lastGroupedMessage) { + lastGroupedMessage.messages.push(currentMessage); + } else { + messageArray.push({ + messages: [currentMessage], + ...currentMessage, + }); + } - return messageArray; - }, []); + return messageArray; + }, []); - return formattedMessages -} + return formattedMessages; +}; class ChatScroller extends LitElement { - static get properties() { - return { - theme: { type: String, reflect: true }, - getNewMessage: { attribute: false }, - getOldMessage: { attribute: false }, - getAfterMessages: { attribute: false }, - escapeHTML: { attribute: false }, - messages: { type: Object }, - hideMessages: { type: Array }, - setRepliedToMessageObj: { attribute: false }, - setEditedMessageObj: { attribute: false }, - sendMessage: { attribute: false }, - sendMessageForward: { attribute: false }, - showLastMessageRefScroller: { attribute: false }, - emojiPicker: { attribute: false }, - isLoadingMessages: { type: Boolean }, - setIsLoadingMessages: { attribute: false }, - chatId: { type: String }, - setForwardProperties: { attribute: false }, - setOpenPrivateMessage: { attribute: false }, - setOpenUserInfo: { attribute: false }, - setOpenTipUser: { attribute: false }, - setUserName: { attribute: false }, - setSelectedHead: { attribute: false }, - openTipUser: { type: Boolean }, - openUserInfo: { type: Boolean }, - userName: { type: String }, - selectedHead: { type: Object }, - goToRepliedMessage: { attribute: false }, - listSeenMessages: { type: Array }, - updateMessageHash: { type: Object }, - messagesToRender: { type: Array }, - oldMessages: { type: Array }, - clearUpdateMessageHashmap: { attribute: false}, - disableFetching: {type: Boolean}, - isLoadingBefore: {type: Boolean}, - isLoadingAfter: {type: Boolean}, - messageQueue: {type: Array}, - loggedInUserName: {type: String}, - loggedInUserAddress: {type: String} - } - } - - static get styles() { - return [chatStyles] - } - - constructor() { - super() - this.messages = { - messages: [], - type: '' - } - this.oldMessages = [] - this._upObserverhandler = this._upObserverhandler.bind(this) - this.newListMessages = this.newListMessages.bind(this) - this.newListMessagesUnreadMessages = this.newListMessagesUnreadMessages.bind(this) - this._downObserverHandler = this._downObserverHandler.bind(this) - this.isLastMessageBeforeUnread = this.isLastMessageBeforeUnread.bind(this) - this.replaceMessagesWithUpdate = this.replaceMessagesWithUpdate.bind(this) - this.__bottomObserverForFetchingMessagesHandler = this.__bottomObserverForFetchingMessagesHandler.bind(this) - this.myAddress = window.parent.reduxStore.getState().app.selectedAddress.address - this.hideMessages = JSON.parse(localStorage.getItem("MessageBlockedAddresses") || "[]") - this.openTipUser = false - this.openUserInfo = false - this.listSeenMessages = [] - this.theme = localStorage.getItem('qortalTheme') ? localStorage.getItem('qortalTheme') : 'light' - this.messagesToRender = [] - this.disableFetching = false - this.isLoadingBefore = false - this.isLoadingAfter = false - this.disableAddingNewMessages = false - this.lastReadMessageTimestamp = null - this.messageQueue = [] - } - - addSeenMessage(val) { - this.listSeenMessages.push(val) - } - goToRepliedMessageFunc(val, val2){ - this.disableFetching = true - this.goToRepliedMessage(val, val2) - } - - shouldGroupWithLastMessage(newMessage, lastGroupedMessage) { - if (!lastGroupedMessage) return false; - - return Math.abs(lastGroupedMessage.timestamp - newMessage.timestamp) < 600000 && - lastGroupedMessage.sender === newMessage.sender && - !lastGroupedMessage.repliedToData; - } - - clearLoaders(){ - this.isLoadingBefore = false - this.isLoadingAfter = false - this.disableFetching = false - } - addNewMessage(newMessage) { - const lastGroupedMessage = this.messagesToRender[this.messagesToRender.length - 1]; - - if (this.shouldGroupWithLastMessage(newMessage, lastGroupedMessage)) { - lastGroupedMessage.messages.push(newMessage); - } else { - this.messagesToRender.push({ - messages: [newMessage], - ...newMessage - }); - } - this.clearLoaders() - this.requestUpdate(); - - } - - async newListMessages(newMessages, message) { - let data = [] - const copy = [...newMessages] - copy.forEach(newMessage => { - const lastGroupedMessage = data[data.length - 1]; - - if (this.shouldGroupWithLastMessage(newMessage, lastGroupedMessage)) { - lastGroupedMessage.messages.push(newMessage); - } else { - data.push({ - messages: [newMessage], - ...newMessage - }); - } - }); - - // const getCount = await parentEpml.request('apiCall', { - // type: 'api', - // url: `/chat/messages?involving=${window.parent.reduxStore.getState().app.selectedAddress.address}&involving=${this._chatId}&limit=${chatLimit}&reverse=true&before=${scrollElement.messageObj.timestamp}&haschatreference=false&encoding=BASE64` - // }) - this.messagesToRender = data - this.clearLoaders() - this.requestUpdate() - await this.updateComplete - - - - - - - - - } - - async newListMessagesUnreadMessages(newMessages, message, lastReadMessageTimestamp, count) { - - let data = []; - const copy = [...newMessages]; - - let dividerPlaced = false; // To ensure the divider is added only once - - // Start from the end of the list (newest messages) - for (let i = copy.length - 1; i >= 0; i--) { - let newMessage = copy[i]; - - // Initialize a property for the divider - newMessage.isDivider = false; - - // Check if this is the message before which the divider should be placed - if (!dividerPlaced && newMessage.timestamp <= lastReadMessageTimestamp) { - newMessage.isDivider = true; - dividerPlaced = true; // Ensure the divider is only added once - break; // Exit once the divider is placed - } - } - - copy.forEach((newMessage, groupIndex) => { - const lastGroupedMessage = data[data.length - 1]; - - if (this.shouldGroupWithLastMessage(newMessage, lastGroupedMessage)) { - lastGroupedMessage.messages.push(newMessage); - } else { - data.push({ - messages: [newMessage], - ...newMessage - }); - } - }); - if(count > 0){ - this.disableAddingNewMessages = true - } - this.messagesToRender = data; - this.clearLoaders(); - this.requestUpdate(); - await this.updateComplete; - const findElement = this.shadowRoot.getElementById('unread-divider-id') - if (findElement) { - findElement.scrollIntoView({ behavior: 'auto', block: 'center' }) - } - } - - - - - - async addNewMessages(newMessages, type) { - if(this.disableAddingNewMessages && type === 'newComingInAuto') return - let previousScrollTop; - let previousScrollHeight; - - const viewElement = this.shadowRoot.querySelector("#viewElement"); - previousScrollTop = viewElement.scrollTop; - previousScrollHeight = viewElement.scrollHeight; - - - const copy = type === 'initial' ? [] : [...this.messagesToRender] - - for (const newMessage of newMessages) { - const lastGroupedMessage = copy[copy.length - 1]; - - if (this.shouldGroupWithLastMessage(newMessage, lastGroupedMessage)) { - lastGroupedMessage.messages.push(newMessage); - } else { - copy.push({ - messages: [newMessage], - ...newMessage - }); - } - } - - - - // Ensure that the total number of individual messages doesn't exceed totalMsgCount - let totalMessagesCount = copy.reduce((acc, group) => acc + group.messages.length, 0); - while (totalMessagesCount > totalMsgCount && copy.length) { - if(newMessages.length < chatLimit && type !== 'newComingInAuto' && type !== 'initial'){ - this.disableAddingNewMessages = false - } - const firstGroup = copy[0]; - if (firstGroup.messages.length <= (totalMessagesCount - totalMsgCount)) { - // If removing the whole first group achieves the goal, remove it - totalMessagesCount -= firstGroup.messages.length; - copy.shift(); - } else { - // Otherwise, trim individual messages from the first group - const messagesToRemove = totalMessagesCount - totalMsgCount; - firstGroup.messages.splice(0, messagesToRemove); - totalMessagesCount = totalMsgCount; - } - } - this.messagesToRender = copy - this.requestUpdate(); - await this.updateComplete; - - if (type === 'initial') { - - viewElement.scrollTop = viewElement.scrollHeight - - - - } - - - this.clearLoaders() - } - - - async prependOldMessages(oldMessages) { - if (!this.messagesToRender) this.messagesToRender = []; // Ensure it's initialized - - let currentMessageGroup = null; - let previousMessage = null; - - for (const message of oldMessages) { - if (!previousMessage || !this.shouldGroupWithLastMessage(message, previousMessage)) { - // If no previous message, or if the current message shouldn't be grouped with the previous, - // push the current group to the front of the formatted messages (since these are older messages) - if (currentMessageGroup) { - this.messagesToRender.unshift(currentMessageGroup); - } - currentMessageGroup = { - id: message.signature, - messages: [message], - ...message - }; - } else { - // Add to the current group - currentMessageGroup.messages.push(message); - } - previousMessage = message; - } - - // After processing all old messages, add the last group - if (currentMessageGroup) { - this.messagesToRender.unshift(currentMessageGroup); - } - - // Ensure that the total number of individual messages doesn't exceed totalMsgCount - let totalMessagesCount = this.messagesToRender.reduce((acc, group) => acc + group.messages.length, 0); - while (totalMessagesCount > totalMsgCount && this.messagesToRender.length) { - this.disableAddingNewMessages = true - const lastGroup = this.messagesToRender[this.messagesToRender.length - 1]; - if (lastGroup.messages.length <= (totalMessagesCount - totalMsgCount)) { - // If removing the whole last group achieves the goal, remove it - totalMessagesCount -= lastGroup.messages.length; - this.messagesToRender.pop(); - } else { - // Otherwise, trim individual messages from the last group - const messagesToRemove = totalMessagesCount - totalMsgCount; - lastGroup.messages.splice(-messagesToRemove, messagesToRemove); - totalMessagesCount = totalMsgCount; - } - } - this.clearLoaders() - this.requestUpdate(); - - } - - - async replaceMessagesWithUpdate(updatedMessages) { - - const viewElement = this.shadowRoot.querySelector("#viewElement"); - if (!viewElement) return; // Ensure the element exists - const isUserAtBottom = (viewElement.scrollTop + viewElement.clientHeight) === viewElement.scrollHeight; - - const previousScrollTop = viewElement.scrollTop; - const previousScrollHeight = viewElement.scrollHeight; - - // Using map to return a new array, rather than mutating the old one - const newMessagesToRender = this.messagesToRender.map(group => { - // For each message, return the updated message if it exists, otherwise return the original message - const updatedGroupMessages = group.messages.map(message => { - return updatedMessages[message.signature] ? {...message, ...updatedMessages[message.signature]} : message; - }); - - // Return a new group object with updated messages - return { - ...group, - messages: updatedGroupMessages - }; - }); - - this.messagesToRender = newMessagesToRender; - this.requestUpdate(); - await this.updateComplete; - - - if (isUserAtBottom) { - viewElement.scrollTop = viewElement.scrollHeight - viewElement.clientHeight; - } else { - // Adjust scroll position based on the difference in scroll heights - const newScrollHeight = viewElement.scrollHeight; - viewElement.scrollTop = viewElement.scrollTop + (newScrollHeight - viewElement.scrollHeight); - } - - - this.clearUpdateMessageHashmap(); - this.clearLoaders() - } - - - async replaceMessagesWithUpdateByArray(updatedMessagesArray) { - let previousScrollTop; - let previousScrollHeight; - - const viewElement = this.shadowRoot.querySelector("#viewElement"); - previousScrollTop = viewElement.scrollTop; - previousScrollHeight = viewElement.scrollHeight; - for (let group of this.messagesToRender) { - for (let i = 0; i < group.messages.length; i++) { - const update = updatedMessagesArray.find(updatedMessage => ((updatedMessage.chatReference === group.messages[i].signature) || (updatedMessage.chatReference === group.messages[i].originalSignature) || (updatedMessage.chatReference === group.messages[i].chatReference))); - if (update) { - Object.assign(group.messages[i], update); - } - } - } - this.requestUpdate(); - const newScrollHeight = viewElement.scrollHeight; - viewElement.scrollTop = previousScrollTop + (newScrollHeight - previousScrollHeight); - this.clearUpdateMessageHashmap() - this.clearLoaders() - } - - - - - async updated(changedProperties) { - if (changedProperties && changedProperties.has('messages')) { - if (this.messages.type === 'initial') { - - this.addNewMessages(this.messages.messages, 'initial') - - - - } else if (this.messages.type === 'initialLastSeen') { - this.newListMessagesUnreadMessages(this.messages.messages, 'initialLastSeen', this.messages.lastReadMessageTimestamp, this.messages.count) - - } - else if (this.messages.type === 'new') this.addNewMessages(this.messages.messages) - else if(this.messages.type === 'newComingInAuto') this.addNewMessages(this.messages.messages, 'newComingInAuto') - else if (this.messages.type === 'old') this.prependOldMessages(this.messages.messages) - else if (this.messages.type === 'inBetween') this.newListMessages(this.messages.messages, this.messages.signature) - else if (this.messages.type === 'update') this.replaceMessagesWithUpdateByArray(this.messages.messages) - - - } - if (changedProperties && changedProperties.has('updateMessageHash') && Object.keys(this.updateMessageHash).length > 0) { - this.replaceMessagesWithUpdate(this.updateMessageHash) - } - if (changedProperties && changedProperties.has('messageQueue') && Object.keys(this.messageQueue).length > 0) { - if(!this.disableAddingNewMessages){ - await new Promise((res)=> { - setTimeout(()=> { - res() - }, 200) - }) - const viewElement = this.shadowRoot.querySelector("#viewElement"); - viewElement.scrollTop = viewElement.scrollHeight + 200 - } - } - - - } - - isLastMessageBeforeUnread(message, formattedMessages) { - // if the message is the last one in the older messages list and its timestamp is before the user's last seen timestamp - if (message.timestamp < this.lastReadMessageTimestamp && formattedMessages.indexOf(message) === (formattedMessages.length - 21)) { - return true; - } - return false; - } - - - render() { - // let formattedMessages = this.messages.reduce((messageArray, message) => { - // const currentMessage = this.updateMessageHash[message.signature] || message; - // const lastGroupedMessage = messageArray[messageArray.length - 1]; - - // currentMessage.firstMessageInChat = messageArray.length === 0; - - // let timestamp, sender, repliedToData; - - // if (lastGroupedMessage) { - // timestamp = lastGroupedMessage.timestamp; - // sender = lastGroupedMessage.sender; - // repliedToData = lastGroupedMessage.repliedToData; - // } else { - // timestamp = currentMessage.timestamp; - // sender = currentMessage.sender; - // repliedToData = currentMessage.repliedToData; - // } - - // const isSameGroup = Math.abs(timestamp - currentMessage.timestamp) < 600000 && - // sender === currentMessage.sender && - // !repliedToData; - - // if (isSameGroup && lastGroupedMessage) { - // lastGroupedMessage.messages.push(currentMessage); - // } else { - // messageArray.push({ - // messages: [currentMessage], - // ...currentMessage - // }); - // } - - // return messageArray; - // }, []); - - let formattedMessages = this.messagesToRender - - - return html` - ${this.isLoadingBefore ? html` -
- -
- ` : ''} -
    -
    - ${repeat( - formattedMessages, - (formattedMessage) => formattedMessage.reference, // Use .id as the unique key for formattedMessage. - (formattedMessage) => html` - - ${repeat( - formattedMessage.messages, - (message) => message.signature, - (message, indexMessage) => html` - - 1} - ?isLastMessageInGroup=${indexMessage === formattedMessage.messages.length - 1} - .setToggledMessage=${this.setToggledMessage} - .setForwardProperties=${this.setForwardProperties} - .setOpenPrivateMessage=${(val) => this.setOpenPrivateMessage(val)} - .setOpenTipUser=${(val) => this.setOpenTipUser(val)} - .setOpenUserInfo=${(val) => this.setOpenUserInfo(val)} - .setUserName=${(val) => this.setUserName(val)} - id=${message.signature} - .goToRepliedMessage=${(val, val2)=> this.goToRepliedMessageFunc(val, val2)} - .addSeenMessage=${(val) => this.addSeenMessage(val)} - .listSeenMessages=${this.listSeenMessages} - chatId=${this.chatId} - > - ${message.isDivider ? html`
    ${translate('chatpage.cchange92')}
    ` : null} - - ` - - )} - - - ` - )} - - ${this.messageQueue.filter((item )=> this.chatId.includes(item._chatId)).length > 0 ? html` -
    - ` : html` -
    - `} - - -
    - - ${this.isLoadingAfter ? html` -
    - -
    - ` : ''} - ${repeat( - this.messageQueue.filter((item )=> this.chatId.includes(item._chatId)), - (message) => message.messageText, - (message, indexMessage) => html` - - this.setOpenPrivateMessage(val)} - .setOpenTipUser=${(val) => this.setOpenTipUser(val)} - .setOpenUserInfo=${(val) => this.setOpenUserInfo(val)} - .setUserName=${(val) => this.setUserName(val)} - id=${message.signature} - .goToRepliedMessage=${(val, val2)=> this.goToRepliedMessageFunc(val, val2)} - .addSeenMessage=${(val) => this.addSeenMessage(val)} - .listSeenMessages=${this.listSeenMessages} - chatId=${this.chatId} - ?isInProgress=${true} - > - - ` - - )} -
- ` - } - - shouldUpdate(changedProperties) { - if (changedProperties.has('isLoadingMessages')) { - return true - } - if (changedProperties.has('chatId') && changedProperties.get('chatId')) { - return true - } - if (changedProperties.has('openTipUser')) { - return true - } - if (changedProperties.has('openUserInfo')) { - return true - } - if (changedProperties.has('userName')) { - return true - } - if(changedProperties.has('loggedInUserName')){ - return true - } - if (changedProperties.has('updateMessageHash')) { - return true - } - if(changedProperties.has('messagesToRender')){ - return true - } - if(changedProperties.has('isLoadingBefore')){ - return true - } - if(changedProperties.has('isLoadingAfter')){ - return true - } - if(changedProperties.has('messageQueue')){ - return true - } - // Only update element if prop1 changed. - return changedProperties.has('messages') - } - - async getUpdateComplete() { - await super.getUpdateComplete() - const marginElements = Array.from(this.shadowRoot.querySelectorAll('message-template')) - await Promise.all(marginElements.map(el => el.updateComplete)) - return true - } - - setToggledMessage(message) { - toggledMessage = message - } - - async firstUpdated() { - this.changeTheme() - window.addEventListener('storage', () => { - const checkTheme = localStorage.getItem('qortalTheme') - - if (checkTheme === 'dark') { - this.theme = 'dark' - } else { - this.theme = 'light' - } - document.querySelector('html').setAttribute('theme', this.theme) - }) - - this.emojiPicker.on('emoji', selection => { - this.sendMessage({ - type: 'reaction', - editedMessageObj: toggledMessage, - reaction: selection.emoji, - }) - }) - this.viewElement = this.shadowRoot.getElementById('viewElement') - this.upObserverElement = this.shadowRoot.getElementById('upObserver') - this.downObserverElement = this.shadowRoot.getElementById('downObserver') - this.bottomObserverForFetchingMessages = this.shadowRoot.getElementById('bottomObserverForFetchingMessages') - // Intialize Observers - this.upElementObserver() - this.downElementObserver() - this.bottomObserver() - - - this.clearConsole() - setInterval(() => { - this.clearConsole() - }, 60000) - } - - clearConsole() { - if (!isElectron()) { - } else { - console.clear() - window.parent.electronAPI.clearCache() - } - } - - changeTheme() { - const checkTheme = localStorage.getItem('qortalTheme') - if (checkTheme === 'dark') { - this.theme = 'dark' - } else { - this.theme = 'light' - } - document.querySelector('html').setAttribute('theme', this.theme) - } - - _getOldMessage(_scrollElement) { - this.getOldMessage(_scrollElement) - } - _getAfterMessages(_scrollElement) { - this.getAfterMessages(_scrollElement) - } - - - - _upObserverhandler(entries) { - if(!entries[0].target || !entries[0].target.nextElementSibling) return - if (entries[0].isIntersecting) { - if (this.disableFetching) { - return - } - this.disableFetching = true - this.isLoadingBefore = true - let _scrollElement = entries[0].target.nextElementSibling - this._getOldMessage(_scrollElement) - } - } - - _downObserverHandler(entries) { - if (!entries[0].isIntersecting) { - this.showLastMessageRefScroller(true) - } else { - this.showLastMessageRefScroller(false) - } - } - - __bottomObserverForFetchingMessagesHandler(entries) { - if (this.messagesToRender.length === 0 || this.disableFetching) { - return - } - if (!entries[0].isIntersecting || !entries[0].target || !entries[0].target.previousElementSibling) { - } else { - this.disableFetching = true - this.isLoadingAfter = true - let _scrollElement = entries[0].target.previousElementSibling - this._getAfterMessages(_scrollElement) - } - } - - upElementObserver() { - const options = { - root: this.viewElement, - rootMargin: '0px', - threshold: 1 - } - const observer = new IntersectionObserver(this._upObserverhandler, options) - observer.observe(this.upObserverElement) - } - - downElementObserver() { - const options = { - - } - // identify an element to observe - const elementToObserve = this.downObserverElement - // passing it a callback function - const observer = new IntersectionObserver(this._downObserverHandler, options) - // call `observe()` on that MutationObserver instance, - // passing it the element to observe, and the options object - observer.observe(elementToObserve) - } - bottomObserver() { - const options = { - - } - // identify an element to observe - const elementToObserve = this.bottomObserverForFetchingMessages - // passing it a callback function - const observer = new IntersectionObserver(this.__bottomObserverForFetchingMessagesHandler, options) - // call `observe()` on that MutationObserver instance, - // passing it the element to observe, and the options object - observer.observe(elementToObserve) - } + static get properties() { + return { + theme: { type: String, reflect: true }, + getNewMessage: { attribute: false }, + getOldMessage: { attribute: false }, + getAfterMessages: { attribute: false }, + escapeHTML: { attribute: false }, + messages: { type: Object }, + hideMessages: { type: Array }, + setRepliedToMessageObj: { attribute: false }, + setEditedMessageObj: { attribute: false }, + sendMessage: { attribute: false }, + sendMessageForward: { attribute: false }, + showLastMessageRefScroller: { attribute: false }, + emojiPicker: { attribute: false }, + isLoadingMessages: { type: Boolean }, + setIsLoadingMessages: { attribute: false }, + chatId: { type: String }, + setForwardProperties: { attribute: false }, + setOpenPrivateMessage: { attribute: false }, + setOpenUserInfo: { attribute: false }, + setOpenTipUser: { attribute: false }, + setUserName: { attribute: false }, + setSelectedHead: { attribute: false }, + openTipUser: { type: Boolean }, + openUserInfo: { type: Boolean }, + userName: { type: String }, + selectedHead: { type: Object }, + goToRepliedMessage: { attribute: false }, + listSeenMessages: { type: Array }, + updateMessageHash: { type: Object }, + messagesToRender: { type: Array }, + oldMessages: { type: Array }, + clearUpdateMessageHashmap: { attribute: false }, + disableFetching: { type: Boolean }, + isLoadingBefore: { type: Boolean }, + isLoadingAfter: { type: Boolean }, + messageQueue: { type: Array }, + loggedInUserName: { type: String }, + loggedInUserAddress: { type: String }, + }; + } + + static get styles() { + return [chatStyles]; + } + + constructor() { + super(); + this.messages = { + messages: [], + type: '', + }; + this.oldMessages = []; + this._upObserverhandler = this._upObserverhandler.bind(this); + this.newListMessages = this.newListMessages.bind(this); + this.newListMessagesUnreadMessages = + this.newListMessagesUnreadMessages.bind(this); + this._downObserverHandler = this._downObserverHandler.bind(this); + this.isLastMessageBeforeUnread = + this.isLastMessageBeforeUnread.bind(this); + this.replaceMessagesWithUpdate = + this.replaceMessagesWithUpdate.bind(this); + this.__bottomObserverForFetchingMessagesHandler = + this.__bottomObserverForFetchingMessagesHandler.bind(this); + this.myAddress = + window.parent.reduxStore.getState().app.selectedAddress.address; + this.hideMessages = JSON.parse( + localStorage.getItem('MessageBlockedAddresses') || '[]' + ); + this.openTipUser = false; + this.openUserInfo = false; + this.listSeenMessages = []; + this.theme = localStorage.getItem('qortalTheme') + ? localStorage.getItem('qortalTheme') + : 'light'; + this.messagesToRender = []; + this.disableFetching = false; + this.isLoadingBefore = false; + this.isLoadingAfter = false; + this.disableAddingNewMessages = false; + this.lastReadMessageTimestamp = null; + this.messageQueue = []; + } + + addSeenMessage(val) { + this.listSeenMessages.push(val); + } + goToRepliedMessageFunc(val, val2) { + this.disableFetching = true; + this.goToRepliedMessage(val, val2); + } + + shouldGroupWithLastMessage(newMessage, lastGroupedMessage) { + if (!lastGroupedMessage) return false; + + return ( + Math.abs(lastGroupedMessage.timestamp - newMessage.timestamp) < + 600000 && + lastGroupedMessage.sender === newMessage.sender && + !lastGroupedMessage.repliedToData + ); + } + + clearLoaders() { + this.isLoadingBefore = false; + this.isLoadingAfter = false; + this.disableFetching = false; + } + addNewMessage(newMessage) { + const lastGroupedMessage = + this.messagesToRender[this.messagesToRender.length - 1]; + + if (this.shouldGroupWithLastMessage(newMessage, lastGroupedMessage)) { + lastGroupedMessage.messages.push(newMessage); + } else { + this.messagesToRender.push({ + messages: [newMessage], + ...newMessage, + }); + } + this.clearLoaders(); + this.requestUpdate(); + } + + async newListMessages(newMessages, message) { + let data = []; + const copy = [...newMessages]; + copy.forEach((newMessage) => { + const lastGroupedMessage = data[data.length - 1]; + + if ( + this.shouldGroupWithLastMessage(newMessage, lastGroupedMessage) + ) { + lastGroupedMessage.messages.push(newMessage); + } else { + data.push({ + messages: [newMessage], + ...newMessage, + }); + } + }); + + // const getCount = await parentEpml.request('apiCall', { + // type: 'api', + // url: `/chat/messages?involving=${window.parent.reduxStore.getState().app.selectedAddress.address}&involving=${this._chatId}&limit=${chatLimit}&reverse=true&before=${scrollElement.messageObj.timestamp}&haschatreference=false&encoding=BASE64` + // }) + this.messagesToRender = data; + this.clearLoaders(); + this.requestUpdate(); + await this.updateComplete; + } + + async newListMessagesUnreadMessages( + newMessages, + message, + lastReadMessageTimestamp, + count + ) { + let data = []; + const copy = [...newMessages]; + + let dividerPlaced = false; // To ensure the divider is added only once + + // Start from the end of the list (newest messages) + for (let i = copy.length - 1; i >= 0; i--) { + let newMessage = copy[i]; + + // Initialize a property for the divider + newMessage.isDivider = false; + + // Check if this is the message before which the divider should be placed + if ( + !dividerPlaced && + newMessage.timestamp <= lastReadMessageTimestamp + ) { + newMessage.isDivider = true; + dividerPlaced = true; // Ensure the divider is only added once + break; // Exit once the divider is placed + } + } + + copy.forEach((newMessage, groupIndex) => { + const lastGroupedMessage = data[data.length - 1]; + + if ( + this.shouldGroupWithLastMessage(newMessage, lastGroupedMessage) + ) { + lastGroupedMessage.messages.push(newMessage); + } else { + data.push({ + messages: [newMessage], + ...newMessage, + }); + } + }); + if (count > 0) { + this.disableAddingNewMessages = true; + } + this.messagesToRender = data; + this.clearLoaders(); + this.requestUpdate(); + await this.updateComplete; + const findElement = this.shadowRoot.getElementById('unread-divider-id'); + if (findElement) { + findElement.scrollIntoView({ behavior: 'auto', block: 'center' }); + } + } + + async addNewMessages(newMessages, type) { + if (this.disableAddingNewMessages && type === 'newComingInAuto') return; + let previousScrollTop; + let previousScrollHeight; + + const viewElement = this.shadowRoot.querySelector('#viewElement'); + previousScrollTop = viewElement.scrollTop; + previousScrollHeight = viewElement.scrollHeight; + + const copy = type === 'initial' ? [] : [...this.messagesToRender]; + + for (const newMessage of newMessages) { + const lastGroupedMessage = copy[copy.length - 1]; + + if ( + this.shouldGroupWithLastMessage(newMessage, lastGroupedMessage) + ) { + lastGroupedMessage.messages.push(newMessage); + } else { + copy.push({ + messages: [newMessage], + ...newMessage, + }); + } + } + + // Ensure that the total number of individual messages doesn't exceed totalMsgCount + let totalMessagesCount = copy.reduce( + (acc, group) => acc + group.messages.length, + 0 + ); + while (totalMessagesCount > totalMsgCount && copy.length) { + if ( + newMessages.length < chatLimit && + type !== 'newComingInAuto' && + type !== 'initial' + ) { + this.disableAddingNewMessages = false; + } + const firstGroup = copy[0]; + if ( + firstGroup.messages.length <= + totalMessagesCount - totalMsgCount + ) { + // If removing the whole first group achieves the goal, remove it + totalMessagesCount -= firstGroup.messages.length; + copy.shift(); + } else { + // Otherwise, trim individual messages from the first group + const messagesToRemove = totalMessagesCount - totalMsgCount; + firstGroup.messages.splice(0, messagesToRemove); + totalMessagesCount = totalMsgCount; + } + } + this.messagesToRender = copy; + this.requestUpdate(); + await this.updateComplete; + + if (type === 'initial') { + viewElement.scrollTop = viewElement.scrollHeight; + } + + this.clearLoaders(); + } + + async prependOldMessages(oldMessages) { + if (!this.messagesToRender) this.messagesToRender = []; // Ensure it's initialized + + let currentMessageGroup = null; + let previousMessage = null; + + for (const message of oldMessages) { + if ( + !previousMessage || + !this.shouldGroupWithLastMessage(message, previousMessage) + ) { + // If no previous message, or if the current message shouldn't be grouped with the previous, + // push the current group to the front of the formatted messages (since these are older messages) + if (currentMessageGroup) { + this.messagesToRender.unshift(currentMessageGroup); + } + currentMessageGroup = { + id: message.signature, + messages: [message], + ...message, + }; + } else { + // Add to the current group + currentMessageGroup.messages.push(message); + } + previousMessage = message; + } + + // After processing all old messages, add the last group + if (currentMessageGroup) { + this.messagesToRender.unshift(currentMessageGroup); + } + + // Ensure that the total number of individual messages doesn't exceed totalMsgCount + let totalMessagesCount = this.messagesToRender.reduce( + (acc, group) => acc + group.messages.length, + 0 + ); + while ( + totalMessagesCount > totalMsgCount && + this.messagesToRender.length + ) { + this.disableAddingNewMessages = true; + const lastGroup = + this.messagesToRender[this.messagesToRender.length - 1]; + if ( + lastGroup.messages.length <= + totalMessagesCount - totalMsgCount + ) { + // If removing the whole last group achieves the goal, remove it + totalMessagesCount -= lastGroup.messages.length; + this.messagesToRender.pop(); + } else { + // Otherwise, trim individual messages from the last group + const messagesToRemove = totalMessagesCount - totalMsgCount; + lastGroup.messages.splice(-messagesToRemove, messagesToRemove); + totalMessagesCount = totalMsgCount; + } + } + this.clearLoaders(); + this.requestUpdate(); + } + + async replaceMessagesWithUpdate(updatedMessages) { + const viewElement = this.shadowRoot.querySelector('#viewElement'); + if (!viewElement) return; // Ensure the element exists + const isUserAtBottom = + viewElement.scrollTop + viewElement.clientHeight === + viewElement.scrollHeight; + + const previousScrollTop = viewElement.scrollTop; + const previousScrollHeight = viewElement.scrollHeight; + + // Using map to return a new array, rather than mutating the old one + const newMessagesToRender = this.messagesToRender.map((group) => { + // For each message, return the updated message if it exists, otherwise return the original message + const updatedGroupMessages = group.messages.map((message) => { + return updatedMessages[message.signature] + ? { ...message, ...updatedMessages[message.signature] } + : message; + }); + + // Return a new group object with updated messages + return { + ...group, + messages: updatedGroupMessages, + }; + }); + + this.messagesToRender = newMessagesToRender; + this.requestUpdate(); + await this.updateComplete; + + if (isUserAtBottom) { + viewElement.scrollTop = + viewElement.scrollHeight - viewElement.clientHeight; + } else { + // Adjust scroll position based on the difference in scroll heights + const newScrollHeight = viewElement.scrollHeight; + viewElement.scrollTop = + viewElement.scrollTop + + (newScrollHeight - viewElement.scrollHeight); + } + + this.clearUpdateMessageHashmap(); + this.clearLoaders(); + } + + async replaceMessagesWithUpdateByArray(updatedMessagesArray) { + let previousScrollTop; + let previousScrollHeight; + + const viewElement = this.shadowRoot.querySelector('#viewElement'); + previousScrollTop = viewElement.scrollTop; + previousScrollHeight = viewElement.scrollHeight; + for (let group of this.messagesToRender) { + for (let i = 0; i < group.messages.length; i++) { + const update = updatedMessagesArray.find( + (updatedMessage) => + updatedMessage.chatReference === + group.messages[i].signature || + updatedMessage.chatReference === + group.messages[i].originalSignature || + updatedMessage.chatReference === + group.messages[i].chatReference + ); + if (update) { + Object.assign(group.messages[i], update); + } + } + } + this.requestUpdate(); + const newScrollHeight = viewElement.scrollHeight; + viewElement.scrollTop = + previousScrollTop + (newScrollHeight - previousScrollHeight); + this.clearUpdateMessageHashmap(); + this.clearLoaders(); + } + + async updated(changedProperties) { + if (changedProperties && changedProperties.has('messages')) { + if (this.messages.type === 'initial') { + this.addNewMessages(this.messages.messages, 'initial'); + } else if (this.messages.type === 'initialLastSeen') { + this.newListMessagesUnreadMessages( + this.messages.messages, + 'initialLastSeen', + this.messages.lastReadMessageTimestamp, + this.messages.count + ); + } else if (this.messages.type === 'new') + this.addNewMessages(this.messages.messages); + else if (this.messages.type === 'newComingInAuto') + this.addNewMessages(this.messages.messages, 'newComingInAuto'); + else if (this.messages.type === 'old') + this.prependOldMessages(this.messages.messages); + else if (this.messages.type === 'inBetween') + this.newListMessages( + this.messages.messages, + this.messages.signature + ); + else if (this.messages.type === 'update') + this.replaceMessagesWithUpdateByArray(this.messages.messages); + } + if ( + changedProperties && + changedProperties.has('updateMessageHash') && + Object.keys(this.updateMessageHash).length > 0 + ) { + this.replaceMessagesWithUpdate(this.updateMessageHash); + } + if ( + changedProperties && + changedProperties.has('messageQueue') && + Object.keys(this.messageQueue).length > 0 + ) { + if (!this.disableAddingNewMessages) { + await new Promise((res) => { + setTimeout(() => { + res(); + }, 200); + }); + const viewElement = + this.shadowRoot.querySelector('#viewElement'); + viewElement.scrollTop = viewElement.scrollHeight + 200; + } + } + } + + isLastMessageBeforeUnread(message, formattedMessages) { + // if the message is the last one in the older messages list and its timestamp is before the user's last seen timestamp + if ( + message.timestamp < this.lastReadMessageTimestamp && + formattedMessages.indexOf(message) === formattedMessages.length - 21 + ) { + return true; + } + return false; + } + + render() { + // let formattedMessages = this.messages.reduce((messageArray, message) => { + // const currentMessage = this.updateMessageHash[message.signature] || message; + // const lastGroupedMessage = messageArray[messageArray.length - 1]; + + // currentMessage.firstMessageInChat = messageArray.length === 0; + + // let timestamp, sender, repliedToData; + + // if (lastGroupedMessage) { + // timestamp = lastGroupedMessage.timestamp; + // sender = lastGroupedMessage.sender; + // repliedToData = lastGroupedMessage.repliedToData; + // } else { + // timestamp = currentMessage.timestamp; + // sender = currentMessage.sender; + // repliedToData = currentMessage.repliedToData; + // } + + // const isSameGroup = Math.abs(timestamp - currentMessage.timestamp) < 600000 && + // sender === currentMessage.sender && + // !repliedToData; + + // if (isSameGroup && lastGroupedMessage) { + // lastGroupedMessage.messages.push(currentMessage); + // } else { + // messageArray.push({ + // messages: [currentMessage], + // ...currentMessage + // }); + // } + + // return messageArray; + // }, []); + + let formattedMessages = this.messagesToRender; + + return html` + ${this.isLoadingBefore + ? html` +
+ +
+ ` + : ''} +
    +
    + ${repeat( + formattedMessages, + (formattedMessage) => formattedMessage.reference, // Use .id as the unique key for formattedMessage. + (formattedMessage) => html` + ${repeat( + formattedMessage.messages, + (message) => message.signature, + (message, indexMessage) => html` + 1} + ?isLastMessageInGroup=${indexMessage === + formattedMessage.messages.length - 1} + .setToggledMessage=${this.setToggledMessage} + .setForwardProperties=${this + .setForwardProperties} + .setOpenPrivateMessage=${(val) => + this.setOpenPrivateMessage(val)} + .setOpenTipUser=${(val) => + this.setOpenTipUser(val)} + .setOpenUserInfo=${(val) => + this.setOpenUserInfo(val)} + .setUserName=${(val) => + this.setUserName(val)} + id=${message.signature} + .goToRepliedMessage=${(val, val2) => + this.goToRepliedMessageFunc(val, val2)} + .addSeenMessage=${(val) => + this.addSeenMessage(val)} + .listSeenMessages=${this.listSeenMessages} + chatId=${this.chatId} + > + ${message.isDivider + ? html`
    + ${translate('chatpage.cchange92')} +
    ` + : null} + ` + )} + ` + )} + ${this.messageQueue.filter((item) => + this.chatId.includes(item._chatId) + ).length > 0 + ? html` +
    + ` + : html` +
    + `} + +
    + + ${this.isLoadingAfter + ? html` +
    + +
    + ` + : ''} + ${repeat( + this.messageQueue.filter((item) => + this.chatId.includes(item._chatId) + ), + (message) => message.messageText, + (message, indexMessage) => html` + + this.setOpenPrivateMessage(val)} + .setOpenTipUser=${(val) => this.setOpenTipUser(val)} + .setOpenUserInfo=${(val) => + this.setOpenUserInfo(val)} + .setUserName=${(val) => this.setUserName(val)} + id=${message.signature} + .goToRepliedMessage=${(val, val2) => + this.goToRepliedMessageFunc(val, val2)} + .addSeenMessage=${(val) => this.addSeenMessage(val)} + .listSeenMessages=${this.listSeenMessages} + chatId=${this.chatId} + ?isInProgress=${true} + > + ` + )} +
+ `; + } + + shouldUpdate(changedProperties) { + if (changedProperties.has('isLoadingMessages')) { + return true; + } + if ( + changedProperties.has('chatId') && + changedProperties.get('chatId') + ) { + return true; + } + if (changedProperties.has('openTipUser')) { + return true; + } + if (changedProperties.has('openUserInfo')) { + return true; + } + if (changedProperties.has('userName')) { + return true; + } + if (changedProperties.has('loggedInUserName')) { + return true; + } + if (changedProperties.has('updateMessageHash')) { + return true; + } + if (changedProperties.has('messagesToRender')) { + return true; + } + if (changedProperties.has('isLoadingBefore')) { + return true; + } + if (changedProperties.has('isLoadingAfter')) { + return true; + } + if (changedProperties.has('messageQueue')) { + return true; + } + // Only update element if prop1 changed. + return changedProperties.has('messages'); + } + + async getUpdateComplete() { + await super.getUpdateComplete(); + const marginElements = Array.from( + this.shadowRoot.querySelectorAll('message-template') + ); + await Promise.all(marginElements.map((el) => el.updateComplete)); + return true; + } + + setToggledMessage(message) { + toggledMessage = message; + } + + async firstUpdated() { + this.changeTheme(); + window.addEventListener('storage', () => { + const checkTheme = localStorage.getItem('qortalTheme'); + + if (checkTheme === 'dark') { + this.theme = 'dark'; + } else { + this.theme = 'light'; + } + document.querySelector('html').setAttribute('theme', this.theme); + }); + + this.emojiPicker.on('emoji', (selection) => { + this.sendMessage({ + type: 'reaction', + editedMessageObj: toggledMessage, + reaction: selection.emoji, + }); + }); + this.viewElement = this.shadowRoot.getElementById('viewElement'); + this.upObserverElement = this.shadowRoot.getElementById('upObserver'); + this.downObserverElement = + this.shadowRoot.getElementById('downObserver'); + this.bottomObserverForFetchingMessages = this.shadowRoot.getElementById( + 'bottomObserverForFetchingMessages' + ); + // Intialize Observers + this.upElementObserver(); + this.downElementObserver(); + this.bottomObserver(); + + this.clearConsole(); + setInterval(() => { + this.clearConsole(); + }, 60000); + } + + clearConsole() { + if (!isElectron()) { + } else { + console.clear(); + window.parent.electronAPI.clearCache(); + } + } + + changeTheme() { + const checkTheme = localStorage.getItem('qortalTheme'); + if (checkTheme === 'dark') { + this.theme = 'dark'; + } else { + this.theme = 'light'; + } + document.querySelector('html').setAttribute('theme', this.theme); + } + + _getOldMessage(_scrollElement) { + this.getOldMessage(_scrollElement); + } + _getAfterMessages(_scrollElement) { + this.getAfterMessages(_scrollElement); + } + + _upObserverhandler(entries) { + if (!entries[0].target || !entries[0].target.nextElementSibling) return; + if (entries[0].isIntersecting) { + if (this.disableFetching) { + return; + } + this.disableFetching = true; + this.isLoadingBefore = true; + let _scrollElement = entries[0].target.nextElementSibling; + this._getOldMessage(_scrollElement); + } + } + + _downObserverHandler(entries) { + if (!entries[0].isIntersecting) { + this.showLastMessageRefScroller(true); + } else { + this.showLastMessageRefScroller(false); + } + } + + __bottomObserverForFetchingMessagesHandler(entries) { + if (this.messagesToRender.length === 0 || this.disableFetching) { + return; + } + if ( + !entries[0].isIntersecting || + !entries[0].target || + !entries[0].target.previousElementSibling + ) { + } else { + this.disableFetching = true; + this.isLoadingAfter = true; + let _scrollElement = entries[0].target.previousElementSibling; + this._getAfterMessages(_scrollElement); + } + } + + upElementObserver() { + const options = { + root: this.viewElement, + rootMargin: '0px', + threshold: 1, + }; + const observer = new IntersectionObserver( + this._upObserverhandler, + options + ); + observer.observe(this.upObserverElement); + } + + downElementObserver() { + const options = {}; + // identify an element to observe + const elementToObserve = this.downObserverElement; + // passing it a callback function + const observer = new IntersectionObserver( + this._downObserverHandler, + options + ); + // call `observe()` on that MutationObserver instance, + // passing it the element to observe, and the options object + observer.observe(elementToObserve); + } + bottomObserver() { + const options = {}; + // identify an element to observe + const elementToObserve = this.bottomObserverForFetchingMessages; + // passing it a callback function + const observer = new IntersectionObserver( + this.__bottomObserverForFetchingMessagesHandler, + options + ); + // call `observe()` on that MutationObserver instance, + // passing it the element to observe, and the options object + observer.observe(elementToObserve); + } } -window.customElements.define('chat-scroller', ChatScroller) - +window.customElements.define('chat-scroller', ChatScroller); class MessageTemplate extends LitElement { - static get properties() { - return { - messageObj: { type: Object }, - emojiPicker: { attribute: false }, - escapeHTML: { attribute: false }, - hideMessages: { type: Array }, - openDialogPrivateMessage: { type: Boolean }, - openDialogBlockUser: { type: Boolean }, - showBlockAddressIcon: { type: Boolean }, - setRepliedToMessageObj: { attribute: false }, - setEditedMessageObj: { attribute: false }, - sendMessage: { attribute: false }, - sendMessageForward: { attribute: false }, - openDialogImage: { type: Boolean }, - openDialogGif: { type: Boolean }, - openDeleteImage: { type: Boolean }, - openDeleteAttachment: { type: Boolean }, - isImageLoaded: { type: Boolean }, - isGifLoaded: { type: Boolean }, - isFirstMessage: { type: Boolean }, - isSingleMessageInGroup: { type: Boolean }, - isLastMessageInGroup: { type: Boolean }, - setToggledMessage: { attribute: false }, - setForwardProperties: { attribute: false }, - viewImage: { type: Boolean }, - setOpenPrivateMessage: { attribute: false }, - setOpenTipUser: { attribute: false }, - setOpenUserInfo: { attribute: false }, - setUserName: { attribute: false }, - openTipUser: { type: Boolean }, - goToRepliedMessage: { attribute: false }, - listSeenMessages: { type: Array }, - addSeenMessage: { attribute: false }, - chatId: { type: String }, - isInProgress: {type: Boolean}, - id: {type: String} - } + static get properties() { + return { + messageObj: { type: Object }, + emojiPicker: { attribute: false }, + escapeHTML: { attribute: false }, + hideMessages: { type: Array }, + openDialogPrivateMessage: { type: Boolean }, + openDialogBlockUser: { type: Boolean }, + showBlockAddressIcon: { type: Boolean }, + setRepliedToMessageObj: { attribute: false }, + setEditedMessageObj: { attribute: false }, + sendMessage: { attribute: false }, + sendMessageForward: { attribute: false }, + openDialogImage: { type: Boolean }, + openDialogGif: { type: Boolean }, + openDeleteImage: { type: Boolean }, + openDeleteAttachment: { type: Boolean }, + isImageLoaded: { type: Boolean }, + isGifLoaded: { type: Boolean }, + isFirstMessage: { type: Boolean }, + isSingleMessageInGroup: { type: Boolean }, + isLastMessageInGroup: { type: Boolean }, + setToggledMessage: { attribute: false }, + setForwardProperties: { attribute: false }, + viewImage: { type: Boolean }, + setOpenPrivateMessage: { attribute: false }, + setOpenTipUser: { attribute: false }, + setOpenUserInfo: { attribute: false }, + setUserName: { attribute: false }, + openTipUser: { type: Boolean }, + goToRepliedMessage: { attribute: false }, + listSeenMessages: { type: Array }, + addSeenMessage: { attribute: false }, + chatId: { type: String }, + isInProgress: { type: Boolean }, + id: { type: String }, + }; + } + + constructor() { + super(); + this.messageObj = {}; + this.openDialogPrivateMessage = false; + this.openDialogBlockUser = false; + this.showBlockAddressIcon = false; + this.myAddress = + window.parent.reduxStore.getState().app.selectedAddress.address; + this.imageFetches = 0; + this.gifFetches = 0; + this.openDialogImage = false; + this.openDialogGif = false; + this.isImageLoaded = false; + this.isGifLoaded = false; + this.isFirstMessage = false; + this.isSingleMessageInGroup = false; + this.isLastMessageInGroup = false; + this.viewImage = false; + this.isInProgress = false; + } + + static get styles() { + return [chatStyles]; + } + + // Open & Close Private Message Chat Modal + showPrivateMessageModal() { + this.openDialogPrivateMessage = true; + } + + hidePrivateMessageModal() { + this.openDialogPrivateMessage = false; + } + + // Open & Close Block User Chat Modal + showBlockUserModal() { + this.openDialogBlockUser = true; + } + + hideBlockUserModal() { + this.openDialogBlockUser = false; + } + + showBlockIconFunc(bool) { + if (bool) { + this.showBlockAddressIcon = true; + } else { + this.showBlockAddressIcon = false; + } + } + + async downloadAttachment(attachment) { + const myNode = + window.parent.reduxStore.getState().app.nodeConfig.knownNodes[ + window.parent.reduxStore.getState().app.nodeConfig.node + ]; + + const nodeUrl = + myNode.protocol + '://' + myNode.domain + ':' + myNode.port; + try { + axios + .get( + `${nodeUrl}/arbitrary/QCHAT_ATTACHMENT/${attachment.name}/${attachment.identifier}?apiKey=${myNode.apiKey}`, + { responseType: 'blob' } + ) + .then((response) => { + let filename = attachment.attachmentName; + let blob = new Blob([response.data], { + type: 'application/octet-stream', + }); + this.saveFileToDisk(blob, filename); + }); + } catch (error) { + console.error(error); + } + } + + async saveFileToDisk(blob, fileName) { + try { + const fileHandle = await self.showSaveFilePicker({ + suggestedName: fileName, + types: [ + { + description: 'File', + }, + ], + }); + const writeFile = async (fileHandle, contents) => { + const writable = await fileHandle.createWritable(); + await writable.write(contents); + await writable.close(); + }; + writeFile(fileHandle, blob).then(() => console.log('FILE SAVED')); + } catch (error) { + console.log(error); + } + } + + setOpenDialogImage(val){ + this.openDialogImage = val } - constructor() { - super() - this.messageObj = {} - this.openDialogPrivateMessage = false - this.openDialogBlockUser = false - this.showBlockAddressIcon = false - this.myAddress = window.parent.reduxStore.getState().app.selectedAddress.address - this.imageFetches = 0 - this.gifFetches = 0 - this.openDialogImage = false - this.openDialogGif = false - this.isImageLoaded = false - this.isGifLoaded = false - this.isFirstMessage = false - this.isSingleMessageInGroup = false - this.isLastMessageInGroup = false - this.viewImage = false - this.isInProgress = false - } + firstUpdated() { + const autoSeeChatList = + window.parent.reduxStore.getState().app.autoLoadImageChats; + if ( + autoSeeChatList.includes(this.chatId) || + this.listSeenMessages.includes(this.messageObj.signature) + ) { + this.viewImage = true; + } - static get styles() { - return [chatStyles] - } + const tooltips = this.shadowRoot.querySelectorAll('vaadin-tooltip'); + tooltips.forEach((tooltip) => { + const overlay = tooltip.shadowRoot.querySelector( + 'vaadin-tooltip-overlay' + ); + overlay.shadowRoot.getElementById('overlay').style.cssText = + 'background-color: transparent; box-shadow: rgb(50 50 93 / 25%) 0px 2px 5px -1px, rgb(0 0 0 / 30%) 0px 1px 3px -1px'; + overlay.shadowRoot.getElementById('content').style.cssText = + 'background-color: var(--reactions-tooltip-bg); color: var(--chat-bubble-msg-color); text-align: center; padding: 20px 10px; border-radius: 8px; font-family: Roboto, sans-serif; letter-spacing: 0.3px; font-weight: 300; font-size: 13.5px; transition: all 0.3s ease-in-out;'; + }); + this.clearConsole(); + setInterval(() => { + this.clearConsole(); + }, 60000); + } + + shouldUpdate(changedProperties) { + if (changedProperties.has('messageObj')) { + return true; + } + if (changedProperties.has('showBlockAddressIcon')) { + return true; + } + if (changedProperties.has('openDialogBlockUser')) { + return true; + } + if (changedProperties.has('viewImage')) { + return true; + } + if (changedProperties.has('isImageLoaded')) { + return true; + } + if (changedProperties.has('openDialogImage')) { + return true; + } + if (changedProperties.has('openDialogPrivateMessage')) { + return true; + } + if (changedProperties.has('openDialogGif')) { + return true; + } + if (changedProperties.has('isGifLoaded')) { + return true; + } + return false; + } + + clearConsole() { + if (!isElectron()) { + } else { + console.clear(); + window.parent.electronAPI.clearCache(); + } + } + + render() { + const hidemsg = this.hideMessages; + let message = ''; + let messageVersion2 = ''; + let messageVersion2WithLink = null; + let reactions = []; + let repliedToData = null; + let image = null; + let gif = null; + let isImageDeleted = false; + let isAttachmentDeleted = false; + let version = 0; + let isForwarded = false; + let isEdited = false; + let attachment = null; + try { + const parsedMessageObj = JSON.parse(this.messageObj.decodedMessage); + if (+parsedMessageObj.version > 1 && parsedMessageObj.messageText) { + messageVersion2 = generateHTML(parsedMessageObj.messageText, [ + StarterKit, + Underline, + Highlight, + // other extensions … + ]); + messageVersion2WithLink = processText(messageVersion2); + } + message = parsedMessageObj.messageText; + repliedToData = this.messageObj.repliedToData; + isImageDeleted = parsedMessageObj.isImageDeleted; + isAttachmentDeleted = parsedMessageObj.isAttachmentDeleted; + // reactions = parsedMessageObj.reactions || [] + version = parsedMessageObj.version; + isForwarded = parsedMessageObj.type === 'forward'; + isEdited = parsedMessageObj.isEdited && true; + if ( + parsedMessageObj.attachments && + Array.isArray(parsedMessageObj.attachments) && + parsedMessageObj.attachments.length > 0 + ) { + attachment = parsedMessageObj.attachments[0]; + } + if ( + parsedMessageObj.images && + Array.isArray(parsedMessageObj.images) && + parsedMessageObj.images.length > 0 + ) { + image = parsedMessageObj.images[0]; + } + if ( + parsedMessageObj.gifs && + Array.isArray(parsedMessageObj.gifs) && + parsedMessageObj.gifs.length > 0 + ) { + gif = parsedMessageObj.gifs[0]; + } + } catch (error) { + message = this.messageObj.decodedMessage; + } + let avatarImg = ''; + let imageHTML = ''; + let imageHTMLDialog = ''; + let imageUrl = ''; + let gifHTML = ''; + let gifHTMLDialog = ''; + let gifUrl = ''; + let nameMenu = ''; + let levelFounder = ''; + let hideit = hidemsg.includes(this.messageObj.sender); + let forwarded = ''; + let edited = ''; + + levelFounder = html``; + + if (this.messageObj.senderName) { + 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 avatarUrl = `${nodeUrl}/arbitrary/THUMBNAIL/${this.messageObj.senderName}/qortal_avatar?async=true&apiKey=${myNode.apiKey}`; + avatarImg = html``; + } else { + avatarImg = html``; + } - // Open & Close Private Message Chat Modal - showPrivateMessageModal() { - this.openDialogPrivateMessage = true - } + const createGif = (gif) => { + const gifHTMLRes = new Image(); + gifHTMLRes.src = gif; + gifHTMLRes.style = + 'max-width:45vh; max-height:40vh; border-radius: 5px; cursor: pointer;'; + gifHTMLRes.onclick = () => { + this.openDialogGif = true; + }; + gifHTMLRes.onload = () => { + this.isGifLoaded = true; + }; + gifHTMLRes.onerror = () => { + if (this.gifFetches < 4) { + setTimeout(() => { + this.gifFetches = this.gifFetches + 1; + gifHTMLRes.src = gif; + }, 10000); + } else { + gifHTMLRes.src = '/img/chain.png'; + gifHTMLRes.style = + 'max-width:45vh; max-height:20vh; border-radius: 5px; filter: opacity(0.5);'; + gifHTMLRes.onclick = () => {}; + this.isGifLoaded = true; + } + }; + return gifHTMLRes; + }; - hidePrivateMessageModal() { - this.openDialogPrivateMessage = false - } + if (image) { + const myNode = + window.parent.reduxStore.getState().app.nodeConfig.knownNodes[ + window.parent.reduxStore.getState().app.nodeConfig.node + ]; + const nodeUrl = + myNode.protocol + '://' + myNode.domain + ':' + myNode.port; + imageUrl = `${nodeUrl}/arbitrary/${image.service}/${image.name}/${image.identifier}?async=true&apiKey=${myNode.apiKey}`; - // Open & Close Block User Chat Modal - showBlockUserModal() { - this.openDialogBlockUser = true - } + if (this.viewImage || this.myAddress === this.messageObj.sender) { + imageHTML = html` this.setOpenDialogImage(val)} + >`; + // imageHTML = createImage(imageUrl) + // imageHTMLDialog = createImage(imageUrl) + // imageHTMLDialog.style = "height: auto; max-height: 80vh; width: auto; max-width: 80vw; object-fit: contain; border-radius: 5px;" + } + } - hideBlockUserModal() { - this.openDialogBlockUser = false - } + if (gif) { + const myNode = + window.parent.reduxStore.getState().app.nodeConfig.knownNodes[ + window.parent.reduxStore.getState().app.nodeConfig.node + ]; + const nodeUrl = + myNode.protocol + '://' + myNode.domain + ':' + myNode.port; + gifUrl = `${nodeUrl}/arbitrary/${gif.service}/${gif.name}/${gif.identifier}?filepath=${gif.filePath}&apiKey=${myNode.apiKey}`; + if (this.viewImage || this.myAddress === this.messageObj.sender) { + gifHTML = createGif(gifUrl); + gifHTMLDialog = createGif(gifUrl); + gifHTMLDialog.style = + 'height: auto; max-height: 80vh; width: auto; max-width: 80vw; object-fit: contain; border-radius: 5px;'; + } + } - showBlockIconFunc(bool) { - if (bool) { - this.showBlockAddressIcon = true - } else { - this.showBlockAddressIcon = false - } - } + nameMenu = html` + + ${this.messageObj.senderName + ? this.messageObj.senderName + : cropAddress(this.messageObj.sender)} + + `; - async downloadAttachment(attachment) { + forwarded = html` + + ${translate('blockpage.bcchange17')} + + `; - const myNode = window.parent.reduxStore.getState().app.nodeConfig.knownNodes[window.parent.reduxStore.getState().app.nodeConfig.node] + edited = html` + + ${translate('chatpage.cchange68')} + + `; - const nodeUrl = myNode.protocol + '://' + myNode.domain + ':' + myNode.port - try { - axios.get(`${nodeUrl}/arbitrary/QCHAT_ATTACHMENT/${attachment.name}/${attachment.identifier}?apiKey=${myNode.apiKey}`, { responseType: 'blob' }) - .then(response => { - let filename = attachment.attachmentName - let blob = new Blob([response.data], { type: "application/octet-stream" }) - this.saveFileToDisk(blob, filename) - }) - } catch (error) { - console.error(error) - } - } + if (repliedToData) { + try { + const parsedMsg = JSON.parse(repliedToData.decodedMessage); + repliedToData.decodedMessage = parsedMsg; + } catch (error) {} + } - async saveFileToDisk(blob, fileName) { - try { - const fileHandle = await self.showSaveFilePicker({ - suggestedName: fileName, - types: [{ - description: "File", - }] - }) - const writeFile = async (fileHandle, contents) => { - const writable = await fileHandle.createWritable() - await writable.write(contents) - await writable.close() - } - writeFile(fileHandle, blob).then(() => console.log("FILE SAVED")) - } catch (error) { - console.log(error) - } - } + let repliedToMessageText = ''; + if ( + repliedToData && + repliedToData.decodedMessage && + repliedToData.decodedMessage.messageText + ) { + try { + repliedToMessageText = generateHTML( + repliedToData.decodedMessage.messageText, + [ + StarterKit, + Underline, + Highlight, + // other extensions … + ] + ); + } catch (error) {} + } - firstUpdated() { - const autoSeeChatList = window.parent.reduxStore.getState().app.autoLoadImageChats - if (autoSeeChatList.includes(this.chatId) || this.listSeenMessages.includes(this.messageObj.signature)) { - this.viewImage = true - } + let replacedMessage = ''; + if (message && +version < 2) { + const escapedMessage = this.escapeHTML(message); + if (escapedMessage) { + replacedMessage = escapedMessage.replace( + new RegExp('\r?\n', 'g'), + '
' + ); + } + } - const tooltips = this.shadowRoot.querySelectorAll('vaadin-tooltip') - tooltips.forEach(tooltip => { - const overlay = tooltip.shadowRoot.querySelector('vaadin-tooltip-overlay') - overlay.shadowRoot.getElementById("overlay").style.cssText = "background-color: transparent; box-shadow: rgb(50 50 93 / 25%) 0px 2px 5px -1px, rgb(0 0 0 / 30%) 0px 1px 3px -1px" - overlay.shadowRoot.getElementById('content').style.cssText = "background-color: var(--reactions-tooltip-bg); color: var(--chat-bubble-msg-color); text-align: center; padding: 20px 10px; border-radius: 8px; font-family: Roboto, sans-serif; letter-spacing: 0.3px; font-weight: 300; font-size: 13.5px; transition: all 0.3s ease-in-out;" - }) - this.clearConsole() - setInterval(() => { - this.clearConsole() - }, 60000) - } - - shouldUpdate(changedProperties){ - if (changedProperties.has('messageObj')) { - return true - } - if(changedProperties.has('showBlockAddressIcon')){ - return true - } - if(changedProperties.has('openDialogBlockUser')){ - return true - } - if(changedProperties.has('viewImage')){ - return true - } - if(changedProperties.has('isImageLoaded')){ - return true - } - if(changedProperties.has('openDialogImage')){ - return true - } - if(changedProperties.has('openDialogPrivateMessage')){ - return true - } - if(changedProperties.has('openDialogGif')){ - return true - } - if(changedProperties.has('isGifLoaded')){ - return true - } - return false - } - - clearConsole() { - if (!isElectron()) { - } else { - console.clear() - window.parent.electronAPI.clearCache() - } - } - - render() { - const hidemsg = this.hideMessages - let message = "" - let messageVersion2 = "" - let messageVersion2WithLink = null - let reactions = [] - let repliedToData = null - let image = null - let gif = null - let isImageDeleted = false - let isAttachmentDeleted = false - let version = 0 - let isForwarded = false - let isEdited = false - let attachment = null - try { - const parsedMessageObj = JSON.parse(this.messageObj.decodedMessage) - if (+parsedMessageObj.version > 1 && parsedMessageObj.messageText) { - messageVersion2 = generateHTML(parsedMessageObj.messageText, [ - StarterKit, - Underline, - Highlight - // other extensions … - ]) - messageVersion2WithLink = processText(messageVersion2) - - } - message = parsedMessageObj.messageText - repliedToData = this.messageObj.repliedToData - isImageDeleted = parsedMessageObj.isImageDeleted - isAttachmentDeleted = parsedMessageObj.isAttachmentDeleted - // reactions = parsedMessageObj.reactions || [] - version = parsedMessageObj.version - isForwarded = parsedMessageObj.type === 'forward' - isEdited = parsedMessageObj.isEdited && true - if (parsedMessageObj.attachments && Array.isArray(parsedMessageObj.attachments) && parsedMessageObj.attachments.length > 0) { - attachment = parsedMessageObj.attachments[0] - } - if (parsedMessageObj.images && Array.isArray(parsedMessageObj.images) && parsedMessageObj.images.length > 0) { - image = parsedMessageObj.images[0] - } - if (parsedMessageObj.gifs && Array.isArray(parsedMessageObj.gifs) && parsedMessageObj.gifs.length > 0) { - gif = parsedMessageObj.gifs[0] - } - } catch (error) { - message = this.messageObj.decodedMessage - } - let avatarImg = '' - let imageHTML = '' - let imageHTMLDialog = '' - let imageUrl = '' - let gifHTML = '' - let gifHTMLDialog = '' - let gifUrl = '' - let nameMenu = '' - let levelFounder = '' - let hideit = hidemsg.includes(this.messageObj.sender) - let forwarded = '' - let edited = '' - - levelFounder = html`` - - if (this.messageObj.senderName) { - 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 avatarUrl = `${nodeUrl}/arbitrary/THUMBNAIL/${this.messageObj.senderName}/qortal_avatar?async=true&apiKey=${myNode.apiKey}` - avatarImg = html`` - } else { - avatarImg = html`` - } - - const createImage = (imageUrl) => { - const imageHTMLRes = new Image() - imageHTMLRes.src = imageUrl - imageHTMLRes.style = "max-width:45vh; max-height:40vh; border-radius: 5px; cursor: pointer;" - imageHTMLRes.onclick = () => { - this.openDialogImage = true - } - imageHTMLRes.onload = () => { - this.isImageLoaded = true - } - imageHTMLRes.onerror = () => { - if (this.imageFetches < 4) { - setTimeout(() => { - this.imageFetches = this.imageFetches + 1 - imageHTMLRes.src = imageUrl - }, 10000) - } else { - setTimeout(() => { - this.imageFetches = this.imageFetches + 1 - imageHTMLRes.src = imageUrl - }, 15000) - } - } - return imageHTMLRes - } - - const createGif = (gif) => { - const gifHTMLRes = new Image() - gifHTMLRes.src = gif - gifHTMLRes.style = "max-width:45vh; max-height:40vh; border-radius: 5px; cursor: pointer;" - gifHTMLRes.onclick = () => { - this.openDialogGif = true - } - gifHTMLRes.onload = () => { - this.isGifLoaded = true - } - gifHTMLRes.onerror = () => { - if (this.gifFetches < 4) { - setTimeout(() => { - this.gifFetches = this.gifFetches + 1 - gifHTMLRes.src = gif - }, 10000) - } else { - gifHTMLRes.src = '/img/chain.png' - gifHTMLRes.style = "max-width:45vh; max-height:20vh; border-radius: 5px; filter: opacity(0.5);" - gifHTMLRes.onclick = () => { } - this.isGifLoaded = true - } - } - return gifHTMLRes - } - - if (image) { - const myNode = window.parent.reduxStore.getState().app.nodeConfig.knownNodes[window.parent.reduxStore.getState().app.nodeConfig.node] - const nodeUrl = myNode.protocol + '://' + myNode.domain + ':' + myNode.port - imageUrl = `${nodeUrl}/arbitrary/${image.service}/${image.name}/${image.identifier}?async=true&apiKey=${myNode.apiKey}` - - if (this.viewImage || this.myAddress === this.messageObj.sender) { - imageHTML = createImage(imageUrl) - imageHTMLDialog = createImage(imageUrl) - imageHTMLDialog.style = "height: auto; max-height: 80vh; width: auto; max-width: 80vw; object-fit: contain; border-radius: 5px;" - } - } - - if (gif) { - const myNode = window.parent.reduxStore.getState().app.nodeConfig.knownNodes[window.parent.reduxStore.getState().app.nodeConfig.node] - const nodeUrl = myNode.protocol + '://' + myNode.domain + ':' + myNode.port - gifUrl = `${nodeUrl}/arbitrary/${gif.service}/${gif.name}/${gif.identifier}?filepath=${gif.filePath}&apiKey=${myNode.apiKey}` - if (this.viewImage || this.myAddress === this.messageObj.sender) { - gifHTML = createGif(gifUrl) - gifHTMLDialog = createGif(gifUrl) - gifHTMLDialog.style = "height: auto; max-height: 80vh; width: auto; max-width: 80vw; object-fit: contain; border-radius: 5px;" - } - } - - nameMenu = html` - - ${this.messageObj.senderName ? this.messageObj.senderName : cropAddress(this.messageObj.sender)} - - ` - - forwarded = html` - - ${translate("blockpage.bcchange17")} - - ` - - edited = html` - - ${translate("chatpage.cchange68")} - - ` - - if (repliedToData) { - try { - const parsedMsg = JSON.parse(repliedToData.decodedMessage) - repliedToData.decodedMessage = parsedMsg - } catch (error) { - } - - } - - let repliedToMessageText = '' - if (repliedToData && repliedToData.decodedMessage && repliedToData.decodedMessage.messageText) { - try { - repliedToMessageText = generateHTML(repliedToData.decodedMessage.messageText, [ - StarterKit, - Underline, - Highlight - // other extensions … - ]) - } catch (error) { - - } - } - - let replacedMessage = '' - if (message && +version < 2) { - const escapedMessage = this.escapeHTML(message) - if (escapedMessage) { - replacedMessage = escapedMessage.replace(new RegExp('\r?\n', 'g'), '
') - } - - } - - return hideit ? html`
  • ` : html` + return hideit + ? html`
  • ` + : html`
  • -
    - ${(this.isSingleMessageInGroup === false || - (this.isSingleMessageInGroup === true && this.isLastMessageInGroup === true)) - ? ( - html` -
    { - if (this.myAddress === this.messageObj.sender) return - this.setOpenUserInfo(true) - this.setUserName(this.messageObj) - }} class="message-data-avatar"> - ${avatarImg} -
    - ` - ) : - html` -
    - `} + style="${ + this.isSingleMessageInGroup === true && + this.isLastMessageInGroup === false && + 'margin-bottom: 0' + }"> +
    + ${ + this.isSingleMessageInGroup === false || + (this.isSingleMessageInGroup === true && + this.isLastMessageInGroup === true) + ? html` +
    { + if ( + this.myAddress === + this.messageObj.sender + ) + return; + this.setOpenUserInfo(true); + this.setUserName( + this.messageObj + ); + }} + class="message-data-avatar" + > + ${avatarImg} +
    + ` + : html` +
    + ` + }
    + ${ + this.myAddress === this.messageObj.sender && + 'message-myBg' + } + ${ + ((this.isFirstMessage === true && + this.isSingleMessageInGroup === false) || + (this.isSingleMessageInGroup === true && + this.isLastMessageInGroup === true)) && + this.myAddress !== this.messageObj.sender + ? 'message-triangle' + : ((this.isFirstMessage === true && + this.isSingleMessageInGroup === + false) || + (this.isSingleMessageInGroup === + true && + this.isLastMessageInGroup === + true)) && + this.myAddress === this.messageObj.sender + ? 'message-myTriangle' + : null + }`}" + style="${ + this.isSingleMessageInGroup === true && + this.isLastMessageInGroup === false + ? 'margin-bottom: 0;' + : null + } + ${ + this.isFirstMessage === false && + this.isSingleMessageInGroup === true && + this.isLastMessageInGroup === false + ? 'border-radius: 8px 25px 25px 8px;' + : this.isFirstMessage === true && + this.isSingleMessageInGroup === true && + this.isLastMessageInGroup === false + ? 'border-radius: 27px 25px 25px 12px;' + : this.isFirstMessage === false && + this.isSingleMessageInGroup === true && + this.isLastMessageInGroup === true + ? 'border-radius: 10px 25px 25px 0;' + : this.isFirstMessage === true && + this.isSingleMessageInGroup === false && + this.isLastMessageInGroup === true + ? 'border-radius: 25px 25px 25px 0px;' + : null + }"> - ${repliedToData && html` -
    { - this.goToRepliedMessage(repliedToData, this.messageObj) - }}> -

    - ${repliedToData.senderName ? repliedToData.senderName : cropAddress(repliedToData.sender)} -

    -

    - ${version && version.toString() === '1' ? html` - ${repliedToData.decodedMessage.messageText} - ` : ''} - ${+version > 1 && repliedToMessageText ? html` - ${unsafeHTML(repliedToMessageText)} - ` - : ''} -

    -
    - `} - ${image && !isImageDeleted && !this.viewImage && this.myAddress !== this.messageObj.sender ? html` -
    { - this.viewImage = true - // this.addSeenMessage(this.messageObj.signature) - }} - class=${[`image-container`, !this.isImageLoaded ? 'defaultSize' : ''].join(' ')} - style=${this.isFirstMessage && "margin-top: 10px;"}> -
    - ${translate("chatpage.cchange40")} -
    -
    - ` : html``} - ${!this.isImageLoaded && image && this.viewImage ? html` -
    -
    -
    - - `: ''} - ${image && !isImageDeleted && (this.viewImage || this.myAddress === this.messageObj.sender) ? html` -
    - ${imageHTML} - ${this.myAddress === this.messageObj.sender ? html` - { - this.openDeleteImage = true - }} - class="image-delete-icon" icon="vaadin:close" slot="icon"> - ` : ''} - -
    - ` : image && isImageDeleted ? html` -

    ${translate("chatpage.cchange80")}

    - ` : html``} - ${gif && !this.viewImage && this.myAddress !== this.messageObj.sender ? html` -
    { - this.viewImage = true - }} - class=${[`image-container`, !this.isImageLoaded ? 'defaultSize' : ''].join(' ')} - style=${this.isFirstMessage && "margin-top: 10px;"}> -
    - ${translate("gifs.gchange25")} -
    -
    - ` : html``} - ${gif && (this.viewImage || this.myAddress === this.messageObj.sender) ? html` -
    - ${gifHTML} -
    - ` : html``} - ${attachment && !isAttachmentDeleted ? - html` -
    await this.downloadAttachment(attachment)} class="attachment-container"> -
    - attachment-icon -
    -
    -

    - ${attachment && attachment.attachmentName} -

    -

    - ${roundToNearestDecimal(attachment.attachmentSize)} mb -

    -
    - - - ${this.myAddress === this.messageObj.sender - ? html` - { - e.stopPropagation() - this.openDeleteAttachment = true - }} - class="image-delete-icon" icon="vaadin:close" slot="icon"> - - ` : html``} -
    - ` - : attachment && isAttachmentDeleted ? - html` -
    -
    -

    - ${translate("chatpage.cchange82")} -

    -
    -
    - ` - : html``} + ${ + repliedToData && + html` +
    { + this.goToRepliedMessage( + repliedToData, + this.messageObj + ); + }} + > +

    + ${repliedToData.senderName + ? repliedToData.senderName + : cropAddress( + repliedToData.sender + )} +

    +

    + ${version && + version.toString() === '1' + ? html` + ${repliedToData + .decodedMessage + .messageText} + ` + : ''} + ${+version > 1 && + repliedToMessageText + ? html` + ${unsafeHTML( + repliedToMessageText + )} + ` + : ''} +

    +
    + ` + } + ${ + image && + !isImageDeleted && + !this.viewImage && + this.myAddress !== + this.messageObj.sender + ? html` +
    { + this.viewImage = true; + // this.addSeenMessage(this.messageObj.signature) + }} + class=${[ + `image-container`, + !this.isImageLoaded + ? 'defaultSize' + : '', + ].join(' ')} + style=${this + .isFirstMessage && + 'margin-top: 10px;'} + > +
    + ${translate( + 'chatpage.cchange40' + )} +
    +
    + ` + : html`` + } + + ${ + image && + !isImageDeleted && + (this.viewImage || + this.myAddress === + this.messageObj.sender) + ? html` +
    + ${imageHTML} + ${this.myAddress === + this.messageObj.sender + ? html` + { + this.openDeleteImage = true; + }} + class="image-delete-icon" + icon="vaadin:close" + slot="icon" + > + ` + : ''} +
    + ` + : image && isImageDeleted + ? html` +

    + ${translate( + 'chatpage.cchange80' + )} +

    + ` + : html`` + } + ${ + gif && + !this.viewImage && + this.myAddress !== + this.messageObj.sender + ? html` +
    { + this.viewImage = true; + }} + class=${[ + `image-container`, + !this.isImageLoaded + ? 'defaultSize' + : '', + ].join(' ')} + style=${this + .isFirstMessage && + 'margin-top: 10px;'} + > +
    + ${translate( + 'gifs.gchange25' + )} +
    +
    + ` + : html`` + } + ${ + gif && + (this.viewImage || + this.myAddress === + this.messageObj.sender) + ? html` +
    + ${gifHTML} +
    + ` + : html`` + } + ${ + attachment && !isAttachmentDeleted + ? html` +
    + await this.downloadAttachment( + attachment + )} + class="attachment-container" + > +
    + attachment-icon +
    +
    +

    + ${attachment && + attachment.attachmentName} +

    +

    + ${roundToNearestDecimal( + attachment.attachmentSize + )} + mb +

    +
    + + + ${this.myAddress === + this.messageObj.sender + ? html` + { + e.stopPropagation(); + this.openDeleteAttachment = true; + }} + class="image-delete-icon" + icon="vaadin:close" + slot="icon" + > + + ` + : html``} +
    + ` + : attachment && isAttachmentDeleted + ? html` +
    +
    +

    + ${translate( + 'chatpage.cchange82' + )} +

    +
    +
    + ` + : html`` + }
    - ${+version > 1 ? messageVersion2WithLink ? html`${messageVersion2WithLink}` : html` - ${unsafeHTML(messageVersion2)} - ` : ''} + style=${ + image && + replacedMessage !== '' && + 'margin-top: 15px;' + }> + ${ + +version > 1 + ? messageVersion2WithLink + ? html`${messageVersion2WithLink}` + : html` + ${unsafeHTML( + messageVersion2 + )} + ` + : '' + } - ${version && version.toString() === '1' ? html` - ${unsafeHTML(this.emojiPicker.parse(replacedMessage))} - ` : ''} - ${version && version.toString() === '0' ? html` - ${unsafeHTML(this.emojiPicker.parse(replacedMessage))} - ` : ''} + ${ + version && + version.toString() === '1' + ? html` + ${unsafeHTML( + this.emojiPicker.parse( + replacedMessage + ) + )} + ` + : '' + } + ${ + version && + version.toString() === '0' + ? html` + ${unsafeHTML( + this.emojiPicker.parse( + replacedMessage + ) + )} + ` + : '' + }
    - ${isEdited ? - html` - - ${edited} - - ` - : '' - } - ${this.isInProgress ? html` -

    ${translate('chatpage.cchange91')}

    - ` : html` - - `} + style=${ + isEdited + ? 'justify-content: space-between;' + : 'justify-content: flex-end;' + } + class="${ + (this.isFirstMessage === + false && + this + .isSingleMessageInGroup === + true && + this + .isLastMessageInGroup === + true) || + (this.isFirstMessage === true && + this + .isSingleMessageInGroup === + false && + this + .isLastMessageInGroup === + true) + ? 'message-data-time' + : 'message-data-time-hidden' + }"> + ${ + isEdited + ? html` + + ${edited} + + ` + : '' + } + ${ + this.isInProgress + ? html` +

    + ${translate( + 'chatpage.cchange91' + )} +

    + ` + : html` + + ` + }
    - ${this.isInProgress ? '' : html` - this.showPrivateMessageModal()} - .showBlockUserModal=${() => this.showBlockUserModal()} - .showBlockIconFunc=${(props) => this.showBlockIconFunc(props)} - .showBlockAddressIcon=${this.showBlockAddressIcon} - .originalMessage=${{ ...this.messageObj, message }} - .setRepliedToMessageObj=${this.setRepliedToMessageObj} - .setEditedMessageObj=${this.setEditedMessageObj} - .myAddress=${this.myAddress} - @blur=${() => this.showBlockIconFunc(false)} - .sendMessage=${this.sendMessage} - .sendMessageForward=${this.sendMessageForward} - version=${version} - .emojiPicker=${this.emojiPicker} - .setToggledMessage=${this.setToggledMessage} - .setForwardProperties=${this.setForwardProperties} - ?firstMessageInChat=${this.messageObj.firstMessageInChat} - .setOpenPrivateMessage=${(val) => this.setOpenPrivateMessage(val)} - .setOpenTipUser=${(val) => this.setOpenTipUser(val)} - .setUserName=${(val) => this.setUserName(val)} - .gif=${!!gif} - > - - `} + ${ + this.isInProgress + ? '' + : html` + + this.showPrivateMessageModal()} + .showBlockUserModal=${() => + this.showBlockUserModal()} + .showBlockIconFunc=${(props) => + this.showBlockIconFunc( + props + )} + .showBlockAddressIcon=${this + .showBlockAddressIcon} + .originalMessage=${{ + ...this.messageObj, + message, + }} + .setRepliedToMessageObj=${this + .setRepliedToMessageObj} + .setEditedMessageObj=${this + .setEditedMessageObj} + .myAddress=${this.myAddress} + @blur=${() => + this.showBlockIconFunc( + false + )} + .sendMessage=${this.sendMessage} + .sendMessageForward=${this + .sendMessageForward} + version=${version} + .emojiPicker=${this.emojiPicker} + .setToggledMessage=${this + .setToggledMessage} + .setForwardProperties=${this + .setForwardProperties} + ?firstMessageInChat=${this + .messageObj + .firstMessageInChat} + .setOpenPrivateMessage=${( + val + ) => + this.setOpenPrivateMessage( + val + )} + .setOpenTipUser=${(val) => + this.setOpenTipUser(val)} + .setUserName=${(val) => + this.setUserName(val)} + .gif=${!!gif} + > + + ` + }
    -
    +
    ${reactions.map((reaction, index) => { - return html` - this.sendMessage({ - type: 'reaction', - editedMessageObj: this.messageObj, - reaction: reaction.type, - })} - id=${`reactions-${indexMessageTemplate}`} - class="reactions-bg"> - ${reaction.type} - ${reaction.qty} - 3 ? - ( - `${reaction.users[0].name - ? reaction.users[0].nameMessageTemplate - : cropAddress(reaction.users[0].address)}, - ${reaction.users[1].name - ? reaction.users[1].name - : cropAddress(reaction.users[1].address)}, - ${reaction.users[2].name - ? reaction.users[2].name - : cropAddress(reaction.users[2].address)} - ${get("chatpage.cchange71")} ${reaction.users.length - 3} ${get("chatpage.cchange72")}${(reaction.users.length - 3) > 1 ? html`${get("chatpage.cchange73")}` : ""} ${get("chatpage.cchange74")} ${reaction.type}` - ) : reaction.users.length === 3 ? - ( - `${reaction.users[0].name - ? reaction.users[0].name - : cropAddress(reaction.users[0].address)}, - ${reaction.users[1].name - ? reaction.users[1].name - : cropAddress(reaction.users[1].address)} - ${get("chatpage.cchange71")} - ${reaction.users[2].name - ? reaction.users[2].name - : cropAddress(reaction.users[2].address)} ${get("chatpage.cchange74")} ${reaction.type}` - ) : reaction.users.length === 2 ? - ( - `${reaction.users[0].name - ? reaction.users[0].name - : cropAddress(reaction.users[0].address)} - ${get("chatpage.cchange71")} - ${reaction.users[1].name - ? reaction.users[1].namMessageTemplatee - : cropAddress(reaction.users[1].address)} ${get("chatpage.cchange74")} ${reaction.type}` - ) : reaction.users.length === 1 ? - ( - `${reaction.users[0].name - ? reaction.users[0].name - : cropAddress(reaction.users[0].address)} ${get("chatpage.cchange74")} ${reaction.type}` - ) - : ""}> - - - ` - })} + return html` + + this.sendMessage({ + type: 'reaction', + editedMessageObj: + this.messageObj, + reaction: reaction.type, + })} + id=${`reactions-${indexMessageTemplate}`} + class="reactions-bg" + > + ${reaction.type} ${reaction.qty} + 3 + ? `${ + reaction.users[0] + .name + ? reaction + .users[0] + .nameMessageTemplate + : cropAddress( + reaction + .users[0] + .address + ) + }, + ${ + reaction.users[1].name + ? reaction.users[1].name + : cropAddress( + reaction.users[1] + .address + ) + }, + ${ + reaction.users[2].name + ? reaction.users[2].name + : cropAddress( + reaction.users[2] + .address + ) + } + ${get('chatpage.cchange71')} ${ + reaction.users + .length - 3 + } ${get( + 'chatpage.cchange72' + )}${ + reaction.users + .length - + 3 > + 1 + ? html`${get( + 'chatpage.cchange73' + )}` + : '' + } ${get( + 'chatpage.cchange74' + )} ${reaction.type}` + : reaction.users.length === + 3 + ? `${ + reaction.users[0] + .name + ? reaction + .users[0] + .name + : cropAddress( + reaction + .users[0] + .address + ) + }, + ${ + reaction.users[1].name + ? reaction.users[1].name + : cropAddress( + reaction.users[1] + .address + ) + } + ${get('chatpage.cchange71')} + ${ + reaction.users[2].name + ? reaction.users[2].name + : cropAddress( + reaction.users[2] + .address + ) + } ${get('chatpage.cchange74')} ${ + reaction.type + }` + : reaction.users.length === + 2 + ? `${ + reaction.users[0] + .name + ? reaction + .users[0] + .name + : cropAddress( + reaction + .users[0] + .address + ) + } + ${get('chatpage.cchange71')} + ${ + reaction.users[1].name + ? reaction.users[1] + .namMessageTemplatee + : cropAddress( + reaction.users[1] + .address + ) + } ${get('chatpage.cchange74')} ${ + reaction.type + }` + : reaction.users.length === + 1 + ? `${ + reaction.users[0] + .name + ? reaction + .users[0] + .name + : cropAddress( + reaction + .users[0] + .address + ) + } ${get( + 'chatpage.cchange74' + )} ${reaction.type}` + : ''} + > + + + `; + })}
    @@ -1698,7 +2241,11 @@ class MessageTemplate extends LitElement { this.hidePrivateMessageModal()} .hideBlockUserModal=${() => this.hideBlockUserModal()} toblockaddress=${this.messageObj.sender} @@ -1708,30 +2255,33 @@ class MessageTemplate extends LitElement { id="showDialogPublicKey" ?open=${this.openDialogImage} @closed=${() => { - this.openDialogImage = false - }}> + this.openDialogImage = false; + }}>
    - ${imageHTMLDialog} + ${this.openDialogImage ? html` + + ` : ''} +
    { -MessageTemplate - this.openDialogImage = false - }} + MessageTemplate; + this.openDialogImage = false; + }} > - ${translate("general.close")} + ${translate('general.close')} { - this.openDialogGif = false - }}>MessageTemplate + this.openDialogGif = false; + }}>MessageTemplate
    ${gifHTMLDialog} @@ -1741,34 +2291,35 @@ MessageTemplate dialogAction="cancel" class="red" @click=${() => { - - this.openDialogGif = false - }} + this.openDialogGif = false; + }} > - ${translate("general.close")} + ${translate('general.close')} MessageTemplate { - this.openDeleteImage = false - }}> + this.openDeleteImage = false; + }}>
    -

    ${translate("chatpage.cchange78")}

    +

    ${translate('chatpage.cchange78')}

    - ` : '' } ${this.status.status === 'READY' ? html` - this.setOpenDialogImage(true)} src=${this.url} /> +
    + this.setOpenDialogImage(true)} src=${this.url} /> + + Copy + +
    ` : ''}
    + ` diff --git a/plugins/plugins/core/components/ChatPage.js b/plugins/plugins/core/components/ChatPage.js index 8ccd94f0..b3bae116 100644 --- a/plugins/plugins/core/components/ChatPage.js +++ b/plugins/plugins/core/components/ChatPage.js @@ -2171,6 +2171,7 @@ class ChatPage extends LitElement { } } } + async pasteImage(e) { const event = e @@ -2185,7 +2186,7 @@ class ChatPage extends LitElement { if (event.clipboardData) { const blobFound = handleTransferIntoURL(event.clipboardData) if (blobFound) { - this.insertImage(blobFound) + this.insertFile(blobFound) return } else { const item_list = await navigator.clipboard.read() @@ -2204,7 +2205,7 @@ class ChatPage extends LitElement { let file = new File([blob], "name", { type: image_type }) - this.insertImage(file) + this.insertFile(file) } catch (error) { console.error(error) let errorMsg = get("chatpage.cchange81") diff --git a/plugins/plugins/core/components/ChatScroller-css.js b/plugins/plugins/core/components/ChatScroller-css.js index 7c337d70..2aad505d 100644 --- a/plugins/plugins/core/components/ChatScroller-css.js +++ b/plugins/plugins/core/components/ChatScroller-css.js @@ -756,9 +756,9 @@ export const chatStyles = css` .unread-divider { width: 100%; - background: #9B111E; padding: 5px; - color: #FAEBD7; + color: var(--black); + border-bottom: 1px solid var(--black); display: flex; justify-content: center; border-radius: 2px; diff --git a/plugins/plugins/core/components/ChatTextEditor.js b/plugins/plugins/core/components/ChatTextEditor.js index 85e6d749..4d4c6b75 100644 --- a/plugins/plugins/core/components/ChatTextEditor.js +++ b/plugins/plugins/core/components/ChatTextEditor.js @@ -502,7 +502,11 @@ mwc-checkbox::shadow .mdc-checkbox::after, mwc-checkbox::shadow .mdc-checkbox::b accept="image/*, .doc, .docx, .pdf, .zip, .pdf, .txt, .odt, .ods, .xls, .xlsx, .ppt, .pptx" />
    - +
    @@ -572,6 +576,29 @@ mwc-checkbox::shadow .mdc-checkbox::after, mwc-checkbox::shadow .mdc-checkbox::b } } + async handlePasteEvent(e) { + if (e.type === 'paste') { + e.preventDefault(); + const item_list = await navigator.clipboard.read(); + let image_type; // we will feed this later + const item = item_list.find( item => // choose the one item holding our image + item.types.some( type => { + if (type.startsWith( 'image/')) { + image_type = type; + return true; + } + }) + ); + if(item){ + const blob = item && await item.getType( image_type ); + var file = new File([blob], "name", { + type: image_type + }); + this.insertFile(file); + } + } + } + async firstUpdated() { window.addEventListener('storage', () => { const checkTheme = localStorage.getItem('qortalTheme'); diff --git a/plugins/plugins/core/messaging/q-chat/q-chat.src.js b/plugins/plugins/core/messaging/q-chat/q-chat.src.js index 9edcaa76..2e0d02f3 100644 --- a/plugins/plugins/core/messaging/q-chat/q-chat.src.js +++ b/plugins/plugins/core/messaging/q-chat/q-chat.src.js @@ -445,9 +445,24 @@ class Chat extends LitElement { if (!isElectron()) { } else { window.addEventListener('contextmenu', (event) => { - event.preventDefault() - window.parent.electronAPI.showMyMenu() - }) + // Check if the clicked element has the class + let target = event.target; + while (target !== null) { + if (target.classList && target.classList.contains('customContextMenuDiv')) { + // Your custom context menu logic + this.showContextMenu(event); + return; + } + target = target.parentNode; + } + + // If it doesn't, show the default Electron context menu + event.preventDefault(); + window.parent.electronAPI.showMyMenu(); + }); + + + } let configLoaded = false From fd54f80e6c155e2e5cd89f01676d75f9023c0924 Mon Sep 17 00:00:00 2001 From: PhilReact Date: Thu, 21 Sep 2023 18:33:39 -0500 Subject: [PATCH 42/57] fix loading new messages when in old messages --- .../plugins/core/components/ChatScroller.js | 54 ++----------------- 1 file changed, 5 insertions(+), 49 deletions(-) diff --git a/plugins/plugins/core/components/ChatScroller.js b/plugins/plugins/core/components/ChatScroller.js index acc3faf7..14f716ac 100644 --- a/plugins/plugins/core/components/ChatScroller.js +++ b/plugins/plugins/core/components/ChatScroller.js @@ -700,40 +700,7 @@ class ChatScroller extends LitElement { } render() { - // let formattedMessages = this.messages.reduce((messageArray, message) => { - // const currentMessage = this.updateMessageHash[message.signature] || message; - // const lastGroupedMessage = messageArray[messageArray.length - 1]; - - // currentMessage.firstMessageInChat = messageArray.length === 0; - - // let timestamp, sender, repliedToData; - - // if (lastGroupedMessage) { - // timestamp = lastGroupedMessage.timestamp; - // sender = lastGroupedMessage.sender; - // repliedToData = lastGroupedMessage.repliedToData; - // } else { - // timestamp = currentMessage.timestamp; - // sender = currentMessage.sender; - // repliedToData = currentMessage.repliedToData; - // } - - // const isSameGroup = Math.abs(timestamp - currentMessage.timestamp) < 600000 && - // sender === currentMessage.sender && - // !repliedToData; - - // if (isSameGroup && lastGroupedMessage) { - // lastGroupedMessage.messages.push(currentMessage); - // } else { - // messageArray.push({ - // messages: [currentMessage], - // ...currentMessage - // }); - // } - - // return messageArray; - // }, []); - + let formattedMessages = this.messagesToRender; return html` @@ -802,21 +769,10 @@ class ChatScroller extends LitElement { )} ` )} - ${this.messageQueue.filter((item) => - this.chatId.includes(item._chatId) - ).length > 0 - ? html` -
    - ` - : html` -
    - `} +
    this.chatId.includes(item._chatId)).length > 0 ? 'height: 1px' : 'height: 1px; margin-top: -100px'} + id="bottomObserverForFetchingMessages" + >
    From 81129398b6672fb6ec8724c6ce31c16f0543f9d3 Mon Sep 17 00:00:00 2001 From: PhilReact Date: Thu, 21 Sep 2023 19:26:27 -0500 Subject: [PATCH 43/57] fix sorting issue when multiple msgs from same user old messages --- plugins/plugins/core/components/ChatScroller.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/plugins/core/components/ChatScroller.js b/plugins/plugins/core/components/ChatScroller.js index 14f716ac..73f8e7e5 100644 --- a/plugins/plugins/core/components/ChatScroller.js +++ b/plugins/plugins/core/components/ChatScroller.js @@ -522,7 +522,7 @@ class ChatScroller extends LitElement { }; } else { // Add to the current group - currentMessageGroup.messages.push(message); + currentMessageGroup.messages.unshift(message); } previousMessage = message; } From a80f6800d8159793d562c1c60994f5f36b2ad798 Mon Sep 17 00:00:00 2001 From: PhilReact Date: Fri, 22 Sep 2023 01:40:14 -0500 Subject: [PATCH 44/57] right side panel for imgs --- core/language/us.json | 3 +- plugins/plugins/core/components/ChatImage.js | 6 +- plugins/plugins/core/components/ChatPage.js | 55 +- .../components/ChatRightPanelResources.js | 651 ++++++++++++++++++ .../plugins/core/components/ChatScroller.js | 43 +- .../plugins/core/components/ReusableImage.js | 337 +++++++++ 6 files changed, 1039 insertions(+), 56 deletions(-) create mode 100644 plugins/plugins/core/components/ChatRightPanelResources.js create mode 100644 plugins/plugins/core/components/ReusableImage.js diff --git a/core/language/us.json b/core/language/us.json index cb820354..aec0628f 100644 --- a/core/language/us.json +++ b/core/language/us.json @@ -835,7 +835,8 @@ "cchange91": "Sending...", "cchange92": "Unread messages below", "cchange93": "Image copied to clipboard", - "cchange94": "loaded" + "cchange94": "loaded", + "cchange95": "Only my resources" }, "welcomepage": { "wcchange1": "Welcome to Q-Chat", diff --git a/plugins/plugins/core/components/ChatImage.js b/plugins/plugins/core/components/ChatImage.js index 238404ba..39aadd91 100644 --- a/plugins/plugins/core/components/ChatImage.js +++ b/plugins/plugins/core/components/ChatImage.js @@ -198,11 +198,7 @@ getMyNode(){ setTimeout(() => { isCalling = false - this.fetchResource({ - name, - service, - identifier - }) + this.fetchResource() }, 25000) return } diff --git a/plugins/plugins/core/components/ChatPage.js b/plugins/plugins/core/components/ChatPage.js index b3bae116..817be13e 100644 --- a/plugins/plugins/core/components/ChatPage.js +++ b/plugins/plugins/core/components/ChatPage.js @@ -10,6 +10,7 @@ import { inputKeyCodes } from '../../utils/keyCodes.js' import { replaceMessagesEdited } from '../../utils/replace-messages-edited.js' import { publishData } from '../../utils/publish-image.js' import { EmojiPicker } from 'emoji-picker-js' +import {ifDefined} from 'lit/directives/if-defined.js'; import * as zip from '@zip.js/zip.js' @@ -38,6 +39,7 @@ import './ChatSideNavHeads.js' import './ChatLeaveGroup.js' import './ChatGroupSettings.js' import './ChatRightPanel.js' +import './ChatRightPanelResources.js' import './ChatSearchResults.js' import '@material/mwc-button' import '@material/mwc-dialog' @@ -105,6 +107,7 @@ class ChatPage extends LitElement { groupAdmin: { type: Array }, groupMembers: { type: Array }, shifted: { type: Boolean }, + shiftedResources: {type: Boolean}, groupInfo: { type: Object }, setActiveChatHeadUrl: { attribute: false }, userFound: { type: Array }, @@ -1058,13 +1061,14 @@ class ChatPage extends LitElement { .group-nav-container { display: flex; height: 40px; - padding: 25px 5px 25px 20px; + padding: 5px; margin: 0px; background-color: var(--chat-bubble-bg); box-sizing: border-box; align-items: center; justify-content: space-between; box-shadow: var(--group-drop-shadow); + z-index: 1; } .top-bar-icon { @@ -1344,6 +1348,7 @@ class ChatPage extends LitElement { this.groupAdmin = [] this.groupMembers = [] this.shifted = false + this.shiftedResources = false this.groupInfo = {} this.pageNumber = 1 this.userFoundModalOpen = false @@ -1389,6 +1394,10 @@ class ChatPage extends LitElement { this.shifted = value === (false || true) ? value : !this.shifted this.requestUpdate() } + _toggleResources(value) { + this.shiftedResources = value === (false || true) ? value : !this.shiftedResources + this.requestUpdate() + } setOpenTipUser(props) { this.openTipUser = props @@ -1489,23 +1498,32 @@ class ChatPage extends LitElement { render() { - + console.log('this.chatId', this.chatId, this._chatId) return html`
    - ${(!this.isReceipient && +this._chatId !== 0) ? - html` + style="grid-template-rows: minmax(40px, auto) minmax(6%, 92vh) minmax(40px, auto); flex: 3;"> +
    + ${this.isReceipient ? '' : +this._chatId === 0 ? html` +

    Qortal General Chat

    + ` : html`

    ${this.groupInfo && this.groupInfo.groupName}

    + `} +
    - + photo_library + ${(!this.isReceipient && +this._chatId !== 0) ? + html` + groups + ` + : ''}
    - ` : null} +
    ${this.isLoadingMessages ? html` @@ -1949,6 +1967,24 @@ class ChatPage extends LitElement { >
    +
    + this.getMoreMembers(val)} + .toggle=${(val) => this._toggleResources(val)} + .selectedAddress=${this.selectedAddress} + .groupMembers=${this.groupMembers} + .groupAdmin=${this.groupAdmin} + .leaveGroupObj=${this.groupInfo} + .setOpenPrivateMessage=${(val) => this.setOpenPrivateMessage(val)} + .setOpenTipUser=${(val) => this.setOpenTipUser(val)} + .setOpenUserInfo=${(val) => this.setOpenUserInfo(val)} + .setUserName=${(val) => this.setUserName(val)} + _chatId=${ifDefined(this._chatId)} + chatId=${this.chatId} + ?isreceipient=${this.isReceipient} + > + +
    ` } @@ -2901,7 +2937,7 @@ class ChatPage extends LitElement { })); let list = [...decodeMsgs] - + this.messagesRendered = { messages: list, @@ -3890,7 +3926,8 @@ class ChatPage extends LitElement { const image = this.imageFile const id = this.uid.rnd() - const identifier = `qchat_${id}` + const groupPart = this.isReceipient ? `direct_${this._chatId.slice(-15)}` : `group_${this._chatId}` + const identifier = `qchat_${groupPart}_${id}` let compressedFile = '' await new Promise(resolve => { new Compressor(image, { diff --git a/plugins/plugins/core/components/ChatRightPanelResources.js b/plugins/plugins/core/components/ChatRightPanelResources.js new file mode 100644 index 00000000..cb5f3703 --- /dev/null +++ b/plugins/plugins/core/components/ChatRightPanelResources.js @@ -0,0 +1,651 @@ +import { LitElement, html, css } from 'lit'; +import { render } from 'lit/html.js'; +import { Epml } from '../../../epml'; +import { getUserNameFromAddress } from '../../utils/getUserNameFromAddress'; +import snackbar from './snackbar.js'; +import '@material/mwc-button'; +import '@material/mwc-dialog'; +import '@polymer/paper-spinner/paper-spinner-lite.js'; +import '@polymer/paper-progress/paper-progress.js'; +import '@material/mwc-icon'; +import '@vaadin/button'; +import './WrapperModal'; +import './TipUser'; +import './UserInfo/UserInfo'; +import './ChatImage'; +import './ReusableImage'; +import { + use, + get, + translate, + translateUnsafeHTML, + registerTranslateConfig, +} from 'lit-translate'; + +const parentEpml = new Epml({ type: 'WINDOW', source: window.parent }); + +class ChatRightPanelResources extends LitElement { + static get properties() { + return { + leaveGroupObj: { type: Object }, + error: { type: Boolean }, + chatHeads: { type: Array }, + groupAdmin: { attribute: false }, + groupMembers: { attribute: false }, + selectedHead: { type: Object }, + toggle: { attribute: false }, + getMoreMembers: { attribute: false }, + setOpenPrivateMessage: { attribute: false }, + userName: { type: String }, + walletBalance: { type: Number }, + sendMoneyLoading: { type: Boolean }, + btnDisable: { type: Boolean }, + errorMessage: { type: String }, + successMessage: { type: String }, + setOpenTipUser: { attribute: false }, + setOpenUserInfo: { attribute: false }, + setUserName: { attribute: false }, + chatId: { type: String }, + _chatId: { type: String }, + isReceipient: { type: Boolean }, + images: { type: Array }, + viewImage: { type: Boolean }, + autoView: {type: Boolean}, + onlyMyImages: {type: Boolean} + }; + } + + constructor() { + super(); + this.leaveGroupObj = {}; + this.leaveFee = 0.001; + this.error = false; + this.chatHeads = []; + this.groupAdmin = []; + this.groupMembers = []; + this.observerHandler = this.observerHandler.bind(this); + this.getMoreImages = this.getMoreImages.bind(this); + this.viewElement = ''; + this.downObserverElement = ''; + + this.sendMoneyLoading = false; + this.btnDisable = false; + this.errorMessage = ''; + this.successMessage = ''; + + this.images = []; + this.viewImage = false; + this.myName = + window.parent.reduxStore.getState().app.accountInfo.names[0].name; + this.autoView =false + this.onlyMyImages = true + } + + static get styles() { + return css` + .top-bar-icon { + cursor: pointer; + height: 18px; + width: 18px; + transition: 0.2s all; + } + + .top-bar-icon:hover { + color: var(--black); + } + + .modal-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; + } + + .close-row { + width: 100%; + display: flex; + justify-content: flex-end; + height: 50px; + flex: 0; + align-items: center; + } + + .container-body { + width: 100%; + display: flex; + flex-direction: column; + flex-grow: 1; + overflow: auto; + margin-top: 5px; + padding: 0px 6px; + box-sizing: border-box; + } + + .container-body::-webkit-scrollbar-track { + background-color: whitesmoke; + border-radius: 7px; + } + + .container-body::-webkit-scrollbar { + width: 6px; + border-radius: 7px; + background-color: whitesmoke; + } + + .container-body::-webkit-scrollbar-thumb { + background-color: rgb(180, 176, 176); + border-radius: 7px; + transition: all 0.3s ease-in-out; + } + + .container-body::-webkit-scrollbar-thumb:hover { + background-color: rgb(148, 146, 146); + cursor: pointer; + } + + p { + color: var(--black); + margin: 0px; + padding: 0px; + word-break: break-all; + } + + .container { + display: flex; + width: 100%; + flex-direction: column; + height: 100%; + } + + .chat-right-panel-label { + font-family: Montserrat, sans-serif; + color: var(--group-header); + padding: 5px; + font-size: 13px; + user-select: none; + } + + .group-info { + display: flex; + flex-direction: column; + justify-content: flex-start; + gap: 10px; + } + + .group-name { + font-family: Raleway, sans-serif; + font-size: 20px; + color: var(--chat-bubble-msg-color); + text-align: center; + user-select: none; + } + + .group-description { + font-family: Roboto, sans-serif; + color: var(--chat-bubble-msg-color); + letter-spacing: 0.3px; + font-weight: 300; + font-size: 14px; + margin-top: 15px; + word-break: break-word; + user-select: none; + } + + .group-subheader { + font-family: Montserrat, sans-serif; + font-size: 14px; + color: var(--chat-bubble-msg-color); + } + + .group-data { + font-family: Roboto, sans-serif; + letter-spacing: 0.3px; + font-weight: 300; + font-size: 14px; + color: var(--chat-bubble-msg-color); + } + .message-myBg { + background-color: var(--chat-bubble-myBg) !important; + margin-bottom: 15px; + border-radius: 5px; + padding: 5px; + } + .message-data-name { + user-select: none; + color: #03a9f4; + margin-bottom: 5px; + } + .message-user-info { + display: flex; + justify-content: space-between; + width: 100%; + gap: 10px; + } + + .hideImg { + visibility: hidden; + } + .checkbox-row { + position: relative; + display: flex; + align-items: center; + align-content: center; + font-family: Montserrat, sans-serif; + font-weight: 600; + color: var(--black); + } + `; + } + + async getMoreImages(reset) { + try { + console.log({reset}) + if(reset){ + this.images = [] + } + const groupPart = this.isReceipient + ? `direct_${this._chatId.slice(-15)}` + : `group_${this._chatId}`; + + let offset = reset ? 0 : this.images.length; + let endpoint = `/arbitrary/resources/search?service=QCHAT_IMAGE&identifier=qchat_${groupPart}&reverse=true&limit=20&reverse=true&offset=${offset}` + if(this.onlyMyImages){ + endpoint = endpoint + `&name=${this.myName}` + } + const qchatImages = await parentEpml.request('apiCall', { + type: 'api', + url: endpoint, + }); + + let list = [] + if(reset){ + list = qchatImages + } else { + list = [...this.images, ...qchatImages] + } + + this.images = list + } catch (error) { + console.log(error); + } + } + + firstUpdated() { + this.viewElement = this.shadowRoot.getElementById('viewElement'); + this.downObserverElement = + this.shadowRoot.getElementById('downObserver'); + this.elementObserver(); + } + + async updated(changedProperties) { + console.log({ changedProperties }); + if (changedProperties && changedProperties.has('_chatId')) { + this.images = []; + this.getMoreImages(true); + } + + if (changedProperties && changedProperties.has('onlyMyImages')) { + this.getMoreImages(true) + } + } + + elementObserver() { + const options = { + root: this.viewElement, + rootMargin: '0px', + threshold: 1, + }; + // identify an element to observe + const elementToObserve = this.downObserverElement; + // passing it a callback function + const observer = new IntersectionObserver( + this.observerHandler, + options + ); + // call `observe()` on that MutationObserver instance, + // passing it the element to observe, and the options object + observer.observe(elementToObserve); + } + + observerHandler(entries) { + if (!entries[0].isIntersecting) { + return; + } else { + console.log('hello', this.images) + if (this.images.length < 20) { + return; + } + this.getMoreImages(); + } + } + + selectAuto(e) { + if (e.target.checked) { + this.autoView = false + } else { + this.autoView = true + } + } + + selectMyImages(e) { + if (e.target.checked) { + this.onlyMyImages = false + } else { + this.onlyMyImages = true + } + } + + render() { + console.log('hello resources3', this.images); + return html` + +
    +
    + { + this.getMoreImages(true) + }} style="color: var(--black); cursor:pointer;">refresh + + this.toggle( + false + )} style="margin: 0px 10px" icon="vaadin:close" slot="icon"> +
    +
    + + this.selectAuto(e)} ?checked=${this.autoView}> +
    +
    + + this.selectMyImages(e)} ?checked=${this.onlyMyImages}> +
    +
    + + ${this.images.map((image) => { + return html``; + })} +
    +
    +
    +
    + `; + } +} + +customElements.define('chat-right-panel-resources', ChatRightPanelResources); + +class ImageParent extends LitElement { + static get properties() { + return { + leaveGroupObj: { type: Object }, + error: { type: Boolean }, + chatHeads: { type: Array }, + groupAdmin: { attribute: false }, + groupMembers: { attribute: false }, + selectedHead: { type: Object }, + toggle: { attribute: false }, + getMoreMembers: { attribute: false }, + setOpenPrivateMessage: { attribute: false }, + userName: { type: String }, + walletBalance: { type: Number }, + sendMoneyLoading: { type: Boolean }, + btnDisable: { type: Boolean }, + errorMessage: { type: String }, + successMessage: { type: String }, + setOpenTipUser: { attribute: false }, + setOpenUserInfo: { attribute: false }, + setUserName: { attribute: false }, + chatId: { type: String }, + _chatId: { type: String }, + isReceipient: { type: Boolean }, + images: { type: Array }, + viewImage: { type: Boolean }, + image: { type: Object }, + autoView: {type: Boolean} + }; + } + + constructor() { + super(); + this.leaveGroupObj = {}; + this.leaveFee = 0.001; + this.error = false; + this.chatHeads = []; + this.groupAdmin = []; + this.groupMembers = []; + this.viewElement = ''; + + this.sendMoneyLoading = false; + this.btnDisable = false; + this.errorMessage = ''; + this.successMessage = ''; + + this.images = []; + this.viewImage = false; + this.myName = + window.parent.reduxStore.getState().app.accountInfo.names[0].name; + } + + static get styles() { + return css` + .top-bar-icon { + cursor: pointer; + height: 18px; + width: 18px; + transition: 0.2s all; + } + + .top-bar-icon:hover { + color: var(--black); + } + + .modal-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; + } + + .close-row { + width: 100%; + display: flex; + justify-content: flex-end; + height: 50px; + flex: 0; + gap: 20px; + align-items: center; + } + + .container-body { + width: 100%; + display: flex; + flex-direction: column; + flex-grow: 1; + overflow: auto; + margin-top: 5px; + padding: 0px 6px; + box-sizing: border-box; + } + + .container-body::-webkit-scrollbar-track { + background-color: whitesmoke; + border-radius: 7px; + } + + .container-body::-webkit-scrollbar { + width: 6px; + border-radius: 7px; + background-color: whitesmoke; + } + + .container-body::-webkit-scrollbar-thumb { + background-color: rgb(180, 176, 176); + border-radius: 7px; + transition: all 0.3s ease-in-out; + } + + .container-body::-webkit-scrollbar-thumb:hover { + background-color: rgb(148, 146, 146); + cursor: pointer; + } + + p { + color: var(--black); + margin: 0px; + padding: 0px; + word-break: break-all; + } + + .container { + display: flex; + width: 100%; + flex-direction: column; + height: 100%; + } + + .chat-right-panel-label { + font-family: Montserrat, sans-serif; + color: var(--group-header); + padding: 5px; + font-size: 13px; + user-select: none; + } + + .group-info { + display: flex; + flex-direction: column; + justify-content: flex-start; + gap: 10px; + } + + .group-name { + font-family: Raleway, sans-serif; + font-size: 20px; + color: var(--chat-bubble-msg-color); + text-align: center; + user-select: none; + } + + .group-description { + font-family: Roboto, sans-serif; + color: var(--chat-bubble-msg-color); + letter-spacing: 0.3px; + font-weight: 300; + font-size: 14px; + margin-top: 15px; + word-break: break-word; + user-select: none; + } + + .group-subheader { + font-family: Montserrat, sans-serif; + font-size: 14px; + color: var(--chat-bubble-msg-color); + } + + .group-data { + font-family: Roboto, sans-serif; + letter-spacing: 0.3px; + font-weight: 300; + font-size: 14px; + color: var(--chat-bubble-msg-color); + } + .message-myBg { + background-color: var(--chat-bubble-myBg) !important; + margin-bottom: 15px; + border-radius: 5px; + padding: 5px; + } + .message-data-name { + user-select: none; + color: #03a9f4; + margin-bottom: 5px; + } + .message-user-info { + display: flex; + justify-content: space-between; + width: 100%; + gap: 10px; + } + + .hideImg { + visibility: hidden; + } + .image-container { + display: flex; + } + `; + } + + firstUpdated() {} + + async updated(changedProperties) { + if (changedProperties && changedProperties.has('chatId')) { + // const autoSeeChatList = + // window.parent.reduxStore.getState().app.autoLoadImageChats; + // if (autoSeeChatList.includes(this.chatId)) { + // this.viewImage = true; + // } + } + } + + render() { + return html` + ${!this.autoView && !this.viewImage && this.myName !== this.image.name + ? html` +
    + +
    { + this.viewImage = true; + }} + class=${[`image-container`].join(' ')} + style="height: 200px" + > +
    + ${translate('chatpage.cchange40')} +
    +
    +
    + ` + : html``} + ${this.autoView || this.viewImage || this.myName === this.image.name + ? html` +
    + + + < +
    + ` + : ''} + `; + } +} + +customElements.define('image-parent', ImageParent); diff --git a/plugins/plugins/core/components/ChatScroller.js b/plugins/plugins/core/components/ChatScroller.js index 73f8e7e5..c19c3b41 100644 --- a/plugins/plugins/core/components/ChatScroller.js +++ b/plugins/plugins/core/components/ChatScroller.js @@ -185,44 +185,7 @@ function processText(input) { return wrapper; } -const formatMessages = (messages) => { - const formattedMessages = messages.reduce((messageArray, message) => { - const currentMessage = message; - const lastGroupedMessage = messageArray[messageArray.length - 1]; - currentMessage.firstMessageInChat = messageArray.length === 0; - - let timestamp, sender, repliedToData; - - if (lastGroupedMessage) { - timestamp = lastGroupedMessage.timestamp; - sender = lastGroupedMessage.sender; - repliedToData = lastGroupedMessage.repliedToData; - } else { - timestamp = currentMessage.timestamp; - sender = currentMessage.sender; - repliedToData = currentMessage.repliedToData; - } - - const isSameGroup = - Math.abs(timestamp - currentMessage.timestamp) < 600000 && - sender === currentMessage.sender && - !repliedToData; - - if (isSameGroup && lastGroupedMessage) { - lastGroupedMessage.messages.push(currentMessage); - } else { - messageArray.push({ - messages: [currentMessage], - ...currentMessage, - }); - } - - return messageArray; - }, []); - - return formattedMessages; -}; class ChatScroller extends LitElement { static get properties() { @@ -510,8 +473,7 @@ class ChatScroller extends LitElement { !previousMessage || !this.shouldGroupWithLastMessage(message, previousMessage) ) { - // If no previous message, or if the current message shouldn't be grouped with the previous, - // push the current group to the front of the formatted messages (since these are older messages) + if (currentMessageGroup) { this.messagesToRender.unshift(currentMessageGroup); } @@ -2207,7 +2169,7 @@ class MessageTemplate extends LitElement { toblockaddress=${this.messageObj.sender} > - { @@ -2225,7 +2187,6 @@ class MessageTemplate extends LitElement { dialogAction="cancel" class="red" @click=${() => { - MessageTemplate; this.openDialogImage = false; }} > diff --git a/plugins/plugins/core/components/ReusableImage.js b/plugins/plugins/core/components/ReusableImage.js new file mode 100644 index 00000000..9b5feb9c --- /dev/null +++ b/plugins/plugins/core/components/ReusableImage.js @@ -0,0 +1,337 @@ +import { LitElement, html, css } from 'lit'; +import { render } from 'lit/html.js'; +import { + use, + get, + translate, + translateUnsafeHTML, + registerTranslateConfig, +} from 'lit-translate'; +import axios from 'axios'; +import { RequestQueueWithPromise } from '../../utils/queue'; +import '@material/mwc-menu'; +import '@material/mwc-list/mwc-list-item.js'; +import { Epml } from '../../../epml'; +import '@material/mwc-dialog' + +const requestQueue = new RequestQueueWithPromise(5); +const requestQueue2 = new RequestQueueWithPromise(5); + +const parentEpml = new Epml({ type: 'WINDOW', source: window.parent }); + +export class ResuableImage extends LitElement { + static get properties() { + return { + resource: { type: Object }, + isReady: { type: Boolean }, + status: { type: Object }, + missingData: {type: Boolean}, + openDialogImage: { type: Boolean }, + + }; + } + + static get styles() { + return css` + * { + --mdc-theme-text-primary-on-background: var(--black); + --mdc-dialog-max-width: 85vw; + --mdc-dialog-max-height: 95vh; + } + img { + width: 100%; + height: auto; + object-fit: contain; + border-radius: 5px; + position: relative; + } + .smallLoading, + .smallLoading:after { + border-radius: 50%; + width: 2px; + height: 2px; + } + + .smallLoading { + border-width: 0.8em; + border-style: solid; + border-color: rgba(3, 169, 244, 0.2) rgba(3, 169, 244, 0.2) + rgba(3, 169, 244, 0.2) rgb(3, 169, 244); + font-size: 30px; + position: relative; + text-indent: -9999em; + transform: translateZ(0px); + animation: 1.1s linear 0s infinite normal none running + loadingAnimation; + } + .imageContainer { + display: flex; + justify-content: center; + align-items: center; + height: 100%; + } + + @-webkit-keyframes loadingAnimation { + 0% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); + } + 100% { + -webkit-transform: rotate(360deg); + transform: rotate(360deg); + } + } + + @keyframes loadingAnimation { + 0% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); + } + 100% { + -webkit-transform: rotate(360deg); + transform: rotate(360deg); + } + } + `; + } + + constructor() { + super(); + this.resource = { + identifier: '', + name: '', + service: '', + }; + this.status = { + status: '', + }; + this.url = ''; + this.isReady = false; + this.nodeUrl = this.getNodeUrl(); + this.myNode = this.getMyNode(); + this.hasCalledWhenDownloaded = false; + this.isFetching = false; + this.missingData = false + this.openDialogImage = false + + this.observer = new IntersectionObserver((entries) => { + for (const entry of entries) { + if (entry.isIntersecting && this.status.status !== 'READY') { + this._fetchImage(); + // Stop observing after the image has started loading + this.observer.unobserve(this); + } + } + }); + } + getNodeUrl() { + const myNode = + window.parent.reduxStore.getState().app.nodeConfig.knownNodes[ + window.parent.reduxStore.getState().app.nodeConfig.node + ]; + + const nodeUrl = + myNode.protocol + '://' + myNode.domain + ':' + myNode.port; + return nodeUrl; + } + getMyNode() { + const myNode = + window.parent.reduxStore.getState().app.nodeConfig.knownNodes[ + window.parent.reduxStore.getState().app.nodeConfig.node + ]; + + return myNode; + } + + getApiKey() { + const myNode = + window.parent.reduxStore.getState().app.nodeConfig.knownNodes[ + window.parent.reduxStore.getState().app.nodeConfig.node + ]; + let apiKey = myNode.apiKey; + return apiKey; + } + + async fetchResource() { + try { + if (this.isFetching) return; + this.isFetching = true; + + await requestQueue2.enqueue(() => { + return axios.get( + `${this.nodeUrl}/arbitrary/resource/properties/${this.resource.service}/${this.resource.name}/${this.resource.identifier}?apiKey=${this.myNode.apiKey}` + ); + }); + this.isFetching = false; + } catch (error) { + this.isFetching = false; + } + } + + async fetchVideoUrl() { + this.fetchResource(); + this.url = `${this.nodeUrl}/arbitrary/${this.resource.service}/${this.resource.name}/${this.resource.identifier}?async=true&apiKey=${this.myNode.apiKey}`; + } + + async fetchStatus() { + let isCalling = false; + let percentLoaded = 0; + let timer = 24; + const response = await axios.get( + `${this.nodeUrl}/arbitrary/resource/status/${this.resource.service}/${this.resource.name}/${this.resource.identifier}?apiKey=${this.myNode.apiKey}` + ); + if (response && response.data && response.data.status === 'READY') { + this.status = response.data; + return; + } + const intervalId = setInterval(async () => { + if (isCalling) return; + isCalling = true; + + const data = await requestQueue.enqueue(() => { + return axios.get( + `${this.nodeUrl}/arbitrary/resource/status/${this.resource.service}/${this.resource.name}/${this.resource.identifier}?apiKey=${this.myNode.apiKey}` + ); + }); + const res = data.data; + + isCalling = false; + if (res.localChunkCount) { + if (res.percentLoaded) { + if ( + res.percentLoaded === percentLoaded && + res.percentLoaded !== 100 + ) { + timer = timer - 5; + } else { + timer = 24; + } + if (timer < 0) { + timer = 24; + isCalling = true; + this.status = { + ...res, + status: 'REFETCHING', + }; + + setTimeout(() => { + isCalling = false; + this.fetchResource(); + }, 25000); + return; + } + percentLoaded = res.percentLoaded; + } + + this.status = res; + if (this.status.status === 'DOWNLOADED') { + this.fetchResource(); + } + } + + // check if progress is 100% and clear interval if true + if (res?.status === 'READY') { + clearInterval(intervalId); + this.status = res; + this.isReady = true; + } + + if(res?.status === 'MISSING_DATA'){ + this.status = res + this.missingData = true + clearInterval(intervalId) + } + }, 5000); // 1 second interval + } + + async _fetchImage() { + try { + this.fetchVideoUrl({ + name: this.resource.name, + service: this.resource.service, + identifier: this.resource.identifier, + }); + this.fetchStatus(); + } catch (error) {} + } + + firstUpdated() { + this.observer.observe(this); + } + + showContextMenu(e) { + e.preventDefault(); + e.stopPropagation(); + + const contextMenu = this.shadowRoot.getElementById('contextMenu'); + const containerRect = e.currentTarget.getBoundingClientRect(); + + // Adjusting the positions + const adjustedX = e.clientX - containerRect.left; + const adjustedY = e.clientY - containerRect.top; + + contextMenu.style.top = `${adjustedY}px`; + contextMenu.style.left = `${adjustedX}px`; + + contextMenu.open = true; + } + + render() { + return html` +
    + ${this.status.status !== 'READY' + ? html` +
    +
    +

    + ${`${Math.round( + this.status.percentLoaded || 0 + ).toFixed(0)}% `}${translate( + 'chatpage.cchange94' + )} +

    +
    + ` + : ''} + ${this.status.status === 'READY' + ? html` +
    { + this.openDialogImage = true; + }}> + +
    + ` + : ''} +
    + + { + this.openDialogImage = false; + }}> +
    +
    + ${this.openDialogImage ? html` + + ` : ''} + +
    + { + this.openDialogImage = false; + }} + > + ${translate('general.close')} + +
    + `; + } +} + +customElements.define('reusable-image', ResuableImage); From e0c809374026276265290e3ff884f2f8f63b2ed5 Mon Sep 17 00:00:00 2001 From: PhilReact Date: Fri, 22 Sep 2023 23:38:48 -0500 Subject: [PATCH 45/57] bug fixes and ability to repost --- plugins/plugins/core/components/ChatImage.js | 2 +- plugins/plugins/core/components/ChatPage.js | 189 +++++++++++------- .../components/ChatRightPanelResources.js | 45 ++++- .../plugins/core/components/ChatScroller.js | 7 +- .../plugins/core/components/ReusableImage.js | 8 +- plugins/plugins/utils/id-generation.js | 14 ++ 6 files changed, 178 insertions(+), 87 deletions(-) create mode 100644 plugins/plugins/utils/id-generation.js diff --git a/plugins/plugins/core/components/ChatImage.js b/plugins/plugins/core/components/ChatImage.js index 39aadd91..71a84c45 100644 --- a/plugins/plugins/core/components/ChatImage.js +++ b/plugins/plugins/core/components/ChatImage.js @@ -212,7 +212,7 @@ getMyNode(){ } // check if progress is 100% and clear interval if true - if (res?.status === 'READY') { + if (res.status === 'READY') { clearInterval(intervalId) this.status = res this.isReady = true diff --git a/plugins/plugins/core/components/ChatPage.js b/plugins/plugins/core/components/ChatPage.js index 817be13e..d06c1a3d 100644 --- a/plugins/plugins/core/components/ChatPage.js +++ b/plugins/plugins/core/components/ChatPage.js @@ -48,6 +48,7 @@ import '@polymer/paper-dialog/paper-dialog.js' import '@polymer/paper-spinner/paper-spinner-lite.js' import { RequestQueue } from '../../utils/queue.js' import { modalHelper } from '../../utils/publish-modal.js' +import { generateIdFromAddresses } from '../../utils/id-generation.js' const chatLastSeen = localForage.createInstance({ name: "chat-last-seen", @@ -1384,8 +1385,29 @@ class ChatPage extends LitElement { this.addToQueue = this.addToQueue.bind(this) this.processQueue = this.processQueue.bind(this) this.isInProcessQueue = false + this.nodeUrl = this.getNodeUrl(); + this.myNode = this.getMyNode(); } + getNodeUrl() { + const myNode = + window.parent.reduxStore.getState().app.nodeConfig.knownNodes[ + window.parent.reduxStore.getState().app.nodeConfig.node + ]; + + const nodeUrl = + myNode.protocol + '://' + myNode.domain + ':' + myNode.port; + return nodeUrl; + } + getMyNode() { + const myNode = + window.parent.reduxStore.getState().app.nodeConfig.knownNodes[ + window.parent.reduxStore.getState().app.nodeConfig.node + ]; + + return myNode; + } + setOpenGifModal(value) { this.openGifModal = value } @@ -1498,7 +1520,6 @@ class ChatPage extends LitElement { render() { - console.log('this.chatId', this.chatId, this._chatId) return html`
    -
    +
    { + if(+this._chatId === 0 || this.isReceipient)return + this._toggle() + }} style=${`height: 100%; display: flex; align-items: center;flex-grow: 1; cursor: pointer; cursor: ${+this._chatId === 0 || this.isReceipient ? 'default': 'pointer'}; user-select: none`}> ${this.isReceipient ? '' : +this._chatId === 0 ? html`

    Qortal General Chat

    ` : html` @@ -1691,7 +1715,7 @@ class ChatPage extends LitElement {
    ${this.imageFile && html` - dialog-img + dialog-img `}
    @@ -2380,6 +2405,11 @@ class ChatPage extends LitElement { } insertFile(file) { + if(file.identifier){ + this.imageFile = file + this.currentEditor = 'newChat' + return + }else if (file.type.includes('image')) { this.imageFile = file this.currentEditor = 'newChat' @@ -3900,7 +3930,9 @@ class ChatPage extends LitElement { const stringifyMessageObject = JSON.stringify(messageObject) return this.sendMessage({messageText: stringifyMessageObject, typeMessage, chatReference, isForward: false, isReceipient, _chatId, _publicKey, messageQueue}) } else if (outSideMsg && outSideMsg.type === 'image') { - this.isUploadingImage = true + if(!this.imageFile.identifier){ + this.isUploadingImage = true + } const userName = await getName(this.selectedAddress.address) if (!userName) { parentEpml.request('showSnackBar', get("chatpage.cchange27")) @@ -3909,78 +3941,94 @@ class ChatPage extends LitElement { this.imageFile = null return } - const arbitraryFeeData = await modalHelper.getArbitraryFee() - const res = await modalHelper.showModalAndWaitPublish( - { - feeAmount: arbitraryFeeData.feeToShow + + + let service = "QCHAT_IMAGE" + let name = userName + let identifier + if(this.imageFile.identifier){ + identifier = this.imageFile.identifier + name = this.imageFile.name + service = this.imageFile.service + } else { + 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) { + this.webWorkerFile.terminate() + this.webWorkerFile = null } - ); - if (res.action !== 'accept') throw new Error('User declined publish') - - if (this.webWorkerFile) { - this.webWorkerFile.terminate() - this.webWorkerFile = null - } - - this.webWorkerFile = new WebWorkerFile() - - const image = this.imageFile - const id = this.uid.rnd() - const groupPart = this.isReceipient ? `direct_${this._chatId.slice(-15)}` : `group_${this._chatId}` - const identifier = `qchat_${groupPart}_${id}` - let compressedFile = '' - await new Promise(resolve => { - new Compressor(image, { - quality: .6, - maxWidth: 1200, - mimeType: 'image/webp', - success(result) { - const file = new File([result], "name", { - type: 'image/webp' - }) - compressedFile = file - resolve() - }, - error(err) { - }, + + this.webWorkerFile = new WebWorkerFile() + const image = this.imageFile + const id = this.uid.rnd() + let groupPart + if(this.isReceipient){ + groupPart = `direct_${generateIdFromAddresses(this._chatId, this.selectedAddress.address)}` + } else { + groupPart = `group_${this._chatId}` + } + identifier = `qchat_${groupPart}_${id}` + let compressedFile = '' + await new Promise(resolve => { + new Compressor(image, { + quality: .6, + maxWidth: 1200, + mimeType: 'image/webp', + success(result) { + const file = new File([result], "name", { + type: 'image/webp' + }) + compressedFile = file + resolve() + }, + error(err) { + }, + }) }) - }) - const fileSize = compressedFile.size - if (fileSize > 500000) { - parentEpml.request('showSnackBar', get("chatpage.cchange26")) - this.isLoading = false - this.isUploadingImage = false - return + const fileSize = compressedFile.size + if (fileSize > 500000) { + parentEpml.request('showSnackBar', get("chatpage.cchange26")) + this.isLoading = false + this.isUploadingImage = false + return + } + + try { + + await publishData({ + registeredName: userName, + file: compressedFile, + service: 'QCHAT_IMAGE', + identifier: identifier, + parentEpml, + metaData: undefined, + uploadType: 'file', + selectedAddress: this.selectedAddress, + worker: this.webWorkerFile, + withFee: true, + feeAmount: arbitraryFeeData.fee + }) + this.isUploadingImage = false + this.removeImage() + } catch (error) { + this.isLoading = false + this.isUploadingImage = false + return + } + } - - try { - - await publishData({ - registeredName: userName, - file: compressedFile, - service: 'QCHAT_IMAGE', - identifier: identifier, - parentEpml, - metaData: undefined, - uploadType: 'file', - selectedAddress: this.selectedAddress, - worker: this.webWorkerFile, - withFee: true, - feeAmount: arbitraryFeeData.fee - }) - this.isUploadingImage = false - this.removeImage() - } catch (error) { - this.isLoading = false - this.isUploadingImage = false - return - } - + const messageObject = { messageText: trimmedMessage, images: [{ - service: "QCHAT_IMAGE", - name: userName, + service: service, + name: name, identifier: identifier, }], isImageDeleted: false, @@ -3988,6 +4036,7 @@ class ChatPage extends LitElement { version: 3 } const stringifyMessageObject = JSON.stringify(messageObject) + this.removeImage() return this.sendMessage({messageText: stringifyMessageObject, typeMessage, chatReference: undefined, isForward: false, isReceipient, _chatId, _publicKey, messageQueue}) } else if (outSideMsg && outSideMsg.type === 'gif') { const userName = await getName(this.selectedAddress.address) diff --git a/plugins/plugins/core/components/ChatRightPanelResources.js b/plugins/plugins/core/components/ChatRightPanelResources.js index cb5f3703..64e16fbe 100644 --- a/plugins/plugins/core/components/ChatRightPanelResources.js +++ b/plugins/plugins/core/components/ChatRightPanelResources.js @@ -21,6 +21,7 @@ import { translateUnsafeHTML, registerTranslateConfig, } from 'lit-translate'; +import { generateIdFromAddresses } from '../../utils/id-generation'; const parentEpml = new Epml({ type: 'WINDOW', source: window.parent }); @@ -51,7 +52,8 @@ class ChatRightPanelResources extends LitElement { images: { type: Array }, viewImage: { type: Boolean }, autoView: {type: Boolean}, - onlyMyImages: {type: Boolean} + onlyMyImages: {type: Boolean}, + repost: {attribute: false} }; } @@ -77,6 +79,8 @@ class ChatRightPanelResources extends LitElement { this.viewImage = false; this.myName = window.parent.reduxStore.getState().app.accountInfo.names[0].name; + this.myAddress = + window.parent.reduxStore.getState().app.selectedAddress.address; this.autoView =false this.onlyMyImages = true } @@ -237,18 +241,18 @@ class ChatRightPanelResources extends LitElement { font-family: Montserrat, sans-serif; font-weight: 600; color: var(--black); + padding-left: 5px; } `; } async getMoreImages(reset) { try { - console.log({reset}) if(reset){ this.images = [] } const groupPart = this.isReceipient - ? `direct_${this._chatId.slice(-15)}` + ? `direct_${generateIdFromAddresses(this._chatId, this.myAddress)}` : `group_${this._chatId}`; let offset = reset ? 0 : this.images.length; @@ -282,7 +286,6 @@ class ChatRightPanelResources extends LitElement { } async updated(changedProperties) { - console.log({ changedProperties }); if (changedProperties && changedProperties.has('_chatId')) { this.images = []; this.getMoreImages(true); @@ -315,7 +318,6 @@ class ChatRightPanelResources extends LitElement { if (!entries[0].isIntersecting) { return; } else { - console.log('hello', this.images) if (this.images.length < 20) { return; } @@ -340,7 +342,6 @@ class ChatRightPanelResources extends LitElement { } render() { - console.log('hello resources3', this.images); return html`
    @@ -368,7 +369,7 @@ class ChatRightPanelResources extends LitElement {
    ${this.images.map((image) => { - return html``; + return html``; })}
    @@ -407,7 +408,9 @@ class ImageParent extends LitElement { images: { type: Array }, viewImage: { type: Boolean }, image: { type: Object }, - autoView: {type: Boolean} + autoView: {type: Boolean}, + repost: {attribute: false}, + isImgLoaded: {type: Boolean} }; } @@ -425,7 +428,7 @@ class ImageParent extends LitElement { this.btnDisable = false; this.errorMessage = ''; this.successMessage = ''; - + this.isImgLoaded = false this.images = []; this.viewImage = false; this.myName = @@ -584,6 +587,17 @@ class ImageParent extends LitElement { .image-container { display: flex; } + .repost-btn { + 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; + } `; } @@ -599,6 +613,11 @@ class ImageParent extends LitElement { } } + onLoad(){ + this.isImgLoaded = true + this.requestUpdate() + } + render() { return html` ${!this.autoView && !this.viewImage && this.myName !== this.image.name @@ -639,8 +658,14 @@ class ImageParent extends LitElement { service: this.image.service, identifier: this.image.identifier, }} + .onLoad=${()=> this.onLoad()} > - < + ${this.isImgLoaded ? html` +
    + +
    + ` : ''} +
    ` : ''} diff --git a/plugins/plugins/core/components/ChatScroller.js b/plugins/plugins/core/components/ChatScroller.js index c19c3b41..6fdd3a8b 100644 --- a/plugins/plugins/core/components/ChatScroller.js +++ b/plugins/plugins/core/components/ChatScroller.js @@ -732,7 +732,7 @@ class ChatScroller extends LitElement { ` )}
    this.chatId.includes(item._chatId)).length > 0 ? 'height: 1px' : 'height: 1px; margin-top: -100px'} + style=${this.messageQueue.filter((item) => this.chatId.includes(item._chatId)).length > 0 ? 'height: 1px' : 'height: 1px'} id="bottomObserverForFetchingMessages" >
    @@ -745,7 +745,7 @@ class ChatScroller extends LitElement {
    ` : ''} - ${repeat( + ${!this.disableAddingNewMessages ? repeat( this.messageQueue.filter((item) => this.chatId.includes(item._chatId) ), @@ -787,7 +787,7 @@ class ChatScroller extends LitElement { ?isInProgress=${true} > ` - )} + ): ''} `; } @@ -934,6 +934,7 @@ class ChatScroller extends LitElement { if (this.messagesToRender.length === 0 || this.disableFetching) { return; } + if(!this.disableAddingNewMessages) return if ( !entries[0].isIntersecting || !entries[0].target || diff --git a/plugins/plugins/core/components/ReusableImage.js b/plugins/plugins/core/components/ReusableImage.js index 9b5feb9c..c21c96e2 100644 --- a/plugins/plugins/core/components/ReusableImage.js +++ b/plugins/plugins/core/components/ReusableImage.js @@ -27,7 +27,7 @@ export class ResuableImage extends LitElement { status: { type: Object }, missingData: {type: Boolean}, openDialogImage: { type: Boolean }, - + onLoad: {attribute: false} }; } @@ -182,6 +182,7 @@ export class ResuableImage extends LitElement { ); if (response && response.data && response.data.status === 'READY') { this.status = response.data; + this.onLoad() return; } const intervalId = setInterval(async () => { @@ -230,13 +231,14 @@ export class ResuableImage extends LitElement { } // check if progress is 100% and clear interval if true - if (res?.status === 'READY') { + if (res.status === 'READY') { + this.onLoad() clearInterval(intervalId); this.status = res; this.isReady = true; } - if(res?.status === 'MISSING_DATA'){ + if(res.status === 'MISSING_DATA'){ this.status = res this.missingData = true clearInterval(intervalId) diff --git a/plugins/plugins/utils/id-generation.js b/plugins/plugins/utils/id-generation.js new file mode 100644 index 00000000..88323156 --- /dev/null +++ b/plugins/plugins/utils/id-generation.js @@ -0,0 +1,14 @@ +export function simpleHash(str) { + let hash = 0; + for (let i = 0; i < str.length; i++) { + hash = (hash << 5) - hash + str.charCodeAt(i); + hash = hash & hash; // Convert to 32bit integer + } + return hash.toString(); +} + +export function generateIdFromAddresses(address1, address2) { + // Sort addresses lexicographically and concatenate + const sortedAddresses = [address1, address2].sort().join(''); + return simpleHash(sortedAddresses); +} \ No newline at end of file From d7d18a61b96cf4f66372999726c4510d56222cce Mon Sep 17 00:00:00 2001 From: PhilReact Date: Sun, 24 Sep 2023 11:19:36 -0500 Subject: [PATCH 46/57] added linting for lit and removed unused vars --- .eslintignore | 1 + .eslintrc.json | 1 + package-lock.json | 953 +++++++++++++ package.json | 76 +- plugins/plugins/core/components/ChatImage.js | 8 +- .../plugins/core/components/ChatPage-css.js | 1154 +++++++++++++++ plugins/plugins/core/components/ChatPage.js | 1236 +---------------- .../components/ChatRightPanelResources.js | 6 - .../plugins/core/components/ChatScroller.js | 65 +- .../plugins/core/components/ReusableImage.js | 9 +- .../core/messaging/q-chat/q-chat-css.src.js | 7 +- .../core/messaging/q-chat/q-chat.src.js | 50 +- 12 files changed, 2226 insertions(+), 1340 deletions(-) create mode 100644 .eslintignore create mode 100644 plugins/plugins/core/components/ChatPage-css.js diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 00000000..30bc1627 --- /dev/null +++ b/.eslintignore @@ -0,0 +1 @@ +/node_modules \ No newline at end of file diff --git a/.eslintrc.json b/.eslintrc.json index a8b6f1a4..3737f840 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -3,6 +3,7 @@ "browser": true, "es2021": true }, + "plugins": ["lit", "wc"], "extends": ["eslint:recommended", "plugin:lit/recommended", "plugin:wc/recommended"], "parserOptions": { "ecmaVersion": 12, diff --git a/package-lock.json b/package-lock.json index 4f99875b..8d3ff292 100644 --- a/package-lock.json +++ b/package-lock.json @@ -105,6 +105,9 @@ "electron-builder": "24.6.4", "electron-packager": "17.1.2", "epml": "0.3.3", + "eslint": "^8.50.0", + "eslint-plugin-lit": "^1.9.1", + "eslint-plugin-wc": "^2.0.3", "file-saver": "2.0.5", "highcharts": "11.1.0", "html-escaper": "3.0.3", @@ -125,6 +128,15 @@ "node": ">=18.16.1" } }, + "node_modules/@aashutoshrathi/word-wrap": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", + "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/@ampproject/remapping": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", @@ -730,6 +742,111 @@ "node": ">= 10.0.0" } }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.8.1.tgz", + "integrity": "sha512-PWiOzLIUAjN/w5K17PoF4n6sKBw0gqLHPhywmYHP4t1VFQQVYeb1yWsJwnMVEMl3tUHME7X/SJPZLmtG7XBDxQ==", + "dev": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.2.tgz", + "integrity": "sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "13.22.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.22.0.tgz", + "integrity": "sha512-H1Ddc/PbZHTDVJSnj8kWptIRSD6AM3pK+mKytuIVF4uoBV7rshFlhhvA58ceJ5wp3Er58w6zj7bykMpYXt3ETw==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/eslintrc/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/js": { + "version": "8.50.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.50.0.tgz", + "integrity": "sha512-NCC3zz2+nvYd+Ckfh87rA47zfu2QsQpvc6k1yzTk+b9KzRj0wkGa8LSoGOXN6Zv4lRf/EIoZ80biDh9HOI+RNQ==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, "node_modules/@fortawesome/fontawesome-common-types": { "version": "6.4.2", "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.4.2.tgz", @@ -1083,6 +1200,61 @@ "@hapi/hoek": "^11.0.2" } }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.11.11", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.11.tgz", + "integrity": "sha512-N2brEuAadi0CcdeMXUkhbZB84eskAc8MEX1By6qEchoVywSgXPIjou4rYsl0V3Hj0ZnuGycGCjdNgockbzeWNA==", + "dev": true, + "dependencies": { + "@humanwhocodes/object-schema": "^1.2.1", + "debug": "^4.1.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "dev": true + }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", @@ -2072,6 +2244,41 @@ "tslib": "^2.1.0" } }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/@open-wc/dedupe-mixin": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/@open-wc/dedupe-mixin/-/dedupe-mixin-1.4.0.tgz", @@ -4322,6 +4529,15 @@ "node": ">=8" } }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/caniuse-lite": { "version": "1.0.30001532", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001532.tgz", @@ -4826,6 +5042,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, "node_modules/deepmerge": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", @@ -4986,6 +5208,18 @@ "node": ">=8" } }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/dot-prop": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-6.0.1.tgz", @@ -5647,12 +5881,417 @@ "node": ">=0.8.0" } }, + "node_modules/eslint": { + "version": "8.50.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.50.0.tgz", + "integrity": "sha512-FOnOGSuFuFLv/Sa+FDVRZl4GGVAAFFi8LecRsI5a1tMO5HIE8nCm4ivAlzt4dT3ol/PaaGC0rJEEXQmHJBGoOg==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.2", + "@eslint/js": "8.50.0", + "@humanwhocodes/config-array": "^0.11.11", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-plugin-lit": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-lit/-/eslint-plugin-lit-1.9.1.tgz", + "integrity": "sha512-XFFVufVxYJwqRB9sLkDXB7SvV1xi9hrC4HRFEdX1h9+iZ3dm4x9uS7EuT9uaXs6zR3DEgcojia1F7pmvWbc4Gg==", + "dev": true, + "dependencies": { + "parse5": "^6.0.1", + "parse5-htmlparser2-tree-adapter": "^6.0.1", + "requireindex": "^1.2.0" + }, + "engines": { + "node": ">= 12" + }, + "peerDependencies": { + "eslint": ">= 5" + } + }, + "node_modules/eslint-plugin-wc": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-wc/-/eslint-plugin-wc-2.0.3.tgz", + "integrity": "sha512-O3i71FodYMArf8DBs+OuDQ8SH8SMiNaJ4GIcXRDsGURPdvBrVDNS9+GQ0xwmzhqUWV0df5xq8irpceA6YBdJmg==", + "dev": true, + "dependencies": { + "is-valid-element-name": "^1.0.0", + "js-levenshtein-esm": "^1.2.0" + }, + "peerDependencies": { + "eslint": ">=5" + } + }, + "node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/eslint/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/eslint/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/eslint/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/eslint/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/eslint/node_modules/globals": { + "version": "13.22.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.22.0.tgz", + "integrity": "sha512-H1Ddc/PbZHTDVJSnj8kWptIRSD6AM3pK+mKytuIVF4uoBV7rshFlhhvA58ceJ5wp3Er58w6zj7bykMpYXt3ETw==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint/node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/eslint/node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree/node_modules/acorn": { + "version": "8.10.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", + "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/espree/node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/esquery": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, "node_modules/estree-walker": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", "dev": true }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/execa": { "version": "0.10.0", "resolved": "https://registry.npmjs.org/execa/-/execa-0.10.0.tgz", @@ -5802,6 +6441,21 @@ "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", "dev": true }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "node_modules/fastq": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", + "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, "node_modules/fd-slicer": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", @@ -5810,6 +6464,18 @@ "pend": "~1.2.0" } }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, "node_modules/file-saver": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/file-saver/-/file-saver-2.0.5.tgz", @@ -5873,6 +6539,26 @@ "node": ">=6" } }, + "node_modules/flat-cache": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.1.0.tgz", + "integrity": "sha512-OHx4Qwrrt0E4jEIcI5/Xb+f+QmJYNj2rrK8wiIdQOIrB9WrrJL8cjZvXdXuBTkkEwEqLycb5BeZDV1o2i9bTew==", + "dev": true, + "dependencies": { + "flatted": "^3.2.7", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.2.9", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.9.tgz", + "integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==", + "dev": true + }, "node_modules/flora-colossus": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/flora-colossus/-/flora-colossus-2.0.0.tgz", @@ -6302,6 +6988,12 @@ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true + }, "node_modules/has": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", @@ -6498,6 +7190,15 @@ } ] }, + "node_modules/ignore": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", + "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, "node_modules/immediate": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", @@ -6508,6 +7209,31 @@ "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.4.tgz", "integrity": "sha512-fsXeu4J4i6WNWSikpI88v/PcVflZz+6kMhUfIwc5SY+poQRPnaf5V7qds6SUyUN3cVxEzuCab7QIoLOQ+DQ1wA==" }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, "node_modules/inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -6690,6 +7416,15 @@ "node": ">=8" } }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/is-plain-obj": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", @@ -6709,6 +7444,12 @@ "node": ">=0.10.0" } }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true + }, "node_modules/is-reference": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz", @@ -6726,6 +7467,15 @@ "node": ">=0.10.0" } }, + "node_modules/is-valid-element-name": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-valid-element-name/-/is-valid-element-name-1.0.0.tgz", + "integrity": "sha512-GZITEJY2LkSjQfaIPBha7eyZv+ge0PhBR7KITeCCWvy7VBQrCUdFkvpI+HrAPQjVtVjy1LvlEkqQTHckoszruw==", + "dev": true, + "dependencies": { + "is-potential-custom-element-name": "^1.0.0" + } + }, "node_modules/is-wsl": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", @@ -6873,6 +7623,12 @@ "node": ">=8" } }, + "node_modules/js-levenshtein-esm": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/js-levenshtein-esm/-/js-levenshtein-esm-1.2.0.tgz", + "integrity": "sha512-fzreKVq1eD7eGcQr7MtRpQH94f8gIfhdrc7yeih38xh684TNMK9v5aAu2wxfIRMk/GpAJRrzcirMAPIaSDaByQ==", + "dev": true + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -6919,6 +7675,12 @@ "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-7.0.3.tgz", "integrity": "sha512-7DE8mpG+/fVw+dTpjbxnx47TaMnDfOI1jwft9g1VybltZCduyRQPJPvc+zzKY9WPHxhPWczyFuYa6I8Mw4iU5A==" }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, "node_modules/json-stringify-safe": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", @@ -6988,6 +7750,19 @@ "node": ">=6" } }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/lie": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/lie/-/lie-3.1.1.tgz", @@ -7097,6 +7872,12 @@ "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==" }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, "node_modules/lowercase-keys": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", @@ -7333,6 +8114,12 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, "node_modules/nice-try": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", @@ -7498,6 +8285,23 @@ "node": ">=6" } }, + "node_modules/optionator": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", + "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", + "dev": true, + "dependencies": { + "@aashutoshrathi/word-wrap": "^1.2.3", + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/orderedmap": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/orderedmap/-/orderedmap-2.1.1.tgz", @@ -7574,6 +8378,18 @@ "node": ">=6" } }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/parse-author": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/parse-author/-/parse-author-2.0.0.tgz", @@ -7598,6 +8414,21 @@ "node": ">=0.10.0" } }, + "node_modules/parse5": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", + "dev": true + }, + "node_modules/parse5-htmlparser2-tree-adapter": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz", + "integrity": "sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==", + "dev": true, + "dependencies": { + "parse5": "^6.0.1" + } + }, "node_modules/passive-events-support": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/passive-events-support/-/passive-events-support-1.1.0.tgz", @@ -7704,6 +8535,15 @@ "node": ">=10.4.0" } }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/process-es6": { "version": "0.11.6", "resolved": "https://registry.npmjs.org/process-es6/-/process-es6-0.11.6.tgz", @@ -7960,6 +8800,26 @@ "integrity": "sha512-4sP/C9sSxQ3w80AATmvCEI3R+MHzCwr2RSZEbLyMkeJgV3cRk7ySZRUrQnBDSA7A0/z6dkYtjuXlkhN1ZFw3iA==", "dev": true }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/quick-lru": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", @@ -8159,6 +9019,15 @@ "node": ">=0.10.0" } }, + "node_modules/requireindex": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/requireindex/-/requireindex-1.2.0.tgz", + "integrity": "sha512-L9jEkOi3ASd9PYit2cwRfyppc9NoABujTP8/5gFcbERmo5jUoAKovIC3fsF17pkTnGsrByysqX+Kxd2OTNI1ww==", + "dev": true, + "engines": { + "node": ">=0.10.5" + } + }, "node_modules/resolve": { "version": "1.22.4", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.4.tgz", @@ -8182,6 +9051,15 @@ "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==", "dev": true }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/responselike": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.1.tgz", @@ -8203,6 +9081,16 @@ "node": ">= 4" } }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, "node_modules/rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", @@ -8361,6 +9249,29 @@ "resolved": "https://registry.npmjs.org/rope-sequence/-/rope-sequence-1.3.4.tgz", "integrity": "sha512-UT5EDe2cu2E/6O4igUr5PSFs23nvvukicWHx6GnOPlHAiiYbzNuCRQCuiUdHJQcqKalLKlrYJnjY0ySGsXNQXQ==" }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -8830,6 +9741,18 @@ "node": ">=0.10.0" } }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/strip-outer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/strip-outer/-/strip-outer-1.0.1.tgz", @@ -8987,6 +9910,12 @@ "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "dev": true }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, "node_modules/throttle-debounce": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/throttle-debounce/-/throttle-debounce-3.0.1.tgz", @@ -9104,6 +10033,18 @@ "graceful-fs": "^4.1.6" } }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/type-fest": { "version": "2.19.0", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", @@ -9391,6 +10332,18 @@ "fd-slicer": "~1.1.0" } }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/zeed-dom": { "version": "0.9.26", "resolved": "https://registry.npmjs.org/zeed-dom/-/zeed-dom-0.9.26.tgz", diff --git a/package.json b/package.json index 8027f6c5..6af10180 100644 --- a/package.json +++ b/package.json @@ -27,18 +27,30 @@ "build-electron": "electron-builder build --publish never", "deploy-electron": "electron-builder build --win --publish never", "release": "NODE_ENV=production electron-builder build --publish never", - "publish": "electron-builder -p always" + "publish": "electron-builder -p always", + "lint": "eslint './**/*.{js,mjs}'" }, "dependencies": { + "@hapi/hapi": "21.3.2", + "@hapi/inert": "7.1.0", + "@lit-labs/motion": "1.0.4", + "@tiptap/core": "2.0.4", + "@tiptap/extension-highlight": "2.0.4", + "@tiptap/extension-image": "2.0.4", + "@tiptap/extension-placeholder": "2.0.4", + "@tiptap/extension-underline": "2.0.4", + "@tiptap/html": "2.0.4", + "@tiptap/pm": "2.0.4", + "@tiptap/starter-kit": "2.0.4", "asmcrypto.js": "2.3.2", "bcryptjs": "2.4.3", "buffer": "6.0.3", "compressorjs": "1.2.1", "crypto-js": "4.1.1", - "electron-log": "4.4.8", - "electron-updater": "6.1.4", "electron-dl": "3.5.0", + "electron-log": "4.4.8", "electron-store": "8.1.0", + "electron-updater": "6.1.4", "emoji-picker-js": "https://github.com/Qortal/emoji-picker-js", "extract-zip": "2.0.1", "jssha": "3.3.1", @@ -57,40 +69,9 @@ "prosemirror-transform": "1.7.5", "prosemirror-view": "1.31.7", "sass": "1.66.1", - "short-unique-id": "5.0.2", - "@hapi/hapi": "21.3.2", - "@hapi/inert": "7.1.0", - "@lit-labs/motion": "1.0.4", - "@tiptap/pm": "2.0.4", - "@tiptap/core": "2.0.4", - "@tiptap/extension-highlight": "2.0.4", - "@tiptap/extension-image": "2.0.4", - "@tiptap/extension-placeholder": "2.0.4", - "@tiptap/extension-underline": "2.0.4", - "@tiptap/html": "2.0.4", - "@tiptap/starter-kit": "2.0.4" + "short-unique-id": "5.0.2" }, "devDependencies": { - "axios": "1.5.0", - "electron": "26.2.0", - "electron-builder": "24.6.4", - "electron-packager": "17.1.2", - "epml": "0.3.3", - "file-saver": "2.0.5", - "highcharts": "11.1.0", - "html-escaper": "3.0.3", - "is-electron": "2.2.2", - "lit": "2.8.0", - "lit-translate": "2.0.1", - "pwa-helpers": "0.9.1", - "passive-events-support": "1.1.0", - "redux": "4.2.1", - "redux-thunk": "2.4.2", - "rollup": "3.29.0", - "rollup-plugin-node-globals": "1.4.0", - "rollup-plugin-progress": "1.1.2", - "rollup-plugin-scss": "3.0.0", - "shelljs": "0.8.5", "@babel/core": "7.22.17", "@material/mwc-button": "0.27.0", "@material/mwc-checkbox": "0.27.0", @@ -140,7 +121,30 @@ "@vaadin/icons": "24.1.7", "@vaadin/password-field": "24.1.7", "@vaadin/tooltip": "24.1.7", - "@zip.js/zip.js": "2.7.29" + "@zip.js/zip.js": "2.7.29", + "axios": "1.5.0", + "electron": "26.2.0", + "electron-builder": "24.6.4", + "electron-packager": "17.1.2", + "epml": "0.3.3", + "eslint": "^8.50.0", + "eslint-plugin-lit": "^1.9.1", + "eslint-plugin-wc": "^2.0.3", + "file-saver": "2.0.5", + "highcharts": "11.1.0", + "html-escaper": "3.0.3", + "is-electron": "2.2.2", + "lit": "2.8.0", + "lit-translate": "2.0.1", + "passive-events-support": "1.1.0", + "pwa-helpers": "0.9.1", + "redux": "4.2.1", + "redux-thunk": "2.4.2", + "rollup": "3.29.0", + "rollup-plugin-node-globals": "1.4.0", + "rollup-plugin-progress": "1.1.2", + "rollup-plugin-scss": "3.0.0", + "shelljs": "0.8.5" }, "engines": { "node": ">=18.16.1" diff --git a/plugins/plugins/core/components/ChatImage.js b/plugins/plugins/core/components/ChatImage.js index 71a84c45..35a3fde2 100644 --- a/plugins/plugins/core/components/ChatImage.js +++ b/plugins/plugins/core/components/ChatImage.js @@ -1,11 +1,7 @@ import { LitElement, html, css } from 'lit'; -import { render } from 'lit/html.js'; import { - use, get, translate, - translateUnsafeHTML, - registerTranslateConfig, } from 'lit-translate'; import axios from 'axios' import { RequestQueueWithPromise } from '../../utils/queue'; @@ -228,9 +224,7 @@ getMyNode(){ identifier: this.resource.identifier }) this.fetchStatus() - } catch (error) { - - } + } catch (error) { /* empty */ } } firstUpdated(){ diff --git a/plugins/plugins/core/components/ChatPage-css.js b/plugins/plugins/core/components/ChatPage-css.js new file mode 100644 index 00000000..50d62389 --- /dev/null +++ b/plugins/plugins/core/components/ChatPage-css.js @@ -0,0 +1,1154 @@ +import { css } from 'lit' + +export const chatpageStyles = css` + html { + scroll-behavior: smooth; + } + + .chat-head-container { + display: flex; + justify-content: flex-start; + flex-direction: column; + height: 50vh; + overflow-y: auto; + overflow-x: hidden; + width: 100%; + } + + .repliedTo-container { + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + padding: 10px 10px 8px 10px; + } + + .senderName { + margin: 0; + color: var(--mdc-theme-primary); + font-weight: bold; + user-select: none; + } + + .original-message { + color: var(--chat-bubble-msg-color); + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + margin: 0; + width: 800px; + } + + .close-icon { + color: #676b71; + width: 18px; + transition: all 0.1s ease-in-out; + } + + .close-icon:hover { + cursor: pointer; + color: #494c50; + } + + .chat-text-area .typing-area .chatbar { + position: relative; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + height: auto; + padding: 5px 5px 5px 7px; + overflow: hidden; + } + + .chat-text-area .typing-area .emoji-button { + width: 45px; + height: 40px; + padding-top: 4px; + border: none; + outline: none; + background: transparent; + cursor: pointer; + max-height: 40px; + color: var(--black); + } + + .emoji-button-caption { + width: 45px; + height: 40px; + padding-top: 4px; + border: none; + outline: none; + background: transparent; + cursor: pointer; + max-height: 40px; + color: var(--black); + } + + .caption-container { + width: 100%; + display: flex; + height: auto; + overflow: hidden; + justify-content: center; + background-color: var(--white); + padding: 5px; + border-radius: 1px; + } + + .chatbar-caption { + font-family: Roboto, sans-serif; + width: 70%; + margin-right: 10px; + outline: none; + align-items: center; + font-size: 18px; + resize: none; + border-top: 0; + border-right: 0; + border-left: 0; + border-bottom: 1px solid #cac8c8; + padding: 3px; + } + + .message-size-container { + display: flex; + justify-content: flex-end; + width: 100%; + } + + .message-size { + font-family: Roboto, sans-serif; + font-size: 12px; + color: black; + } + + .lds-grid { + width: 120px; + height: 120px; + position: absolute; + left: 50%; + top: 40%; + } + + img { + border-radius: 25%; + } + + .dialogCustom { + position: fixed; + z-index: 10000; + display: flex; + justify-content: center; + flex-direction: column; + align-items: center; + top: 10px; + right: 20px; + user-select: none; + } + + .dialogCustomInner { + min-width: 300px; + height: 40px; + background-color: var(--white); + box-shadow: rgb(119 119 119 / 32%) 0px 4px 12px; + padding: 10px; + border-radius: 4px; + } + + .dialogCustomInner ul { + padding-left: 0px + } + + .dialogCustomInner li { + margin-bottom: 10px; + } + + .marginLoader { + margin-right: 8px; + } + + .last-message-ref { + position: absolute; + font-size: 18px; + top: -40px; + right: 30px; + width: 50; + height: 50; + z-index: 5; + color: black; + background-color: white; + border-radius: 50%; + transition: all 0.1s ease-in-out; + } + + .last-message-ref:hover { + cursor: pointer; + transform: scale(1.1); + } + + .arrow-down-icon { + transform: scale(1.15); + } + + .chat-container { + display: grid; + max-height: 100%; + } + + .chat-text-area { + display: flex; + position: relative; + justify-content: center; + min-height: 60px; + max-height: 100%; + } + + .chat-text-area .typing-area { + display: flex; + flex-direction: column; + width: 98%; + box-sizing: border-box; + margin-bottom: 8px; + border: 1px solid var(--chat-bubble-bg); + border-radius: 10px; + background: var(--chat-bubble-bg); + } + + .chat-text-area .typing-area textarea { + display: none; + } + + .chat-text-area .typing-area .chat-editor { + display: flex; + max-height: -webkit-fill-available; + width: 100%; + border-color: transparent; + margin: 0; + padding: 0; + border: none; + } + + .repliedTo-container { + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + padding: 10px 10px 8px 10px; + } + + .repliedTo-subcontainer { + display: flex; + flex-direction: row; + align-items: center; + gap: 15px; + width: 100%; + } + + .repliedTo-message { + display: flex; + flex-direction: column; + gap: 5px; + width: 100%; + word-break: break-all; + text-overflow: ellipsis; + overflow: hidden; + max-height: 60px; + } + .repliedTo-message p { + margin: 0px; + padding: 0px; + } + + .repliedTo-message pre { + white-space: pre-wrap; + } + + .repliedTo-message p mark { + background-color: #ffe066; + border-radius: 0.25em; + box-decoration-break: clone; + padding: 0.125em 0; + } + + .reply-icon { + width: 20px; + color: var(--mdc-theme-primary); + } + + .close-icon { + color: #676b71; + width: 18px; + transition: all 0.1s ease-in-out; + } + + .close-icon:hover { + cursor: pointer; + color: #494c50; + } + + .chatbar-container { + width: 100%; + display: flex; + height: auto; + overflow: hidden; + } + + .lds-grid { + width: 120px; + height: 120px; + position: absolute; + left: 50%; + top: 40%; + } + + .lds-grid div { + position: absolute; + width: 34px; + height: 34px; + border-radius: 50%; + background: #03a9f4; + animation: lds-grid 1.2s linear infinite; + } + + .lds-grid div:nth-child(1) { + top: 4px; + left: 4px; + animation-delay: 0s; + } + + .lds-grid div:nth-child(2) { + top: 4px; + left: 48px; + animation-delay: -0.4s; + } + + .lds-grid div:nth-child(3) { + top: 4px; + left: 90px; + animation-delay: -0.8s; + } + + .lds-grid div:nth-child(4) { + top: 50px; + left: 4px; + animation-delay: -0.4s; + } + + .lds-grid div:nth-child(5) { + top: 50px; + left: 48px; + animation-delay: -0.8s; + } + + .lds-grid div:nth-child(6) { + top: 50px; + left: 90px; + animation-delay: -1.2s; + } + + .lds-grid div:nth-child(7) { + top: 95px; + left: 4px; + animation-delay: -0.8s; + } + + .lds-grid div:nth-child(8) { + top: 95px; + left: 48px; + animation-delay: -1.2s; + } + + .lds-grid div:nth-child(9) { + top: 95px; + left: 90px; + animation-delay: -1.6s; + } + + @keyframes lds-grid { + 0%, 100% { + opacity: 1; + } + 50% { + opacity: 0.5; + } + } + + .float-left { + float: left; + } + + img { + border-radius: 25%; + } + + paper-dialog.warning { + width: 50%; + max-width: 50vw; + height: 30%; + max-height: 30vh; + text-align: center; + background-color: var(--white); + color: var(--black); + border: 1px solid var(--black); + border-radius: 15px; + line-height: 1.6; + overflow-y: auto; + overflow-x: hidden; + width: 100%; + } + + .repliedTo-container { + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + padding: 10px 10px 8px 10px; + } + + .senderName { + margin: 0; + color: var(--mdc-theme-primary); + font-weight: bold; + user-select: none; + } + + .original-message { + color: var(--chat-bubble-msg-color); + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + margin: 0; + width: 800px; + } + + + .close-icon { + color: #676b71; + width: 18px; + transition: all 0.1s ease-in-out; + } + + .close-icon:hover { + cursor: pointer; + color: #494c50; + } + + .chat-text-area .typing-area .chatbar { + position: relative; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + height: auto; + padding: 5px 5px 5px 7px; + overflow: hidden; + } + + .chat-text-area .typing-area .emoji-button { + width: 45px; + height: 40px; + padding-top: 4px; + border: none; + outline: none; + background: transparent; + cursor: pointer; + max-height: 40px; + color: var(--black); + } + + .emoji-button-caption { + width: 45px; + height: 40px; + padding-top: 4px; + border: none; + outline: none; + background: transparent; + cursor: pointer; + max-height: 40px; + color: var(--black); + } + + .caption-container { + width: 100%; + display: flex; + height: auto; + overflow: hidden; + justify-content: center; + background-color: var(--white); + padding: 5px; + border-radius: 1px; + } + + .chatbar-caption { + font-family: Roboto, sans-serif; + width: 70%; + margin-right: 10px; + outline: none; + align-items: center; + font-size: 18px; + resize: none; + border-top: 0; + border-right: 0; + border-left: 0; + border-bottom: 1px solid #cac8c8; + padding: 3px; + } + + .message-size-container { + display: flex; + justify-content: flex-end; + width: 100%; + } + + .message-size { + font-family: Roboto, sans-serif; + font-size: 12px; + color: black; + } + + .lds-grid { + width: 120px; + height: 120px; + position: absolute; + left: 50%; + top: 40%; + } + + img { + border-radius: 25%; + } + + .dialogCustom { + position: fixed; + z-index: 10000; + display: flex; + justify-content: center; + flex-direction: column; + align-items: center; + top: 10px; + right: 20px; + user-select: none; + } + + .dialogCustomInner { + min-width: 300px; + height: 40px; + background-color: var(--white); + box-shadow: rgb(119 119 119 / 32%) 0px 4px 12px; + padding: 10px; + border-radius: 4px; + } + + .dialogCustomInner ul { + padding-left: 0px + } + + .dialogCustomInner li { + margin-bottom: 10px; + } + + .marginLoader { + margin-right: 8px; + } + + .last-message-ref { + position: absolute; + font-size: 18px; + top: -40px; + right: 30px; + width: 50; + height: 50; + z-index: 5; + color: black; + background-color: white; + border-radius: 50%; + transition: all 0.1s ease-in-out; + } + + .last-message-ref:hover { + cursor: pointer; + transform: scale(1.1); + } + + .arrow-down-icon { + transform: scale(1.15); + } + + .chat-container { + display: grid; + max-height: 100%; + } + + .chat-text-area { + display: flex; + position: relative; + justify-content: center; + min-height: 60px; + max-height: 100%; + } + + .chat-text-area .typing-area { + display: flex; + flex-direction: column; + width: 98%; + box-sizing: border-box; + margin-bottom: 8px; + border: 1px solid var(--chat-bubble-bg); + border-radius: 10px; + background: var(--chat-bubble-bg); + } + + .chat-text-area .typing-area textarea { + display: none; + } + + .chat-text-area .typing-area .chat-editor { + display: flex; + max-height: -webkit-fill-available; + width: 100%; + border-color: transparent; + margin: 0; + padding: 0; + border: none; + } + + .repliedTo-container { + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + padding: 10px 10px 8px 10px; + } + + .repliedTo-subcontainer { + display: flex; + flex-direction: row; + align-items: center; + gap: 15px; + width: 100%; + } + + .repliedTo-message { + display: flex; + flex-direction: column; + gap: 5px; + width: 100%; + word-break: break-all; + text-overflow: ellipsis; + overflow: hidden; + max-height: 60px; + } + .repliedTo-message p { + margin: 0px; + padding: 0px; + } + + .repliedTo-message pre { + white-space: pre-wrap; + } + + .repliedTo-message p mark { + background-color: #ffe066; + border-radius: 0.25em; + box-decoration-break: clone; + padding: 0.125em 0; + } + + .reply-icon { + width: 20px; + color: var(--mdc-theme-primary); + } + + .close-icon { + color: #676b71; + width: 18px; + transition: all 0.1s ease-in-out; + } + + .close-icon:hover { + cursor: pointer; + color: #494c50; + } + + .chatbar-container { + width: 100%; + display: flex; + height: auto; + overflow: hidden; + } + + .lds-grid { + width: 120px; + height: 120px; + position: absolute; + left: 50%; + top: 40%; + } + + .lds-grid div { + position: absolute; + width: 34px; + height: 34px; + border-radius: 50%; + background: #03a9f4; + animation: lds-grid 1.2s linear infinite; + } + + .lds-grid div:nth-child(1) { + top: 4px; + left: 4px; + animation-delay: 0s; + } + + .lds-grid div:nth-child(2) { + top: 4px; + left: 48px; + animation-delay: -0.4s; + } + + .lds-grid div:nth-child(3) { + top: 4px; + left: 90px; + animation-delay: -0.8s; + } + + .lds-grid div:nth-child(4) { + top: 50px; + left: 4px; + animation-delay: -0.4s; + } + + .lds-grid div:nth-child(5) { + top: 50px; + left: 48px; + animation-delay: -0.8s; + } + + .lds-grid div:nth-child(6) { + top: 50px; + left: 90px; + animation-delay: -1.2s; + } + + .lds-grid div:nth-child(7) { + top: 95px; + left: 4px; + animation-delay: -0.8s; + } + + .lds-grid div:nth-child(8) { + top: 95px; + left: 48px; + animation-delay: -1.2s; + } + + .lds-grid div:nth-child(9) { + top: 95px; + left: 90px; + animation-delay: -1.6s; + } + + @keyframes lds-grid { + 0%, 100% { + opacity: 1; + } + 50% { + opacity: 0.5; + } + } + + .float-left { + float: left; + } + + img { + border-radius: 25%; + } + + paper-dialog.warning { + width: 50%; + max-width: 50vw; + height: 30%; + max-height: 30vh; + text-align: center; + background-color: var(--white); + color: var(--black); + border: 1px solid var(--black); + border-radius: 15px; + line-height: 1.6; + overflow-y: auto; + } + .buttons { + text-align:right; + } + + .dialogCustom { + position: fixed; + z-index: 10000; + display: flex; + justify-content: center; + flex-direction: column; + align-items: center; + top: 10px; + right: 20px; + user-select: none; + } + + .dialogCustom p { + color: var(--black) + } + + .dialogCustomInner { + min-width: 300px; + height: 40px; + background-color: var(--white); + box-shadow: rgb(119 119 119 / 32%) 0px 4px 12px; + padding: 10px; + border-radius: 4px; + } + + .dialogCustomInner ul { + padding-left: 0px + } + + .dialogCustomInner li { + margin-bottom: 10px; + } + + .marginLoader { + margin-right: 8px; + } + + .smallLoading, + .smallLoading:after { + border-radius: 50%; + width: 2px; + height: 2px; + } + + .smallLoading { + border-width: 0.8em; + border-style: solid; + border-color: rgba(3, 169, 244, 0.2) rgba(3, 169, 244, 0.2) + rgba(3, 169, 244, 0.2) rgb(3, 169, 244); + font-size: 10px; + position: relative; + text-indent: -9999em; + transform: translateZ(0px); + animation: 1.1s linear 0s infinite normal none running loadingAnimation; + } + + @-webkit-keyframes loadingAnimation { + 0% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); + } + 100% { + -webkit-transform: rotate(360deg); + transform: rotate(360deg); + } + } + + @keyframes loadingAnimation { + 0% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); + } + 100% { + -webkit-transform: rotate(360deg); + transform: rotate(360deg); + } + } + + /* Add Image Modal Dialog Styling */ + + .dialog-container { + position: relative; + display: flex; + align-items: center; + flex-direction: column; + padding: 0 10px; + gap: 10px; + height: 100%; + } + + .dialog-container-title { + font-family: Montserrat; + color: var(--black); + font-size: 20px; + margin: 15px 0 0 0; + } + + .divider { + height: 1px; + background-color: var(--chat-bubble-msg-color); + user-select: none; + width: 70%; + margin-bottom: 20px; + } + + .dialog-container-loader { + position: relative; + display: flex; + align-items: center; + padding: 0 10px; + gap: 10px; + height: 100%; + } + + .dialog-image { + width: 100%; + max-height: 300px; + border-radius: 0; + object-fit: contain; + } + + .chat-right-panel { + flex: 0; + border-left: 3px solid rgb(221, 221, 221); + height: 100%; + overflow-y: auto; + background: transparent; + } + + .movedin { + flex: 1 !important; + background: transparent; + } + + .main-container { + display: flex; + height: 100%; + } + + .group-nav-container { + display: flex; + height: 40px; + padding: 5px; + margin: 0px; + background-color: var(--chat-bubble-bg); + box-sizing: border-box; + align-items: center; + justify-content: space-between; + box-shadow: var(--group-drop-shadow); + z-index: 1; + } + + .top-bar-icon { + border-radius: 50%; + color: var(--chat-bubble-msg-color); + transition: 0.3s all ease-in-out; + padding: 5px; + background-color: transparent; + } + + .top-bar-icon:hover { + background-color: #e6e6e69b; + cursor: pointer; + color: var(--black) + } + + .group-name { + font-family: Raleway, sans-serif; + font-size: 16px; + color: var(--black); + margin:0px; + padding:0px; + } + + + .modal-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; + } + + .modal-button-red { + font-family: Roboto, sans-serif; + font-size: 16px; + color: #F44336; + background-color: transparent; + padding: 8px 10px; + border-radius: 5px; + border: none; + transition: all 0.3s ease-in-out; + } + + .modal-button-red:hover { + cursor: pointer; + background-color: #f4433663; + } + + .modal-button:hover { + cursor: pointer; + background-color: #03a8f475; + } + + .name-input { + width: 100%; + margin-bottom: 15px; + outline: 0; + border-width: 0 0 2px; + border-color: var(--mdc-theme-primary); + background-color: transparent; + padding: 10px; + font-family: Roboto, sans-serif; + font-size: 15px; + color: var(--chat-bubble-msg-color); + box-sizing: border-box; + } + + .name-input::selection { + background-color: var(--mdc-theme-primary); + color: white; + } + + .name-input::placeholder { + opacity: 0.9; + color: var(--black); + } + + .search-results-div { + position: absolute; + top: 25px; + right: 25px; + } + + .search-field { + width: 100%; + position: relative; + margin-bottom: 5px; + } + + .search-icon { + position: absolute; + right: 3px; + top: 0; + color: var(--chat-bubble-msg-color); + transition: all 0.3s ease-in-out; + background: none; + border-radius: 50%; + padding: 6px 3px; + font-size: 21px; + } + + .search-icon:hover { + cursor: pointer; + background: #d7d7d75c; + } + + .user-verified { + position: absolute; + top: 0; + right: 5px; + display: flex; + align-items: center; + gap: 10px; + color: #04aa2e; + font-size: 13px; + } + + .user-selected { + display: flex; + justify-content: space-between; + align-items: center; + margin: 0; + box-shadow: rgb(0 0 0 / 16%) 0px 3px 6px, rgb(0 0 0 / 23%) 0px 3px 6px; + padding: 18px 20px; + color: var(--chat-bubble-msg-color); + border-radius: 5px; + background-color: #ececec96; + } + + .user-selected-name { + font-family: Roboto, sans-serif; + margin: 0; + font-size: 16px; + } + + .forwarding-container { + display: flex; + gap: 15px; + } + + .user-selected-forwarding { + font-family: Livvic, sans-serif; + margin: 0; + font-size: 16px; + } + + .close-forwarding { + color: #676b71; + width: 14px; + transition: all 0.1s ease-in-out; + } + + .close-forwarding:hover { + cursor: pointer; + color: #4e5054; + } + + .chat-gifs { + position: absolute; + right: 15px; + bottom: 100px; + justify-self: flex-end; + width: fit-content; + height: auto; + transform: translateY(30%); + animation: smooth-appear 0.5s ease forwards; + z-index: 5; + } + + @keyframes smooth-appear { + to { + transform: translateY(0); + } + } + + .gifs-backdrop { + top: 0; + height: 100vh; + width: 100vw; + background: transparent; + position: fixed; + } + + .modal-button-row { + display: flex; + align-items: center; + justify-content: space-between; + width: 100%; + } + + + .attachment-icon-container { + display: flex; + align-items: center; + justify-content: center; + height: 120px; + width: 120px; + border-radius: 50%; + border: none; + background-color: var(--mdc-theme-primary); + } + + .attachment-icon { + width: 70%; + } + + .attachment-name { + font-family: Work Sans, sans-serif; + font-size: 20px; + color: var(--chat-bubble-msg-color); + margin: 0px; + letter-spacing: 1px; + padding: 5px 0px; + } +` diff --git a/plugins/plugins/core/components/ChatPage.js b/plugins/plugins/core/components/ChatPage.js index d06c1a3d..ccc3b70c 100644 --- a/plugins/plugins/core/components/ChatPage.js +++ b/plugins/plugins/core/components/ChatPage.js @@ -1,7 +1,7 @@ -import { LitElement, html, css } from 'lit' +import { LitElement, html } from 'lit' import { animate } from '@lit-labs/motion' import { Epml } from '../../../epml.js' -import { use, get, translate, translateUnsafeHTML, registerTranslateConfig } from 'lit-translate' +import { get, translate } from 'lit-translate' import { generateHTML } from '@tiptap/core' import { unsafeHTML } from 'lit/directives/unsafe-html.js' import { Editor, Extension } from '@tiptap/core' @@ -12,7 +12,6 @@ import { publishData } from '../../utils/publish-image.js' import { EmojiPicker } from 'emoji-picker-js' import {ifDefined} from 'lit/directives/if-defined.js'; -import * as zip from '@zip.js/zip.js' import localForage from 'localforage' import StarterKit from '@tiptap/starter-kit' @@ -49,6 +48,7 @@ import '@polymer/paper-spinner/paper-spinner-lite.js' import { RequestQueue } from '../../utils/queue.js' import { modalHelper } from '../../utils/publish-modal.js' import { generateIdFromAddresses } from '../../utils/id-generation.js' +import { chatpageStyles } from './ChatPage-css.js' const chatLastSeen = localForage.createInstance({ name: "chat-last-seen", @@ -137,1160 +137,11 @@ class ChatPage extends LitElement { } static get styles() { - return css` - html { - scroll-behavior: smooth; - } - - .chat-head-container { - display: flex; - justify-content: flex-start; - flex-direction: column; - height: 50vh; - overflow-y: auto; - overflow-x: hidden; - width: 100%; - } - - .repliedTo-container { - display: flex; - flex-direction: row; - justify-content: space-between; - align-items: center; - padding: 10px 10px 8px 10px; - } - - .senderName { - margin: 0; - color: var(--mdc-theme-primary); - font-weight: bold; - user-select: none; - } - - .original-message { - color: var(--chat-bubble-msg-color); - text-overflow: ellipsis; - overflow: hidden; - white-space: nowrap; - margin: 0; - width: 800px; - } - - .close-icon { - color: #676b71; - width: 18px; - transition: all 0.1s ease-in-out; - } - - .close-icon:hover { - cursor: pointer; - color: #494c50; - } - - .chat-text-area .typing-area .chatbar { - position: relative; - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - height: auto; - padding: 5px 5px 5px 7px; - overflow: hidden; - } - - .chat-text-area .typing-area .emoji-button { - width: 45px; - height: 40px; - padding-top: 4px; - border: none; - outline: none; - background: transparent; - cursor: pointer; - max-height: 40px; - color: var(--black); - } - - .emoji-button-caption { - width: 45px; - height: 40px; - padding-top: 4px; - border: none; - outline: none; - background: transparent; - cursor: pointer; - max-height: 40px; - color: var(--black); - } - - .caption-container { - width: 100%; - display: flex; - height: auto; - overflow: hidden; - justify-content: center; - background-color: var(--white); - padding: 5px; - border-radius: 1px; - } - - .chatbar-caption { - font-family: Roboto, sans-serif; - width: 70%; - margin-right: 10px; - outline: none; - align-items: center; - font-size: 18px; - resize: none; - border-top: 0; - border-right: 0; - border-left: 0; - border-bottom: 1px solid #cac8c8; - padding: 3px; - } - - .message-size-container { - display: flex; - justify-content: flex-end; - width: 100%; - } - - .message-size { - font-family: Roboto, sans-serif; - font-size: 12px; - color: black; - } - - .lds-grid { - width: 120px; - height: 120px; - position: absolute; - left: 50%; - top: 40%; - } - - img { - border-radius: 25%; - } - - .dialogCustom { - position: fixed; - z-index: 10000; - display: flex; - justify-content: center; - flex-direction: column; - align-items: center; - top: 10px; - right: 20px; - user-select: none; - } - - .dialogCustomInner { - min-width: 300px; - height: 40px; - background-color: var(--white); - box-shadow: rgb(119 119 119 / 32%) 0px 4px 12px; - padding: 10px; - border-radius: 4px; - } - - .dialogCustomInner ul { - padding-left: 0px - } - - .dialogCustomInner li { - margin-bottom: 10px; - } - - .marginLoader { - margin-right: 8px; - } - - .last-message-ref { - position: absolute; - font-size: 18px; - top: -40px; - right: 30px; - width: 50; - height: 50; - z-index: 5; - color: black; - background-color: white; - border-radius: 50%; - transition: all 0.1s ease-in-out; - } - - .last-message-ref:hover { - cursor: pointer; - transform: scale(1.1); - } - - .arrow-down-icon { - transform: scale(1.15); - } - - .chat-container { - display: grid; - max-height: 100%; - } - - .chat-text-area { - display: flex; - position: relative; - justify-content: center; - min-height: 60px; - max-height: 100%; - } - - .chat-text-area .typing-area { - display: flex; - flex-direction: column; - width: 98%; - box-sizing: border-box; - margin-bottom: 8px; - border: 1px solid var(--chat-bubble-bg); - border-radius: 10px; - background: var(--chat-bubble-bg); - } - - .chat-text-area .typing-area textarea { - display: none; - } - - .chat-text-area .typing-area .chat-editor { - display: flex; - max-height: -webkit-fill-available; - width: 100%; - border-color: transparent; - margin: 0; - padding: 0; - border: none; - } - - .repliedTo-container { - display: flex; - flex-direction: row; - justify-content: space-between; - align-items: center; - padding: 10px 10px 8px 10px; - } - - .repliedTo-subcontainer { - display: flex; - flex-direction: row; - align-items: center; - gap: 15px; - width: 100%; - } - - .repliedTo-message { - display: flex; - flex-direction: column; - gap: 5px; - width: 100%; - word-break: break-all; - text-overflow: ellipsis; - overflow: hidden; - max-height: 60px; - } - .repliedTo-message p { - margin: 0px; - padding: 0px; - } - - .repliedTo-message pre { - white-space: pre-wrap; - } - - .repliedTo-message p mark { - background-color: #ffe066; - border-radius: 0.25em; - box-decoration-break: clone; - padding: 0.125em 0; - } - - .reply-icon { - width: 20px; - color: var(--mdc-theme-primary); - } - - .close-icon { - color: #676b71; - width: 18px; - transition: all 0.1s ease-in-out; - } - - .close-icon:hover { - cursor: pointer; - color: #494c50; - } - - .chatbar-container { - width: 100%; - display: flex; - height: auto; - overflow: hidden; - } - - .lds-grid { - width: 120px; - height: 120px; - position: absolute; - left: 50%; - top: 40%; - } - - .lds-grid div { - position: absolute; - width: 34px; - height: 34px; - border-radius: 50%; - background: #03a9f4; - animation: lds-grid 1.2s linear infinite; - } - - .lds-grid div:nth-child(1) { - top: 4px; - left: 4px; - animation-delay: 0s; - } - - .lds-grid div:nth-child(2) { - top: 4px; - left: 48px; - animation-delay: -0.4s; - } - - .lds-grid div:nth-child(3) { - top: 4px; - left: 90px; - animation-delay: -0.8s; - } - - .lds-grid div:nth-child(4) { - top: 50px; - left: 4px; - animation-delay: -0.4s; - } - - .lds-grid div:nth-child(5) { - top: 50px; - left: 48px; - animation-delay: -0.8s; - } - - .lds-grid div:nth-child(6) { - top: 50px; - left: 90px; - animation-delay: -1.2s; - } - - .lds-grid div:nth-child(7) { - top: 95px; - left: 4px; - animation-delay: -0.8s; - } - - .lds-grid div:nth-child(8) { - top: 95px; - left: 48px; - animation-delay: -1.2s; - } - - .lds-grid div:nth-child(9) { - top: 95px; - left: 90px; - animation-delay: -1.6s; - } - - @keyframes lds-grid { - 0%, 100% { - opacity: 1; - } - 50% { - opacity: 0.5; - } - } - - .float-left { - float: left; - } - - img { - border-radius: 25%; - } - - paper-dialog.warning { - width: 50%; - max-width: 50vw; - height: 30%; - max-height: 30vh; - text-align: center; - background-color: var(--white); - color: var(--black); - border: 1px solid var(--black); - border-radius: 15px; - line-height: 1.6; - overflow-y: auto; - overflow-x: hidden; - width: 100%; - } - - .repliedTo-container { - display: flex; - flex-direction: row; - justify-content: space-between; - align-items: center; - padding: 10px 10px 8px 10px; - } - - .senderName { - margin: 0; - color: var(--mdc-theme-primary); - font-weight: bold; - user-select: none; - } - - .original-message { - color: var(--chat-bubble-msg-color); - text-overflow: ellipsis; - overflow: hidden; - white-space: nowrap; - margin: 0; - width: 800px; - } - - - .close-icon { - color: #676b71; - width: 18px; - transition: all 0.1s ease-in-out; - } - - .close-icon:hover { - cursor: pointer; - color: #494c50; - } - - .chat-text-area .typing-area .chatbar { - position: relative; - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - height: auto; - padding: 5px 5px 5px 7px; - overflow: hidden; - } - - .chat-text-area .typing-area .emoji-button { - width: 45px; - height: 40px; - padding-top: 4px; - border: none; - outline: none; - background: transparent; - cursor: pointer; - max-height: 40px; - color: var(--black); - } - - .emoji-button-caption { - width: 45px; - height: 40px; - padding-top: 4px; - border: none; - outline: none; - background: transparent; - cursor: pointer; - max-height: 40px; - color: var(--black); - } - - .caption-container { - width: 100%; - display: flex; - height: auto; - overflow: hidden; - justify-content: center; - background-color: var(--white); - padding: 5px; - border-radius: 1px; - } - - .chatbar-caption { - font-family: Roboto, sans-serif; - width: 70%; - margin-right: 10px; - outline: none; - align-items: center; - font-size: 18px; - resize: none; - border-top: 0; - border-right: 0; - border-left: 0; - border-bottom: 1px solid #cac8c8; - padding: 3px; - } - - .message-size-container { - display: flex; - justify-content: flex-end; - width: 100%; - } - - .message-size { - font-family: Roboto, sans-serif; - font-size: 12px; - color: black; - } - - .lds-grid { - width: 120px; - height: 120px; - position: absolute; - left: 50%; - top: 40%; - } - - img { - border-radius: 25%; - } - - .dialogCustom { - position: fixed; - z-index: 10000; - display: flex; - justify-content: center; - flex-direction: column; - align-items: center; - top: 10px; - right: 20px; - user-select: none; - } - - .dialogCustomInner { - min-width: 300px; - height: 40px; - background-color: var(--white); - box-shadow: rgb(119 119 119 / 32%) 0px 4px 12px; - padding: 10px; - border-radius: 4px; - } - - .dialogCustomInner ul { - padding-left: 0px - } - - .dialogCustomInner li { - margin-bottom: 10px; - } - - .marginLoader { - margin-right: 8px; - } - - .last-message-ref { - position: absolute; - font-size: 18px; - top: -40px; - right: 30px; - width: 50; - height: 50; - z-index: 5; - color: black; - background-color: white; - border-radius: 50%; - transition: all 0.1s ease-in-out; - } - - .last-message-ref:hover { - cursor: pointer; - transform: scale(1.1); - } - - .arrow-down-icon { - transform: scale(1.15); - } - - .chat-container { - display: grid; - max-height: 100%; - } - - .chat-text-area { - display: flex; - position: relative; - justify-content: center; - min-height: 60px; - max-height: 100%; - } - - .chat-text-area .typing-area { - display: flex; - flex-direction: column; - width: 98%; - box-sizing: border-box; - margin-bottom: 8px; - border: 1px solid var(--chat-bubble-bg); - border-radius: 10px; - background: var(--chat-bubble-bg); - } - - .chat-text-area .typing-area textarea { - display: none; - } - - .chat-text-area .typing-area .chat-editor { - display: flex; - max-height: -webkit-fill-available; - width: 100%; - border-color: transparent; - margin: 0; - padding: 0; - border: none; - } - - .repliedTo-container { - display: flex; - flex-direction: row; - justify-content: space-between; - align-items: center; - padding: 10px 10px 8px 10px; - } - - .repliedTo-subcontainer { - display: flex; - flex-direction: row; - align-items: center; - gap: 15px; - width: 100%; - } - - .repliedTo-message { - display: flex; - flex-direction: column; - gap: 5px; - width: 100%; - word-break: break-all; - text-overflow: ellipsis; - overflow: hidden; - max-height: 60px; - } - .repliedTo-message p { - margin: 0px; - padding: 0px; - } - - .repliedTo-message pre { - white-space: pre-wrap; - } - - .repliedTo-message p mark { - background-color: #ffe066; - border-radius: 0.25em; - box-decoration-break: clone; - padding: 0.125em 0; - } - - .reply-icon { - width: 20px; - color: var(--mdc-theme-primary); - } - - .close-icon { - color: #676b71; - width: 18px; - transition: all 0.1s ease-in-out; - } - - .close-icon:hover { - cursor: pointer; - color: #494c50; - } - - .chatbar-container { - width: 100%; - display: flex; - height: auto; - overflow: hidden; - } - - .lds-grid { - width: 120px; - height: 120px; - position: absolute; - left: 50%; - top: 40%; - } - - .lds-grid div { - position: absolute; - width: 34px; - height: 34px; - border-radius: 50%; - background: #03a9f4; - animation: lds-grid 1.2s linear infinite; - } - - .lds-grid div:nth-child(1) { - top: 4px; - left: 4px; - animation-delay: 0s; - } - - .lds-grid div:nth-child(2) { - top: 4px; - left: 48px; - animation-delay: -0.4s; - } - - .lds-grid div:nth-child(3) { - top: 4px; - left: 90px; - animation-delay: -0.8s; - } - - .lds-grid div:nth-child(4) { - top: 50px; - left: 4px; - animation-delay: -0.4s; - } - - .lds-grid div:nth-child(5) { - top: 50px; - left: 48px; - animation-delay: -0.8s; - } - - .lds-grid div:nth-child(6) { - top: 50px; - left: 90px; - animation-delay: -1.2s; - } - - .lds-grid div:nth-child(7) { - top: 95px; - left: 4px; - animation-delay: -0.8s; - } - - .lds-grid div:nth-child(8) { - top: 95px; - left: 48px; - animation-delay: -1.2s; - } - - .lds-grid div:nth-child(9) { - top: 95px; - left: 90px; - animation-delay: -1.6s; - } - - @keyframes lds-grid { - 0%, 100% { - opacity: 1; - } - 50% { - opacity: 0.5; - } - } - - .float-left { - float: left; - } - - img { - border-radius: 25%; - } - - paper-dialog.warning { - width: 50%; - max-width: 50vw; - height: 30%; - max-height: 30vh; - text-align: center; - background-color: var(--white); - color: var(--black); - border: 1px solid var(--black); - border-radius: 15px; - line-height: 1.6; - overflow-y: auto; - } - .buttons { - text-align:right; - } - - .dialogCustom { - position: fixed; - z-index: 10000; - display: flex; - justify-content: center; - flex-direction: column; - align-items: center; - top: 10px; - right: 20px; - user-select: none; - } - - .dialogCustom p { - color: var(--black) - } - - .dialogCustomInner { - min-width: 300px; - height: 40px; - background-color: var(--white); - box-shadow: rgb(119 119 119 / 32%) 0px 4px 12px; - padding: 10px; - border-radius: 4px; - } - - .dialogCustomInner ul { - padding-left: 0px - } - - .dialogCustomInner li { - margin-bottom: 10px; - } - - .marginLoader { - margin-right: 8px; - } - - .smallLoading, - .smallLoading:after { - border-radius: 50%; - width: 2px; - height: 2px; - } - - .smallLoading { - border-width: 0.8em; - border-style: solid; - border-color: rgba(3, 169, 244, 0.2) rgba(3, 169, 244, 0.2) - rgba(3, 169, 244, 0.2) rgb(3, 169, 244); - font-size: 10px; - position: relative; - text-indent: -9999em; - transform: translateZ(0px); - animation: 1.1s linear 0s infinite normal none running loadingAnimation; - } - - @-webkit-keyframes loadingAnimation { - 0% { - -webkit-transform: rotate(0deg); - transform: rotate(0deg); - } - 100% { - -webkit-transform: rotate(360deg); - transform: rotate(360deg); - } - } - - @keyframes loadingAnimation { - 0% { - -webkit-transform: rotate(0deg); - transform: rotate(0deg); - } - 100% { - -webkit-transform: rotate(360deg); - transform: rotate(360deg); - } - } - - /* Add Image Modal Dialog Styling */ - - .dialog-container { - position: relative; - display: flex; - align-items: center; - flex-direction: column; - padding: 0 10px; - gap: 10px; - height: 100%; - } - - .dialog-container-title { - font-family: Montserrat; - color: var(--black); - font-size: 20px; - margin: 15px 0 0 0; - } - - .divider { - height: 1px; - background-color: var(--chat-bubble-msg-color); - user-select: none; - width: 70%; - margin-bottom: 20px; - } - - .dialog-container-loader { - position: relative; - display: flex; - align-items: center; - padding: 0 10px; - gap: 10px; - height: 100%; - } - - .dialog-image { - width: 100%; - max-height: 300px; - border-radius: 0; - object-fit: contain; - } - - .chat-right-panel { - flex: 0; - border-left: 3px solid rgb(221, 221, 221); - height: 100%; - overflow-y: auto; - background: transparent; - } - - .movedin { - flex: 1 !important; - background: transparent; - } - - .main-container { - display: flex; - height: 100%; - } - - .group-nav-container { - display: flex; - height: 40px; - padding: 5px; - margin: 0px; - background-color: var(--chat-bubble-bg); - box-sizing: border-box; - align-items: center; - justify-content: space-between; - box-shadow: var(--group-drop-shadow); - z-index: 1; - } - - .top-bar-icon { - border-radius: 50%; - color: var(--chat-bubble-msg-color); - transition: 0.3s all ease-in-out; - padding: 5px; - background-color: transparent; - } - - .top-bar-icon:hover { - background-color: #e6e6e69b; - cursor: pointer; - color: var(--black) - } - - .group-name { - font-family: Raleway, sans-serif; - font-size: 16px; - color: var(--black); - margin:0px; - padding:0px; - } - - - .modal-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; - } - - .modal-button-red { - font-family: Roboto, sans-serif; - font-size: 16px; - color: #F44336; - background-color: transparent; - padding: 8px 10px; - border-radius: 5px; - border: none; - transition: all 0.3s ease-in-out; - } - - .modal-button-red:hover { - cursor: pointer; - background-color: #f4433663; - } - - .modal-button:hover { - cursor: pointer; - background-color: #03a8f475; - } - - .name-input { - width: 100%; - margin-bottom: 15px; - outline: 0; - border-width: 0 0 2px; - border-color: var(--mdc-theme-primary); - background-color: transparent; - padding: 10px; - font-family: Roboto, sans-serif; - font-size: 15px; - color: var(--chat-bubble-msg-color); - box-sizing: border-box; - } - - .name-input::selection { - background-color: var(--mdc-theme-primary); - color: white; - } - - .name-input::placeholder { - opacity: 0.9; - color: var(--black); - } - - .search-results-div { - position: absolute; - top: 25px; - right: 25px; - } - - .search-field { - width: 100%; - position: relative; - margin-bottom: 5px; - } - - .search-icon { - position: absolute; - right: 3px; - top: 0; - color: var(--chat-bubble-msg-color); - transition: all 0.3s ease-in-out; - background: none; - border-radius: 50%; - padding: 6px 3px; - font-size: 21px; - } - - .search-icon:hover { - cursor: pointer; - background: #d7d7d75c; - } - - .user-verified { - position: absolute; - top: 0; - right: 5px; - display: flex; - align-items: center; - gap: 10px; - color: #04aa2e; - font-size: 13px; - } - - .user-selected { - display: flex; - justify-content: space-between; - align-items: center; - margin: 0; - box-shadow: rgb(0 0 0 / 16%) 0px 3px 6px, rgb(0 0 0 / 23%) 0px 3px 6px; - padding: 18px 20px; - color: var(--chat-bubble-msg-color); - border-radius: 5px; - background-color: #ececec96; - } - - .user-selected-name { - font-family: Roboto, sans-serif; - margin: 0; - font-size: 16px; - } - - .forwarding-container { - display: flex; - gap: 15px; - } - - .user-selected-forwarding { - font-family: Livvic, sans-serif; - margin: 0; - font-size: 16px; - } - - .close-forwarding { - color: #676b71; - width: 14px; - transition: all 0.1s ease-in-out; - } - - .close-forwarding:hover { - cursor: pointer; - color: #4e5054; - } - - .chat-gifs { - position: absolute; - right: 15px; - bottom: 100px; - justify-self: flex-end; - width: fit-content; - height: auto; - transform: translateY(30%); - animation: smooth-appear 0.5s ease forwards; - z-index: 5; - } - - @keyframes smooth-appear { - to { - transform: translateY(0); - } - } - - .gifs-backdrop { - top: 0; - height: 100vh; - width: 100vw; - background: transparent; - position: fixed; - } - - .modal-button-row { - display: flex; - align-items: center; - justify-content: space-between; - width: 100%; - } + return [chatpageStyles]; + } - .attachment-icon-container { - display: flex; - align-items: center; - justify-content: center; - height: 120px; - width: 120px; - border-radius: 50%; - border: none; - background-color: var(--mdc-theme-primary); - } - - .attachment-icon { - width: 70%; - } - - .attachment-name { - font-family: Work Sans, sans-serif; - font-size: 20px; - color: var(--chat-bubble-msg-color); - margin: 0px; - letter-spacing: 1px; - padding: 5px 0px; - } -` - } - + constructor() { super() this.getOldMessage = this.getOldMessage.bind(this) @@ -2029,16 +880,14 @@ class ChatPage extends LitElement { address: member.member, name: name ? name : undefined } - } catch (error) { - } + } catch (error) { /* empty */ } return memberItem }) const membersWithName = await Promise.all(getMembersWithName) this.groupMembers = [...this.groupMembers, ...membersWithName] this.pageNumber = this.pageNumber + 1 - } catch (error) { - } + } catch (error) { /* empty */ } } async connectedCallback() { @@ -2160,7 +1009,7 @@ class ChatPage extends LitElement { document.addEventListener('keydown', this.initialChat) document.addEventListener('paste', this.pasteImage) - let callback = (entries, observer) => { + let callback = (entries) => { entries.forEach(entry => { if (entry.isIntersecting) { @@ -2223,8 +1072,7 @@ class ChatPage extends LitElement { initialChat(e) { if (this.editor && !this.editor.isFocused && this.currentEditor === '_chatEditorDOM' && !this.openForwardOpen && !this.openTipUser && !this.openGifModal) { // WARNING: Deprecated methods from KeyBoard Event - if (e.code === "Space" || e.keyCode === 32 || e.which === 32) { - } else if (inputKeyCodes.includes(e.keyCode)) { + if (e.code === "Space" || e.keyCode === 32 || e.which === 32) { /* empty */ } else if (inputKeyCodes.includes(e.keyCode)) { this.editor.commands.insertContent(e.key) this.editor.commands.focus('end') } else { @@ -2241,8 +1089,7 @@ class ChatPage extends LitElement { const [firstItem] = dataTransfer.items const blob = firstItem.getAsFile() return blob - } catch (error) { - } + } catch (error) { /* empty */ } } if (event.clipboardData) { const blobFound = handleTransferIntoURL(event.clipboardData) @@ -2396,8 +1243,7 @@ class ChatPage extends LitElement { delete message.reactions const stringifyMessageObject = JSON.stringify(message) this.sendMessage({messageText: stringifyMessageObject, chatReference: undefined, isForward: true}) - } catch (error) { - } + } catch (error) { /* empty */ } } showLastMessageRefScroller(props) { @@ -2514,8 +1360,7 @@ class ChatPage extends LitElement { address: member.member, name: name ? name : undefined } - } catch (error) { - } + } catch (error) { /* empty */ } return memberItem }) @@ -2529,16 +1374,14 @@ class ChatPage extends LitElement { address: member.member, name: name ? name : undefined } - } catch (error) { - } + } catch (error) { /* empty */ } return memberItem }) const membersWithName = await Promise.all(getMembersWithName) this.groupAdmin = membersAdminsWithName this.groupMembers = membersWithName this.groupInfo = getGroupInfo - } catch (error) { - } + } catch (error) { /* empty */ } } } @@ -2775,7 +1618,7 @@ class ChatPage extends LitElement { res() } - this.webWorkerDecodeMessages.onerror = e => { + this.webWorkerDecodeMessages.onerror = () => { rej() } @@ -2794,7 +1637,7 @@ class ChatPage extends LitElement { let list = [...decodeMsgs] - await new Promise((res, rej) => { + await new Promise((res) => { this.webWorkerSortMessages.postMessage({list}); @@ -2834,7 +1677,7 @@ class ChatPage extends LitElement { res() } - this.webWorkerDecodeMessages.onerror = e => { + this.webWorkerDecodeMessages.onerror = () => { rej() } @@ -2852,7 +1695,7 @@ class ChatPage extends LitElement { let list = [...decodeMsgs] - await new Promise((res, rej) => { + await new Promise((res) => { this.webWorkerSortMessages.postMessage({list}); @@ -2899,7 +1742,7 @@ class ChatPage extends LitElement { res() } - this.webWorkerDecodeMessages.onerror = e => { + this.webWorkerDecodeMessages.onerror = () => { rej() } @@ -2950,7 +1793,7 @@ class ChatPage extends LitElement { res() } - this.webWorkerDecodeMessages.onerror = e => { + this.webWorkerDecodeMessages.onerror = () => { rej() } @@ -3009,7 +1852,7 @@ class ChatPage extends LitElement { res() } - this.webWorkerDecodeMessages.onerror = e => { + this.webWorkerDecodeMessages.onerror = () => { rej() } @@ -3027,7 +1870,7 @@ class ChatPage extends LitElement { let list = [ ...decodeMsgs] - await new Promise((res, rej) => { + await new Promise((res) => { this.webWorkerSortMessages.postMessage({list}); @@ -3068,7 +1911,7 @@ class ChatPage extends LitElement { res() } - this.webWorkerDecodeMessages.onerror = e => { + this.webWorkerDecodeMessages.onerror = () => { rej() } @@ -3088,7 +1931,7 @@ class ChatPage extends LitElement { let list = [...decodeMsgs] - await new Promise((res, rej) => { + await new Promise((res) => { this.webWorkerSortMessages.postMessage({list}); @@ -3178,7 +2021,7 @@ class ChatPage extends LitElement { res() } - this.webWorkerDecodeMessages.onerror = e => { + this.webWorkerDecodeMessages.onerror = () => { rej() } @@ -3205,7 +2048,7 @@ class ChatPage extends LitElement { let list = decodedMessages - await new Promise((res, rej) => { + await new Promise((res) => { this.webWorkerSortMessages.postMessage({list}); @@ -3489,8 +2332,7 @@ class ChatPage extends LitElement { if (e.data) { this.processMessages(JSON.parse(e.data), false) } - } catch (error) { - } + } catch (error) { /* empty */ } } } @@ -3617,8 +2459,7 @@ class ChatPage extends LitElement { if (e.data) { this.processMessages(JSON.parse(e.data), false) } - } catch (error) { - } + } catch (error) { /* empty */ } } } @@ -3696,8 +2537,7 @@ class ChatPage extends LitElement { _publicKey.key = '' _publicKey.hasPubKey = false } - } catch (error) { - } + } catch (error) { /* empty */ } if (!hasPublicKey || !_publicKey.hasPubKey) { let err4string = get("chatpage.cchange39") @@ -3781,7 +2621,7 @@ class ChatPage extends LitElement { compressedFile = file resolve() }, - error(err) { + error() { }, }) }) @@ -3834,7 +2674,7 @@ class ChatPage extends LitElement { } else if (outSideMsg && outSideMsg.type === 'deleteAttachment') { this.isDeletingAttachment = true let compressedFile = '' - var str = "iVBORw0KGgoAAAANSUhEUgAAAsAAAAGMAQMAAADuk4YmAAAAA1BMVEX///+nxBvIAAAAAXRSTlMAQObYZgAAADlJREFUeF7twDEBAAAAwiD7p7bGDlgYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAGJrAABgPqdWQAAAABJRU5ErkJggg==" + const str = "iVBORw0KGgoAAAANSUhEUgAAAsAAAAGMAQMAAADuk4YmAAAAA1BMVEX///+nxBvIAAAAAXRSTlMAQObYZgAAADlJREFUeF7twDEBAAAAwiD7p7bGDlgYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAGJrAABgPqdWQAAAABJRU5ErkJggg==" const userName = outSideMsg.name const identifier = outSideMsg.identifier @@ -3878,7 +2718,7 @@ class ChatPage extends LitElement { compressedFile = file resolve() }, - error(err) { + error() { }, }) }) @@ -3987,7 +2827,7 @@ class ChatPage extends LitElement { compressedFile = file resolve() }, - error(err) { + error() { }, }) }) @@ -4334,8 +3174,7 @@ class ChatPage extends LitElement { publicKey.key = '' publicKey.hasPubKey = false } - } catch (error) { - } + } catch (error) { /* empty */ } } if (!this.forwardActiveChatHeadUrl.selected && this.shadowRoot.getElementById("sendTo").value !== "") { @@ -4403,8 +3242,7 @@ class ChatPage extends LitElement { publicKey.key = '' publicKey.hasPubKey = false } - } catch (error) { - } + } catch (error) { /* empty */ } } const isRecipient = this.forwardActiveChatHeadUrl.url.includes('direct') === true ? true : false @@ -4491,7 +3329,7 @@ class ChatPage extends LitElement { return _response } - const getSendChatResponse = (response, isForward, customErrorMessage) => { + const getSendChatResponse = (response, isForward) => { if (response === true) { // this.resetChatEditor() if (isForward) { diff --git a/plugins/plugins/core/components/ChatRightPanelResources.js b/plugins/plugins/core/components/ChatRightPanelResources.js index 64e16fbe..23686aac 100644 --- a/plugins/plugins/core/components/ChatRightPanelResources.js +++ b/plugins/plugins/core/components/ChatRightPanelResources.js @@ -1,8 +1,5 @@ import { LitElement, html, css } from 'lit'; -import { render } from 'lit/html.js'; import { Epml } from '../../../epml'; -import { getUserNameFromAddress } from '../../utils/getUserNameFromAddress'; -import snackbar from './snackbar.js'; import '@material/mwc-button'; import '@material/mwc-dialog'; import '@polymer/paper-spinner/paper-spinner-lite.js'; @@ -15,11 +12,8 @@ import './UserInfo/UserInfo'; import './ChatImage'; import './ReusableImage'; import { - use, get, translate, - translateUnsafeHTML, - registerTranslateConfig, } from 'lit-translate'; import { generateIdFromAddresses } from '../../utils/id-generation'; diff --git a/plugins/plugins/core/components/ChatScroller.js b/plugins/plugins/core/components/ChatScroller.js index 6fdd3a8b..48faa283 100644 --- a/plugins/plugins/core/components/ChatScroller.js +++ b/plugins/plugins/core/components/ChatScroller.js @@ -1,22 +1,16 @@ -import { LitElement, html, css } from 'lit'; -import { render } from 'lit/html.js'; +import { LitElement, html, } from 'lit'; import { repeat } from 'lit/directives/repeat.js'; import { - use, get, translate, - translateUnsafeHTML, - registerTranslateConfig, } from 'lit-translate'; import { unsafeHTML } from 'lit/directives/unsafe-html.js'; import { chatStyles } from './ChatScroller-css.js'; import { Epml } from '../../../epml'; import { cropAddress } from '../../utils/cropAddress'; import { roundToNearestDecimal } from '../../utils/roundToNearestDecimal.js'; -import { EmojiPicker } from 'emoji-picker-js'; import { generateHTML } from '@tiptap/core'; import isElectron from 'is-electron'; -import localForage from 'localforage'; import axios from 'axios'; import Highlight from '@tiptap/extension-highlight'; @@ -40,9 +34,6 @@ import '@vaadin/tooltip'; import { chatLimit, totalMsgCount } from './ChatPage.js'; const parentEpml = new Epml({ type: 'WINDOW', source: window.parent }); -const chatLastSeen = localForage.createInstance({ - name: 'chat-last-seen', -}); let toggledMessage = {}; const uid = new ShortUniqueId(); @@ -61,7 +52,7 @@ const extractComponents = async (url) => { return null; } - url = url.replace(/^(qortal\:\/\/)/, ''); + url = url.replace(/^(qortal:\/\/)/, ''); if (url.includes('/')) { let parts = url.split('/'); const service = parts[0].toUpperCase(); @@ -313,7 +304,7 @@ class ChatScroller extends LitElement { this.requestUpdate(); } - async newListMessages(newMessages, message) { + async newListMessages(newMessages) { let data = []; const copy = [...newMessages]; copy.forEach((newMessage) => { @@ -370,7 +361,7 @@ class ChatScroller extends LitElement { } } - copy.forEach((newMessage, groupIndex) => { + copy.forEach((newMessage) => { const lastGroupedMessage = data[data.length - 1]; if ( @@ -399,13 +390,8 @@ class ChatScroller extends LitElement { async addNewMessages(newMessages, type) { if (this.disableAddingNewMessages && type === 'newComingInAuto') return; - let previousScrollTop; - let previousScrollHeight; const viewElement = this.shadowRoot.querySelector('#viewElement'); - previousScrollTop = viewElement.scrollTop; - previousScrollHeight = viewElement.scrollHeight; - const copy = type === 'initial' ? [] : [...this.messagesToRender]; for (const newMessage of newMessages) { @@ -531,8 +517,6 @@ class ChatScroller extends LitElement { viewElement.scrollTop + viewElement.clientHeight === viewElement.scrollHeight; - const previousScrollTop = viewElement.scrollTop; - const previousScrollHeight = viewElement.scrollHeight; // Using map to return a new array, rather than mutating the old one const newMessagesToRender = this.messagesToRender.map((group) => { @@ -750,7 +734,7 @@ class ChatScroller extends LitElement { this.chatId.includes(item._chatId) ), (message) => message.messageText, - (message, indexMessage) => html` + (message) => html` ' ); @@ -2016,7 +1997,7 @@ class MessageTemplate extends LitElement { this.messageObj, reaction: reaction.type, })} - id=${`reactions-${indexMessageTemplate}`} + id=${`reactions-${index}`} class="reactions-bg" > ${reaction.type} ${reaction.qty} @@ -2359,7 +2340,7 @@ class ChatMenu extends LitElement { publicKey.key = ''; publicKey.hasPubKey = false; } - } catch (error) {} + } catch (error) { /* empty */ } try { const message = { @@ -2368,29 +2349,11 @@ class ChatMenu extends LitElement { }; const stringifyMessageObject = JSON.stringify(message); this.setForwardProperties(stringifyMessageObject); - } catch (error) {} + } catch (error) { /* empty */ } } render() { return html`
    -
      ${this.isEmptyArray(this.chatHeads) ? this.renderLoadingText() : this.renderChatHead(this.chatHeads)}
    - + { // Check if the clicked element has the class let target = event.target; @@ -497,8 +486,7 @@ class Chat extends LitElement { clearConsole() { - if (!isElectron()) { - } else { + if (!isElectron()) { /* empty */ } else { console.clear() window.parent.electronAPI.clearCache() } @@ -588,7 +576,7 @@ class Chat extends LitElement { recipient = _recipient } else { recipient = myNameRes.owner - }; + } const getAddressPublicKey = async () => { let isEncrypted; @@ -643,7 +631,7 @@ class Chat extends LitElement { const worker = new WebWorker() let nonce = null let chatBytesArray = null; - await new Promise((res, rej) => { + await new Promise((res) => { worker.postMessage({chatBytes, path, difficulty}) worker.onmessage = e => { worker.terminate() From fa1e4692a0f23ce1e6f33ce80f3726499430c24f Mon Sep 17 00:00:00 2001 From: PhilReact Date: Thu, 28 Sep 2023 00:40:29 -0500 Subject: [PATCH 47/57] added tx notification for join group --- core/language/us.json | 7 +- core/src/components/app-view.js | 5 + core/src/components/login-view/login-view.js | 5 +- .../notification-bell-general.js | 535 ++++++++++++++++++ .../notification-view/notification-bell.js | 3 +- .../components/notification-view/popover.js | 74 +++ core/src/components/show-plugin.js | 11 +- core/src/redux/app/actions/app-core.js | 8 +- core/src/redux/app/app-action-types.js | 3 +- core/src/redux/app/app-reducer.js | 12 +- package-lock.json | 1 + package.json | 1 + .../core/components/ChatGroupManager.js | 348 ++++++++++++ .../core/components/ChatGroupsModal.js | 98 ++++ plugins/plugins/core/components/ChatPage.js | 2 + .../group-management/group-management.src.js | 20 +- .../core/messaging/q-chat/q-chat-css.src.js | 1 + .../core/messaging/q-chat/q-chat.src.js | 79 ++- 18 files changed, 1186 insertions(+), 27 deletions(-) create mode 100644 core/src/components/notification-view/notification-bell-general.js create mode 100644 core/src/components/notification-view/popover.js create mode 100644 plugins/plugins/core/components/ChatGroupManager.js create mode 100644 plugins/plugins/core/components/ChatGroupsModal.js diff --git a/core/language/us.json b/core/language/us.json index aec0628f..2498cc47 100644 --- a/core/language/us.json +++ b/core/language/us.json @@ -836,7 +836,8 @@ "cchange92": "Unread messages below", "cchange93": "Image copied to clipboard", "cchange94": "loaded", - "cchange95": "Only my resources" + "cchange95": "Only my resources", + "cchange96": "Open Group Management" }, "welcomepage": { "wcchange1": "Welcome to Q-Chat", @@ -1168,5 +1169,9 @@ "lot11": "There are no open lotteries!", "lot12": "There are no finished lotteries!", "lot13": "Players" + }, + "notifications": { + "notify1": "Confirming transaction", + "notify2": "Transaction confirmed" } } \ No newline at end of file diff --git a/core/src/components/app-view.js b/core/src/components/app-view.js index 08b9c019..ca5a6732 100644 --- a/core/src/components/app-view.js +++ b/core/src/components/app-view.js @@ -42,6 +42,8 @@ import '../functional-components/side-menu.js' import '../functional-components/side-menu-item.js' import './start-minting.js' import './notification-view/notification-bell.js' +import './notification-view/notification-bell-general.js' + const parentEpml = new Epml({ type: 'WINDOW', source: window.parent }) @@ -556,7 +558,10 @@ class AppView extends connect(store)(LitElement) {
    +
    + +
    diff --git a/core/src/components/login-view/login-view.js b/core/src/components/login-view/login-view.js index 24bef336..c6ef8a5b 100644 --- a/core/src/components/login-view/login-view.js +++ b/core/src/components/login-view/login-view.js @@ -15,7 +15,7 @@ import './login-section.js' import '../qort-theme-toggle.js' import settings from '../../functional-components/settings-page.js' -import { addAutoLoadImageChat, removeAutoLoadImageChat, addChatLastSeen, allowQAPPAutoAuth, removeQAPPAutoAuth, removeQAPPAutoLists, allowQAPPAutoLists, addTabInfo, setTabNotifications, setNewTab } from '../../redux/app/app-actions.js' +import { addAutoLoadImageChat, removeAutoLoadImageChat, addChatLastSeen, allowQAPPAutoAuth, removeQAPPAutoAuth, removeQAPPAutoLists, allowQAPPAutoLists, addTabInfo, setTabNotifications, setNewTab, setNewNotification } from '../../redux/app/app-actions.js' window.reduxStore = store window.reduxAction = { @@ -28,7 +28,8 @@ window.reduxAction = { removeQAPPAutoLists: removeQAPPAutoLists, addTabInfo: addTabInfo, setTabNotifications: setTabNotifications, - setNewTab: setNewTab + setNewTab: setNewTab, + setNewNotification: setNewNotification } const animationDuration = 0.7 // Seconds diff --git a/core/src/components/notification-view/notification-bell-general.js b/core/src/components/notification-view/notification-bell-general.js new file mode 100644 index 00000000..804c63b2 --- /dev/null +++ b/core/src/components/notification-view/notification-bell-general.js @@ -0,0 +1,535 @@ +import { LitElement, html, css } from 'lit'; +import { connect } from 'pwa-helpers'; + +import '@vaadin/item'; +import '@vaadin/list-box'; +import '@polymer/paper-icon-button/paper-icon-button.js'; +import '@polymer/iron-icons/iron-icons.js'; +import { store } from '../../store.js'; +import { setNewNotification, setNewTab } from '../../redux/app/app-actions.js'; +import { routes } from '../../plugins/routes.js'; +import '@material/mwc-icon'; +import { translate } from 'lit-translate'; +import { repeat } from 'lit/directives/repeat.js'; + +import config from '../../notifications/config.js'; +import '../../../../plugins/plugins/core/components/TimeAgo.js'; +import './popover.js'; + +const currentNotification = { + type: 'JOIN_GROUP', + timestamp: Date.now(), + status: 'confirming', + reference: { + signature: + '5wpPP7ngE13z8x7FKr3tkx5AhMyzWAcFeTmkyefSbddRZ3ieMRcbwt4VDz5bakJzpFaE16NcSofa8w35AGLN4J47', + }, +}; + +const notifications = [currentNotification]; +class NotificationBellGeneral extends connect(store)(LitElement) { + static properties = { + notifications: { type: Array }, + showNotifications: { type: Boolean }, + notificationCount: { type: Boolean }, + theme: { type: String, reflect: true }, + notifications: { type: Array }, + currentNotification: { type: Object }, + }; + + constructor() { + super(); + this.notifications = []; + this.showNotifications = false; + this.notificationCount = false; + this.initialFetch = false; + this.theme = localStorage.getItem('qortalTheme') + ? localStorage.getItem('qortalTheme') + : 'light'; + this.currentNotification = null; + } + + firstUpdated() { + try { + let value = JSON.parse(localStorage.getItem('isFirstTimeUser')); + if (!value && value !== false) { + value = true; + } + this.isFirstTimeUser = value; + } catch (error) {} + } + + async stateChanged(state) { + if (state.app.newNotification) { + const newNotification = state.app.newNotification; + this.notifications = [newNotification, ...this.notifications]; + store.dispatch(setNewNotification(null)); + if (this.isFirstTimeUser) { + const target = this.shadowRoot.getElementById( + 'popover-notification' + ); + const popover = + this.shadowRoot.querySelector('popover-component'); + if (popover) { + popover.openPopover(target); + } + + localStorage.setItem('isFirstTimeUser', JSON.stringify(false)); + this.isFirstTimeUser = false; + } + } + } + + handleBlur() { + setTimeout(() => { + if (!this.shadowRoot.contains(document.activeElement)) { + this.showNotifications = false; + } + }, 0); + } + + changeStatus(signature, statusTx) { + const copyNotifications = [...this.notifications]; + const findNotification = this.notifications.findIndex( + (notification) => notification.reference.signature === signature + ); + if (findNotification !== -1) { + copyNotifications[findNotification] = { + ...copyNotifications[findNotification], + status: statusTx, + }; + this.notifications = copyNotifications; + + } + } + + render() { + const hasOngoing = this.notifications.find( + (notification) => notification.status !== 'confirmed' + ); + return html` +
    + +
    this._toggleNotifications()} + > + ${hasOngoing + ? html` + notifications + ` + : html` + notifications + `} +
    + ${hasOngoing + ? html` + this._toggleNotifications()} + > + pending + + ` + : ''} + +
    +
    + ${repeat( + this.notifications, + (notification) => notification.reference.signature, // key function + (notification) => html` + + this.changeStatus(val1, val2)} + status=${notification.status} + timestamp=${notification.timestamp} + type=${notification.type} + signature=${notification.reference + .signature} + > + ` + )} +
    +
    +
    + `; + } + + _toggleNotifications() { + if (this.notifications.length === 0) return; + this.showNotifications = !this.showNotifications; + if (this.showNotifications) { + requestAnimationFrame(() => { + this.shadowRoot.getElementById('notification-panel').focus(); + }); + } + } + + static styles = css` + .layout { + display: flex; + flex-direction: column; + align-items: center; + position: relative; + margin-right: 20px; + } + + .count { + position: absolute; + top: -5px; + right: -5px; + font-size: 12px; + background-color: red; + color: white; + border-radius: 50%; + width: 16px; + height: 16px; + display: flex; + align-items: center; + justify-content: center; + } + + .nocount { + display: none; + } + + .popover-panel { + position: absolute; + width: 200px; + padding: 10px; + background-color: var(--white); + border: 1px solid var(--black); + border-radius: 4px; + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); + top: 40px; + max-height: 350px; + overflow: auto; + scrollbar-width: thin; + scrollbar-color: #6a6c75 #a1a1a1; + } + + .popover-panel::-webkit-scrollbar { + width: 11px; + } + + .popover-panel::-webkit-scrollbar-track { + background: #a1a1a1; + } + + .popover-panel::-webkit-scrollbar-thumb { + background-color: #6a6c75; + border-radius: 6px; + border: 3px solid #a1a1a1; + } + + .notifications-list { + display: flex; + flex-direction: column; + } + + .notification-item { + padding: 5px; + border-bottom: 1px solid; + display: flex; + justify-content: space-between; + cursor: pointer; + transition: 0.2s all; + } + + .notification-item:hover { + background: var(--nav-color-hover); + } + + p { + font-size: 14px; + color: var(--black); + margin: 0px; + padding: 0px; + } + `; +} + +customElements.define('notification-bell-general', NotificationBellGeneral); + +class NotificationItemTx extends connect(store)(LitElement) { + static properties = { + status: { type: String }, + type: { type: String }, + timestamp: { type: Number }, + signature: { type: String }, + changeStatus: { attribute: false }, + }; + + constructor() { + super(); + this.nodeUrl = this.getNodeUrl(); + this.myNode = this.getMyNode(); + } + + getNodeUrl() { + const myNode = + store.getState().app.nodeConfig.knownNodes[ + window.parent.reduxStore.getState().app.nodeConfig.node + ]; + + const nodeUrl = + myNode.protocol + '://' + myNode.domain + ':' + myNode.port; + return nodeUrl; + } + getMyNode() { + const myNode = + store.getState().app.nodeConfig.knownNodes[ + window.parent.reduxStore.getState().app.nodeConfig.node + ]; + + return myNode; + } + + async getStatus() { + let interval = null; + let stop = false; + const getAnswer = async () => { + const getTx = async (minterAddr) => { + const url = `${this.nodeUrl}/transactions/signature/${this.signature}`; + const res = await fetch(url); + const data = await res.json(); + return data; + }; + + if (!stop) { + stop = true; + try { + const txTransaction = await getTx(); + if (!txTransaction.error && txTransaction.signature && txTransaction.blockHeight) { + clearInterval(interval); + this.changeStatus(this.signature, 'confirmed'); + } + } catch (error) {} + stop = false; + } + }; + interval = setInterval(getAnswer, 20000); + } + + firstUpdated() { + this.getStatus(); + } + + render() { + return html` +
    {}}> +
    +

    + ${translate('transpage.tchange1')} +

    +
    +
    +

    + ${translate('walletpage.wchange35')}: ${this.type} +

    +

    + ${translate('tubespage.schange28')}: + ${this.status === 'confirming' + ? translate('notifications.notify1') + : translate('notifications.notify2')} +

    + ${this.status !== 'confirmed' + ? html` +
    +
    Loading...
    +
    + ` + : ''} +
    + + ${this.status === 'confirmed' + ? html` + done + ` + : ''} +
    +
    +
    + `; + } + + _toggleNotifications() { + if (this.notifications.length === 0) return; + this.showNotifications = !this.showNotifications; + } + + static styles = css` + .centered { + display: flex; + justify-content: center; + align-items: center; + } + .layout { + width: 100px; + display: flex; + flex-direction: column; + align-items: center; + position: relative; + } + + .count { + position: absolute; + top: -5px; + right: -5px; + font-size: 12px; + background-color: red; + color: white; + border-radius: 50%; + width: 16px; + height: 16px; + display: flex; + align-items: center; + justify-content: center; + } + + .nocount { + display: none; + } + + .popover-panel { + position: absolute; + width: 200px; + padding: 10px; + background-color: var(--white); + border: 1px solid var(--black); + border-radius: 4px; + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); + top: 40px; + max-height: 350px; + overflow: auto; + scrollbar-width: thin; + scrollbar-color: #6a6c75 #a1a1a1; + } + + .popover-panel::-webkit-scrollbar { + width: 11px; + } + + .popover-panel::-webkit-scrollbar-track { + background: #a1a1a1; + } + + .popover-panel::-webkit-scrollbar-thumb { + background-color: #6a6c75; + border-radius: 6px; + border: 3px solid #a1a1a1; + } + + .notifications-list { + display: flex; + flex-direction: column; + } + + .notification-item { + padding: 5px; + border-bottom: 1px solid; + display: flex; + flex-direction: column; + cursor: default; + } + + .notification-item:hover { + background: var(--nav-color-hover); + } + + p { + font-size: 14px; + color: var(--black); + margin: 0px; + padding: 0px; + } + + .loader, + .loader:before, + .loader:after { + border-radius: 50%; + width: 10px; + height: 10px; + -webkit-animation-fill-mode: both; + animation-fill-mode: both; + -webkit-animation: load7 1.8s infinite ease-in-out; + animation: load7 1.8s infinite ease-in-out; + } + .loader { + color: var(--black); + font-size: 5px; + margin-bottom: 20px; + position: relative; + text-indent: -9999em; + -webkit-transform: translateZ(0); + -ms-transform: translateZ(0); + transform: translateZ(0); + -webkit-animation-delay: -0.16s; + animation-delay: -0.16s; + } + .loader:before, + .loader:after { + content: ''; + position: absolute; + top: 0; + } + .loader:before { + left: -3.5em; + -webkit-animation-delay: -0.32s; + animation-delay: -0.32s; + } + .loader:after { + left: 3.5em; + } + @-webkit-keyframes load7 { + 0%, + 80%, + 100% { + box-shadow: 0 2.5em 0 -1.3em; + } + 40% { + box-shadow: 0 2.5em 0 0; + } + } + @keyframes load7 { + 0%, + 80%, + 100% { + box-shadow: 0 2.5em 0 -1.3em; + } + 40% { + box-shadow: 0 2.5em 0 0; + } + } + `; +} + +customElements.define('notification-item-tx', NotificationItemTx); diff --git a/core/src/components/notification-view/notification-bell.js b/core/src/components/notification-view/notification-bell.js index cf78f907..8a4b0aec 100644 --- a/core/src/components/notification-view/notification-bell.js +++ b/core/src/components/notification-view/notification-bell.js @@ -210,7 +210,6 @@ class NotificationBell extends connect(store)(LitElement) { static styles = css` .layout { - width: 100px; display: flex; flex-direction: column; align-items: center; @@ -220,7 +219,7 @@ class NotificationBell extends connect(store)(LitElement) { .count { position: absolute; top: 2px; - right: 32px; + right: 0px; font-size: 12px; background-color: red; color: white; diff --git a/core/src/components/notification-view/popover.js b/core/src/components/notification-view/popover.js new file mode 100644 index 00000000..43e85a94 --- /dev/null +++ b/core/src/components/notification-view/popover.js @@ -0,0 +1,74 @@ +// popover-component.js +import { LitElement, html, css } from 'lit'; +import { createPopper } from '@popperjs/core'; +import '@material/mwc-icon' + +export class PopoverComponent extends LitElement { + static styles = css` + :host { + display: none; + position: absolute; + background-color: var(--white); + border: 1px solid #ddd; + padding: 8px; + z-index: 10; + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); + color: var(--black); + max-width: 250px; + } + + .close-icon { + cursor: pointer; + float: right; + margin-left: 10px; + color: var(--black) + } + `; + + static properties = { + for: { type: String, reflect: true }, + message: { type: String } + }; + + constructor() { + super(); + this.message = ''; + } + + firstUpdated() { + // We'll defer the popper attachment to the openPopover() method to ensure target availability + } + + attachToTarget(target) { + console.log({target}) + if (!this.popperInstance && target) { + this.popperInstance = createPopper(target, this, { + placement: 'bottom', + strategy: 'fixed' + }); + } + } + + openPopover(target) { + this.attachToTarget(target); + this.style.display = 'block'; + } + + closePopover() { + this.style.display = 'none'; + if (this.popperInstance) { + this.popperInstance.destroy(); + this.popperInstance = null; + } + this.requestUpdate(); + } + + render() { + return html` + close +
    info ${this.message}
    + `; + } +} + +customElements.define('popover-component', PopoverComponent); diff --git a/core/src/components/show-plugin.js b/core/src/components/show-plugin.js index fddcd127..7f8cddd2 100644 --- a/core/src/components/show-plugin.js +++ b/core/src/components/show-plugin.js @@ -759,6 +759,7 @@ class ShowPlugin extends connect(store)(LitElement) { } changePage(page) { + console.log({page}) const copiedTabs = [...this.tabs] copiedTabs[this.currentTab] = { ...copiedTabs[this.currentTab], @@ -819,7 +820,15 @@ class ShowPlugin extends connect(store)(LitElement) { if (state.app.newTab) { const newTab = state.app.newTab - if (!this.tabs.find((tab) => tab.id === newTab.id)) { + console.log('this.tabs', this.tabs) + if(newTab.openExisting && this.tabs.find((tab)=> tab.url === newTab.url)){ + const findIndex = this.tabs.findIndex((tab) => tab.url === newTab.url) + if (findIndex !== -1) { + this.currentTab = findIndex + } + + store.dispatch(setNewTab(null)) + } else if (!this.tabs.find((tab) => tab.id === newTab.id)) { this.addTab(newTab) this.currentTab = this.tabs.length - 1 store.dispatch(setNewTab(null)) diff --git a/core/src/redux/app/actions/app-core.js b/core/src/redux/app/actions/app-core.js index 7c7ab9bd..0b5f59dd 100644 --- a/core/src/redux/app/actions/app-core.js +++ b/core/src/redux/app/actions/app-core.js @@ -1,5 +1,5 @@ // 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, IS_OPEN_DEV_DIALOG } 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, SET_NEW_NOTIFICATION } from '../app-action-types.js' export const doUpdateBlockInfo = (blockObj) => { return (dispatch, getState) => { @@ -126,6 +126,12 @@ export const setNewTab = (payload) => { payload } } +export const setNewNotification = (payload) => { + return { + type: SET_NEW_NOTIFICATION, + payload + } +} export const setIsOpenDevDialog = (payload)=> { return { type: IS_OPEN_DEV_DIALOG, diff --git a/core/src/redux/app/app-action-types.js b/core/src/redux/app/app-action-types.js index d98ff2c6..eb12f9fa 100644 --- a/core/src/redux/app/app-action-types.js +++ b/core/src/redux/app/app-action-types.js @@ -30,4 +30,5 @@ export const ADD_CHAT_LAST_SEEN = 'ADD_CHAT_LAST_SEEN' export const SET_NEW_TAB = 'SET_NEW_TAB' export const ADD_TAB_INFO = 'ADD_TAB_INFO' export const SET_TAB_NOTIFICATIONS = 'SET_TAB_NOTIFICATIONS' -export const IS_OPEN_DEV_DIALOG = 'IS_OPEN_DEV_DIALOG' \ No newline at end of file +export const IS_OPEN_DEV_DIALOG = 'IS_OPEN_DEV_DIALOG' +export const SET_NEW_NOTIFICATION = 'SET_NEW_NOTIFICATION' diff --git a/core/src/redux/app/app-reducer.js b/core/src/redux/app/app-reducer.js index d8d8abd2..63a062f0 100644 --- a/core/src/redux/app/app-reducer.js +++ b/core/src/redux/app/app-reducer.js @@ -1,6 +1,6 @@ // Loading state, login state, isNavDrawOpen state etc. None of this needs to be saved to localstorage. 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, IS_OPEN_DEV_DIALOG, REMOVE_NODE, EDIT_NODE } 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, REMOVE_NODE, EDIT_NODE, SET_NEW_NOTIFICATION } from './app-action-types.js' import { initWorkersReducer } from './reducers/init-workers.js' import { loginReducer } from './reducers/login-reducer.js' import { setNode, addNode, removeNode, editNode } from './reducers/manage-node.js' @@ -50,7 +50,8 @@ const INITIAL_STATE = { chatLastSeen: [], newTab: null, tabInfo: {}, - isOpenDevDialog: false + isOpenDevDialog: false, + newNotification: null } export default (state = INITIAL_STATE, action) => { @@ -277,6 +278,13 @@ export default (state = INITIAL_STATE, action) => { } } + case SET_NEW_NOTIFICATION: { + return { + ...state, + newNotification: action.payload + } + } + default: return state } diff --git a/package-lock.json b/package-lock.json index 8d3ff292..b088ddad 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,7 @@ "@hapi/hapi": "21.3.2", "@hapi/inert": "7.1.0", "@lit-labs/motion": "1.0.4", + "@popperjs/core": "^2.11.8", "@tiptap/core": "2.0.4", "@tiptap/extension-highlight": "2.0.4", "@tiptap/extension-image": "2.0.4", diff --git a/package.json b/package.json index 6af10180..94b6eafe 100644 --- a/package.json +++ b/package.json @@ -34,6 +34,7 @@ "@hapi/hapi": "21.3.2", "@hapi/inert": "7.1.0", "@lit-labs/motion": "1.0.4", + "@popperjs/core": "^2.11.8", "@tiptap/core": "2.0.4", "@tiptap/extension-highlight": "2.0.4", "@tiptap/extension-image": "2.0.4", diff --git a/plugins/plugins/core/components/ChatGroupManager.js b/plugins/plugins/core/components/ChatGroupManager.js new file mode 100644 index 00000000..a6fc68c2 --- /dev/null +++ b/plugins/plugins/core/components/ChatGroupManager.js @@ -0,0 +1,348 @@ +import { LitElement, html, css } from 'lit'; +import { Epml } from '../../../epml'; +import '@material/mwc-button'; +import '@material/mwc-dialog'; +import '@polymer/paper-spinner/paper-spinner-lite.js'; +import '@polymer/paper-progress/paper-progress.js'; +import '@material/mwc-icon'; +import '@vaadin/button'; +import './WrapperModal'; +import './TipUser'; +import './UserInfo/UserInfo'; +import './ChatImage'; +import './ReusableImage'; +import { + get +} from 'lit-translate'; + +const parentEpml = new Epml({ type: 'WINDOW', source: window.parent }); + +class ChatGroupsManager extends LitElement { + static get properties() { + return { + leaveGroupObj: { type: Object }, + error: { type: Boolean }, + chatHeads: { type: Array }, + groupAdmin: { attribute: false }, + groupMembers: { attribute: false }, + selectedHead: { type: Object }, + toggle: { attribute: false }, + getMoreMembers: { attribute: false }, + setOpenPrivateMessage: { attribute: false }, + userName: { type: String }, + walletBalance: { type: Number }, + sendMoneyLoading: { type: Boolean }, + btnDisable: { type: Boolean }, + errorMessage: { type: String }, + successMessage: { type: String }, + setOpenTipUser: { attribute: false }, + setOpenUserInfo: { attribute: false }, + setUserName: { attribute: false }, + chatId: { type: String }, + _chatId: { type: String }, + isReceipient: { type: Boolean }, + groups: { type: Array }, + viewImage: { type: Boolean }, + autoView: {type: Boolean}, + onlyMyImages: {type: Boolean}, + repost: {attribute: false} + }; + } + + constructor() { + super(); + this.leaveGroupObj = {}; + this.leaveFee = 0.001; + this.error = false; + this.chatHeads = []; + this.groupAdmin = []; + this.groupMembers = []; + this.observerHandler = this.observerHandler.bind(this); + this.getGroups = this.getGroups.bind(this); + this.viewElement = ''; + this.downObserverElement = ''; + + this.sendMoneyLoading = false; + this.btnDisable = false; + this.errorMessage = ''; + this.successMessage = ''; + + this.groups = []; + this.viewImage = false; + this.myName = + window.parent.reduxStore.getState().app.accountInfo.names[0].name; + this.myAddress = + window.parent.reduxStore.getState().app.selectedAddress.address; + this.autoView =false + this.onlyMyImages = true + } + + static get styles() { + return css` + .top-bar-icon { + cursor: pointer; + height: 18px; + width: 18px; + transition: 0.2s all; + } + + .top-bar-icon:hover { + color: var(--black); + } + + .modal-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; + } + + .close-row { + width: 100%; + display: flex; + justify-content: flex-end; + height: 50px; + flex: 0; + align-items: center; + } + + .container-body { + width: 100%; + display: flex; + flex-direction: column; + flex-grow: 1; + overflow: auto; + margin-top: 5px; + padding: 0px 6px; + box-sizing: border-box; + } + + .container-body::-webkit-scrollbar-track { + background-color: whitesmoke; + border-radius: 7px; + } + + .container-body::-webkit-scrollbar { + width: 6px; + border-radius: 7px; + background-color: whitesmoke; + } + + .container-body::-webkit-scrollbar-thumb { + background-color: rgb(180, 176, 176); + border-radius: 7px; + transition: all 0.3s ease-in-out; + } + + .container-body::-webkit-scrollbar-thumb:hover { + background-color: rgb(148, 146, 146); + cursor: pointer; + } + + p { + color: var(--black); + margin: 0px; + padding: 0px; + word-break: break-all; + } + + .container { + display: flex; + width: 100%; + flex-direction: column; + height: 100%; + } + + .chat-right-panel-label { + font-family: Montserrat, sans-serif; + color: var(--group-header); + padding: 5px; + font-size: 13px; + user-select: none; + } + + .group-info { + display: flex; + flex-direction: column; + justify-content: flex-start; + gap: 10px; + } + + .group-name { + font-family: Raleway, sans-serif; + font-size: 20px; + color: var(--chat-bubble-msg-color); + text-align: center; + user-select: none; + } + + .group-description { + font-family: Roboto, sans-serif; + color: var(--chat-bubble-msg-color); + letter-spacing: 0.3px; + font-weight: 300; + font-size: 14px; + margin-top: 15px; + word-break: break-word; + user-select: none; + } + + .group-subheader { + font-family: Montserrat, sans-serif; + font-size: 14px; + color: var(--chat-bubble-msg-color); + } + + .group-data { + font-family: Roboto, sans-serif; + letter-spacing: 0.3px; + font-weight: 300; + font-size: 14px; + color: var(--chat-bubble-msg-color); + } + .message-myBg { + background-color: var(--chat-bubble-myBg) !important; + margin-bottom: 15px; + border-radius: 5px; + padding: 5px; + } + .message-data-name { + user-select: none; + color: #03a9f4; + margin-bottom: 5px; + } + .message-user-info { + display: flex; + justify-content: space-between; + width: 100%; + gap: 10px; + } + + .hideImg { + visibility: hidden; + } + .checkbox-row { + position: relative; + display: flex; + align-items: center; + align-content: center; + font-family: Montserrat, sans-serif; + font-weight: 600; + color: var(--black); + padding-left: 5px; + } + `; + } + + async getGroups() { + try { + + let endpoint = `/groups` + + + const groups = await parentEpml.request('apiCall', { + type: 'api', + url: endpoint, + }); + + let list = groups + + this.groups = list + } catch (error) { + console.log(error); + } + } + + firstUpdated() { + // this.viewElement = this.shadowRoot.getElementById('viewElement'); + // this.downObserverElement = + // this.shadowRoot.getElementById('downObserver'); + // this.elementObserver(); + this.getGroups() + } + + + elementObserver() { + const options = { + root: this.viewElement, + rootMargin: '0px', + threshold: 1, + }; + // identify an element to observe + const elementToObserve = this.downObserverElement; + // passing it a callback function + const observer = new IntersectionObserver( + this.observerHandler, + options + ); + // call `observe()` on that MutationObserver instance, + // passing it the element to observe, and the options object + observer.observe(elementToObserve); + } + + observerHandler(entries) { + if (!entries[0].isIntersecting) { + return; + } else { + if (this.images.length < 20) { + return; + } + this.getMoreImages(); + } + } + + selectAuto(e) { + if (e.target.checked) { + this.autoView = false + } else { + this.autoView = true + } + } + + selectMyImages(e) { + if (e.target.checked) { + this.onlyMyImages = false + } else { + this.onlyMyImages = true + } + } + + render() { + console.log('this.groups', this.groups) + return html` + +
    +
    + { + this.getGroups() + }} style="color: var(--black); cursor:pointer;">refresh +
    +
    + + this.selectAuto(e)} ?checked=${this.autoView}> +
    +
    + + this.selectMyImages(e)} ?checked=${this.onlyMyImages}> +
    +
    + + +
    +
    +
    +
    + `; + } +} + +customElements.define('chat-groups-manager', ChatGroupsManager); + diff --git a/plugins/plugins/core/components/ChatGroupsModal.js b/plugins/plugins/core/components/ChatGroupsModal.js new file mode 100644 index 00000000..f6a299d0 --- /dev/null +++ b/plugins/plugins/core/components/ChatGroupsModal.js @@ -0,0 +1,98 @@ +import { LitElement, html, css } from 'lit'; +import { + translate, +} from 'lit-translate'; +import '@material/mwc-menu'; +import '@material/mwc-list/mwc-list-item.js'; +import '@material/mwc-dialog' +import './ChatGroupManager' + +export class ChatGroupsModal extends LitElement { + static get properties() { + return { + openDialogGroupsModal: { type: Boolean }, + setOpenDialogGroupsModal: { attribute: false} + }; + } + + static get styles() { + return css` + * { + --mdc-theme-text-primary-on-background: var(--black); + --mdc-dialog-max-width: 85vw; + --mdc-dialog-max-height: 95vh; + } + + .imageContainer { + display: flex; + justify-content: center; + align-items: center; + height: 100%; + } + + @-webkit-keyframes loadingAnimation { + 0% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); + } + 100% { + -webkit-transform: rotate(360deg); + transform: rotate(360deg); + } + } + + @keyframes loadingAnimation { + 0% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); + } + 100% { + -webkit-transform: rotate(360deg); + transform: rotate(360deg); + } + } + `; + } + + constructor() { + super(); + this.openDialogGroupsModal = false + + } + + + + firstUpdated() { + } + + + render() { + console.log('hello') + return html` + + { + this.setOpenDialogGroupsModal(false) + }}> +
    +
    + +
    + { + this.setOpenDialogGroupsModal(false) + }} + > + ${translate('general.close')} + +
    + `; + } +} + +customElements.define('chat-groups-modal', ChatGroupsModal); diff --git a/plugins/plugins/core/components/ChatPage.js b/plugins/plugins/core/components/ChatPage.js index ccc3b70c..351e5a1f 100644 --- a/plugins/plugins/core/components/ChatPage.js +++ b/plugins/plugins/core/components/ChatPage.js @@ -1095,6 +1095,7 @@ class ChatPage extends LitElement { const blobFound = handleTransferIntoURL(event.clipboardData) if (blobFound) { this.insertFile(blobFound) + e.preventDefault(); return } else { const item_list = await navigator.clipboard.read() @@ -1114,6 +1115,7 @@ class ChatPage extends LitElement { type: image_type }) this.insertFile(file) + e.preventDefault(); } catch (error) { console.error(error) let errorMsg = get("chatpage.cchange81") diff --git a/plugins/plugins/core/group-management/group-management.src.js b/plugins/plugins/core/group-management/group-management.src.js index bd35cc58..56e4e7b7 100644 --- a/plugins/plugins/core/group-management/group-management.src.js +++ b/plugins/plugins/core/group-management/group-management.src.js @@ -2849,6 +2849,17 @@ class GroupManagement extends LitElement { } } + setTxNotification(tx){ + window.parent.reduxStore.dispatch( + window.parent.reduxAction.setNewNotification({ + type: 'JOIN_GROUP', + status: 'confirming', + reference: tx, + timestamp: Date.now() + }) + ); + } + async _joinGroup(groupId, groupName) { this.resetDefaultSettings() const joinFeeInput = this.joinFee @@ -2885,7 +2896,8 @@ class GroupManagement extends LitElement { lastReference: lastRef, groupdialog1: groupdialog1, groupdialog2: groupdialog2 - } + }, + apiVersion: 2 }) return myTxnrequest } @@ -2897,6 +2909,12 @@ class GroupManagement extends LitElement { throw new Error(txnResponse) } else if (txnResponse.success === true && !txnResponse.data.error) { this.message = this.renderErr8Text() + this.setTxNotification({ + groupName, + groupId, + timestamp: Date.now(), + ...(txnResponse.data || {}) + }) this.error = false } else { this.error = true diff --git a/plugins/plugins/core/messaging/q-chat/q-chat-css.src.js b/plugins/plugins/core/messaging/q-chat/q-chat-css.src.js index ef1ff3e1..80b11a08 100644 --- a/plugins/plugins/core/messaging/q-chat/q-chat-css.src.js +++ b/plugins/plugins/core/messaging/q-chat/q-chat-css.src.js @@ -160,6 +160,7 @@ export const qchatStyles = css` display: flex; align-items: center; gap: 10px; + justify-content: space-between; } .center { diff --git a/plugins/plugins/core/messaging/q-chat/q-chat.src.js b/plugins/plugins/core/messaging/q-chat/q-chat.src.js index 15ce69d4..f8bcf947 100644 --- a/plugins/plugins/core/messaging/q-chat/q-chat.src.js +++ b/plugins/plugins/core/messaging/q-chat/q-chat.src.js @@ -12,19 +12,22 @@ import Underline from '@tiptap/extension-underline'; import Placeholder from '@tiptap/extension-placeholder' import Highlight from '@tiptap/extension-highlight' import snackbar from '../../components/snackbar.js' +import ShortUniqueId from 'short-unique-id'; import '../../components/ChatWelcomePage.js' import '../../components/ChatHead.js' import '../../components/ChatPage.js' import '../../components/WrapperModal.js' import '../../components/ChatSearchResults.js' - +import '../../components/ChatGroupsModal.js' import '@material/mwc-button' import '@material/mwc-dialog' import '@material/mwc-icon' import '@material/mwc-snackbar' import '@polymer/paper-spinner/paper-spinner-lite.js' import '@vaadin/grid' +import '@vaadin/tooltip'; + passiveSupport({ events: ['touchstart'] }) @@ -55,6 +58,7 @@ class Chat extends LitElement { groupInvites: { type: Array }, loggedInUserName: {type: String}, loggedInUserAddress: {type: String}, + openDialogGroupsModal: {type: Boolean} } } @@ -96,6 +100,9 @@ class Chat extends LitElement { this.userSelected = {} this.groupInvites = [] this.loggedInUserName = "" + this.openDialogGroupsModal = false + this.uid = new ShortUniqueId(); + } async setActiveChatHeadUrl(url) { @@ -188,34 +195,74 @@ class Chat extends LitElement { }) } + setOpenDialogGroupsModal(val){ + this.openDialogGroupsModal = val + } + + openTabToGroupManagement(){ + window.parent.reduxStore.dispatch( + window.parent.reduxAction.setNewTab({ + url: `group-management`, + id: this.uid.rnd(), + myPlugObj: { + "url": "group-management", + "domain": "core", + "page": "group-management/index.html", + "title": "Group Management", + "icon": "vaadin:group", + "mwcicon": "group", + "pluginNumber": "plugin-fJZNpyLGTl", + "menus": [], + "parent": false + }, + openExisting: true + }) + ); + } + render() { return html`