4
1
mirror of https://github.com/Qortal/qortal-ui.git synced 2025-02-11 17:55:51 +00:00

Changed chat menu

This commit is contained in:
Justin Ferrari 2022-09-11 12:12:28 -05:00
parent 47b490a943
commit e22ff8cba0
7 changed files with 992 additions and 109 deletions

3
.gitignore vendored
View File

@ -4,9 +4,8 @@ yarn.lock
# Derived js files
qortal-ui-plugins/plugins/core/**/*.js
!*.src.js
qortal-ui-plugins/plugins/core/components/*.js
!*.js
qortal-ui-core/src/redux/app/version.js
!qortal-ui-plugins/plugins/core/components/*.js
# Node modules
node_modules/

View File

@ -0,0 +1,407 @@
import { LitElement, html, css} from 'lit-element';
import { get, translate } from 'lit-translate';
import { Epml } from '../../../epml';
import snackbar from './snackbar.js'
import '@material/mwc-button';
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 },
}
}
constructor() {
super();
this.isLoading = false;
this.hidePrivateMessageModal = () => {};
this.hideBlockUserModal = () => {};
this.chatBlockedAdresses = []
}
static get styles() {
return css`
.input {
width: 90%;
border: none;
display: inline-block;
font-size: 16px;
padding: 10px 20px;
border-radius: 5px;
resize: none;
background: #eee;
}
.textarea {
width: 90%;
border: none;
display: inline-block;
font-size: 16px;
padding: 10px 20px;
border-radius: 5px;
height: 120px;
resize: none;
background: #eee;
}
.close-button {
display:block;
--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);
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()
}
// Send Private Message
_sendMessage() {
this.isLoading = true
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()
}
}
async sendMessage() {
this.isLoading = true
const _recipient = this.shadowRoot.getElementById('sendTo').value
const messageBox = this.shadowRoot.getElementById('messageBox')
const messageText = messageBox.value
let recipient
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 myNameRes = await validateName(_recipient)
if (!myNameRes) {
recipient = _recipient
} else {
recipient = myNameRes.owner
}
let _reference = new Uint8Array(64);
window.crypto.getRandomValues(_reference);
let sendTimestamp = Date.now()
let reference = window.parent.Base58.encode(_reference)
const getAddressPublicKey = async () => {
let isEncrypted
let _publicKey
let addressPublicKey = await parentEpml.request('apiCall', {
type: 'api',
url: `/addresses/publickey/${recipient}`
})
if (addressPublicKey.error === 102) {
_publicKey = false
// Do something here...
let err1string = get('welcomepage.wcchange7')
parentEpml.request('showSnackBar', `${err1string}`)
this.isLoading = false
} else if (addressPublicKey !== false) {
isEncrypted = 1
_publicKey = addressPublicKey
sendMessageRequest(isEncrypted, _publicKey)
} else {
isEncrypted = 0
_publicKey = this.selectedAddress.address
sendMessageRequest(isEncrypted, _publicKey)
}
};
const sendMessageRequest = async (isEncrypted, _publicKey) => {
let chatResponse = await parentEpml.request('chat', {
type: 18,
nonce: this.selectedAddress.nonce,
params: {
timestamp: sendTimestamp,
recipient: recipient,
recipientPublicKey: _publicKey,
message: messageText,
lastReference: reference,
proofOfWorkNonce: 0,
isEncrypted: isEncrypted,
isText: 1
}
})
_computePow(chatResponse)
}
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 difficulty = this.balance === 0 ? 12 : 8;
const workBufferLength = 8 * 1024 * 1024;
const workBufferPtr = window.parent.sbrk(workBufferLength, window.parent.heap);
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)
}
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()
}
}
getAddressPublicKey()
}
_textArea(e) {
if (e.keyCode === 13 && !e.shiftKey) this._sendMessage()
}
getApiKey() {
const myNode = window.parent.reduxStore.getState().app.nodeConfig.knownNodes[window.parent.reduxStore.getState().app.nodeConfig.node];
let apiKey = myNode.apiKey;
return apiKey;
}
getChatBlockedList() {
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 blockedAddressesUrl = `${nodeUrl}/lists/blockedAddresses?apiKey=${this.getApiKey()}`
const err3string = 'No regitered name'
localStorage.removeItem("ChatBlockedAddresses")
var obj = [];
fetch(blockedAddressesUrl).then(response => {
return response.json()
}).then(data => {
return data.map(item => {
const noName = {
name: err3string,
owner: item
}
fetch(`${nodeUrl}/names/address/${item}?limit=0&reverse=true`).then(res => {
return res.json()
}).then(jsonRes => {
if(jsonRes.length) {
jsonRes.map (item => {
obj.push(item)
})
} else {
obj.push(noName)
}
localStorage.setItem("ChatBlockedAddresses", JSON.stringify(obj))
})
})
})
}
relMessages() {
setTimeout(() => {
window.location.href = window.location.href.split( '#' )[0]
}, 500)
}
async getChatBlockedAdresses() {
const chatBlockedAdresses = await parentEpml.request('apiCall', {
url: `/lists/blockedAddresses?apiKey=${this.getApiKey()}`
})
this.chatBlockedAdresses = chatBlockedAdresses
}
// Chat Block Address
async chatBlockAddress() {
let address = this.toblockaddress
let items = [
address
]
let addressJsonString = JSON.stringify({ "items": items })
let ret = await parentEpml.request('apiCall', {
url: `/lists/blockedAddresses?apiKey=${this.getApiKey()}`,
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: `${addressJsonString}`
})
if (ret === true) {
this.chatBlockedAdresses = this.chatBlockedAdresses.filter(item => item != address)
this.chatBlockedAdresses.push(address)
this.getChatBlockedList()
this.hideBlockUserModal()
let err1string = get("blockpage.bcchange2")
snackbar.add({
labelText: `${err1string}`,
dismiss: true
})
this.relMessages()
} else {
this.hideBlockUserModal()
let err2string = get("blockpage.bcchange2")
snackbar.add({
labelText: `${err2string}`,
dismiss: true
})
}
console.log({ret})
return ret
}
render() {
return html`
<mwc-dialog
id='sendPMDialog'
tabindex='0'
?open=${this.openDialogPrivateMessage}
scrimClickAction='${this.isLoading ? '' : 'close'}'
escapeKeyAction='close'
defaultAction='close'
@blur=${() => this.hidePrivateMessageModal()}
>
<div style='text-align:center'>
<h1>${translate('welcomepage.wcchange2')}</h1>
<hr>
</div>
<p>${translate('welcomepage.wcchange3')}</p>
<textarea class='input' ?disabled=${this.isLoading} id='sendTo' rows='1'>${this.nametodialog}</textarea>
<p style='margin-bottom:0;'>
<textarea class='textarea' @keydown=${(e) => this._textArea(e)} ?disabled=${this.isLoading} id='messageBox' placeholder='${translate('welcomepage.wcchange5')}' rows='1'></textarea>
</p>
<mwc-button ?disabled='${this.isLoading}' slot='primaryAction' @click=${this._sendMessage}>${translate('welcomepage.wcchange6')}
</mwc-button>
<mwc-button
?disabled='${this.isLoading}'
slot='secondaryAction'
@click='${() => this.hidePrivateMessageModal()}'
class='close-button'
>
${translate('general.close')}
</mwc-button>
</mwc-dialog>
<mwc-dialog
id='blockNameDialog'
tabindex='0'
?open=${this.openDialogBlockUser}
escapeKeyAction='close'
defaultAction='close'
@blur=${() => this.hideBlockUserModal()}
>
<div style='text-align:center'>
<h1>${translate('blockpage.bcchange5')}</h1>
<hr>
<h2>${translate('blockpage.bcchange6')}</h2><br>
<h2>${this.nametodialog}</h2>
</div>
<mwc-button
slot='secondaryAction'
@click='${() => this.chatBlockAddress()}'
class='block'
>
${translate('general.yes')}
</mwc-button>
<mwc-button
slot='primaryAction'
@click='${() => this.hideBlockUserModal()}'
class='close-button'
>
${translate('general.no')}
</mwc-button>
</mwc-dialog>
`;
}
}
customElements.define('chat-modals', ChatModals);

View File

@ -4,7 +4,7 @@ import { Epml } from '../../../epml.js'
import { use, get, translate, translateUnsafeHTML, registerTranslateConfig } from 'lit-translate'
registerTranslateConfig({
loader: lang => fetch(`/language/${lang}.json`).then(res => res.json())
loader: lang => fetch(`/language/${lang}.json`).then(res => res.json())
})
import { escape, unescape } from 'html-escaper';
@ -44,7 +44,8 @@ class ChatPage extends LitElement {
isPasteMenuOpen: { type: Boolean },
showNewMesssageBar: { attribute: false },
hideNewMesssageBar: { attribute: false },
chatEditorPlaceholder: { type: String }
chatEditorPlaceholder: { type: String },
messagesRendered: { type: Array },
}
}
@ -133,6 +134,7 @@ class ChatPage extends LitElement {
this.isUserDown = false
this.isPasteMenuOpen = false
this.chatEditorPlaceholder = this.renderPlaceholder()
this.messagesRendered = []
}
render() {
@ -153,7 +155,7 @@ class ChatPage extends LitElement {
firstUpdated() {
// TODO: Load and fetch messages from localstorage (maybe save messages to localstorage...)
this.changeLanguage();
// this.changeLanguage();
this.emojiPickerHandler = this.shadowRoot.querySelector('.emoji-button');
this.mirrorChatInput = this.shadowRoot.getElementById('messageBox');
this.chatMessageInput = this.shadowRoot.getElementById('_chatEditorDOM');
@ -169,7 +171,7 @@ class ChatPage extends LitElement {
} else {
return this.chatEditor.focus();
}
};
}
});
// Init EmojiPicker
@ -279,19 +281,32 @@ class ChatPage extends LitElement {
}
renderChatScroller(initialMessages) {
return html`<chat-scroller .initialMessages=${initialMessages} .emojiPicker=${this.emojiPicker} .escapeHTML=${escape} .getOldMessage=${this.getOldMessage} > </chat-scroller>`
return html`<chat-scroller .initialMessages=${initialMessages} .messages=${this.messagesRendered} .emojiPicker=${this.emojiPicker} .escapeHTML=${escape} .getOldMessage=${this.getOldMessage} > </chat-scroller>`
}
getOldMessage(scrollElement) {
async getUpdateComplete() {
await super.getUpdateComplete();
const marginElements = Array.from(this.shadowRoot.querySelectorAll('chat-scroller'));
await Promise.all(marginElements.map(el => el.updateComplete));
return true;
}
async getOldMessage(scrollElement) {
if (this._messages.length <= 15 && this._messages.length >= 1) { // 15 is the default number of messages...
let __msg = [...this._messages]
this._messages = []
this.messagesRendered = [...__msg, ...this.messagesRendered]
await this.getUpdateComplete();
scrollElement.scrollIntoView({ behavior: 'auto', block: 'center' });
return { oldMessages: __msg, scrollElement: scrollElement }
} else if (this._messages.length > 15) {
this.messagesRendered = [...this._messages.splice(this._messages.length - 15), ...this.messagesRendered]
await this.getUpdateComplete();
scrollElement.scrollIntoView({ behavior: 'auto', block: 'center' });
return { oldMessages: this._messages.splice(this._messages.length - 15), scrollElement: scrollElement }
} else {
@ -299,8 +314,8 @@ class ChatPage extends LitElement {
}
}
processMessages(messages, isInitial) {
async processMessages(messages, isInitial) {
console.log({ messages })
if (isInitial) {
this.messages = messages.map((eachMessage) => {
@ -327,7 +342,19 @@ class ChatPage extends LitElement {
// TODO: Determine number of initial messages by screen height...
this._messages.length <= 15 ? adjustMessages() : this._initialMessages = this._messages.splice(this._messages.length - 15);
this.messagesRendered = this._initialMessages
// try {
// const viewElement = this.shadowRoot.querySelector('chat-scroller')
// console.log({viewElement})
// // viewElement.scrollTop = this.viewElement.scrollHeight + 50
// } catch (error) {
// console.error(error)
// }
this.isLoadingMessages = false
setTimeout(() => this.downElementObserver(), 500)
} else {
@ -354,6 +381,7 @@ class ChatPage extends LitElement {
})
this.newMessages = this.newMessages.concat(_newMessages)
}
}
@ -387,7 +415,7 @@ class ChatPage extends LitElement {
nameMenu = `<name-menu toblockaddress="${messageObj.sender}" nametodialog="${messageObj.senderName ? messageObj.senderName : messageObj.sender}"></name-menu>`
}
if ( hideit === true ) {
if (hideit === true) {
return `
<li class="clearfix"></li>
`
@ -406,7 +434,7 @@ class ChatPage extends LitElement {
}
}
renderNewMessage(newMessage) {
async renderNewMessage(newMessage) {
const viewElement = this.shadowRoot.querySelector('chat-scroller').shadowRoot.getElementById('viewElement');
const downObserver = this.shadowRoot.querySelector('chat-scroller').shadowRoot.getElementById('downObserver');
@ -416,16 +444,22 @@ class ChatPage extends LitElement {
if (newMessage.sender === this.selectedAddress.address) {
viewElement.insertBefore(li, downObserver);
this.messagesRendered = [...this.messagesRendered, newMessage]
await this.getUpdateComplete();
viewElement.scrollTop = viewElement.scrollHeight;
} else if (this.isUserDown) {
// Append the message and scroll to the bottom if user is down the page
viewElement.insertBefore(li, downObserver);
this.messagesRendered = [...this.messagesRendered, newMessage]
await this.getUpdateComplete();
viewElement.scrollTop = viewElement.scrollHeight;
} else {
viewElement.insertBefore(li, downObserver);
this.messagesRendered = [...this.messagesRendered, newMessage]
await this.getUpdateComplete();
this.showNewMesssageBar();
}
}
@ -843,7 +877,7 @@ class ChatPage extends LitElement {
vertical-align: bottom;
}
`;
editor.content.head.appendChild(editor.styles);
editor.content.head.appendChild(editor.styles);
};
ChatEditor.prototype.enable = function () {
@ -1030,7 +1064,7 @@ class ChatPage extends LitElement {
function doInit() {
return new ChatEditor();
};
}
return doInit();
};

View File

@ -1,16 +1,18 @@
import { LitElement, html, css } from 'lit'
import { render } from 'lit/html.js'
import { Epml } from '../../../epml.js'
import './LevelFounder.js'
import './NameMenu.js'
import '@material/mwc-button'
import '@material/mwc-dialog'
import '@material/mwc-icon'
import { LitElement, html, css } from 'lit';
import { render } from 'lit/html.js';
import { repeat } from 'lit/directives/repeat.js';
import { translate, get } from 'lit-translate';
import { Epml } from "../../../epml";
import './LevelFounder.js';
import './NameMenu.js';
import './ChatModals.js';
import '@vaadin/icons';
import '@vaadin/icon';
import '@material/mwc-button';
import '@material/mwc-dialog';
import '@material/mwc-icon';
const parentEpml = new Epml({ type: 'WINDOW', source: window.parent })
class ChatScroller extends LitElement {
static get properties() {
return {
@ -63,8 +65,29 @@ class ChatScroller extends LitElement {
padding: 20px;
}
.last-message-ref {
position: fixed;
font-size: 20px;
right: 40px;
bottom: 100px;
width: 50;
height: 50;
z-index: 5;
opacity: 0;
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);
}
.chat-list {
overflow-y: auto;
overflow-x: hidden;
height: 92vh;
box-sizing: border-box;
}
@ -77,7 +100,6 @@ class ChatScroller extends LitElement {
.message-data-name {
color: var(--black);
cursor: pointer;
}
.message-data-time {
@ -190,7 +212,7 @@ class ChatScroller extends LitElement {
super()
this.messages = []
this._upObserverhandler = this._upObserverhandler.bind(this)
this.isLoading = false
this._downObserverHandler = this._downObserverHandler.bind(this)
this.myAddress = window.parent.reduxStore.getState().app.selectedAddress.address
this.hideMessages = JSON.parse(localStorage.getItem("MessageBlockedAddresses") || "[]")
}
@ -200,103 +222,61 @@ class ChatScroller extends LitElement {
return html`
<ul id="viewElement" class="chat-list clearfix">
<div id="upObserver"></div>
<div id="downObserver"></div>
${repeat(
this.messages,
(message) => message.reference,
(message) => html`<message-template .emojiPicker=${this.emojiPicker} .escapeHTML=${this.escapeHTML} .messageObj=${message} .hideMessages=${this.hideMessages}></message-template>`
)}
<div id='downObserver'></div>
<div class='last-message-ref'>
<vaadin-icon icon='vaadin:arrow-circle-down' slot='icon' @click=${() => {
console.log("yo500")
this.shadowRoot.getElementById('downObserver').scrollIntoView({
behavior: 'smooth',
})
}}></vaadin-icon>
</div>
</ul>
`
}
firstUpdated() {
async firstUpdated() {
this.viewElement = this.shadowRoot.getElementById('viewElement')
this.upObserverElement = this.shadowRoot.getElementById('upObserver')
this.downObserverElement = this.shadowRoot.getElementById('downObserver')
this.renderChatMessages(this.initialMessages)
// Intialize Observers
this.upElementObserver()
this.downElementObserver()
await this.updateComplete
this.viewElement.scrollTop = this.viewElement.scrollHeight + 50
}
chatMessageTemplate(messageObj) {
const hidemsg = this.hideMessages
let avatarImg = ''
let nameMenu = ''
let levelFounder = ''
let hideit = hidemsg.includes(messageObj.sender)
levelFounder = `<level-founder checkleveladdress="${messageObj.sender}"></level-founder>`
if (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/${messageObj.senderName}/qortal_avatar?async=true&apiKey=${myNode.apiKey}`
avatarImg = `<img src="${avatarUrl}" style="max-width:100%; max-height:100%;" onerror="this.onerror=null; this.src='/img/incognito.png';" />`
}
if (messageObj.sender === this.myAddress) {
nameMenu = `<span style="color: #03a9f4;">${messageObj.senderName ? messageObj.senderName : messageObj.sender}</span>`
} else {
nameMenu = `<name-menu toblockaddress="${messageObj.sender}" nametodialog="${messageObj.senderName ? messageObj.senderName : messageObj.sender}"></name-menu>`
}
if ( hideit === true ) {
return `
<li class="clearfix"></li>
`
} else {
return `
<li class="clearfix">
<div class="message-data ${messageObj.sender === this.myAddress ? "" : ""}">
<span class="message-data-name">${nameMenu}</span>
<span class="message-data-level">${levelFounder}</span>
<span class="message-data-time"><message-time timestamp=${messageObj.timestamp}></message-time></span>
</div>
<div class="message-data-avatar" style="width:42px; height:42px; ${messageObj.sender === this.myAddress ? "float:left;" : "float:left;"} margin:3px;">${avatarImg}</div>
<div id="messageContent" class="message ${messageObj.sender === this.myAddress ? "my-message float-left" : "other-message float-left"}">${this.emojiPicker.parse(this.escapeHTML(messageObj.decodedMessage))}</div>
</li>
`
}
}
renderChatMessages(messages) {
messages.forEach(message => {
const li = document.createElement('li');
li.innerHTML = this.chatMessageTemplate(message);
li.id = message.signature;
this.downObserverElement.before(li);
});
}
renderOldMessages(listOfOldMessages) {
let { oldMessages, scrollElement } = listOfOldMessages;
let _oldMessages = oldMessages.reverse();
_oldMessages.forEach(oldMessage => {
const li = document.createElement('li');
li.innerHTML = this.chatMessageTemplate(oldMessage);
li.id = oldMessage.signature;
this.upObserverElement.after(li);
scrollElement.scrollIntoView({ behavior: 'auto', block: 'center' });
});
}
_getOldMessage(_scrollElement) {
let listOfOldMessages = this.getOldMessage(_scrollElement)
this.getOldMessage(_scrollElement)
if (listOfOldMessages) {
this.renderOldMessages(listOfOldMessages)
}
}
_upObserverhandler(entries) {
if (entries[0].isIntersecting) {
let _scrollElement = entries[0].target.nextElementSibling
console.log({ _scrollElement })
this._getOldMessage(_scrollElement)
}
}
_downObserverHandler(entries) {
if (!entries[0].isIntersecting) {
this.shadowRoot.querySelector(".last-message-ref").style.opacity = '1'
} else {
this.shadowRoot.querySelector(".last-message-ref").style.opacity = '0'
}
}
upElementObserver() {
const options = {
root: this.viewElement,
@ -307,6 +287,475 @@ class ChatScroller extends LitElement {
const observer = new IntersectionObserver(this._upObserverhandler, options)
observer.observe(this.upObserverElement)
}
downElementObserver() {
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._downObserverHandler, 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)
class MessageTemplate extends LitElement {
static get properties() {
return {
messageObj: { type: Object },
hideMessages: { type: Array },
openDialogPrivateMessage: {type: Boolean},
openDialogBlockUser: {type: Boolean},
showBlockAddressIcon: { type: Boolean },
};
}
constructor() {
super();
this.messageObj = {}
this.openDialogPrivateMessage = false
this.openDialogBlockUser = false
this.showBlockAddressIcon = false
this.myAddress = window.parent.reduxStore.getState().app.selectedAddress.address
}
static get styles() {
return css`
html {
--scrollbarBG: #a1a1a1;
--thumbBG: #6a6c75;
}
*::-webkit-scrollbar {
width: 11px;
}
* {
scrollbar-width: thin;
scrollbar-color: var(--thumbBG) var(--scrollbarBG);
--mdc-theme-primary: rgb(3, 169, 244);
--mdc-theme-secondary: var(--mdc-theme-primary);
}
*::-webkit-scrollbar-track {
background: var(--scrollbarBG);
}
*::-webkit-scrollbar-thumb {
background-color: var(--thumbBG);
border-radius: 6px;
border: 3px solid var(--scrollbarBG);
}
a {
color: var(--black);
text-decoration: none;
}
ul {
list-style: none;
margin: 0;
padding: 20px;
}
.chat-list {
overflow-y: auto;
overflow-x: hidden;
height: 92vh;
box-sizing: border-box;
}
.message-data {
width: 92%;
margin-bottom: 15px;
margin-left: 50px;
}
.message-data-name {
color: var(--black);
}
.message-data-time {
color: #a8aab1;
font-size: 13px;
padding-left: 6px;
padding-bottom: 4px;
}
.message-data-level {
color: #03a9f4;
font-size: 13px;
padding-left: 8px;
padding-bottom: 4px;
}
.message-container {
position: relative;
}
.message {
color: black;
padding: 12px 10px;
line-height: 19px;
white-space: pre-line;
word-wrap: break-word;
-webkit-user-select: text;
-moz-user-select: text;
-ms-user-select: text;
user-select: text;
font-size: 16px;
border-radius: 7px;
margin-bottom: 20px;
width: 90%;
position: relative;
}
.message:after {
bottom: 100%;
left: 93%;
border: solid transparent;
content: " ";
height: 0;
width: 0;
position: absolute;
white-space: pre-line;
word-wrap: break-word;
pointer-events: none;
border-bottom-color: #ddd;
border-width: 10px;
margin-left: -10px;
}
.message-parent:hover .chat-hover {
display: block;
}
.message-parent:hover .message{
filter:brightness(0.90);
}
.chat-hover {
display: none;
position: absolute;
top: -32px;
left: 86%;
}
.emoji {
width: 1.7em;
height: 1.5em;
margin-bottom: -2px;
vertical-align: bottom;
object-fit: contain;
}
.my-message {
background: #d1d1d1;
border: 2px solid #eeeeee;
}
.my-message:after {
border-bottom-color: #d1d1d1;
left: 7%;
}
.other-message {
background: #f1f1f1;
border: 2px solid #dedede;
}
.other-message:after {
border-bottom-color: #f1f1f1;
left: 7%;
}
.align-left {
text-align: left;
}
.align-right {
text-align: right;
}
.float-left {
float: left;
}
.float-right {
float: right;
}
.clearfix:after {
visibility: hidden;
display: block;
font-size: 0;
content: " ";
clear: both;
height: 0;
}
img {
border-radius: 25%;
}
`
}
// 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) {
this.shadowRoot.querySelector(".chat-hover").focus({ preventScroll: true })
if(bool) {
this.showBlockAddressIcon = true;
} else {
this.showBlockAddressIcon = false;
}
}
render() {
console.log(this.showBlockAddressIcon)
const hidemsg = this.hideMessages
let avatarImg = ''
let nameMenu = ''
let levelFounder = ''
let hideit = hidemsg.includes(this.messageObj.sender)
levelFounder = html`<level-founder checkleveladdress="${this.messageObj.sender}"></level-founder>`
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`<img src="${avatarUrl}" style="max-width:100%; max-height:100%;" onerror="this.onerror=null; this.src='/img/incognito.png';" />`
}
if (this.messageObj.sender === this.myAddress) {
nameMenu = html`<span style="color: #03a9f4;">${this.messageObj.senderName ? this.messageObj.senderName : this.messageObj.sender}</span>`
} else {
nameMenu = html`<span>${this.messageObj.senderName ? this.messageObj.senderName : this.messageObj.sender}</span>`
}
return hideit ? html`<li class="clearfix"></li>` : html`
<li class="clearfix message-parent">
<div class="message-data ${this.messageObj.sender === this.myAddress ? "" : ""}">
<span class="message-data-name">${nameMenu}</span>
<span class="message-data-level">${levelFounder}</span>
<span class="message-data-time"><message-time timestamp=${this.messageObj.timestamp}></message-time></span>
</div>
<div class="message-data-avatar" style="width:42px; height:42px; ${this.messageObj.sender === this.myAddress ? "float:left;" : "float:left;"} margin:3px;">${avatarImg}</div>
<div class="message-container">
<div id="messageContent" class="message ${this.messageObj.sender === this.myAddress ? "my-message float-left" : "other-message float-left"}">${this.emojiPicker.parse(this.escapeHTML(this.messageObj.decodedMessage))}</div>
<chat-menu
tabindex="0"
class="chat-hover"
style=${this.showBlockAddressIcon && "display: block"}
toblockaddress="${this.messageObj.sender}"
.showPrivateMessageModal=${() => this.showPrivateMessageModal()}
.showBlockUserModal=${() => this.showBlockUserModal()}
.showBlockIconFunc=${(props) => this.showBlockIconFunc(props)}
.showBlockAddressIcon=${this.showBlockAddressIcon}
@blur=${() => this.showBlockIconFunc(false)}
>
</chat-menu>
</div>
</li>
<chat-modals
.openDialogPrivateMessage=${this.openDialogPrivateMessage}
.openDialogBlockUser=${this.openDialogBlockUser}
nametodialog="${this.messageObj.senderName ? this.messageObj.senderName : this.messageObj.sender}"
.hidePrivateMessageModal=${() => this.hidePrivateMessageModal()}
.hideBlockUserModal=${() => this.hideBlockUserModal()}
toblockaddress=${this.messageObj.sender}
>
</chat-modals>
`;
}
}
window.customElements.define('message-template', MessageTemplate);
class ChatMenu extends LitElement {
static get properties() {
return {
menuItems: { type: Array },
selectedAddress: { type: Object },
showPrivateMessageModal: {type: Function},
showBlockUserModal: {type: Function},
toblockaddress: { type: String, attribute: true },
showBlockIconFunc: {type: Function},
showBlockAddressIcon: {type: Boolean}
};
}
constructor() {
super();
this.selectedAddress = window.parent.reduxStore.getState().app.selectedAddress.address;
this.showPrivateMessageModal = () => {};
this.showBlockUserModal = () => {};
}
static get styles() {
return css`
.container {
display: flex;
flex-direction: row;
align-items: center;
gap: 5px;
background-color: white;
border: 1px solid #dad9d9;
border-radius: 5px;
height:100%;
width: 100px;
position: relative;
}
.menu-icon {
width: 100%;
padding: 5px;
display: flex;
align-items: center;
font-size: 13px;
}
.menu-icon:hover {
background-color: #dad9d9;
transition: all 0.1s ease-in-out;
cursor: pointer;
}
.tooltip {
position: relative;
}
.tooltip:before {
content: attr(data-text);
position: absolute;
top: -47px;
left: 50%;
transform: translateX(-50%);
width: 90px;
padding: 10px;
border-radius: 10px;
background:#fff;
color: #000;
text-align: center;
box-shadow: rgba(149, 157, 165, 0.2) 0px 8px 24px;
font-size: 12px;
z-index: 5;
display: none;
}
.tooltip:hover:before {
display: block;
}
.tooltip:after {
content: "";
position: absolute;
margin-top: -7px;
top: -7px;
border: 10px solid #fff;
border-color: white transparent transparent transparent;
z-index: 5;
display: none;
}
.tooltip:hover:before, .tooltip:hover:after {
display: block;
}
.block-user-container {
display: block;
position: absolute;
left: -48px;
}
.block-user {
justify-content: space-between;
border: 1px solid rgb(218, 217, 217);
border-radius: 5px;
background-color: white;
width: 100%;
height: 32px;
padding: 3px 8px;
box-shadow: rgba(77, 77, 82, 0.2) 0px 7px 29px 0px;
}
`
}
// Copy address to clipboard
async copyToClipboard(text) {
try {
let copyString1 = get("walletpage.wchange4")
await navigator.clipboard.writeText(text)
parentEpml.request('showSnackBar', `${copyString1}`)
} catch (err) {
let copyString2 = get("walletpage.wchange39")
parentEpml.request('showSnackBar', `${copyString2}`)
console.error('Copy to clipboard error:', err)
}
}
render() {
return html`
<div class="container" style=${this.showBlockAddressIcon && "width: 70px" }>
<div class="menu-icon tooltip" data-text="Private Message"
@click="${() => this.showPrivateMessageModal()}">
<vaadin-icon icon="vaadin:paperplane" slot="icon"></vaadin-icon>
</div>
<div class="menu-icon tooltip" data-text="Copy Address" @click="${() => this.copyToClipboard(this.toblockaddress)}">
<vaadin-icon icon="vaadin:copy" slot="icon"></vaadin-icon>
</div>
<div class="menu-icon tooltip" data-text="More" @click="${() => this.showBlockIconFunc(true)}">
<vaadin-icon icon="vaadin:ellipsis-dots-h" slot="icon"></vaadin-icon>
</div>
${this.showBlockAddressIcon ? html`
<div class="block-user-container">
<div class="menu-icon block-user" @click="${() => this.showBlockUserModal()}">
<p>${translate("blockpage.bcchange1")}</p>
<vaadin-icon icon="vaadin:close-circle" slot="icon"></vaadin-icon>
</div>
</div>
` : html`
<div></div>
`}
</div>
`
}
}
window.customElements.define('chat-menu', ChatMenu);

View File

@ -245,9 +245,7 @@ class ChatWelcomePage extends LitElement {
}
firstUpdated() {
this.changeTheme()
this.changeLanguage()
const stopKeyEventPropagation = (e) => {
e.stopPropagation();

View File

@ -95,8 +95,6 @@ class LevelFounder extends LitElement {
}
firstUpdated() {
this.changeLanguage()
this.checkAddressInfo()
window.addEventListener('storage', () => {

View File

@ -246,8 +246,6 @@ class NameMenu extends LitElement {
}
firstUpdated() {
this.changeLanguage()
this.getChatBlockedAdresses()
setInterval(() => {