Merge pull request #90 from PhillipLangMartinez/feature/replies-lookup
New chat features
14
.eslintrc.json
Normal file
@ -0,0 +1,14 @@
|
||||
{
|
||||
"env": {
|
||||
"browser": true,
|
||||
"es2021": true
|
||||
},
|
||||
"extends": ["eslint:recommended", "plugin:lit/recommended", "plugin:wc/recommended"],
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 12,
|
||||
"sourceType": "module"
|
||||
},
|
||||
"rules": {
|
||||
"no-mixed-spaces-and-tabs": 0
|
||||
}
|
||||
}
|
2
.gitignore
vendored
@ -5,7 +5,7 @@ yarn.lock
|
||||
qortal-ui-plugins/plugins/core/**/*.js
|
||||
!*.src.js
|
||||
qortal-ui-core/src/redux/app/version.js
|
||||
!qortal-ui-plugins/plugins/core/components/*.js
|
||||
!qortal-ui-plugins/plugins/core/components/**/*.js
|
||||
|
||||
# Node modules
|
||||
node_modules/
|
||||
|
BIN
img/badges/level-0.png
Normal file
After Width: | Height: | Size: 4.8 KiB |
BIN
img/badges/level-1.png
Normal file
After Width: | Height: | Size: 4.3 KiB |
BIN
img/badges/level-2.png
Normal file
After Width: | Height: | Size: 5.2 KiB |
BIN
img/badges/level-3.png
Normal file
After Width: | Height: | Size: 5.5 KiB |
BIN
img/badges/level-4.png
Normal file
After Width: | Height: | Size: 7.4 KiB |
BIN
img/badges/level-5.png
Normal file
After Width: | Height: | Size: 6.6 KiB |
BIN
img/chain.png
Normal file
After Width: | Height: | Size: 174 KiB |
4
img/qchat-send-message-icon.svg
Normal file
@ -0,0 +1,4 @@
|
||||
<svg xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48">
|
||||
<path d="M4.02 42l41.98-18-41.98-18-.02 14 30 4-30 4z" fill="#03a9f4"/>
|
||||
<path d="M0 0h48v48h-48z" fill="none"/>
|
||||
</svg>
|
After Width: | Height: | Size: 253 B |
BIN
img/qortal-chat-logo.png
Normal file
After Width: | Height: | Size: 11 KiB |
15
package.json
@ -19,12 +19,12 @@
|
||||
"install_link:all": "(cd qortal-ui-core && yarn install && yarn link) && (cd qortal-ui-plugins && yarn install && yarn link) && (cd qortal-ui-crypto && yarn install && yarn link) && (yarn link qortal-ui-core && yarn link qortal-ui-plugins && yarn link qortal-ui-crypto)",
|
||||
"dev": "node server.js",
|
||||
"prebuild": "node -p \"'export const UI_VERSION = ' + JSON.stringify(require('./package.json').version) + ';'\" > qortal-ui-core/src/redux/app/version.js",
|
||||
"build-dev": "node build.js",
|
||||
"build": "NODE_ENV=production node build.js",
|
||||
"server": "NODE_ENV=production node server.js",
|
||||
"watch": "node watch.js",
|
||||
"watch-inline": "node watch-inline.js",
|
||||
"start-electron": "NODE_ENV=production electron .",
|
||||
"build-dev": "node --max-old-space-size=8192 build.js",
|
||||
"build": "NODE_ENV=production node --max-old-space-size=8192 build.js",
|
||||
"server": "NODE_ENV=production node --max-old-space-size=8192 server.js",
|
||||
"watch": "node --max-old-space-size=8192 watch.js",
|
||||
"watch-inline": "node --max-old-space-size=8192 watch-inline.js",
|
||||
"start-electron": "NODE_ENV=production electron --js-flags=--max-old-space-size=8192 .",
|
||||
"build-electron": "electron-builder build --publish never",
|
||||
"deploy-electron": "electron-builder build --win --publish never",
|
||||
"release": "NODE_ENV=production electron-builder build --publish never",
|
||||
@ -40,7 +40,8 @@
|
||||
"electron": "22.0.2",
|
||||
"electron-builder": "23.6.0",
|
||||
"electron-packager": "17.1.1",
|
||||
"@electron/notarize": "1.2.3",
|
||||
"eslint-plugin-lit": "1.8.0",
|
||||
"eslint-plugin-wc": "1.4.0",
|
||||
"shelljs": "0.8.5"
|
||||
},
|
||||
"engines": {
|
||||
|
BIN
qortal-ui-core/font/KoHo.ttf
Normal file
BIN
qortal-ui-core/font/Livvic.ttf
Normal file
BIN
qortal-ui-core/font/MaterialSymbolsOutlined.ttf
Normal file
BIN
qortal-ui-core/font/MaterialSymbolsOutlined.woff2
Normal file
BIN
qortal-ui-core/font/Montserrat.ttf
Normal file
BIN
qortal-ui-core/font/Raleway.ttf
Normal file
@ -2,7 +2,8 @@
|
||||
font-family: 'Material Icons';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: url(MaterialIcons-Regular.eot); /* For IE6-8 */
|
||||
src: url(MaterialIcons-Regular.eot);
|
||||
/* For IE6-8 */
|
||||
src: local('Material Icons'),
|
||||
local('MaterialIcons-Regular'),
|
||||
url(MaterialIcons-Regular.woff2) format('woff2'),
|
||||
@ -10,11 +11,48 @@
|
||||
url(MaterialIcons-Regular.ttf) format('truetype');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Material Symbols Outlined';
|
||||
font-style: normal;
|
||||
src: local('MaterialSymbolsOutlined'),
|
||||
url(MaterialSymbolsOutlined.ttf) format('truetype'),
|
||||
url(MaterialSymbolsOutlined.woff2) format('woff2')
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Montserrat';
|
||||
src: local('Montserrat'),
|
||||
local('Montserrat'),
|
||||
url(Montserrat.ttf) format('truetype');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Raleway';
|
||||
src: local('Raleway'),
|
||||
local('Raleway'),
|
||||
url(Raleway.ttf) format('truetype');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'KoHo';
|
||||
src: local('KoHo'),
|
||||
local('KoHo'),
|
||||
url(KoHo.ttf) format('truetype');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Livvic';
|
||||
src: local('Livvic'),
|
||||
local('Livvic'),
|
||||
url(Livvic.ttf) format('truetype');
|
||||
}
|
||||
|
||||
.material-icons {
|
||||
font-family: 'Material Icons';
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
font-size: 24px; /* Preferred icon size */
|
||||
font-size: 24px;
|
||||
/* Preferred icon size */
|
||||
display: inline-block;
|
||||
line-height: 1;
|
||||
text-transform: none;
|
||||
@ -34,3 +72,17 @@
|
||||
/* Support for IE. */
|
||||
font-feature-settings: 'liga';
|
||||
}
|
||||
|
||||
.material-symbols-outlined {
|
||||
font-family: 'Material Symbols Outlined';
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
font-size: 24px; /* Preferred icon size */
|
||||
display: inline-block;
|
||||
line-height: 1;
|
||||
text-transform: none;
|
||||
letter-spacing: normal;
|
||||
word-wrap: normal;
|
||||
white-space: nowrap;
|
||||
direction: ltr;
|
||||
}
|
@ -7,6 +7,17 @@ html {
|
||||
--border: #d0d6de;
|
||||
--border2: #dde2e8;
|
||||
--copybutton: #707584;
|
||||
--chat-group: #080808;
|
||||
--chat-bubble: #9f9f9f0a;
|
||||
--chat-bubble-bg: #e6e6e6;
|
||||
--chat-bubble-msg-color: #080808;
|
||||
--reaction-bubble-outline: #6b6969;
|
||||
--chat-menu-bg: #ffffff;
|
||||
--chat-menu-outline: #dad9d9;
|
||||
--chat-menu-icon: #3b3b3c;
|
||||
--chat-menu-icon-hover: #dad9d9;
|
||||
--block-user-bg-hover: #dad9d9;
|
||||
--paperclip-icon: #494949;
|
||||
--sectxt: #576374;
|
||||
--vdicon: #707b8a;
|
||||
--tradehead: #6a6c75;
|
||||
@ -17,8 +28,9 @@ html {
|
||||
--relaynodetxt: #646464;
|
||||
--menuhover: #eeeeee;
|
||||
--menuactive: #ebebeb;
|
||||
--mainmenutext:#080808;
|
||||
--mainmenutexthover:#080808;
|
||||
--menuactivergb: 235, 235, 235;
|
||||
--mainmenutext: #080808;
|
||||
--mainmenutexthover: #080808;
|
||||
--switchbackground: #666666;
|
||||
--switchborder: #333333;
|
||||
--sidetopbar: #ffffff;
|
||||
@ -32,6 +44,13 @@ html {
|
||||
--nav-border-selected-color: #03a9f4;
|
||||
--error: #d50000;
|
||||
--background: url("/img/qortal_background_light_.jpg");
|
||||
--chatHeadBg: #ebebeb;
|
||||
--chatHeadBgActive: #ebebeb;
|
||||
--chatHeadText: #080808;
|
||||
--chatHeadTextActive: #080808;
|
||||
--lightChatHeadHover: #1e1f201a;
|
||||
--group-header: #929292;
|
||||
--group-drop-shadow: rgb(17 17 26 / 10%) 0px 1px 0px;
|
||||
}
|
||||
|
||||
html[theme="dark"] {
|
||||
@ -43,6 +62,17 @@ html[theme="dark"] {
|
||||
--border: #0b305e;
|
||||
--border2: #0b305e;
|
||||
--copybutton: #d0d6de;
|
||||
--chat-group: #ffffff;
|
||||
--chat-bubble: #9694941a;
|
||||
--chat-bubble-bg: #2d3749;
|
||||
--chat-bubble-msg-color: #ffffff;
|
||||
--reaction-bubble-outline: #ffffff;
|
||||
--chat-menu-bg: #32394c;
|
||||
--chat-menu-outline: #32394c;
|
||||
--chat-menu-icon: #ffffff;
|
||||
--chat-menu-icon-hover: #a49a9a36;
|
||||
--block-user-bg-hover: #121a2f;
|
||||
--paperclip-icon: #d0c9c9;
|
||||
--sectxt: #bbc3cd;
|
||||
--vdicon: #d0d6de;
|
||||
--tradehead: #008fd5;
|
||||
@ -53,8 +83,9 @@ html[theme="dark"] {
|
||||
--relaynodetxt: #d4d4d4;
|
||||
--menuhover: #008fd5;
|
||||
--menuactive: #008fd5;
|
||||
--mainmenutext:#008fd5;
|
||||
--mainmenutexthover:#0f1a2e;
|
||||
--menuactivergb: 0, 143, 213;
|
||||
--mainmenutext: #008fd5;
|
||||
--mainmenutexthover: #0f1a2e;
|
||||
--switchbackground: #eeeeee;
|
||||
--switchborder: #03a9f4;
|
||||
--sidetopbar: #070d19;
|
||||
@ -68,4 +99,11 @@ html[theme="dark"] {
|
||||
--nav-border-selected-color: #76c8f5;
|
||||
--error: #d50000;
|
||||
--background: url("/img/qortal_background_dark_.jpg");
|
||||
--chatHeadBg: #008fd5;
|
||||
--chatHeadBgActive: #0f1a2e;
|
||||
--chatHeadText: #ffffff;
|
||||
--chatHeadTextActive: #ffffff;
|
||||
--lightChatHeadHover: #e0e1e31a;
|
||||
--group-header: #c8c8c8;
|
||||
--group-drop-shadow: rgb(191 191 191 / 32%) 0px 1px 0px
|
||||
}
|
@ -76,7 +76,8 @@
|
||||
"rollup": "3.10.0",
|
||||
"rollup-plugin-node-globals": "1.4.0",
|
||||
"rollup-plugin-progress": "1.1.2",
|
||||
"rollup-plugin-scss": "3.0.0"
|
||||
"rollup-plugin-scss": "3.0.0",
|
||||
"rollup-plugin-web-worker-loader": "1.6.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.17.1"
|
||||
|
@ -3,6 +3,8 @@ import { connect } from 'pwa-helpers'
|
||||
import { store } from '../store.js'
|
||||
import { doPageUrl } from '../redux/app/app-actions.js'
|
||||
import { translate, translateUnsafeHTML } from 'lit-translate'
|
||||
import WebWorker from 'web-worker:./computePowWorker.js';
|
||||
import { routes } from '../plugins/routes.js';
|
||||
|
||||
import '@material/mwc-icon'
|
||||
import '@material/mwc-button'
|
||||
@ -94,6 +96,8 @@ class AppInfo extends connect(store)(LitElement) {
|
||||
this.nodeStatus = {}
|
||||
this.pageUrl = ''
|
||||
this.theme = localStorage.getItem('qortalTheme') ? localStorage.getItem('qortalTheme') : 'light'
|
||||
this.publicKeyisOnChainConfirmation = false
|
||||
this.interval
|
||||
}
|
||||
|
||||
render() {
|
||||
@ -108,9 +112,113 @@ class AppInfo extends connect(store)(LitElement) {
|
||||
`
|
||||
}
|
||||
|
||||
async confirmPublicKeyOnChain(address) {
|
||||
const _computePow2 = async (chatBytes) => {
|
||||
const difficulty = 14;
|
||||
const path = window.parent.location.origin + '/memory-pow/memory-pow.wasm.full'
|
||||
const worker = new WebWorker();
|
||||
let nonce = null
|
||||
let chatBytesArray = null
|
||||
await new Promise((res, rej) => {
|
||||
worker.postMessage({chatBytes, path, difficulty});
|
||||
|
||||
worker.onmessage = e => {
|
||||
worker.terminate()
|
||||
chatBytesArray = e.data.chatBytesArray
|
||||
nonce = e.data.nonce
|
||||
res()
|
||||
|
||||
}
|
||||
})
|
||||
|
||||
let _response = await routes.sign_chat({
|
||||
data: {
|
||||
nonce: store.getState().app.selectedAddress.nonce,
|
||||
chatBytesArray: chatBytesArray,
|
||||
chatNonce: nonce
|
||||
},
|
||||
|
||||
});
|
||||
return _response
|
||||
};
|
||||
|
||||
|
||||
let stop = false
|
||||
const checkPublicKey = async () => {
|
||||
if (!stop) {
|
||||
stop = true;
|
||||
try {
|
||||
if(this.publicKeyisOnChainConfirmation){
|
||||
clearInterval(this.interval)
|
||||
return
|
||||
}
|
||||
const myNode = store.getState().app.nodeConfig.knownNodes[store.getState().app.nodeConfig.node];
|
||||
const nodeUrl = myNode.protocol + '://' + myNode.domain + ':' + myNode.port;
|
||||
const url = `${nodeUrl}/addresses/publickey/${address}`;
|
||||
const res = await fetch(url)
|
||||
let data = ''
|
||||
try {
|
||||
data = await res.text();
|
||||
} catch (error) {
|
||||
data = {
|
||||
error: 'error'
|
||||
}
|
||||
}
|
||||
if(data === 'false' && this.nodeInfo.isSynchronizing !== true){
|
||||
let _reference = new Uint8Array(64);
|
||||
window.crypto.getRandomValues(_reference);
|
||||
let reference = window.parent.Base58.encode(_reference);
|
||||
const chatRes = await routes.chat({
|
||||
data: {
|
||||
type: 19,
|
||||
nonce: store.getState().app.selectedAddress.nonce,
|
||||
params: {
|
||||
lastReference: reference,
|
||||
proofOfWorkNonce: 0,
|
||||
fee: 0,
|
||||
timestamp: Date.now(),
|
||||
|
||||
},
|
||||
disableModal: true
|
||||
},
|
||||
disableModal: true,
|
||||
});
|
||||
|
||||
try {
|
||||
const powRes = await _computePow2(chatRes)
|
||||
if(powRes === true){
|
||||
clearInterval(this.interval)
|
||||
|
||||
this.publicKeyisOnChainConfirmation = true
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
|
||||
if (!data.error && data !== 'false' && data) {
|
||||
clearInterval(this.interval)
|
||||
|
||||
this.publicKeyisOnChainConfirmation = true
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
}
|
||||
stop = false
|
||||
}
|
||||
};
|
||||
this.interval = setInterval(checkPublicKey, 5000);
|
||||
}
|
||||
|
||||
firstUpdated() {
|
||||
this.getNodeInfo()
|
||||
this.getCoreInfo()
|
||||
try {
|
||||
this.confirmPublicKeyOnChain(store.getState().app.selectedAddress.address)
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
|
||||
|
||||
setInterval(() => {
|
||||
this.getNodeInfo()
|
||||
|
@ -142,8 +142,6 @@ class AppView extends connect(store)(LitElement) {
|
||||
|
||||
app-drawer {
|
||||
box-shadow: var(--shadow-2);
|
||||
background: var(--sidetopbar);
|
||||
--app-drawer-scrim-background: rgba(0,0,0,0);
|
||||
}
|
||||
|
||||
app-header {
|
||||
@ -154,6 +152,8 @@ class AppView extends connect(store)(LitElement) {
|
||||
background: var(--sidetopbar);
|
||||
color: var(--black);
|
||||
border-top: var(--border);
|
||||
height: 48px;
|
||||
padding: 3px;
|
||||
}
|
||||
|
||||
paper-progress {
|
||||
@ -183,24 +183,26 @@ class AppView extends connect(store)(LitElement) {
|
||||
background: var(--sidetopbar);
|
||||
}
|
||||
|
||||
.sideBarMenu{
|
||||
.sideBarMenu {
|
||||
overflow-y: auto;
|
||||
flex: 1 1;
|
||||
}
|
||||
|
||||
#sideBar::-webkit-scrollbar {
|
||||
width: 7px;
|
||||
background-color: transparent;
|
||||
.sideBarMenu::-webkit-scrollbar-track {
|
||||
background-color: whitesmoke;
|
||||
border-radius: 7px;
|
||||
}
|
||||
|
||||
#sideBar::-webkit-scrollbar-track {
|
||||
background-color: transparent;
|
||||
.sideBarMenu::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
border-radius: 7px;
|
||||
background-color: whitesmoke;
|
||||
}
|
||||
|
||||
#sideBar::-webkit-scrollbar-thumb {
|
||||
background-color: #333;
|
||||
border-radius: 6px;
|
||||
border: 3px solid #333;
|
||||
.sideBarMenu::-webkit-scrollbar-thumb {
|
||||
background-color: rgb(180, 176, 176);
|
||||
border-radius: 7px;
|
||||
transition: all 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
#balanceheader {
|
||||
@ -323,6 +325,11 @@ class AppView extends connect(store)(LitElement) {
|
||||
0%,100% { opacity: 0; }
|
||||
50% { opacity: 10; }
|
||||
}
|
||||
|
||||
.sideBarMenu::-webkit-scrollbar-thumb:hover {
|
||||
background-color: rgb(148, 146, 146);
|
||||
cursor: pointer;
|
||||
}
|
||||
`
|
||||
]
|
||||
}
|
||||
|
82
qortal-ui-core/src/components/computePowWorker.js
Normal file
@ -0,0 +1,82 @@
|
||||
import { Sha256 } from 'asmcrypto.js'
|
||||
|
||||
|
||||
function sbrk(size, heap){
|
||||
let brk = 512 * 1024 // stack top
|
||||
let old = brk
|
||||
brk += size
|
||||
|
||||
if (brk > heap.length)
|
||||
throw new Error('heap exhausted')
|
||||
|
||||
return old
|
||||
}
|
||||
|
||||
|
||||
|
||||
self.addEventListener('message', async e => {
|
||||
const response = await computePow(e.data.chatBytes, e.data.path, e.data.difficulty)
|
||||
postMessage(response)
|
||||
|
||||
})
|
||||
|
||||
|
||||
const memory = new WebAssembly.Memory({ initial: 256, maximum: 256 })
|
||||
const heap = new Uint8Array(memory.buffer)
|
||||
|
||||
|
||||
|
||||
const computePow = async (chatBytes, path, difficulty) => {
|
||||
|
||||
let response = null
|
||||
|
||||
await new Promise((resolve, reject)=> {
|
||||
|
||||
const _chatBytesArray = Object.keys(chatBytes).map(function (key) { return chatBytes[key]; });
|
||||
const chatBytesArray = new Uint8Array(_chatBytesArray);
|
||||
const chatBytesHash = new Sha256().process(chatBytesArray).finish().result;
|
||||
const hashPtr = sbrk(32, heap);
|
||||
const hashAry = new Uint8Array(memory.buffer, hashPtr, 32);
|
||||
hashAry.set(chatBytesHash);
|
||||
|
||||
|
||||
const workBufferLength = 8 * 1024 * 1024;
|
||||
const workBufferPtr = sbrk(workBufferLength, heap);
|
||||
|
||||
|
||||
|
||||
const importObject = {
|
||||
env: {
|
||||
memory: memory
|
||||
},
|
||||
};
|
||||
|
||||
function loadWebAssembly(filename, imports) {
|
||||
// Fetch the file and compile it
|
||||
return fetch(filename)
|
||||
.then(response => response.arrayBuffer())
|
||||
.then(buffer => WebAssembly.compile(buffer))
|
||||
.then(module => {
|
||||
|
||||
// Create the instance.
|
||||
return new WebAssembly.Instance(module, importObject);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
loadWebAssembly(path)
|
||||
.then(wasmModule => {
|
||||
response = {
|
||||
nonce : wasmModule.exports.compute2(hashPtr, workBufferPtr, workBufferLength, difficulty),
|
||||
chatBytesArray
|
||||
}
|
||||
|
||||
resolve()
|
||||
|
||||
});
|
||||
|
||||
|
||||
})
|
||||
|
||||
return response
|
||||
}
|
@ -7,6 +7,17 @@ html {
|
||||
--border: #d0d6de;
|
||||
--border2: #dde2e8;
|
||||
--copybutton: #707584;
|
||||
--chat-group: #080808;
|
||||
--chat-bubble: #9f9f9f0a;
|
||||
--chat-bubble-bg: #e6e6e6;
|
||||
--chat-bubble-msg-color: #080808;
|
||||
--reaction-bubble-outline: #6b6969;
|
||||
--chat-menu-bg: #ffffff;
|
||||
--chat-menu-outline: #dad9d9;
|
||||
--chat-menu-icon: #3b3b3c;
|
||||
--chat-menu-icon-hover: #dad9d9;
|
||||
--block-user-bg-hover: #dad9d9;
|
||||
--paperclip-icon: #494949;
|
||||
--sectxt: #576374;
|
||||
--vdicon: #707b8a;
|
||||
--tradehead: #6a6c75;
|
||||
@ -17,8 +28,8 @@ html {
|
||||
--relaynodetxt: #646464;
|
||||
--menuhover: #eeeeee;
|
||||
--menuactive: #ebebeb;
|
||||
--mainmenutext:#080808;
|
||||
--mainmenutexthover:#080808;
|
||||
--mainmenutext: #080808;
|
||||
--mainmenutexthover: #080808;
|
||||
--switchbackground: #666666;
|
||||
--switchborder: #333333;
|
||||
--sidetopbar: #ffffff;
|
||||
@ -31,6 +42,12 @@ html {
|
||||
--nav-border-color: #eeeeee;
|
||||
--nav-border-selected-color: #03a9f4;
|
||||
--background: url("/img/qortal_background_light_.jpg");
|
||||
--chatHeadBg: #ebebeb;
|
||||
--chatHeadBgActive: #ebebeb;
|
||||
--chatHeadText: #080808;
|
||||
--chatHeadTextActive: #080808;
|
||||
--group-header: #929292;
|
||||
--group-drop-shadow: rgb(17 17 26 / 10%) 0px 1px 0px;
|
||||
}
|
||||
|
||||
html[theme="dark"] {
|
||||
@ -42,6 +59,17 @@ html[theme="dark"] {
|
||||
--border: #0b305e;
|
||||
--border2: #0b305e;
|
||||
--copybutton: #d0d6de;
|
||||
--chat-group: #ffffff;
|
||||
--chat-bubble: #9694941a;
|
||||
--chat-bubble-bg: #2d3749;
|
||||
--chat-bubble-msg-color: #ffffff;
|
||||
--reaction-bubble-outline: #ffffff;
|
||||
--chat-menu-bg: #32394c;
|
||||
--chat-menu-outline: #32394c;
|
||||
--chat-menu-icon: #ffffff;
|
||||
--chat-menu-icon-hover: #a49a9a36;
|
||||
--block-user-bg-hover: #121a2f;
|
||||
--paperclip-icon: #d0c9c9;
|
||||
--sectxt: #bbc3cd;
|
||||
--vdicon: #d0d6de;
|
||||
--tradehead: #008fd5;
|
||||
@ -52,8 +80,8 @@ html[theme="dark"] {
|
||||
--relaynodetxt: #d4d4d4;
|
||||
--menuhover: #008fd5;
|
||||
--menuactive: #008fd5;
|
||||
--mainmenutext:#008fd5;
|
||||
--mainmenutexthover:#0f1a2e;
|
||||
--mainmenutext: #008fd5;
|
||||
--mainmenutexthover: #0f1a2e;
|
||||
--switchbackground: #eeeeee;
|
||||
--switchborder: #03a9f4;
|
||||
--sidetopbar: #070d19;
|
||||
@ -66,4 +94,10 @@ html[theme="dark"] {
|
||||
--nav-border-color: #0b305e;
|
||||
--nav-border-selected-color: #76c8f5;
|
||||
--background: url("/img/qortal_background_dark_.jpg");
|
||||
--chatHeadBg: #008fd5;
|
||||
--chatHeadBgActive: #0f1a2e;
|
||||
--chatHeadText: #ffffff;
|
||||
--chatHeadTextActive: #ffffff;
|
||||
--group-header: #c8c8c8;
|
||||
--group-drop-shadow: rgb(191 191 191 / 32%) 0px 1px 0px
|
||||
}
|
@ -7,6 +7,8 @@ const commonjs = require('@rollup/plugin-commonjs')
|
||||
const alias = require('@rollup/plugin-alias')
|
||||
const terser = require('@rollup/plugin-terser');
|
||||
const scss = require('rollup-plugin-scss')
|
||||
const webWorkerLoader = require('rollup-plugin-web-worker-loader');
|
||||
|
||||
const generateES5BuildConfig = require('./generateES5BuildConfig')
|
||||
|
||||
|
||||
@ -61,6 +63,7 @@ const generateBuildConfig = ({ elementComponents, functionalComponents, otherOut
|
||||
commonjs(),
|
||||
globals(),
|
||||
progress(),
|
||||
webWorkerLoader(),
|
||||
scss({
|
||||
output: options.sassOutputDir
|
||||
}),
|
||||
|
@ -5,6 +5,7 @@ const commonjs = require('@rollup/plugin-commonjs');
|
||||
const progress = require('rollup-plugin-progress');
|
||||
const terser = require('@rollup/plugin-terser');
|
||||
const alias = require('@rollup/plugin-alias');
|
||||
const webWorkerLoader = require('rollup-plugin-web-worker-loader');
|
||||
const path = require('path');
|
||||
|
||||
|
||||
@ -37,6 +38,7 @@ const generateRollupConfig = (file, { outputDir, aliases }) => {
|
||||
}),
|
||||
commonjs(),
|
||||
progress(),
|
||||
webWorkerLoader(),
|
||||
babel.babel({
|
||||
babelHelpers: 'bundled',
|
||||
exclude: 'node_modules/**'
|
||||
|
@ -1,5 +1,6 @@
|
||||
'use strict'
|
||||
import ChatBase from './chat/ChatBase.js'
|
||||
"use strict";
|
||||
import ChatBase from "./chat/ChatBase.js"
|
||||
import { QORT_DECIMALS } from "../constants.js"
|
||||
|
||||
export default class PublicizeTransaction extends ChatBase {
|
||||
constructor() {
|
||||
@ -11,13 +12,16 @@ export default class PublicizeTransaction extends ChatBase {
|
||||
set proofOfWorkNonce(proofOfWorkNonce) {
|
||||
this._proofOfWorkNonce = this.constructor.utils.int32ToBytes(proofOfWorkNonce)
|
||||
}
|
||||
|
||||
set fee(fee) {
|
||||
this._fee = fee * QORT_DECIMALS
|
||||
this._feeBytes = this.constructor.utils.int64ToBytes(this._fee)
|
||||
}
|
||||
get params() {
|
||||
const params = super.params
|
||||
const params = super.params;
|
||||
params.push(
|
||||
this._proofOfWorkNonce,
|
||||
this._feeBytes
|
||||
)
|
||||
return params
|
||||
return params;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,72 @@
|
||||
'use strict';
|
||||
import TransactionBase from '../TransactionBase.js'
|
||||
import Base58 from '../../deps/Base58.js'
|
||||
import { store } from '../../../api.js'
|
||||
import { QORT_DECIMALS } from "../../constants.js"
|
||||
|
||||
export default class UpdateGroupTransaction extends TransactionBase {
|
||||
constructor() {
|
||||
super()
|
||||
this.type = 23
|
||||
}
|
||||
|
||||
render(html) {
|
||||
const conf = store.getState().config
|
||||
return html`
|
||||
Are you sure to update this group ?
|
||||
<div style="background: #eee; padding: 8px; margin: 8px 0; border-radius: 5px;">
|
||||
|
||||
</div>
|
||||
On pressing confirm, the group details will be updated!
|
||||
`
|
||||
}
|
||||
|
||||
|
||||
set fee(fee) {
|
||||
this._fee = fee * QORT_DECIMALS
|
||||
this._feeBytes = this.constructor.utils.int64ToBytes(this._fee)
|
||||
}
|
||||
set newOwner(newOwner) {
|
||||
this._newOwner = newOwner instanceof Uint8Array ? newOwner : this.constructor.Base58.decode(newOwner)
|
||||
}
|
||||
set newIsOpen(newIsOpen) {
|
||||
|
||||
this._rGroupType = new Uint8Array(1)
|
||||
this._rGroupType[0] = newIsOpen
|
||||
}
|
||||
set newDescription(newDescription) {
|
||||
this._rGroupDescBytes = this.constructor.utils.stringtoUTF8Array(newDescription.toLocaleLowerCase())
|
||||
this._rGroupDescLength = this.constructor.utils.int32ToBytes(this._rGroupDescBytes.length)
|
||||
}
|
||||
set newApprovalThreshold(newApprovalThreshold) {
|
||||
this._rGroupApprovalThreshold = new Uint8Array(1)
|
||||
this._rGroupApprovalThreshold[0] = newApprovalThreshold;
|
||||
}
|
||||
set newMinimumBlockDelay(newMinimumBlockDelay) {
|
||||
this._rGroupMinimumBlockDelayBytes = this.constructor.utils.int32ToBytes(newMinimumBlockDelay)
|
||||
}
|
||||
set newMaximumBlockDelay(newMaximumBlockDelay) {
|
||||
|
||||
this._rGroupMaximumBlockDelayBytes = this.constructor.utils.int32ToBytes(newMaximumBlockDelay)
|
||||
}
|
||||
|
||||
set _groupId(_groupId){
|
||||
this._groupBytes = this.constructor.utils.int32ToBytes(_groupId)
|
||||
}
|
||||
get params() {
|
||||
const params = super.params
|
||||
params.push(
|
||||
this._groupBytes,
|
||||
this._newOwner,
|
||||
this._rGroupDescLength,
|
||||
this._rGroupDescBytes,
|
||||
this._rGroupType,
|
||||
this._rGroupApprovalThreshold,
|
||||
this._rGroupMinimumBlockDelayBytes,
|
||||
this._rGroupMaximumBlockDelayBytes,
|
||||
this._feeBytes
|
||||
)
|
||||
console.log('verify params', params)
|
||||
return params
|
||||
}
|
||||
}
|
@ -16,6 +16,7 @@ import GroupKickTransaction from './groups/GroupKickTransaction.js'
|
||||
import GroupInviteTransaction from './groups/GroupInviteTransaction.js'
|
||||
import CancelGroupInviteTransaction from './groups/CancelGroupInviteTransaction.js'
|
||||
import JoinGroupTransaction from './groups/JoinGroupTransaction.js'
|
||||
import UpdateGroupTransaction from './groups/UpdateGroupTransaction.js'
|
||||
import LeaveGroupTransaction from './groups/LeaveGroupTransaction.js'
|
||||
import RewardShareTransaction from './reward-share/RewardShareTransaction.js'
|
||||
import RemoveRewardShareTransaction from './reward-share/RemoveRewardShareTransaction.js'
|
||||
|
@ -8,6 +8,8 @@ const commonjs = require('@rollup/plugin-commonjs');
|
||||
const alias = require('@rollup/plugin-alias');
|
||||
const terser = require('@rollup/plugin-terser');
|
||||
const babel = require('@rollup/plugin-babel');
|
||||
const webWorkerLoader = require('rollup-plugin-web-worker-loader');
|
||||
|
||||
|
||||
const aliases = {};
|
||||
|
||||
@ -40,6 +42,7 @@ const generateRollupConfig = (inputFile, outputFile) => {
|
||||
commonjs(),
|
||||
globals(),
|
||||
progress(),
|
||||
webWorkerLoader(),
|
||||
babel.babel({
|
||||
babelHelpers: 'bundled',
|
||||
exclude: 'node_modules/**',
|
||||
|
@ -17,9 +17,31 @@
|
||||
"author": "QORTAL <admin@qortal.org>",
|
||||
"license": "GPL-3.0",
|
||||
"dependencies": {
|
||||
"@lit-labs/motion": "1.0.3",
|
||||
"@material/mwc-list": "0.27.0",
|
||||
"@material/mwc-select": "0.27.0",
|
||||
"emoji-picker-js": "https://github.com/Qortal/emoji-picker-js"
|
||||
"@tiptap/core": "2.0.0-beta.209",
|
||||
"@tiptap/extension-image": "2.0.0-beta.209",
|
||||
"@tiptap/extension-placeholder": "2.0.0-beta.209",
|
||||
"@tiptap/extension-underline": "2.0.0-beta.209",
|
||||
"@tiptap/extension-highlight": "2.0.0-beta.209",
|
||||
"@tiptap/html": "2.0.0-beta.209",
|
||||
"@tiptap/starter-kit": "2.0.0-beta.209",
|
||||
"asmcrypto.js": "2.3.2",
|
||||
"compressorjs": "1.1.1",
|
||||
"emoji-picker-js": "https://github.com/Qortal/emoji-picker-js",
|
||||
"prosemirror-commands": "1.5.0",
|
||||
"prosemirror-dropcursor": "1.6.1",
|
||||
"prosemirror-gapcursor": "1.3.1",
|
||||
"prosemirror-history": "1.3.0",
|
||||
"prosemirror-keymap": "1.2.0",
|
||||
"prosemirror-model": "1.18.3",
|
||||
"prosemirror-schema-list": "1.2.2",
|
||||
"prosemirror-state": "1.4.2",
|
||||
"prosemirror-transform": "1.7.0",
|
||||
"prosemirror-view": "1.29.1",
|
||||
"localforage": "1.10.0",
|
||||
"short-unique-id": "4.4.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "7.20.12",
|
||||
@ -60,7 +82,8 @@
|
||||
"lit-translate": "2.0.1",
|
||||
"rollup": "3.10.0",
|
||||
"rollup-plugin-node-globals": "1.4.0",
|
||||
"rollup-plugin-progress": "1.1.2"
|
||||
"rollup-plugin-progress": "1.1.2",
|
||||
"rollup-plugin-web-worker-loader": "1.6.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.17.1"
|
||||
|
335
qortal-ui-plugins/plugins/core/components/ChatGroupInvites.js
Normal file
@ -0,0 +1,335 @@
|
||||
import { LitElement, html, css } from "lit"
|
||||
import { render } from "lit/html.js"
|
||||
import { get, translate } from "lit-translate"
|
||||
import { Epml } from "../../../epml"
|
||||
import snackbar from "./snackbar.js"
|
||||
import "@material/mwc-button"
|
||||
import "@material/mwc-dialog"
|
||||
import "@polymer/paper-spinner/paper-spinner-lite.js"
|
||||
import "@material/mwc-icon"
|
||||
import "./WrapperModal"
|
||||
|
||||
const parentEpml = new Epml({ type: "WINDOW", source: window.parent })
|
||||
|
||||
class ChatGroupInvites extends LitElement {
|
||||
static get properties() {
|
||||
return {
|
||||
isLoading: { type: Boolean },
|
||||
isOpenLeaveModal: { type: Boolean },
|
||||
leaveGroupObj: { type: Object },
|
||||
error: { type: Boolean },
|
||||
message: { type: String },
|
||||
chatHeads: { type: Array },
|
||||
groupAdmin: { attribute: false },
|
||||
groupMembers: { attribute: false },
|
||||
selectedHead: { type: Object },
|
||||
}
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
this.isLoading = false
|
||||
this.isOpenLeaveModal = false
|
||||
this.leaveGroupObj = {}
|
||||
this.leaveFee = 0.001
|
||||
this.error = false
|
||||
this.message = ""
|
||||
this.chatHeads = []
|
||||
this.groupAdmin = []
|
||||
this.groupMembers = []
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
`
|
||||
}
|
||||
|
||||
firstUpdated() {}
|
||||
|
||||
timeIsoString(timestamp) {
|
||||
let myTimestamp = timestamp === undefined ? 1587560082346 : timestamp
|
||||
let time = new Date(myTimestamp)
|
||||
return time.toISOString()
|
||||
}
|
||||
|
||||
resetDefaultSettings() {
|
||||
this.error = false
|
||||
this.message = ""
|
||||
this.isLoading = false
|
||||
}
|
||||
|
||||
renderErr9Text() {
|
||||
return html`${translate("grouppage.gchange49")}`
|
||||
}
|
||||
|
||||
async confirmRelationship(reference) {
|
||||
let interval = null
|
||||
let stop = false
|
||||
const getAnswer = async () => {
|
||||
|
||||
|
||||
if (!stop) {
|
||||
stop = true
|
||||
try {
|
||||
let myRef = await parentEpml.request("apiCall", {
|
||||
type: "api",
|
||||
url: `/transactions/reference/${reference}`,
|
||||
})
|
||||
if (myRef && myRef.type) {
|
||||
clearInterval(interval)
|
||||
this.isLoading = false
|
||||
this.isOpenLeaveModal = false
|
||||
}
|
||||
} catch (error) {}
|
||||
stop = false
|
||||
}
|
||||
}
|
||||
interval = setInterval(getAnswer, 5000)
|
||||
}
|
||||
|
||||
async getLastRef() {
|
||||
let myRef = await parentEpml.request("apiCall", {
|
||||
type: "api",
|
||||
url: `/addresses/lastreference/${this.selectedAddress.address}`,
|
||||
})
|
||||
return myRef
|
||||
}
|
||||
|
||||
getTxnRequestResponse(txnResponse, reference) {
|
||||
if (txnResponse === true) {
|
||||
this.message = this.renderErr9Text()
|
||||
this.error = false
|
||||
this.confirmRelationship(reference)
|
||||
} else {
|
||||
this.error = true
|
||||
this.message = ""
|
||||
throw new Error(txnResponse)
|
||||
}
|
||||
}
|
||||
|
||||
async convertBytesForSigning(transactionBytesBase58) {
|
||||
let convertedBytes = await parentEpml.request("apiCall", {
|
||||
type: "api",
|
||||
method: "POST",
|
||||
url: `/transactions/convert`,
|
||||
body: `${transactionBytesBase58}`,
|
||||
})
|
||||
return convertedBytes
|
||||
}
|
||||
|
||||
async signTx(body){
|
||||
return await parentEpml.request("apiCall", {
|
||||
type: "api",
|
||||
method: "POST",
|
||||
url: `/transactions/sign`,
|
||||
body: body,
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async process(body){
|
||||
return await parentEpml.request("apiCall", {
|
||||
type: "api",
|
||||
method: "POST",
|
||||
url: `/transactions/process`,
|
||||
body: body,
|
||||
})
|
||||
}
|
||||
async _addAdmin(groupId) {
|
||||
// Reset Default Settings...
|
||||
this.resetDefaultSettings()
|
||||
const leaveFeeInput = this.leaveFee
|
||||
|
||||
this.isLoading = true
|
||||
|
||||
// Get Last Ref
|
||||
|
||||
const validateReceiver = async () => {
|
||||
let lastRef = await this.getLastRef()
|
||||
let myTransaction = await makeTransactionRequest(lastRef)
|
||||
this.getTxnRequestResponse(myTransaction, lastRef )
|
||||
}
|
||||
|
||||
// Make Transaction Request
|
||||
const makeTransactionRequest = async (lastRef) => {
|
||||
const body = {
|
||||
timestamp: Date.now(),
|
||||
reference: lastRef,
|
||||
fee: leaveFeeInput,
|
||||
ownerPublicKey: window.parent.Base58.encode(
|
||||
window.parent.reduxStore.getState().app.selectedAddress
|
||||
.keyPair.publicKey
|
||||
),
|
||||
groupId: groupId,
|
||||
member: this.selectedHead.address,
|
||||
}
|
||||
const bodyToString = JSON.stringify(body)
|
||||
let transactionBytes = await parentEpml.request("apiCall", {
|
||||
type: "api",
|
||||
method: "POST",
|
||||
url: `/groups/addadmin`,
|
||||
body: bodyToString,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
})
|
||||
const readforsign = await this.convertBytesForSigning(
|
||||
transactionBytes
|
||||
)
|
||||
const body2 = {
|
||||
privateKey: window.parent.Base58.encode(
|
||||
window.parent.reduxStore.getState().app.selectedAddress
|
||||
.keyPair.privateKey
|
||||
),
|
||||
transactionBytes: readforsign,
|
||||
}
|
||||
const bodyToString2 = JSON.stringify(body2)
|
||||
let signTransaction = await this.signTx(bodyToString2)
|
||||
let processTransaction = await this.process(signTransaction)
|
||||
return processTransaction
|
||||
}
|
||||
|
||||
validateReceiver()
|
||||
}
|
||||
|
||||
async _removeAdmin(groupId) {
|
||||
// Reset Default Settings...
|
||||
this.resetDefaultSettings()
|
||||
const leaveFeeInput = this.leaveFee
|
||||
|
||||
this.isLoading = true
|
||||
|
||||
// Get Last Ref
|
||||
|
||||
const validateReceiver = async () => {
|
||||
let lastRef = await this.getLastRef()
|
||||
let myTransaction = await makeTransactionRequest(lastRef)
|
||||
this.getTxnRequestResponse(myTransaction, lastRef)
|
||||
}
|
||||
|
||||
// Make Transaction Request
|
||||
const makeTransactionRequest = async (lastRef) => {
|
||||
const body = {
|
||||
timestamp: Date.now(),
|
||||
reference: lastRef,
|
||||
fee: leaveFeeInput,
|
||||
ownerPublicKey: window.parent.Base58.encode(
|
||||
window.parent.reduxStore.getState().app.selectedAddress
|
||||
.keyPair.publicKey
|
||||
),
|
||||
groupId: groupId,
|
||||
admin: this.selectedHead.address,
|
||||
}
|
||||
const bodyToString = JSON.stringify(body)
|
||||
let transactionBytes = await parentEpml.request("apiCall", {
|
||||
type: "api",
|
||||
method: "POST",
|
||||
url: `/groups/removeadmin`,
|
||||
body: bodyToString,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
})
|
||||
const readforsign = await this.convertBytesForSigning(
|
||||
transactionBytes
|
||||
)
|
||||
const body2 = {
|
||||
privateKey: window.parent.Base58.encode(
|
||||
window.parent.reduxStore.getState().app.selectedAddress
|
||||
.keyPair.privateKey
|
||||
),
|
||||
transactionBytes: readforsign,
|
||||
}
|
||||
const bodyToString2 = JSON.stringify(body2)
|
||||
let signTransaction = await this.signTx(bodyToString2)
|
||||
let processTransaction = await this.process(signTransaction)
|
||||
return processTransaction
|
||||
}
|
||||
|
||||
validateReceiver()
|
||||
}
|
||||
|
||||
render() {
|
||||
console.log("leaveGroupObj", this.leaveGroupObj)
|
||||
return html`
|
||||
<vaadin-icon @click=${()=> {
|
||||
this.isOpenLeaveModal = true
|
||||
}} class="top-bar-icon" style="margin: 0px 20px" icon="vaadin:users" slot="icon"></vaadin-icon>
|
||||
|
||||
<wrapper-modal
|
||||
.removeImage=${() => {
|
||||
if (this.isLoading) return
|
||||
this.isOpenLeaveModal = false
|
||||
}}
|
||||
style=${
|
||||
this.isOpenLeaveModal ? "display: block" : "display: none"
|
||||
}>
|
||||
<div style="text-align:center">
|
||||
<h1>${translate("grouppage.gchange35")}</h1>
|
||||
<hr>
|
||||
</div>
|
||||
|
||||
<button @click=${() =>
|
||||
this._addAdmin(
|
||||
this.leaveGroupObj.groupId
|
||||
)}>Promote to Admin</button>
|
||||
<button @click=${() =>
|
||||
this._removeAdmin(
|
||||
this.leaveGroupObj.groupId
|
||||
)}>Remove as Admin</button>
|
||||
<div style="text-align:right; height:36px;">
|
||||
<span ?hidden="${!this.isLoading}">
|
||||
<!-- loading message -->
|
||||
${translate("grouppage.gchange36")}
|
||||
<paper-spinner-lite
|
||||
style="margin-top:12px;"
|
||||
?active="${this.isLoading}"
|
||||
alt="Leaving"
|
||||
>
|
||||
</paper-spinner-lite>
|
||||
</span>
|
||||
<span ?hidden=${this.message === ""} style="${
|
||||
this.error ? "color:red;" : ""
|
||||
}">
|
||||
${this.message}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
|
||||
<button
|
||||
@click=${() => {
|
||||
this.isOpenLeaveModal = false
|
||||
}}
|
||||
class="modal-button"
|
||||
?disabled="${this.isLoading}"
|
||||
|
||||
>
|
||||
${translate("general.close")}
|
||||
</button>
|
||||
</wrapper-modal >
|
||||
`
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("chat-right-panel", ChatGroupInvites)
|
283
qortal-ui-plugins/plugins/core/components/ChatGroupSettings.js
Normal file
@ -0,0 +1,283 @@
|
||||
import { LitElement, html, css } from 'lit';
|
||||
import { render } from 'lit/html.js';
|
||||
import { get, translate } from 'lit-translate';
|
||||
import { Epml } from '../../../epml';
|
||||
import snackbar from './snackbar.js'
|
||||
import '@material/mwc-button';
|
||||
import '@material/mwc-dialog';
|
||||
import '@polymer/paper-spinner/paper-spinner-lite.js'
|
||||
import '@material/mwc-icon';
|
||||
import './WrapperModal';
|
||||
|
||||
const parentEpml = new Epml({ type: 'WINDOW', source: window.parent })
|
||||
|
||||
class ChatGroupSettings extends LitElement {
|
||||
static get properties() {
|
||||
return {
|
||||
isLoading: { type: Boolean },
|
||||
isOpenLeaveModal: {type: Boolean},
|
||||
leaveGroupObj: { type: Object },
|
||||
error: {type: Boolean},
|
||||
message: {type: String},
|
||||
chatHeads: {type: Array},
|
||||
setActiveChatHeadUrl: {attribute: false}
|
||||
}
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.isLoading = false;
|
||||
this.isOpenLeaveModal = false
|
||||
this.leaveGroupObj = {}
|
||||
this.leaveFee = 0.001
|
||||
this.error = false
|
||||
this.message = ''
|
||||
this.chatHeads = []
|
||||
}
|
||||
|
||||
static get styles() {
|
||||
return css`
|
||||
.top-bar-icon {
|
||||
cursor: pointer;
|
||||
height: 18px;
|
||||
width: 18px;
|
||||
transition: .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;
|
||||
}
|
||||
`
|
||||
}
|
||||
|
||||
firstUpdated() {
|
||||
|
||||
}
|
||||
|
||||
timeIsoString(timestamp) {
|
||||
let myTimestamp = timestamp === undefined ? 1587560082346 : timestamp
|
||||
let time = new Date(myTimestamp)
|
||||
return time.toISOString()
|
||||
}
|
||||
|
||||
resetDefaultSettings() {
|
||||
this.error = false
|
||||
this.message = ''
|
||||
this.isLoading = false
|
||||
}
|
||||
|
||||
renderErr9Text() {
|
||||
return html`${translate("grouppage.gchange49")}`
|
||||
}
|
||||
|
||||
async confirmRelationship() {
|
||||
|
||||
|
||||
let interval = null
|
||||
let stop = false
|
||||
const getAnswer = async () => {
|
||||
const currentChats = this.chatHeads
|
||||
|
||||
if (!stop) {
|
||||
stop = true;
|
||||
try {
|
||||
const findGroup = currentChats.find((item)=> item.groupId === this.leaveGroupObj.groupId)
|
||||
if (!findGroup) {
|
||||
clearInterval(interval)
|
||||
this.isLoading = false
|
||||
this.isOpenLeaveModal= false
|
||||
this.setActiveChatHeadUrl('')
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
}
|
||||
stop = false
|
||||
}
|
||||
};
|
||||
interval = setInterval(getAnswer, 5000);
|
||||
}
|
||||
|
||||
async _convertToPrivate(groupId) {
|
||||
// Reset Default Settings...
|
||||
this.resetDefaultSettings()
|
||||
const leaveFeeInput = this.leaveFee
|
||||
|
||||
this.isLoading = true
|
||||
|
||||
// Get Last Ref
|
||||
const getLastRef = async () => {
|
||||
let myRef = await parentEpml.request('apiCall', {
|
||||
type: 'api',
|
||||
url: `/addresses/lastreference/${this.selectedAddress.address}`
|
||||
})
|
||||
return myRef
|
||||
};
|
||||
|
||||
const validateReceiver = async () => {
|
||||
let lastRef = await getLastRef();
|
||||
let myTransaction = await makeTransactionRequest(lastRef)
|
||||
getTxnRequestResponse(myTransaction)
|
||||
|
||||
}
|
||||
const convertBytesForSigning = async (transactionBytesBase58) => {
|
||||
let convertedBytes = await parentEpml.request("apiCall", {
|
||||
type: "api",
|
||||
method: "POST",
|
||||
url: `/transactions/convert`,
|
||||
body: `${transactionBytesBase58}`,
|
||||
})
|
||||
return convertedBytes
|
||||
}
|
||||
|
||||
|
||||
// Make Transaction Request
|
||||
const makeTransactionRequest = async (lastRef) => {
|
||||
let groupdialog3 = get("transactions.groupdialog3")
|
||||
let groupdialog4 = get("transactions.groupdialog4")
|
||||
|
||||
const body = {
|
||||
"timestamp": Date.now(),
|
||||
"reference": lastRef,
|
||||
"fee": leaveFeeInput,
|
||||
"ownerPublicKey": window.parent.Base58.encode(window.parent.reduxStore.getState().app.selectedAddress.keyPair.publicKey),
|
||||
"groupId": groupId,
|
||||
"newOwner": "QdR4bQ1fJFnSZgswtW27eE8ToXwHqUQyaU",
|
||||
"newIsOpen": false,
|
||||
"newDescription": "my group for accounts I like",
|
||||
"newApprovalThreshold": "NONE",
|
||||
"newMinimumBlockDelay": 5,
|
||||
"newMaximumBlockDelay": 60
|
||||
}
|
||||
console.log('STRING3')
|
||||
// const bodyToString = JSON.stringify(body)
|
||||
// let transactionBytes = await parentEpml.request("apiCall", {
|
||||
// type: "api",
|
||||
// method: "POST",
|
||||
// url: `/groups/update`,
|
||||
// body: bodyToString,
|
||||
// headers: {
|
||||
// 'Content-Type': 'application/json'
|
||||
// }
|
||||
// })
|
||||
// console.log({transactionBytes})
|
||||
// const readforsign = await convertBytesForSigning(transactionBytes)
|
||||
// // const res = await signAndProcess(transactionBytes)
|
||||
// const body2 = {
|
||||
// "privateKey": window.parent.Base58.encode(window.parent.reduxStore.getState().app.selectedAddress.keyPair.privateKey),
|
||||
// "transactionBytes": readforsign
|
||||
// }
|
||||
// const bodyToString2 = JSON.stringify(body2)
|
||||
// let signTransaction = await parentEpml.request("apiCall", {
|
||||
// type: "api",
|
||||
// method: "POST",
|
||||
// url: `/transactions/sign`,
|
||||
// body: bodyToString2,
|
||||
// headers: {
|
||||
// 'Content-Type': 'application/json'
|
||||
// }
|
||||
// })
|
||||
// let processTransaction = await parentEpml.request("apiCall", {
|
||||
// type: "api",
|
||||
// method: "POST",
|
||||
// url: `/transactions/process`,
|
||||
// body: signTransaction,
|
||||
// })
|
||||
// return processTransaction
|
||||
console.log('this.selectedAddress.nonce', this.selectedAddress.nonce)
|
||||
let myTxnrequest = await parentEpml.request('transaction', {
|
||||
type: 23,
|
||||
nonce: this.selectedAddress.nonce,
|
||||
params: {
|
||||
_groupId: groupId,
|
||||
lastReference: lastRef,
|
||||
fee: leaveFeeInput,
|
||||
"newOwner": "QdR4bQ1fJFnSZgswtW27eE8ToXwHqUQyaU",
|
||||
"newIsOpen": false,
|
||||
"newDescription": "my group for accounts I like",
|
||||
"newApprovalThreshold": "NONE",
|
||||
"newMinimumBlockDelay": 5,
|
||||
"newMaximumBlockDelay": 60
|
||||
}
|
||||
})
|
||||
return myTxnrequest
|
||||
}
|
||||
|
||||
const getTxnRequestResponse = (txnResponse) => {
|
||||
|
||||
if (txnResponse === true) {
|
||||
this.message = this.renderErr9Text()
|
||||
this.error = false
|
||||
this.confirmRelationship()
|
||||
} else {
|
||||
this.error = true
|
||||
this.message = ""
|
||||
throw new Error(txnResponse)
|
||||
}
|
||||
}
|
||||
validateReceiver()
|
||||
}
|
||||
|
||||
render() {
|
||||
console.log('leaveGroupObj', this.leaveGroupObj)
|
||||
return html`
|
||||
<vaadin-icon @click=${()=> {
|
||||
this.isOpenLeaveModal = true
|
||||
}} class="top-bar-icon" style="margin: 0px 20px" icon="vaadin:cog" slot="icon"></vaadin-icon>
|
||||
<!-- Leave Group Dialog -->
|
||||
<wrapper-modal
|
||||
.removeImage=${() => {
|
||||
if(this.isLoading) return
|
||||
this.isOpenLeaveModal = false
|
||||
} }
|
||||
style=${(this.isOpenLeaveModal) ? "display: block" : "display: none"}>
|
||||
<div style="text-align:center">
|
||||
<h1>${translate("grouppage.gchange35")}</h1>
|
||||
<hr>
|
||||
</div>
|
||||
<
|
||||
<button @click=${() => this._convertToPrivate(this.leaveGroupObj.groupId, this.leaveGroupObj.groupName)}> Convert a public group to private</button>
|
||||
|
||||
<div style="text-align:right; height:36px;">
|
||||
<span ?hidden="${!this.isLoading}">
|
||||
<!-- loading message -->
|
||||
${translate("grouppage.gchange36")}
|
||||
<paper-spinner-lite
|
||||
style="margin-top:12px;"
|
||||
?active="${this.isLoading}"
|
||||
alt="Leaving"
|
||||
>
|
||||
</paper-spinner-lite>
|
||||
</span>
|
||||
<span ?hidden=${this.message === ''} style="${this.error ? 'color:red;' : ''}">
|
||||
${this.message}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
|
||||
<button
|
||||
@click=${() => {
|
||||
this.isOpenLeaveModal= false
|
||||
}}
|
||||
class="modal-button"
|
||||
?disabled="${this.isLoading}"
|
||||
|
||||
>
|
||||
${translate("general.close")}
|
||||
</button>
|
||||
</wrapper-modal >
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('chat-group-settings', ChatGroupSettings);
|
@ -0,0 +1,296 @@
|
||||
import { LitElement, html, css } from 'lit';
|
||||
import { render } from 'lit/html.js';
|
||||
import { get, translate } from 'lit-translate';
|
||||
import { Epml } from '../../../epml';
|
||||
import snackbar from './snackbar.js'
|
||||
import '@material/mwc-button';
|
||||
import '@material/mwc-dialog';
|
||||
import '@polymer/paper-spinner/paper-spinner-lite.js'
|
||||
import '@material/mwc-icon';
|
||||
import './WrapperModal';
|
||||
import '@vaadin/tabs'
|
||||
import '@vaadin/tabs/theme/material/vaadin-tabs.js';
|
||||
import '@vaadin/avatar';
|
||||
import '@vaadin/grid';
|
||||
import '@vaadin/grid/vaadin-grid-filter-column.js';
|
||||
import { columnBodyRenderer } from '@vaadin/grid/lit.js';
|
||||
|
||||
const parentEpml = new Epml({ type: 'WINDOW', source: window.parent })
|
||||
|
||||
class ChatGroupsManagement extends LitElement {
|
||||
static get properties() {
|
||||
return {
|
||||
isLoading: { type: Boolean },
|
||||
isOpenLeaveModal: {type: Boolean},
|
||||
leaveGroupObj: { type: Object },
|
||||
error: {type: Boolean},
|
||||
message: {type: String},
|
||||
chatHeads: {type: Array},
|
||||
setActiveChatHeadUrl: {attribute: false},
|
||||
selectedAddress: {attribute: Object},
|
||||
currentTab: {type: Number},
|
||||
groups: {type: Array}
|
||||
}
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.isLoading = false;
|
||||
this.isOpenLeaveModal = false
|
||||
this.leaveGroupObj = {}
|
||||
this.fee = null
|
||||
this.error = false
|
||||
this.message = ''
|
||||
this.chatHeads = []
|
||||
this.currentTab = 0
|
||||
this.groups = []
|
||||
}
|
||||
|
||||
static get styles() {
|
||||
return css`
|
||||
.top-bar-icon {
|
||||
cursor: pointer;
|
||||
height: 18px;
|
||||
width: 18px;
|
||||
transition: .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;
|
||||
}
|
||||
`
|
||||
}
|
||||
|
||||
async getJoinedGroups(){
|
||||
let joinedG = await parentEpml.request('apiCall', {
|
||||
url: `/groups/member/${this.selectedAddress.address}`
|
||||
})
|
||||
return joinedG
|
||||
}
|
||||
|
||||
async firstUpdated() {
|
||||
|
||||
try {
|
||||
let _joinedGroups = await this.getJoinedGroups()
|
||||
this.joinedGroups = _joinedGroups
|
||||
} catch (error) {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
_tabChanged(e) {
|
||||
this.currentTab = e.detail.value
|
||||
}
|
||||
|
||||
async unitFee() {
|
||||
const myNode = window.parent.reduxStore.getState().app.nodeConfig.knownNodes[window.parent.reduxStore.getState().app.nodeConfig.node]
|
||||
const nodeUrl = myNode.protocol + '://' + myNode.domain + ':' + myNode.port
|
||||
const url = `${nodeUrl}/transactions/unitfee?txType=LEAVE_GROUP`
|
||||
let fee = null
|
||||
|
||||
try {
|
||||
const res = await fetch(url)
|
||||
const data = await res.json()
|
||||
fee = (Number(data) / 1e8).toFixed(3)
|
||||
} catch (error) {
|
||||
fee = null
|
||||
}
|
||||
|
||||
return fee
|
||||
}
|
||||
|
||||
timeIsoString(timestamp) {
|
||||
let myTimestamp = timestamp === undefined ? 1587560082346 : timestamp
|
||||
let time = new Date(myTimestamp)
|
||||
return time.toISOString()
|
||||
}
|
||||
|
||||
resetDefaultSettings() {
|
||||
this.error = false
|
||||
this.message = ''
|
||||
this.isLoading = false
|
||||
}
|
||||
|
||||
renderErr9Text() {
|
||||
return html`${translate("grouppage.gchange49")}`
|
||||
}
|
||||
|
||||
async confirmRelationship() {
|
||||
|
||||
|
||||
let interval = null
|
||||
let stop = false
|
||||
const getAnswer = async () => {
|
||||
const currentChats = this.chatHeads
|
||||
|
||||
if (!stop) {
|
||||
stop = true;
|
||||
try {
|
||||
const findGroup = currentChats.find((item)=> item.groupId === this.leaveGroupObj.groupId)
|
||||
if (!findGroup) {
|
||||
clearInterval(interval)
|
||||
this.isLoading = false
|
||||
this.isOpenLeaveModal= false
|
||||
this.setActiveChatHeadUrl('')
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
}
|
||||
stop = false
|
||||
}
|
||||
};
|
||||
interval = setInterval(getAnswer, 5000);
|
||||
}
|
||||
|
||||
async _leaveGroup(groupId, groupName) {
|
||||
// Reset Default Settings...
|
||||
this.resetDefaultSettings()
|
||||
|
||||
const leaveFeeInput = await this.unitFee()
|
||||
if(!leaveFeeInput){
|
||||
throw Error()
|
||||
}
|
||||
this.isLoading = true
|
||||
|
||||
// Get Last Ref
|
||||
const getLastRef = async () => {
|
||||
let myRef = await parentEpml.request('apiCall', {
|
||||
type: 'api',
|
||||
url: `/addresses/lastreference/${this.selectedAddress.address}`
|
||||
})
|
||||
return myRef
|
||||
};
|
||||
|
||||
const validateReceiver = async () => {
|
||||
let lastRef = await getLastRef();
|
||||
let myTransaction = await makeTransactionRequest(lastRef)
|
||||
getTxnRequestResponse(myTransaction)
|
||||
|
||||
}
|
||||
|
||||
// Make Transaction Request
|
||||
const makeTransactionRequest = async (lastRef) => {
|
||||
let groupdialog3 = get("transactions.groupdialog3")
|
||||
let groupdialog4 = get("transactions.groupdialog4")
|
||||
let myTxnrequest = await parentEpml.request('transaction', {
|
||||
type: 32,
|
||||
nonce: this.selectedAddress.nonce,
|
||||
params: {
|
||||
fee: leaveFeeInput,
|
||||
registrantAddress: this.selectedAddress.address,
|
||||
rGroupName: groupName,
|
||||
rGroupId: groupId,
|
||||
lastReference: lastRef,
|
||||
groupdialog3: groupdialog3,
|
||||
groupdialog4: groupdialog4,
|
||||
}
|
||||
})
|
||||
return myTxnrequest
|
||||
}
|
||||
|
||||
const getTxnRequestResponse = (txnResponse) => {
|
||||
|
||||
if (txnResponse.success === false && txnResponse.message) {
|
||||
this.error = true
|
||||
this.message = txnResponse.message
|
||||
throw new Error(txnResponse)
|
||||
} else if (txnResponse.success === true && !txnResponse.data.error) {
|
||||
this.message = this.renderErr9Text()
|
||||
this.error = false
|
||||
this.confirmRelationship()
|
||||
} else {
|
||||
this.error = true
|
||||
this.message = txnResponse.data.message
|
||||
throw new Error(txnResponse)
|
||||
}
|
||||
}
|
||||
validateReceiver()
|
||||
}
|
||||
|
||||
nameRenderer(person){
|
||||
console.log({person})
|
||||
return html`
|
||||
<vaadin-horizontal-layout style="align-items: center;display:flex" theme="spacing">
|
||||
<vaadin-avatar style="margin-right:5px" img="${person.pictureUrl}" .name="${person.displayName}"></vaadin-avatar>
|
||||
<span> ${person.displayName} </span>
|
||||
</vaadin-horizontal-layout>
|
||||
`;
|
||||
};
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<!-- <vaadin-icon @click=${()=> {
|
||||
this.isOpenLeaveModal = true
|
||||
}} class="top-bar-icon" style="margin: 0px 20px" icon="vaadin:exit" slot="icon"></vaadin-icon> -->
|
||||
<!-- Leave Group Dialog -->
|
||||
<wrapper-modal
|
||||
.removeImage=${() => {
|
||||
if(this.isLoading) return
|
||||
this.isOpenLeaveModal = false
|
||||
} }
|
||||
customStyle=${"width: 90%; max-width: 900px; height: 90%"}
|
||||
style=${(this.isOpenLeaveModal) ? "display: block" : "display: none"}>
|
||||
<div style="width: 100%;height: 100%;display: flex; flex-direction: column;background:var(--mdc-theme-surface)">
|
||||
<div style="height: 50px;display: flex; flex:0">
|
||||
<vaadin-tabs id="tabs" selected="${this.currentTab}" @selected-changed="${this._tabChanged}" style="width: 100%">
|
||||
|
||||
<vaadin-tab>Groups</vaadin-tab>
|
||||
<vaadin-tab>Group Join Requests</vaadin-tab>
|
||||
<vaadin-tab>Invites</vaadin-tab>
|
||||
<vaadin-tab>Blocked Users</vaadin-tab>
|
||||
</vaadin-tabs>
|
||||
</div>
|
||||
|
||||
<div style="width: 100%;display: flex; flex-direction: column; flex-grow: 1; overflow:auto;background:var(--mdc-theme-surface)">
|
||||
|
||||
${this.currentTab === 0 ? html`
|
||||
<div>
|
||||
|
||||
|
||||
<!-- Groups tab -->
|
||||
<!-- Search groups and be able to join -->
|
||||
<p>Search groups</p>
|
||||
<!-- Click group and it goes to that group and open right panel and settings -->
|
||||
<p>Current groups as owner</p>
|
||||
<p>Current groups as member</p>
|
||||
</div>
|
||||
` : ''}
|
||||
|
||||
|
||||
</div>
|
||||
<div style="width: 100%;height: 50;display: flex; flex: 0">
|
||||
<button
|
||||
class="modal-button"
|
||||
?disabled="${this.isLoading}"
|
||||
@click=${() => this._leaveGroup(this.leaveGroupObj.groupId, this.leaveGroupObj.groupName)}
|
||||
>
|
||||
${translate("grouppage.gchange37")}
|
||||
</button>
|
||||
<button
|
||||
@click=${() => {
|
||||
this.isOpenLeaveModal= false
|
||||
}}
|
||||
class="modal-button"
|
||||
?disabled="${this.isLoading}"
|
||||
|
||||
>
|
||||
${translate("general.close")}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</wrapper-modal >
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('chat-groups-management', ChatGroupsManagement);
|
@ -13,14 +13,18 @@ class ChatHead extends LitElement {
|
||||
config: { type: Object },
|
||||
chatInfo: { type: Object },
|
||||
iconName: { type: String },
|
||||
activeChatHeadUrl: { type: String }
|
||||
activeChatHeadUrl: { type: String },
|
||||
isImageLoaded: { type: Boolean },
|
||||
setActiveChatHeadUrl: {attribute: false}
|
||||
}
|
||||
}
|
||||
|
||||
static get styles() {
|
||||
return css`
|
||||
li {
|
||||
padding: 10px 2px 20px 5px;
|
||||
|
||||
width: 100%;
|
||||
padding: 7px 5px 7px 5px;
|
||||
cursor: pointer;
|
||||
width: 100%;
|
||||
}
|
||||
@ -37,7 +41,7 @@ class ChatHead extends LitElement {
|
||||
.img-icon {
|
||||
float: left;
|
||||
font-size:40px;
|
||||
color: var(--black);
|
||||
color: var(--chat-group);
|
||||
}
|
||||
|
||||
.about {
|
||||
@ -76,14 +80,54 @@ class ChatHead extends LitElement {
|
||||
this.chatInfo = {}
|
||||
this.iconName = ''
|
||||
this.activeChatHeadUrl = ''
|
||||
this.isImageLoaded = false
|
||||
this.imageFetches = 0
|
||||
}
|
||||
|
||||
createImage(imageUrl) {
|
||||
const imageHTMLRes = new Image();
|
||||
imageHTMLRes.src = imageUrl;
|
||||
imageHTMLRes.style= "width:40px; height:40px; float: left; border-radius:50%";
|
||||
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;
|
||||
}, 500);
|
||||
} else {
|
||||
|
||||
|
||||
this.isImageLoaded = false
|
||||
}
|
||||
};
|
||||
return imageHTMLRes;
|
||||
}
|
||||
|
||||
render() {
|
||||
let avatarImg = '';
|
||||
let backupAvatarImg = ''
|
||||
if(this.chatInfo.name){
|
||||
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.chatInfo.name}/qortal_avatar?async=true&apiKey=${myNode.apiKey}`;
|
||||
avatarImg= this.createImage(avatarUrl)
|
||||
|
||||
}
|
||||
|
||||
return html`
|
||||
<li @click=${() => this.getUrl(this.chatInfo.url)} class="clearfix ${this.activeChatHeadUrl === this.chatInfo.url ? 'active' : ''}">
|
||||
<mwc-icon class="img-icon">account_circle</mwc-icon>
|
||||
${this.isImageLoaded ? html`${avatarImg}` : html`` }
|
||||
${!this.isImageLoaded && !this.chatInfo.name && !this.chatInfo.groupName ? html`<mwc-icon class="img-icon">account_circle</mwc-icon>` : html`` }
|
||||
${!this.isImageLoaded && this.chatInfo.name ? html`<div style="width:40px; height:40px; float: left; border-radius:50%; background: ${this.activeChatHeadUrl === this.chatInfo.url ? 'var(--chatHeadBgActive)' : 'var(--chatHeadBg)' }; color: ${this.activeChatHeadUrl === this.chatInfo.url ? 'var(--chatHeadTextActive)' : 'var(--chatHeadText)' }; font-weight:bold; display: flex; justify-content: center; align-items: center; text-transform: capitalize">${this.chatInfo.name.charAt(0)}</div>`: ''}
|
||||
${!this.isImageLoaded && this.chatInfo.groupName ? html`<div style="width:40px; height:40px; float: left; border-radius:50%; background: ${this.activeChatHeadUrl === this.chatInfo.url ? 'var(--chatHeadBgActive)' : 'var(--chatHeadBg)' }; color: ${this.activeChatHeadUrl === this.chatInfo.url ? 'var(--chatHeadTextActive)' : 'var(--chatHeadText)' }; font-weight:bold; display: flex; justify-content: center; align-items: center; text-transform: capitalize">${this.chatInfo.groupName.charAt(0)}</div>`: ''}
|
||||
<div class="about">
|
||||
<div class="name"><span style="float:left; padding-left: 8px; color: var(--black);">${this.chatInfo.groupName ? this.chatInfo.groupName : this.chatInfo.name !== undefined ? this.chatInfo.name : this.chatInfo.address.substr(0, 15)} </span> <mwc-icon style="float:right; padding: 0 1rem; color: var(--black);">${this.chatInfo.groupId !== undefined ? 'lock_open' : 'lock'}</mwc-icon> </div>
|
||||
<div class="name"><span style="float:left; padding-left: 8px; color: var(--chat-group);">${this.chatInfo.groupName ? this.chatInfo.groupName : this.chatInfo.name !== undefined ? this.chatInfo.name : this.chatInfo.address.substr(0, 15)} </span> <mwc-icon style="float:right; padding: 0 1rem; color: var(--chat-group);">${this.chatInfo.groupId !== undefined ? 'lock_open' : 'lock'}</mwc-icon> </div>
|
||||
</div>
|
||||
</li>
|
||||
`
|
||||
@ -108,8 +152,19 @@ class ChatHead extends LitElement {
|
||||
parentEpml.imReady()
|
||||
}
|
||||
|
||||
shouldUpdate(changedProperties) {
|
||||
if(changedProperties.has('activeChatHeadUrl')){
|
||||
return true
|
||||
}
|
||||
if(changedProperties.has('chatInfo')){
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
getUrl(chatUrl) {
|
||||
this.onPageNavigation(`/app/q-chat/${chatUrl}`)
|
||||
this.setActiveChatHeadUrl(chatUrl)
|
||||
}
|
||||
|
||||
onPageNavigation(pageUrl) {
|
||||
|
268
qortal-ui-plugins/plugins/core/components/ChatLeaveGroup.js
Normal file
@ -0,0 +1,268 @@
|
||||
import { LitElement, html, css } from 'lit';
|
||||
import { render } from 'lit/html.js';
|
||||
import { get, translate } from 'lit-translate';
|
||||
import { Epml } from '../../../epml';
|
||||
import snackbar from './snackbar.js'
|
||||
import '@material/mwc-button';
|
||||
import '@material/mwc-dialog';
|
||||
import '@polymer/paper-spinner/paper-spinner-lite.js'
|
||||
import '@material/mwc-icon';
|
||||
import './WrapperModal';
|
||||
const parentEpml = new Epml({ type: 'WINDOW', source: window.parent })
|
||||
|
||||
class ChatLeaveGroup extends LitElement {
|
||||
static get properties() {
|
||||
return {
|
||||
isLoading: { type: Boolean },
|
||||
isOpenLeaveModal: {type: Boolean},
|
||||
leaveGroupObj: { type: Object },
|
||||
error: {type: Boolean},
|
||||
message: {type: String},
|
||||
chatHeads: {type: Array},
|
||||
setActiveChatHeadUrl: {attribute: false},
|
||||
selectedAddress: {attribute: Object}
|
||||
}
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.isLoading = false;
|
||||
this.isOpenLeaveModal = false
|
||||
this.leaveGroupObj = {}
|
||||
this.fee = null
|
||||
this.error = false
|
||||
this.message = ''
|
||||
this.chatHeads = []
|
||||
}
|
||||
|
||||
static get styles() {
|
||||
return css`
|
||||
.top-bar-icon {
|
||||
cursor: pointer;
|
||||
height: 18px;
|
||||
width: 18px;
|
||||
transition: .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;
|
||||
}
|
||||
`
|
||||
}
|
||||
|
||||
firstUpdated() {
|
||||
|
||||
}
|
||||
|
||||
async unitFee() {
|
||||
const myNode = window.parent.reduxStore.getState().app.nodeConfig.knownNodes[window.parent.reduxStore.getState().app.nodeConfig.node]
|
||||
const nodeUrl = myNode.protocol + '://' + myNode.domain + ':' + myNode.port
|
||||
const url = `${nodeUrl}/transactions/unitfee?txType=LEAVE_GROUP`
|
||||
let fee = null
|
||||
|
||||
try {
|
||||
const res = await fetch(url)
|
||||
const data = await res.json()
|
||||
fee = (Number(data) / 1e8).toFixed(3)
|
||||
} catch (error) {
|
||||
fee = null
|
||||
}
|
||||
|
||||
return fee
|
||||
}
|
||||
|
||||
timeIsoString(timestamp) {
|
||||
let myTimestamp = timestamp === undefined ? 1587560082346 : timestamp
|
||||
let time = new Date(myTimestamp)
|
||||
return time.toISOString()
|
||||
}
|
||||
|
||||
resetDefaultSettings() {
|
||||
this.error = false
|
||||
this.message = ''
|
||||
this.isLoading = false
|
||||
}
|
||||
|
||||
renderErr9Text() {
|
||||
return html`${translate("grouppage.gchange49")}`
|
||||
}
|
||||
|
||||
async confirmRelationship() {
|
||||
|
||||
|
||||
let interval = null
|
||||
let stop = false
|
||||
const getAnswer = async () => {
|
||||
const currentChats = this.chatHeads
|
||||
|
||||
if (!stop) {
|
||||
stop = true;
|
||||
try {
|
||||
const findGroup = currentChats.find((item)=> item.groupId === this.leaveGroupObj.groupId)
|
||||
if (!findGroup) {
|
||||
clearInterval(interval)
|
||||
this.isLoading = false
|
||||
this.isOpenLeaveModal= false
|
||||
this.setActiveChatHeadUrl('')
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
}
|
||||
stop = false
|
||||
}
|
||||
};
|
||||
interval = setInterval(getAnswer, 5000);
|
||||
}
|
||||
|
||||
async _leaveGroup(groupId, groupName) {
|
||||
// Reset Default Settings...
|
||||
this.resetDefaultSettings()
|
||||
|
||||
const leaveFeeInput = await this.unitFee()
|
||||
if(!leaveFeeInput){
|
||||
throw Error()
|
||||
}
|
||||
this.isLoading = true
|
||||
|
||||
// Get Last Ref
|
||||
const getLastRef = async () => {
|
||||
let myRef = await parentEpml.request('apiCall', {
|
||||
type: 'api',
|
||||
url: `/addresses/lastreference/${this.selectedAddress.address}`
|
||||
})
|
||||
return myRef
|
||||
};
|
||||
|
||||
const validateReceiver = async () => {
|
||||
let lastRef = await getLastRef();
|
||||
let myTransaction = await makeTransactionRequest(lastRef)
|
||||
getTxnRequestResponse(myTransaction)
|
||||
|
||||
}
|
||||
|
||||
// Make Transaction Request
|
||||
const makeTransactionRequest = async (lastRef) => {
|
||||
let groupdialog3 = get("transactions.groupdialog3")
|
||||
let groupdialog4 = get("transactions.groupdialog4")
|
||||
let myTxnrequest = await parentEpml.request('transaction', {
|
||||
type: 32,
|
||||
nonce: this.selectedAddress.nonce,
|
||||
params: {
|
||||
fee: leaveFeeInput,
|
||||
registrantAddress: this.selectedAddress.address,
|
||||
rGroupName: groupName,
|
||||
rGroupId: groupId,
|
||||
lastReference: lastRef,
|
||||
groupdialog3: groupdialog3,
|
||||
groupdialog4: groupdialog4,
|
||||
}
|
||||
})
|
||||
return myTxnrequest
|
||||
}
|
||||
|
||||
const getTxnRequestResponse = (txnResponse) => {
|
||||
|
||||
if (txnResponse.success === false && txnResponse.message) {
|
||||
this.error = true
|
||||
this.message = txnResponse.message
|
||||
throw new Error(txnResponse)
|
||||
} else if (txnResponse.success === true && !txnResponse.data.error) {
|
||||
this.message = this.renderErr9Text()
|
||||
this.error = false
|
||||
this.confirmRelationship()
|
||||
} else {
|
||||
this.error = true
|
||||
this.message = txnResponse.data.message
|
||||
throw new Error(txnResponse)
|
||||
}
|
||||
}
|
||||
validateReceiver()
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<vaadin-icon @click=${()=> {
|
||||
this.isOpenLeaveModal = true
|
||||
}} class="top-bar-icon" style="margin: 0px 20px" icon="vaadin:exit" slot="icon"></vaadin-icon>
|
||||
<!-- Leave Group Dialog -->
|
||||
<wrapper-modal
|
||||
.removeImage=${() => {
|
||||
if(this.isLoading) return
|
||||
this.isOpenLeaveModal = false
|
||||
} }
|
||||
style=${(this.isOpenLeaveModal) ? "display: block" : "display: none"}>
|
||||
<div style="text-align:center">
|
||||
<h1>${translate("grouppage.gchange35")}</h1>
|
||||
<hr>
|
||||
</div>
|
||||
|
||||
<div class="itemList">
|
||||
<span class="title">${translate("grouppage.gchange4")}</span>
|
||||
<br>
|
||||
<div><span>${this.leaveGroupObj.groupName}</span></div>
|
||||
|
||||
<span class="title">${translate("grouppage.gchange5")}</span>
|
||||
<br>
|
||||
<div><span>${this.leaveGroupObj.description}</span></div>
|
||||
|
||||
<span class="title">${translate("grouppage.gchange10")}</span>
|
||||
<br>
|
||||
<div><span>${this.leaveGroupObj.owner}</span></div>
|
||||
|
||||
<span class="title">${translate("grouppage.gchange31")}</span>
|
||||
<br>
|
||||
<div><span><time-ago datetime=${this.timeIsoString(this.leaveGroupObj.created)}></time-ago></span></div>
|
||||
|
||||
${!this.leaveGroupObj.updated ? "" : html`<span class="title">${translate("grouppage.gchange32")}</span>
|
||||
<br>
|
||||
<div><span><time-ago datetime=${this.timeIsoString(this.leaveGroupObj.updated)}></time-ago></span></div>`}
|
||||
</div>
|
||||
|
||||
<div style="text-align:right; height:36px;">
|
||||
<span ?hidden="${!this.isLoading}">
|
||||
<!-- loading message -->
|
||||
${translate("grouppage.gchange36")}
|
||||
<paper-spinner-lite
|
||||
style="margin-top:12px;"
|
||||
?active="${this.isLoading}"
|
||||
alt="Leaving"
|
||||
>
|
||||
</paper-spinner-lite>
|
||||
</span>
|
||||
<span ?hidden=${this.message === ''} style="${this.error ? 'color:red;' : ''}">
|
||||
${this.message}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<button
|
||||
class="modal-button"
|
||||
?disabled="${this.isLoading}"
|
||||
@click=${() => this._leaveGroup(this.leaveGroupObj.groupId, this.leaveGroupObj.groupName)}
|
||||
>
|
||||
${translate("grouppage.gchange37")}
|
||||
</button>
|
||||
<button
|
||||
@click=${() => {
|
||||
this.isOpenLeaveModal= false
|
||||
}}
|
||||
class="modal-button"
|
||||
?disabled="${this.isLoading}"
|
||||
|
||||
>
|
||||
${translate("general.close")}
|
||||
</button>
|
||||
</wrapper-modal >
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('chat-leave-group', ChatLeaveGroup);
|
@ -92,11 +92,11 @@ class ChatModals extends LitElement {
|
||||
// Send Private Message
|
||||
|
||||
_sendMessage() {
|
||||
this.isLoading = true
|
||||
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
|
||||
@ -105,22 +105,21 @@ class ChatModals extends LitElement {
|
||||
} 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
|
||||
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 myRes;
|
||||
let myNameRes = await parentEpml.request('apiCall', {
|
||||
type: 'api',
|
||||
url: `/names/${receiverName}`
|
||||
})
|
||||
});
|
||||
|
||||
if (myNameRes.error === 401) {
|
||||
myRes = false
|
||||
@ -128,7 +127,7 @@ class ChatModals extends LitElement {
|
||||
myRes = myNameRes
|
||||
}
|
||||
|
||||
return myRes
|
||||
return myRes;
|
||||
}
|
||||
|
||||
const myNameRes = await validateName(_recipient)
|
||||
@ -139,7 +138,6 @@ class ChatModals extends LitElement {
|
||||
|
||||
recipient = myNameRes.owner
|
||||
}
|
||||
|
||||
let _reference = new Uint8Array(64);
|
||||
window.crypto.getRandomValues(_reference);
|
||||
|
||||
@ -175,7 +173,13 @@ 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,
|
||||
@ -184,7 +188,7 @@ class ChatModals extends LitElement {
|
||||
recipient: recipient,
|
||||
recipientPublicKey: _publicKey,
|
||||
hasChatReference: 0,
|
||||
message: messageText,
|
||||
message: stringifyMessageObject,
|
||||
lastReference: reference,
|
||||
proofOfWorkNonce: 0,
|
||||
isEncrypted: isEncrypted,
|
||||
@ -361,7 +365,10 @@ class ChatModals extends LitElement {
|
||||
<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 ?disabled='${this.isLoading}' slot='primaryAction' @click=${() => {
|
||||
this._sendMessage();
|
||||
}
|
||||
}>${translate('welcomepage.wcchange6')}
|
||||
</mwc-button>
|
||||
<mwc-button
|
||||
?disabled='${this.isLoading}'
|
||||
|
306
qortal-ui-plugins/plugins/core/components/ChatRightPanel.js
Normal file
@ -0,0 +1,306 @@
|
||||
import { LitElement, html, css } from "lit";
|
||||
import { render } from "lit/html.js";
|
||||
import { get, translate } from "lit-translate";
|
||||
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";
|
||||
|
||||
class ChatRightPanel 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 },
|
||||
}
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
this.leaveGroupObj = {}
|
||||
this.leaveFee = 0.001
|
||||
this.error = false
|
||||
this.chatHeads = []
|
||||
this.groupAdmin = []
|
||||
this.groupMembers = []
|
||||
this.observerHandler = this.observerHandler.bind(this)
|
||||
this.viewElement = ''
|
||||
this.downObserverElement = ''
|
||||
this.myAddress = window.parent.reduxStore.getState().app.selectedAddress.address
|
||||
this.sendMoneyLoading = false
|
||||
this.btnDisable = false
|
||||
this.errorMessage = ""
|
||||
this.successMessage = ""
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
}
|
||||
|
||||
.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);
|
||||
}
|
||||
`
|
||||
}
|
||||
|
||||
firstUpdated() {
|
||||
this.viewElement = this.shadowRoot.getElementById('viewElement');
|
||||
this.downObserverElement = this.shadowRoot.getElementById('downObserver');
|
||||
this.elementObserver();
|
||||
}
|
||||
|
||||
async updated(changedProperties) {
|
||||
if (changedProperties && changedProperties.has('selectedHead')) {
|
||||
if (this.selectedHead !== {}) {
|
||||
const userName = await getUserNameFromAddress(this.selectedHead.address);
|
||||
this.userName = userName;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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.groupMembers.length < 20){
|
||||
return
|
||||
}
|
||||
console.log('this.leaveGroupObjp', this.leaveGroupObj)
|
||||
this.getMoreMembers(this.leaveGroupObj.groupId)
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const owner = this.groupAdmin.filter((admin)=> admin.address === this.leaveGroupObj.owner)
|
||||
return html`
|
||||
<div class="container">
|
||||
<div class="close-row" style="margin-top: 15px">
|
||||
<vaadin-icon class="top-bar-icon" @click=${()=> this.toggle(false)} style="margin: 0px 10px" icon="vaadin:close" slot="icon"></vaadin-icon>
|
||||
</div>
|
||||
<div id="viewElement" class="container-body">
|
||||
<p class="group-name">${this.leaveGroupObj && this.leaveGroupObj.groupName}</p>
|
||||
<div class="group-info">
|
||||
<p class="group-description">${this.leaveGroupObj && this.leaveGroupObj.description}</p>
|
||||
<p class="group-subheader">Members: <span class="group-data">${this.leaveGroupObj && this.leaveGroupObj.memberCount}</span></p>
|
||||
|
||||
<p class="group-subheader">Date created : <span class="group-data">${new Date(this.leaveGroupObj.created).toLocaleDateString("en-US")}</span></p>
|
||||
</div>
|
||||
<br />
|
||||
<p class="chat-right-panel-label">GROUP OWNER</p>
|
||||
${owner.map((item) => {
|
||||
return html`<chat-side-nav-heads
|
||||
activeChatHeadUrl=""
|
||||
.setActiveChatHeadUrl=${(val) => {
|
||||
if (val.address === this.myAddress) return;
|
||||
console.log({ val });
|
||||
this.selectedHead = val;
|
||||
this.setOpenUserInfo(true);
|
||||
this.setUserName({
|
||||
sender: val.address,
|
||||
senderName: val.name ? val.name : ""
|
||||
});
|
||||
}}
|
||||
chatInfo=${JSON.stringify(item)}
|
||||
></chat-side-nav-heads>`
|
||||
})}
|
||||
<p class="chat-right-panel-label">ADMINS</p>
|
||||
${this.groupAdmin.map((item) => {
|
||||
return html`<chat-side-nav-heads
|
||||
activeChatHeadUrl=""
|
||||
.setActiveChatHeadUrl=${(val) => {
|
||||
if (val.address === this.myAddress) return;
|
||||
console.log({ val });
|
||||
this.selectedHead = val;
|
||||
this.setOpenUserInfo(true);
|
||||
this.setUserName({
|
||||
sender: val.address,
|
||||
senderName: val.name ? val.name : ""
|
||||
});
|
||||
}}
|
||||
chatInfo=${JSON.stringify(item)}
|
||||
></chat-side-nav-heads>`
|
||||
})}
|
||||
<p class="chat-right-panel-label">MEMBERS</p>
|
||||
${this.groupMembers.map((item) => {
|
||||
return html`<chat-side-nav-heads
|
||||
activeChatHeadUrl=""
|
||||
.setActiveChatHeadUrl=${(val) => {
|
||||
if (val.address === this.myAddress) return;
|
||||
console.log({ val });
|
||||
this.selectedHead = val;
|
||||
this.setOpenUserInfo(true);
|
||||
this.setUserName({
|
||||
sender: val.address,
|
||||
senderName: val.name ? val.name : ""
|
||||
});
|
||||
}}
|
||||
chatInfo=${JSON.stringify(item)}
|
||||
></chat-side-nav-heads>`
|
||||
})}
|
||||
<div id='downObserver'></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("chat-right-panel", ChatRightPanel)
|
@ -15,6 +15,12 @@ export const chatStyles = css`
|
||||
scrollbar-color: var(--thumbBG) var(--scrollbarBG);
|
||||
--mdc-theme-primary: rgb(3, 169, 244);
|
||||
--mdc-theme-secondary: var(--mdc-theme-primary);
|
||||
--mdc-dialog-max-width: 85vw;
|
||||
--mdc-dialog-max-height: 95vh;
|
||||
}
|
||||
|
||||
* :focus-visible {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
*::-webkit-scrollbar-track {
|
||||
@ -35,110 +41,214 @@ export const chatStyles = css`
|
||||
ul {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
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);
|
||||
padding: 20px 17px;
|
||||
}
|
||||
|
||||
.chat-list {
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
height: 92vh;
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.message-data {
|
||||
width: 92%;
|
||||
margin-bottom: 15px;
|
||||
margin-left: 50px;
|
||||
margin-left: 55px;
|
||||
}
|
||||
|
||||
.message-data-name {
|
||||
color: var(--black);
|
||||
user-select: none;
|
||||
color: #03a9f4;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.forwarded-text {
|
||||
user-select: none;
|
||||
color: #03a9f4;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.message-data-forward {
|
||||
user-select: none;
|
||||
color: var(--mainmenutext);
|
||||
margin-bottom: 5px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.message-data-my-name {
|
||||
color: #cf21e8;
|
||||
text-shadow: 0 0 3px #cf21e8;
|
||||
}
|
||||
|
||||
.message-data-time {
|
||||
color: #a8aab1;
|
||||
color: #888888;
|
||||
font-size: 13px;
|
||||
padding-left: 6px;
|
||||
padding-bottom: 4px;
|
||||
user-select: none;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
padding-top: 2px;
|
||||
}
|
||||
|
||||
.message-data-level {
|
||||
color: #03a9f4;
|
||||
.message-data-time-hidden {
|
||||
visibility: hidden;
|
||||
transition: all 0.1s ease-in-out;
|
||||
color: #888888;
|
||||
font-size: 13px;
|
||||
padding-left: 8px;
|
||||
padding-bottom: 4px;
|
||||
user-select: none;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
padding-top: 2px;
|
||||
}
|
||||
|
||||
.message-user-info {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.chat-bubble-container {
|
||||
display:flex;
|
||||
gap: 7px;
|
||||
}
|
||||
|
||||
.message-container {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.message {
|
||||
color: black;
|
||||
padding: 12px 10px;
|
||||
.message-subcontainer1 {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
.message-subcontainer2 {
|
||||
position: relative;
|
||||
display: flex;
|
||||
background-color: var(--chat-bubble-bg);
|
||||
flex-grow: 0;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
justify-content: center;
|
||||
border-radius: 5px;
|
||||
padding: 12px 15px 4px 15px;
|
||||
width: fit-content;
|
||||
min-width: 150px;
|
||||
}
|
||||
|
||||
.message-triangle {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.message-triangle:after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
bottom: 0px;
|
||||
left: -9px;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-style: solid;
|
||||
border-width: 0px 0px 7px 9px;
|
||||
border-color: transparent transparent var(--chat-bubble-bg) transparent;
|
||||
}
|
||||
|
||||
.message-reactions {
|
||||
background-color: transparent;
|
||||
width: calc(100% - 54px);
|
||||
margin-left: 54px;
|
||||
}
|
||||
|
||||
.original-message {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
color: var(--chat-bubble-msg-color);
|
||||
line-height: 19px;
|
||||
white-space: pre-line;
|
||||
word-wrap: break-word;
|
||||
user-select: text;
|
||||
font-size: 15px;
|
||||
width: 90%;
|
||||
border-radius: 5px;
|
||||
padding: 8px 5px 8px 25px;
|
||||
margin-bottom: 10px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.original-message:before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
left: 10px;
|
||||
height: 75%;
|
||||
width: 2.6px;
|
||||
background-color: var(--mdc-theme-primary);
|
||||
}
|
||||
|
||||
.original-message-sender {
|
||||
margin: 0 0 5px 0;
|
||||
color: var(--mdc-theme-primary);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.replied-message {
|
||||
margin: 0;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
max-width: 300px;
|
||||
max-height: 40px;
|
||||
}
|
||||
.replied-message p {
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
.message {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
color: var(--chat-bubble-msg-color);
|
||||
line-height: 19px;
|
||||
overflow-wrap: anywhere;
|
||||
-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%;
|
||||
width: 100%;
|
||||
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-data-avatar {
|
||||
margin: 0px 10px 0px 3px;
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
float: left;
|
||||
}
|
||||
|
||||
.message-parent {
|
||||
padding: 3px;
|
||||
background: rgba(245, 245, 245, 0);
|
||||
transition: all 0.1s ease-in-out;
|
||||
}
|
||||
|
||||
.message-parent:hover {
|
||||
background: var(--chat-bubble);
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.message-parent:hover .chat-hover {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.message-parent:hover .message{
|
||||
filter:brightness(0.90);
|
||||
.message-parent:hover .message-data-time-hidden {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.chat-hover {
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: -38px;
|
||||
left: 88.2%;
|
||||
top: -25px;
|
||||
right: 5px;
|
||||
}
|
||||
|
||||
.emoji {
|
||||
@ -149,26 +259,6 @@ export const chatStyles = css`
|
||||
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;
|
||||
}
|
||||
@ -202,25 +292,29 @@ export const chatStyles = css`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
background-color: white;
|
||||
border: 1px solid #dad9d9;
|
||||
background-color: var(--chat-menu-bg);
|
||||
border: 1px solid var(--chat-menu-outline);
|
||||
border-radius: 5px;
|
||||
height:100%;
|
||||
width: 100px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.container:focus-visible {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.menu-icon {
|
||||
width: 100%;
|
||||
padding: 5px;
|
||||
padding: 5px 7px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 13px;
|
||||
color: var(--chat-menu-icon);
|
||||
}
|
||||
|
||||
.menu-icon:hover {
|
||||
background-color: #dad9d9;
|
||||
border-radius: 5px;
|
||||
background-color: var(--chat-menu-icon-hover);
|
||||
transition: all 0.1s ease-in-out;
|
||||
cursor: pointer;
|
||||
}
|
||||
@ -231,11 +325,12 @@ export const chatStyles = css`
|
||||
|
||||
.tooltip:before {
|
||||
content: attr(data-text);
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: -47px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 90px;
|
||||
width: auto;
|
||||
padding: 10px;
|
||||
border-radius: 10px;
|
||||
background:#fff;
|
||||
@ -244,7 +339,8 @@ export const chatStyles = css`
|
||||
box-shadow: rgba(149, 157, 165, 0.2) 0px 8px 24px;
|
||||
font-size: 12px;
|
||||
z-index: 5;
|
||||
display: none;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.tooltip:hover:before {
|
||||
@ -269,17 +365,299 @@ export const chatStyles = css`
|
||||
.block-user-container {
|
||||
display: block;
|
||||
position: absolute;
|
||||
left: -48px;
|
||||
left: -5px;
|
||||
}
|
||||
|
||||
.block-user {
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
padding: 5px 7px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 13px;
|
||||
color: var(--chat-menu-icon);
|
||||
justify-content: space-evenly;
|
||||
border: 1px solid rgb(218, 217, 217);
|
||||
border-radius: 5px;
|
||||
background-color: white;
|
||||
width: 100%;
|
||||
background-color: var(--chat-menu-bg);
|
||||
width: 150px;
|
||||
height: 32px;
|
||||
padding: 3px 8px;
|
||||
box-shadow: rgba(77, 77, 82, 0.2) 0px 7px 29px 0px;
|
||||
}
|
||||
|
||||
.block-user:hover {
|
||||
cursor:pointer;
|
||||
background-color: var(--block-user-bg-hover);
|
||||
transition: all 0.1s ease-in-out 0s;
|
||||
}
|
||||
|
||||
.reactions-bg {
|
||||
background-color: #d5d5d5;
|
||||
border-radius: 10px;
|
||||
padding: 5px;
|
||||
color: black;
|
||||
margin-right: 10px;
|
||||
transition: all 0.1s ease-in-out;
|
||||
border: 0.5px solid transparent;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.reactions-bg:hover {
|
||||
border: 0.5px solid var(--reaction-bubble-outline);
|
||||
}
|
||||
|
||||
.image-container {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.message-data-level {
|
||||
height: 21px;
|
||||
width: 21px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.defaultSize {
|
||||
width: 45vh;
|
||||
height: 40vh;
|
||||
}
|
||||
|
||||
.image-deleted-msg {
|
||||
font-family: Roboto, sans-serif;
|
||||
font-size: 14px;
|
||||
font-style: italic;
|
||||
color: var(--chat-bubble-msg-color);
|
||||
margin: 0;
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
.image-delete-icon {
|
||||
margin-left: 5px;
|
||||
height: 20px;
|
||||
cursor: pointer;
|
||||
visibility: hidden;
|
||||
transition: .2s all;
|
||||
opacity: 0.8;
|
||||
color: rgb(228, 222, 222);
|
||||
padding-left: 7px;
|
||||
}
|
||||
|
||||
.image-delete-icon:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.message-parent:hover .image-delete-icon {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.imageContainer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
}
|
||||
.spinnerContainer {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
justify-content: center
|
||||
}
|
||||
|
||||
.delete-image-msg {
|
||||
font-family: Livvic, sans-serif;
|
||||
font-size: 20px;
|
||||
color: var(--chat-bubble-msg-color);
|
||||
letter-spacing: 0.3px;
|
||||
font-weight: 300;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.modal-button-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
#messageContent p {
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
#messageContent p mark {
|
||||
background-color: #ffe066;
|
||||
border-radius: 0.25em;
|
||||
box-decoration-break: clone;
|
||||
padding: 0.125em 0;
|
||||
}
|
||||
|
||||
#messageContent > * + * {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
#messageContent ul,
|
||||
ol {
|
||||
padding: 0 1rem;
|
||||
}
|
||||
|
||||
#messageContent h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
#messageContent code {
|
||||
background-color: rgba(#616161, 0.1);
|
||||
color: #616161;
|
||||
}
|
||||
|
||||
#messageContent pre {
|
||||
background: #0D0D0D;
|
||||
color: #FFF;
|
||||
font-family: 'JetBrainsMono', monospace;
|
||||
padding: 0.75rem 1rem;
|
||||
border-radius: 0.5rem;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
#messageContent pre code {
|
||||
color: inherit;
|
||||
padding: 0;
|
||||
background: none;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
|
||||
#messageContent img {
|
||||
width: 1.7em;
|
||||
height: 1.5em;
|
||||
margin: 0px;
|
||||
|
||||
}
|
||||
|
||||
#messageContent blockquote {
|
||||
padding-left: 1rem;
|
||||
border-left: 2px solid rgba(#0D0D0D, 0.1);
|
||||
}
|
||||
|
||||
#messageContent hr {
|
||||
border: none;
|
||||
border-top: 2px solid rgba(#0D0D0D, 0.1);
|
||||
margin: 2rem 0;
|
||||
}
|
||||
|
||||
.replied-message p {
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
.replied-message > * + * {
|
||||
margin-top: 0.75em;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.replied-message ul,
|
||||
ol {
|
||||
padding: 0 1rem;
|
||||
}
|
||||
|
||||
.replied-message h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
.replied-message code {
|
||||
background-color: rgba(#616161, 0.1);
|
||||
color: #616161;
|
||||
}
|
||||
|
||||
.replied-message pre {
|
||||
background: #0D0D0D;
|
||||
color: #FFF;
|
||||
font-family: 'JetBrainsMono', monospace;
|
||||
padding: 0.75rem 1rem;
|
||||
border-radius: 0.5rem;
|
||||
white-space: pre-wrap;
|
||||
margin: 0px;
|
||||
}
|
||||
.replied-message pre code {
|
||||
color: inherit;
|
||||
padding: 0;
|
||||
background: none;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
|
||||
.replied-message img {
|
||||
width: 1.7em;
|
||||
height: 1.5em;
|
||||
margin: 0px;
|
||||
|
||||
}
|
||||
|
||||
.replied-message blockquote {
|
||||
padding-left: 1rem;
|
||||
border-left: 2px solid rgba(#0D0D0D, 0.1);
|
||||
}
|
||||
|
||||
.replied-message hr {
|
||||
border: none;
|
||||
border-top: 2px solid rgba(#0D0D0D, 0.1);
|
||||
margin: 2rem 0;
|
||||
}
|
||||
|
||||
.edited-message-style {
|
||||
font-family: "Work Sans", sans-serif;
|
||||
font-style: italic;
|
||||
font-size: 13px;
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.blink-bg{
|
||||
border-radius: 8px;
|
||||
animation: blinkingBackground 3s;
|
||||
}
|
||||
@keyframes blinkingBackground{
|
||||
0% { background-color: rgba(var(--menuactivergb), 1)}
|
||||
|
||||
100% { background-color:rgba(var(--menuactivergb), 0)}
|
||||
}
|
||||
|
||||
|
||||
`
|
||||
|
@ -5,26 +5,54 @@ import { translate, get } 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 './LevelFounder.js';
|
||||
import './NameMenu.js';
|
||||
import './ChatModals.js';
|
||||
import './WrapperModal';
|
||||
import "./UserInfo/UserInfo";
|
||||
import '@vaadin/icons';
|
||||
import '@vaadin/icon';
|
||||
import '@material/mwc-button';
|
||||
import '@material/mwc-dialog';
|
||||
import '@material/mwc-icon';
|
||||
import { EmojiPicker } from 'emoji-picker-js';
|
||||
import { generateHTML } from '@tiptap/core'
|
||||
import StarterKit from '@tiptap/starter-kit'
|
||||
import Underline from '@tiptap/extension-underline';
|
||||
import Highlight from '@tiptap/extension-highlight'
|
||||
|
||||
const parentEpml = new Epml({ type: 'WINDOW', source: window.parent })
|
||||
let toggledMessage = {}
|
||||
class ChatScroller extends LitElement {
|
||||
static get properties() {
|
||||
return {
|
||||
getNewMessage: { attribute: false },
|
||||
getOldMessage: { attribute: false },
|
||||
emojiPicker: { attribute: false },
|
||||
escapeHTML: { attribute: false },
|
||||
initialMessages: { type: Array }, // First set of messages to load.. 15 messages max ( props )
|
||||
messages: { type: Array },
|
||||
hideMessages: { type: Array }
|
||||
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 },
|
||||
getOldMessageAfter: {attribute: false}
|
||||
}
|
||||
}
|
||||
|
||||
@ -37,60 +65,164 @@ class ChatScroller extends LitElement {
|
||||
this._downObserverHandler = this._downObserverHandler.bind(this)
|
||||
this.myAddress = window.parent.reduxStore.getState().app.selectedAddress.address
|
||||
this.hideMessages = JSON.parse(localStorage.getItem("MessageBlockedAddresses") || "[]")
|
||||
this.openTipUser = false;
|
||||
this.openUserInfo = false;
|
||||
}
|
||||
|
||||
|
||||
render() {
|
||||
let formattedMessages = this.messages.reduce((messageArray, message, index) => {
|
||||
const lastGroupedMessage = messageArray[messageArray.length - 1];
|
||||
let timestamp;
|
||||
let sender;
|
||||
let repliedToData;
|
||||
|
||||
let firstMessageInChat;
|
||||
|
||||
if (index === 0) {
|
||||
firstMessageInChat = true;
|
||||
} else {
|
||||
firstMessageInChat = false;
|
||||
}
|
||||
|
||||
message = {...message, firstMessageInChat}
|
||||
|
||||
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({
|
||||
messages: [message],
|
||||
...message
|
||||
});
|
||||
}
|
||||
return messageArray;
|
||||
}, [])
|
||||
|
||||
|
||||
return html`
|
||||
${this.isLoadingMessages ? html`
|
||||
<div class="spinnerContainer">
|
||||
<paper-spinner-lite active></paper-spinner-lite>
|
||||
</div>
|
||||
` : ''}
|
||||
<ul id="viewElement" class="chat-list clearfix">
|
||||
<div id="upObserver"></div>
|
||||
${repeat(
|
||||
this.messages,
|
||||
${formattedMessages.map((formattedMessage) => {
|
||||
return repeat(
|
||||
formattedMessage.messages,
|
||||
(message) => message.reference,
|
||||
(message) => html`<message-template .emojiPicker=${this.emojiPicker} .escapeHTML=${this.escapeHTML} .messageObj=${message} .hideMessages=${this.hideMessages}></message-template>`
|
||||
)}
|
||||
(message, indexMessage) => html`
|
||||
<message-template
|
||||
.emojiPicker=${this.emojiPicker}
|
||||
.escapeHTML=${this.escapeHTML}
|
||||
.messageObj=${message}
|
||||
.hideMessages=${this.hideMessages}
|
||||
.setRepliedToMessageObj=${this.setRepliedToMessageObj}
|
||||
.setEditedMessageObj=${this.setEditedMessageObj}
|
||||
.sendMessage=${this.sendMessage}
|
||||
.sendMessageForward=${this.sendMessageForward}
|
||||
?isFirstMessage=${indexMessage === 0}
|
||||
?isSingleMessageInGroup=${formattedMessage.messages.length > 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.reference}
|
||||
.goToRepliedMessage=${this.goToRepliedMessage}
|
||||
>
|
||||
</message-template>`
|
||||
)
|
||||
})}
|
||||
<div id='downObserver'></div>
|
||||
<div class='last-message-ref'>
|
||||
<vaadin-icon icon='vaadin:arrow-circle-down' slot='icon' @click=${() => {
|
||||
this.shadowRoot.getElementById('downObserver').scrollIntoView({
|
||||
behavior: 'smooth',
|
||||
})
|
||||
}}>
|
||||
</vaadin-icon>
|
||||
</div>
|
||||
</ul>
|
||||
`
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
// 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.viewElement = this.shadowRoot.getElementById('viewElement')
|
||||
this.upObserverElement = this.shadowRoot.getElementById('upObserver')
|
||||
this.downObserverElement = this.shadowRoot.getElementById('downObserver')
|
||||
|
||||
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');
|
||||
// Intialize Observers
|
||||
this.upElementObserver()
|
||||
this.downElementObserver()
|
||||
await this.updateComplete
|
||||
this.viewElement.scrollTop = this.viewElement.scrollHeight + 50
|
||||
this.upElementObserver();
|
||||
this.downElementObserver();
|
||||
await this.getUpdateComplete();
|
||||
this.viewElement.scrollTop = this.viewElement.scrollHeight + 50;
|
||||
}
|
||||
|
||||
_getOldMessage(_scrollElement) {
|
||||
this.getOldMessage(_scrollElement)
|
||||
}
|
||||
|
||||
_getOldMessageAfter(_scrollElement) {
|
||||
this.getOldMessageAfter(_scrollElement)
|
||||
}
|
||||
|
||||
_upObserverhandler(entries) {
|
||||
if (entries[0].isIntersecting) {
|
||||
let _scrollElement = entries[0].target.nextElementSibling
|
||||
this._getOldMessage(_scrollElement)
|
||||
if(this.messages.length < 20){
|
||||
return
|
||||
}
|
||||
this.setIsLoadingMessages(true);
|
||||
let _scrollElement = entries[0].target.nextElementSibling;
|
||||
this._getOldMessage(_scrollElement);
|
||||
}
|
||||
}
|
||||
|
||||
_downObserverHandler(entries) {
|
||||
if (!entries[0].isIntersecting) {
|
||||
this.shadowRoot.querySelector(".last-message-ref").style.opacity = '1'
|
||||
let _scrollElement = entries[0].target.previousElementSibling;
|
||||
// this._getOldMessageAfter(_scrollElement);
|
||||
this.showLastMessageRefScroller(true);
|
||||
} else {
|
||||
this.shadowRoot.querySelector(".last-message-ref").style.opacity = '0'
|
||||
this.showLastMessageRefScroller(false);
|
||||
}
|
||||
}
|
||||
|
||||
@ -100,9 +232,8 @@ class ChatScroller extends LitElement {
|
||||
rootMargin: '0px',
|
||||
threshold: 1
|
||||
};
|
||||
|
||||
const observer = new IntersectionObserver(this._upObserverhandler, options)
|
||||
observer.observe(this.upObserverElement)
|
||||
const observer = new IntersectionObserver(this._upObserverhandler, options);
|
||||
observer.observe(this.upObserverElement);
|
||||
}
|
||||
|
||||
downElementObserver() {
|
||||
@ -111,17 +242,13 @@ class ChatScroller extends LitElement {
|
||||
rootMargin: '0px',
|
||||
threshold: 1
|
||||
}
|
||||
|
||||
// identify an element to observe
|
||||
const elementToObserve = this.downObserverElement
|
||||
|
||||
const elementToObserve = this.downObserverElement;
|
||||
// passing it a callback function
|
||||
const observer = new IntersectionObserver(this._downObserverHandler, options)
|
||||
|
||||
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)
|
||||
|
||||
observer.observe(elementToObserve);
|
||||
}
|
||||
}
|
||||
|
||||
@ -135,9 +262,28 @@ class MessageTemplate extends LitElement {
|
||||
emojiPicker: { attribute: false },
|
||||
escapeHTML: { attribute: false },
|
||||
hideMessages: { type: Array },
|
||||
openDialogPrivateMessage: {type: Boolean},
|
||||
openDialogBlockUser: {type: Boolean},
|
||||
showBlockAddressIcon: { type: Boolean }
|
||||
openDialogPrivateMessage: { type: Boolean },
|
||||
openDialogBlockUser: { type: Boolean },
|
||||
showBlockAddressIcon: { type: Boolean },
|
||||
setRepliedToMessageObj: { attribute: false },
|
||||
setEditedMessageObj: { attribute: false },
|
||||
sendMessage: { attribute: false },
|
||||
sendMessageForward: { attribute: false },
|
||||
openDialogImage: { attribute: false },
|
||||
openDeleteImage: { type: Boolean },
|
||||
isImageLoaded: { 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 },
|
||||
}
|
||||
}
|
||||
|
||||
@ -148,6 +294,13 @@ class MessageTemplate extends LitElement {
|
||||
this.openDialogBlockUser = false
|
||||
this.showBlockAddressIcon = false
|
||||
this.myAddress = window.parent.reduxStore.getState().app.selectedAddress.address
|
||||
this.imageFetches = 0
|
||||
this.openDialogImage = false
|
||||
this.isImageLoaded = false
|
||||
this.isFirstMessage = false
|
||||
this.isSingleMessageInGroup = false
|
||||
this.isLastMessageInGroup = false
|
||||
this.viewImage = false
|
||||
}
|
||||
|
||||
static styles = [chatStyles]
|
||||
@ -171,8 +324,7 @@ class MessageTemplate extends LitElement {
|
||||
}
|
||||
|
||||
showBlockIconFunc(bool) {
|
||||
this.shadowRoot.querySelector(".chat-hover").focus({ preventScroll: true })
|
||||
if(bool) {
|
||||
if (bool) {
|
||||
this.showBlockAddressIcon = true;
|
||||
} else {
|
||||
this.showBlockAddressIcon = false;
|
||||
@ -180,51 +332,343 @@ class MessageTemplate extends LitElement {
|
||||
}
|
||||
|
||||
render() {
|
||||
const hidemsg = this.hideMessages
|
||||
const hidemsg = this.hideMessages;
|
||||
let message = "";
|
||||
let messageVersion2 = ""
|
||||
let reactions = [];
|
||||
let repliedToData = null;
|
||||
let image = null;
|
||||
let isImageDeleted = false;
|
||||
let version = 0;
|
||||
let isForwarded = false
|
||||
let isEdited = false
|
||||
try {
|
||||
const parsedMessageObj = JSON.parse(this.messageObj.decodedMessage);
|
||||
if(parsedMessageObj.version.toString() === '2'){
|
||||
|
||||
let avatarImg = ''
|
||||
let nameMenu = ''
|
||||
let levelFounder = ''
|
||||
let hideit = hidemsg.includes(this.messageObj.sender)
|
||||
|
||||
levelFounder = html`<level-founder checkleveladdress="${this.messageObj.sender}"></level-founder>`
|
||||
messageVersion2 = generateHTML(parsedMessageObj.messageText, [
|
||||
StarterKit,
|
||||
Underline,
|
||||
Highlight
|
||||
// other extensions …
|
||||
])
|
||||
}
|
||||
message = parsedMessageObj.messageText;
|
||||
repliedToData = this.messageObj.repliedToData;
|
||||
isImageDeleted = parsedMessageObj.isImageDeleted;
|
||||
reactions = parsedMessageObj.reactions || [];
|
||||
version = parsedMessageObj.version
|
||||
isForwarded = parsedMessageObj.type === 'forward'
|
||||
isEdited = this.messageObj.editedTimestamp && true
|
||||
if (parsedMessageObj.images && Array.isArray(parsedMessageObj.images) && parsedMessageObj.images.length > 0) {
|
||||
image = parsedMessageObj.images[0];
|
||||
}
|
||||
} catch (error) {
|
||||
message = this.messageObj.decodedMessage;
|
||||
}
|
||||
let avatarImg = '';
|
||||
let imageHTML = '';
|
||||
let imageHTMLDialog = '';
|
||||
let imageUrl = '';
|
||||
let nameMenu = '';
|
||||
let levelFounder = '';
|
||||
let hideit = hidemsg.includes(this.messageObj.sender);
|
||||
let forwarded = ''
|
||||
let edited = ''
|
||||
|
||||
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';" />`
|
||||
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/qortal-chat-logo.png';" />`;
|
||||
} else {
|
||||
avatarImg = html`<img src='/img/qortal-chat-logo.png' style="max-width:100%; max-height:100%;" onerror="this.onerror=null;" />`
|
||||
}
|
||||
|
||||
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>`
|
||||
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;
|
||||
}, 500);
|
||||
} else {
|
||||
imageHTMLRes.src = '/img/chain.png';
|
||||
imageHTMLRes.style= "max-width:45vh; max-height:20vh; border-radius: 5px; filter: opacity(0.5)";
|
||||
imageHTMLRes.onclick= () => {
|
||||
|
||||
}
|
||||
|
||||
this.isImageLoaded = true
|
||||
}
|
||||
};
|
||||
return imageHTMLRes;
|
||||
}
|
||||
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";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
nameMenu = html`
|
||||
<span class="${this.messageObj.sender === this.myAddress && 'message-data-my-name'}">
|
||||
${this.messageObj.senderName ? this.messageObj.senderName : cropAddress(this.messageObj.sender)}
|
||||
</span>
|
||||
`;
|
||||
forwarded = html`
|
||||
<span class="${this.messageObj.sender === this.myAddress && 'message-data-forward'}">
|
||||
${translate("blockpage.bcchange17")}
|
||||
</span>
|
||||
`;
|
||||
|
||||
edited = html`
|
||||
<span class="edited-message-style">
|
||||
${translate("chatpage.cchange68")}
|
||||
</span>
|
||||
`;
|
||||
|
||||
if (repliedToData) {
|
||||
try {
|
||||
const parsedMsg = JSON.parse(repliedToData.decodedMessage);
|
||||
repliedToData.decodedMessage = parsedMsg;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
|
||||
}
|
||||
const escapedMessage = this.escapeHTML(message)
|
||||
const replacedMessage = escapedMessage.replace(new RegExp('\r?\n','g'), '<br />');
|
||||
|
||||
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>
|
||||
<li
|
||||
class="clearfix message-parent"
|
||||
style="${(this.isSingleMessageInGroup === true && this.isLastMessageInGroup === false && reactions.length === 0) ?
|
||||
'padding-bottom: 0;'
|
||||
: null}
|
||||
${this.isFirstMessage && 'margin-top: 20px;'}">
|
||||
<div>
|
||||
<div
|
||||
class="message-container"
|
||||
style="${(this.isSingleMessageInGroup === true && this.isLastMessageInGroup === false) && 'margin-bottom: 0'}">
|
||||
<div class="message-subcontainer1">
|
||||
${(this.isSingleMessageInGroup === false ||
|
||||
(this.isSingleMessageInGroup === true && this.isLastMessageInGroup === true))
|
||||
? (
|
||||
html`
|
||||
<div
|
||||
style=${this.myAddress === this.messageObj.sender ? "cursor: auto;" : "cursor: pointer;"}
|
||||
@click=${() => {
|
||||
if (this.myAddress === this.messageObj.sender) return;
|
||||
this.setOpenUserInfo(true);
|
||||
this.setUserName(this.messageObj);
|
||||
}} class="message-data-avatar">
|
||||
${avatarImg}
|
||||
</div>
|
||||
`
|
||||
) :
|
||||
html`
|
||||
<div class="message-data-avatar"></div>
|
||||
`}
|
||||
<div
|
||||
class="${`message-subcontainer2
|
||||
${((this.isFirstMessage === true && this.isSingleMessageInGroup === false) ||
|
||||
(this.isSingleMessageInGroup === true && this.isLastMessageInGroup === true)) &&
|
||||
'message-triangle'}`}"
|
||||
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
|
||||
}">
|
||||
<div class="message-user-info">
|
||||
${this.isFirstMessage ?
|
||||
html`
|
||||
<span
|
||||
style=${this.myAddress === this.messageObj.sender ? "cursor: auto;" : "cursor: pointer;"}
|
||||
@click=${() => {
|
||||
if (this.myAddress === this.messageObj.sender) return;
|
||||
this.setOpenUserInfo(true);
|
||||
this.setUserName(this.messageObj);
|
||||
}}
|
||||
class="message-data-name">
|
||||
${nameMenu}
|
||||
</span>
|
||||
`
|
||||
: null
|
||||
}
|
||||
${isForwarded ?
|
||||
html`
|
||||
<span class="forwarded-text">
|
||||
${forwarded}
|
||||
</span>
|
||||
`
|
||||
: null
|
||||
}
|
||||
${this.isFirstMessage ? (
|
||||
html`
|
||||
<span class="message-data-level">${levelFounder}</span>
|
||||
`
|
||||
) : null}
|
||||
</div>
|
||||
${repliedToData && html`
|
||||
<div class="original-message"
|
||||
@click=${()=> {
|
||||
this.goToRepliedMessage(repliedToData)
|
||||
}}>
|
||||
<p
|
||||
|
||||
|
||||
class="original-message-sender">
|
||||
${repliedToData.senderName ?? cropAddress(repliedToData.sender)}
|
||||
</p>
|
||||
<p class="replied-message">
|
||||
|
||||
|
||||
${version.toString() === '1' ? html`
|
||||
${repliedToData.decodedMessage.messageText}
|
||||
` : ''}
|
||||
${version.toString() === '2' ? html`
|
||||
${unsafeHTML(generateHTML(repliedToData.decodedMessage.messageText, [
|
||||
StarterKit,
|
||||
Underline,
|
||||
Highlight
|
||||
// other extensions …
|
||||
]))}
|
||||
` : ''}
|
||||
|
||||
<!-- ${repliedToData.decodedMessage.messageText} -->
|
||||
</p>
|
||||
</div>
|
||||
`}
|
||||
${image && !isImageDeleted && !this.viewImage && this.myAddress !== this.messageObj.sender ? html`
|
||||
<div
|
||||
@click=${()=> {
|
||||
this.viewImage = true
|
||||
}}
|
||||
class=${[`image-container`, !this.isImageLoaded ? 'defaultSize' : ''].join(' ')}
|
||||
style=${this.isFirstMessage && "margin-top: 10px;"}>
|
||||
<div style="display:flex;width:100%;height:100%;justify-content:center;align-items:center;cursor:pointer;color:var(--black)">
|
||||
${translate("chatpage.cchange40")}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
` : html``}
|
||||
${image && !isImageDeleted && (this.viewImage || this.myAddress === this.messageObj.sender) ? html`
|
||||
<div
|
||||
class=${[`image-container`, !this.isImageLoaded ? 'defaultSize' : ''].join(' ')}
|
||||
style=${this.isFirstMessage && "margin-top: 10px;"}>
|
||||
${imageHTML}<vaadin-icon
|
||||
@click=${() => {
|
||||
this.openDeleteImage = true;
|
||||
this.chatE
|
||||
}}
|
||||
class="image-delete-icon" icon="vaadin:close" slot="icon"></vaadin-icon>
|
||||
</div>
|
||||
` : image && isImageDeleted ? html`
|
||||
<p class="image-deleted-msg">This image has been deleted</p>
|
||||
` : html``}
|
||||
<div
|
||||
id="messageContent"
|
||||
class="message"
|
||||
style=${(image && replacedMessage !== "") &&"margin-top: 15px;"}>
|
||||
${version.toString() === '2' ? html`
|
||||
${unsafeHTML(messageVersion2)}
|
||||
` : ''}
|
||||
${version.toString() === '1' ? html`
|
||||
${unsafeHTML(this.emojiPicker.parse(replacedMessage))}
|
||||
` : ''}
|
||||
<div
|
||||
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`
|
||||
<span>
|
||||
${edited}
|
||||
</span>
|
||||
`
|
||||
: null
|
||||
}
|
||||
<message-time timestamp=${this.messageObj.timestamp}></message-time>
|
||||
</div>
|
||||
</div>
|
||||
</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"}">${unsafeHTML(this.emojiPicker.parse(this.escapeHTML(this.messageObj.decodedMessage)))}</div>
|
||||
<chat-menu
|
||||
tabindex="0"
|
||||
class="chat-hover"
|
||||
style=${this.showBlockAddressIcon && "display: block"}
|
||||
style="${this.showBlockAddressIcon && 'display: block;'}"
|
||||
toblockaddress="${this.messageObj.sender}"
|
||||
.showPrivateMessageModal=${() => 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)}
|
||||
>
|
||||
</chat-menu>
|
||||
</div>
|
||||
<div class="message-reactions" style="${reactions.length > 0 &&
|
||||
'margin-top: 10px; margin-bottom: 5px;'}">
|
||||
${reactions.map((reaction)=> {
|
||||
return html`
|
||||
<span
|
||||
@click=${() => this.sendMessage({
|
||||
type: 'reaction',
|
||||
editedMessageObj: this.messageObj,
|
||||
reaction: reaction.type,
|
||||
})}
|
||||
class="reactions-bg">
|
||||
${reaction.type} ${reaction.qty}
|
||||
</span>`
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<chat-modals
|
||||
.openDialogPrivateMessage=${this.openDialogPrivateMessage}
|
||||
@ -235,6 +679,53 @@ class MessageTemplate extends LitElement {
|
||||
toblockaddress=${this.messageObj.sender}
|
||||
>
|
||||
</chat-modals>
|
||||
<mwc-dialog
|
||||
id="showDialogPublicKey"
|
||||
?open=${this.openDialogImage}
|
||||
@closed=${()=> {
|
||||
this.openDialogImage = false
|
||||
}}>
|
||||
<div class="dialog-header"></div>
|
||||
<div class="dialog-container imageContainer">
|
||||
${imageHTMLDialog}
|
||||
</div>
|
||||
<mwc-button
|
||||
slot="primaryAction"
|
||||
dialogAction="cancel"
|
||||
class="red"
|
||||
@click=${()=>{
|
||||
|
||||
this.openDialogImage = false
|
||||
}}
|
||||
>
|
||||
${translate("general.close")}
|
||||
</mwc-button>
|
||||
</mwc-dialog>
|
||||
<mwc-dialog
|
||||
hideActions
|
||||
?open=${this.openDeleteImage}
|
||||
@closed=${()=> {
|
||||
this.openDeleteImage = false;
|
||||
}}>
|
||||
<div class="delete-image-msg">
|
||||
<p>Are you sure you want to delete this image?</p>
|
||||
</div>
|
||||
<div class="modal-button-row" @click=${() => this.openDeleteImage = false}>
|
||||
<button class="modal-button-red">
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
class="modal-button"
|
||||
@click=${() => this.sendMessage({
|
||||
type: 'delete',
|
||||
name: image.name,
|
||||
identifier: image.identifier,
|
||||
editedMessageObj: this.messageObj,
|
||||
})}>
|
||||
Yes
|
||||
</button>
|
||||
</div>
|
||||
</mwc-dialog>
|
||||
`
|
||||
}
|
||||
}
|
||||
@ -245,18 +736,30 @@ class ChatMenu extends LitElement {
|
||||
static get properties() {
|
||||
return {
|
||||
menuItems: { type: Array },
|
||||
selectedAddress: { type: Object },
|
||||
showPrivateMessageModal: {type: Function},
|
||||
showBlockUserModal: {type: Function},
|
||||
showPrivateMessageModal: {attribute: false},
|
||||
showBlockUserModal: {attribute: false},
|
||||
toblockaddress: { type: String, attribute: true },
|
||||
showBlockIconFunc: {type: Function},
|
||||
showBlockAddressIcon: {type: Boolean}
|
||||
showBlockIconFunc: {attribute: false},
|
||||
showBlockAddressIcon: { type: Boolean },
|
||||
originalMessage: { type: Object },
|
||||
setRepliedToMessageObj: {attribute: false},
|
||||
setEditedMessageObj: {attribute: false},
|
||||
myAddress: { type: Object },
|
||||
emojiPicker: { attribute: false },
|
||||
sendMessage: { attribute: false },
|
||||
version: { type: String },
|
||||
setToggledMessage: { attribute: false },
|
||||
sendMessageForward: { attribute: false },
|
||||
setForwardProperties: { attribute: false },
|
||||
firstMessageInChat: { type: Boolean },
|
||||
setOpenPrivateMessage: { attribute: false },
|
||||
setOpenTipUser: { attribute: false },
|
||||
setUserName: { attribute: false },
|
||||
}
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.selectedAddress = window.parent.reduxStore.getState().app.selectedAddress.address;
|
||||
this.showPrivateMessageModal = () => {};
|
||||
this.showBlockUserModal = () => {};
|
||||
}
|
||||
@ -276,22 +779,148 @@ class ChatMenu extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
versionErrorSnack(){
|
||||
let errorMsg = get("chatpage.cchange34")
|
||||
parentEpml.request('showSnackBar', `${errorMsg}`)
|
||||
}
|
||||
|
||||
async messageForwardFunc(){
|
||||
let parsedMessageObj = {}
|
||||
let publicKey = {
|
||||
hasPubKey: false,
|
||||
key: ''
|
||||
}
|
||||
try {
|
||||
parsedMessageObj = JSON.parse(this.originalMessage.decodedMessage);
|
||||
|
||||
} catch (error) {
|
||||
parsedMessageObj = {}
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await parentEpml.request('apiCall', {
|
||||
type: 'api',
|
||||
url: `/addresses/publickey/${this._chatId}`
|
||||
})
|
||||
if (res.error === 102) {
|
||||
publicKey.key = ''
|
||||
publicKey.hasPubKey = false
|
||||
} else if (res !== false) {
|
||||
publicKey.key = res
|
||||
publicKey.hasPubKey = true
|
||||
} else {
|
||||
publicKey.key = ''
|
||||
publicKey.hasPubKey = false
|
||||
}
|
||||
} catch (error) {
|
||||
|
||||
}
|
||||
|
||||
try {
|
||||
const message = {
|
||||
...parsedMessageObj,
|
||||
type: 'forward'
|
||||
}
|
||||
const stringifyMessageObject = JSON.stringify(message)
|
||||
this.setForwardProperties(stringifyMessageObject)
|
||||
|
||||
} catch (error) {
|
||||
console.log({error})
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<div class="container" style=${this.showBlockAddressIcon && "width: 70px" }>
|
||||
<div class="menu-icon tooltip" data-text="${translate("blockpage.bcchange9")}" @click="${() => this.showPrivateMessageModal()}">
|
||||
<div class="container">
|
||||
<div
|
||||
class=${`menu-icon reaction ${!this.firstMessageInChat ? "tooltip" : ""}`}
|
||||
data-text="${translate("blockpage.bcchange13")}"
|
||||
@click=${(e) => {
|
||||
if(this.version === '0'){
|
||||
this.versionErrorSnack()
|
||||
return
|
||||
}
|
||||
try {
|
||||
this.setToggledMessage(this.originalMessage)
|
||||
this.emojiPicker.togglePicker(e.target)
|
||||
} catch (error) {
|
||||
console.log({error})
|
||||
}
|
||||
|
||||
}}
|
||||
>
|
||||
<vaadin-icon icon="vaadin:smiley-o" slot="icon"></vaadin-icon>
|
||||
</div>
|
||||
<div
|
||||
class=${`menu-icon ${!this.firstMessageInChat ? "tooltip" : ""}`}
|
||||
data-text="${translate("blockpage.bcchange14")}"
|
||||
@click="${() => {
|
||||
this.messageForwardFunc()
|
||||
}}">
|
||||
<vaadin-icon icon="vaadin:arrow-forward" slot="icon"></vaadin-icon>
|
||||
</div>
|
||||
<div
|
||||
class=${`menu-icon ${!this.firstMessageInChat ? "tooltip" : ""}`}
|
||||
data-text="${translate("blockpage.bcchange9")}"
|
||||
@click="${() => this.setOpenPrivateMessage({
|
||||
name: this.originalMessage.senderName ? this.originalMessage.senderName : this.originalMessage.sender,
|
||||
open: true
|
||||
})}">
|
||||
<vaadin-icon icon="vaadin:paperplane" slot="icon"></vaadin-icon>
|
||||
</div>
|
||||
<div class="menu-icon tooltip" data-text="${translate("blockpage.bcchange8")}" @click="${() => this.copyToClipboard(this.toblockaddress)}">
|
||||
<div class=${`menu-icon ${!this.firstMessageInChat ? "tooltip" : ""}`} data-text="${translate("blockpage.bcchange8")}" @click="${() => this.copyToClipboard(this.toblockaddress)}">
|
||||
<vaadin-icon icon="vaadin:copy" slot="icon"></vaadin-icon>
|
||||
</div>
|
||||
<div class="menu-icon tooltip" data-text="${translate("blockpage.bcchange10")}" @click="${() => this.showBlockIconFunc(true)}">
|
||||
<div
|
||||
class=${`menu-icon ${!this.firstMessageInChat ? "tooltip" : ""}`}
|
||||
data-text="${translate("blockpage.bcchange11")}"
|
||||
@click="${() => {
|
||||
if (this.version === '0') {
|
||||
this.versionErrorSnack()
|
||||
return
|
||||
}
|
||||
this.setRepliedToMessageObj({...this.originalMessage, version: this.version});
|
||||
}}">
|
||||
<vaadin-icon icon="vaadin:reply" slot="icon"></vaadin-icon>
|
||||
</div>
|
||||
|
||||
${this.myAddress === this.originalMessage.sender ? (
|
||||
html`
|
||||
<div
|
||||
class=${`menu-icon ${!this.firstMessageInChat ? "tooltip" : ""}`}
|
||||
data-text="${translate("blockpage.bcchange12")}"
|
||||
@click=${() => {
|
||||
if(this.version === '0'){
|
||||
this.versionErrorSnack()
|
||||
return
|
||||
}
|
||||
this.setEditedMessageObj(this.originalMessage);
|
||||
}}>
|
||||
<vaadin-icon icon="vaadin:pencil" slot="icon"></vaadin-icon>
|
||||
</div>
|
||||
`
|
||||
) : html`<div></div>`}
|
||||
${this.myAddress !== this.originalMessage.sender ? (
|
||||
html`
|
||||
<div
|
||||
class=${`menu-icon ${!this.firstMessageInChat ? "tooltip" : ""}`}
|
||||
data-text="${translate("blockpage.bcchange18")}"
|
||||
@click=${(e) => {
|
||||
e.preventDefault();
|
||||
this.setUserName(this.originalMessage);
|
||||
this.setOpenTipUser(true);
|
||||
}}>
|
||||
<vaadin-icon icon="vaadin:dollar" slot="icon"></vaadin-icon>
|
||||
</div>
|
||||
`
|
||||
) : html`<div></div>`}
|
||||
<div class=${`menu-icon ${!this.firstMessageInChat ? "tooltip" : ""}`} data-text="${translate("blockpage.bcchange10")}" @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()}">
|
||||
<div class="block-user" @click="${() => this.showBlockUserModal()}">
|
||||
<p>${translate("blockpage.bcchange1")}</p>
|
||||
<vaadin-icon icon="vaadin:close-circle" slot="icon"></vaadin-icon>
|
||||
</div>
|
||||
|
@ -0,0 +1,66 @@
|
||||
import { LitElement, html } from 'lit';
|
||||
import { render } from 'lit/html.js';
|
||||
import { chatSearchResultsStyles } from './ChatSearchResults-css.js'
|
||||
import { translate } from 'lit-translate';
|
||||
export class ChatSearchResults extends LitElement {
|
||||
static get properties() {
|
||||
return {
|
||||
onClickFunc: { attribute: false },
|
||||
closeFunc: { attribute: false },
|
||||
searchResults: { type: Array },
|
||||
isOpen: { type: Boolean },
|
||||
loading: { type: Boolean }
|
||||
}
|
||||
}
|
||||
|
||||
static styles = [chatSearchResultsStyles]
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<div class="chat-results-card" style=${this.isOpen ? "display: block;" : "display: none;"}>
|
||||
<vaadin-icon
|
||||
@click=${() => this.closeFunc()}
|
||||
icon="vaadin:close-small"
|
||||
slot="icon"
|
||||
class="close-icon"
|
||||
>
|
||||
</vaadin-icon>
|
||||
${this.loading ? (
|
||||
html`
|
||||
<div class="spinner-container">
|
||||
<paper-spinner-lite active></paper-spinner-lite>
|
||||
</div>
|
||||
`
|
||||
) : (
|
||||
html`
|
||||
<p class="chat-result-header">${translate("chatpage.cchange36")}</p>
|
||||
<div class="divider"></div>
|
||||
<div class="chat-result-container">
|
||||
${this.searchResults.length === 0 ? (
|
||||
html`<p class="no-results">${translate("chatpage.cchange37")}</p>`
|
||||
) : (
|
||||
html`
|
||||
${this.searchResults.map((result) => {
|
||||
return (
|
||||
html`
|
||||
<div class="chat-result-card" @click=${() => {
|
||||
this.shadowRoot.querySelector(".chat-result-card").classList.add("active");
|
||||
this.onClickFunc(result);
|
||||
}}>
|
||||
<p class="chat-result">
|
||||
${result.name}
|
||||
</p>
|
||||
</div>
|
||||
`
|
||||
)}
|
||||
)}
|
||||
`
|
||||
)}
|
||||
</div>
|
||||
`
|
||||
)}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
customElements.define('chat-search-results', ChatSearchResults);
|
@ -0,0 +1,120 @@
|
||||
import { css } from 'lit'
|
||||
|
||||
export const chatSearchResultsStyles = css`
|
||||
.chat-results-card {
|
||||
position: relative;
|
||||
padding: 25px 20px;
|
||||
box-shadow: rgba(100, 100, 111, 0.2) 0px 7px 29px 0px;
|
||||
width: 300px;
|
||||
min-height: 200px;
|
||||
height: auto;
|
||||
border-radius: 5px;
|
||||
background-color: var(--white);
|
||||
}
|
||||
|
||||
.chat-result-header {
|
||||
color: var(--chat-bubble-msg-color);
|
||||
font-size: 18px;
|
||||
font-family: Montserrat, sans-serif;
|
||||
text-align: center;
|
||||
margin: 0 0 10px 0;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.divider {
|
||||
height: 1px;
|
||||
background: var(--chat-bubble-msg-color);
|
||||
margin: 0 40px;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.no-results {
|
||||
font-family: Roboto, sans-serif;
|
||||
font-weight: 300;
|
||||
letter-spacing: 0.3px;
|
||||
font-size: 16px;
|
||||
color: var(--chat-bubble-msg-color);
|
||||
text-align: center;
|
||||
margin: 20px 0 0 0;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.chat-result-container {
|
||||
height: 200px;
|
||||
overflow-y: auto;
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
.chat-result-container::-webkit-scrollbar-track {
|
||||
background-color: whitesmoke;
|
||||
border-radius: 7px;
|
||||
}
|
||||
|
||||
.chat-result-container::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
border-radius: 7px;
|
||||
background-color: whitesmoke;
|
||||
}
|
||||
|
||||
.chat-result-container::-webkit-scrollbar-thumb {
|
||||
background-color: rgb(180, 176, 176);
|
||||
border-radius: 7px;
|
||||
transition: all 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
.chat-result-container::-webkit-scrollbar-thumb:hover {
|
||||
background-color: rgb(148, 146, 146);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.chat-result-card {
|
||||
padding: 12px;
|
||||
margin-bottom: 15px;
|
||||
margin-top: 15px;
|
||||
transition: all 0.2s ease-in-out;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.chat-result-card:active {
|
||||
background-color: #09b814;
|
||||
}
|
||||
|
||||
.chat-result-card:hover {
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
box-sizing: border-box;
|
||||
-webkit-box-shadow: rgba(132, 132, 132, 40%) 0px 0px 6px -1px;
|
||||
box-shadow: rgba(132, 132, 132, 40%) 0px 0px 6px -1px;
|
||||
}
|
||||
|
||||
.chat-result {
|
||||
font-family: Roboto, sans-serif;
|
||||
font-weight: 300;
|
||||
letter-spacing: 0.3px;
|
||||
font-size: 15px;
|
||||
color: var(--chat-bubble-msg-color);
|
||||
margin: 0;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.spinner-container {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
justify-content: center
|
||||
}
|
||||
|
||||
.close-icon {
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
right: 5px;
|
||||
color: var(--chat-bubble-msg-color);
|
||||
font-size: 14px;
|
||||
transition: all 0.1s ease-in-out;
|
||||
}
|
||||
|
||||
.close-icon:hover {
|
||||
cursor: pointer;
|
||||
font-size: 15px;
|
||||
}
|
||||
`
|
229
qortal-ui-plugins/plugins/core/components/ChatSelect.js
Normal file
@ -0,0 +1,229 @@
|
||||
import { LitElement, html, css } from 'lit'
|
||||
import { Epml } from '../../../epml.js'
|
||||
|
||||
import '@material/mwc-icon'
|
||||
|
||||
const parentEpml = new Epml({ type: 'WINDOW', source: window.parent })
|
||||
|
||||
class ChatSelect extends LitElement {
|
||||
static get properties() {
|
||||
return {
|
||||
selectedAddress: { type: Object },
|
||||
config: { type: Object },
|
||||
chatInfo: { type: Object },
|
||||
iconName: { type: String },
|
||||
activeChatHeadUrl: { type: String },
|
||||
isImageLoaded: { type: Boolean },
|
||||
setActiveChatHeadUrl: {attribute: false}
|
||||
}
|
||||
}
|
||||
|
||||
static get styles() {
|
||||
return css`
|
||||
ul {
|
||||
list-style-type: none;
|
||||
}
|
||||
li {
|
||||
padding: 10px 2px 20px 5px;
|
||||
cursor: pointer;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
li:hover {
|
||||
background-color: var(--menuhover);
|
||||
}
|
||||
|
||||
.active {
|
||||
background: var(--menuactive);
|
||||
border-left: 4px solid #3498db;
|
||||
}
|
||||
|
||||
.img-icon {
|
||||
font-size:40px;
|
||||
color: var(--chat-group);
|
||||
}
|
||||
|
||||
.about {
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.about {
|
||||
padding-left: 8px;
|
||||
}
|
||||
|
||||
.status {
|
||||
color: #92959e;
|
||||
}
|
||||
|
||||
.name {
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.clearfix:after {
|
||||
visibility: hidden;
|
||||
display: block;
|
||||
font-size: 0;
|
||||
content: " ";
|
||||
clear: both;
|
||||
height: 0;
|
||||
}
|
||||
`
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
this.selectedAddress = {}
|
||||
this.config = {
|
||||
user: {
|
||||
node: {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
this.chatInfo = {}
|
||||
this.iconName = ''
|
||||
this.activeChatHeadUrl = ''
|
||||
this.isImageLoaded = false
|
||||
this.imageFetches = 0
|
||||
}
|
||||
|
||||
createImage(imageUrl) {
|
||||
const imageHTMLRes = new Image();
|
||||
imageHTMLRes.src = imageUrl;
|
||||
imageHTMLRes.style= "width:40px; height:40px; float: left; border-radius:50%";
|
||||
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;
|
||||
}, 500);
|
||||
} else {
|
||||
|
||||
|
||||
this.isImageLoaded = false
|
||||
}
|
||||
};
|
||||
return imageHTMLRes;
|
||||
}
|
||||
|
||||
render() {
|
||||
let avatarImg = '';
|
||||
let backupAvatarImg = ''
|
||||
if(this.chatInfo.name){
|
||||
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.chatInfo.name}/qortal_avatar?async=true&apiKey=${myNode.apiKey}`;
|
||||
avatarImg= this.createImage(avatarUrl)
|
||||
|
||||
}
|
||||
|
||||
return html`
|
||||
<li
|
||||
@click=${() => this.getUrl(this.chatInfo.url)}
|
||||
class="clearfix ${this.activeChatHeadUrl === this.chatInfo.url ? 'active' : ''}">
|
||||
${this.isImageLoaded ? html`${avatarImg}` : html``}
|
||||
${!this.isImageLoaded && !this.chatInfo.name && !this.chatInfo.groupName ? html`<mwc-icon class="img-icon">account_circle</mwc-icon>` :
|
||||
html``
|
||||
}
|
||||
${!this.isImageLoaded && this.chatInfo.name ?
|
||||
html`
|
||||
<div
|
||||
style="width:40px; height:40px; float: left; border-radius:50%; background: ${this.activeChatHeadUrl === this.chatInfo.url ?
|
||||
'var(--chatHeadBgActive)' :
|
||||
'var(--chatHeadBg)' };
|
||||
color: ${this.activeChatHeadUrl === this.chatInfo.url ?
|
||||
'var(--chatHeadTextActive)' :
|
||||
'var(--chatHeadText)'};
|
||||
font-weight:bold;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
text-transform: capitalize">
|
||||
${this.chatInfo.name.charAt(0)}
|
||||
</div>`:
|
||||
''}
|
||||
${!this.isImageLoaded && this.chatInfo.groupName ?
|
||||
html`
|
||||
<div
|
||||
style="width:40px;
|
||||
height:40px;
|
||||
float: left;
|
||||
border-radius:50%;
|
||||
background: ${this.activeChatHeadUrl === this.chatInfo.url ?
|
||||
'var(--chatHeadBgActive)' :
|
||||
'var(--chatHeadBg)' };
|
||||
color: ${this.activeChatHeadUrl === this.chatInfo.url ?
|
||||
'var(--chatHeadTextActive)' :
|
||||
'var(--chatHeadText)' };
|
||||
font-weight:bold;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
text-transform: capitalize">
|
||||
${this.chatInfo.groupName.charAt(0)}
|
||||
</div>`:
|
||||
''}
|
||||
<div class="about">
|
||||
<div class="name">
|
||||
<span style="float:left; padding-left: 8px; color: var(--chat-group);">
|
||||
${this.chatInfo.groupName ?
|
||||
this.chatInfo.groupName :
|
||||
this.chatInfo.name !== undefined ? this.chatInfo.name :
|
||||
this.chatInfo.address.substr(0, 15)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
`
|
||||
}
|
||||
|
||||
firstUpdated() {
|
||||
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) {
|
||||
configLoaded = true
|
||||
}
|
||||
this.config = JSON.parse(c)
|
||||
})
|
||||
})
|
||||
parentEpml.imReady()
|
||||
|
||||
|
||||
}
|
||||
|
||||
shouldUpdate(changedProperties) {
|
||||
if(changedProperties.has('activeChatHeadUrl')){
|
||||
return true
|
||||
}
|
||||
if(changedProperties.has('chatInfo')){
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
getUrl(chatUrl) {
|
||||
this.setActiveChatHeadUrl(chatUrl)
|
||||
}
|
||||
|
||||
onPageNavigation(pageUrl) {
|
||||
parentEpml.request('setPageUrl', pageUrl)
|
||||
}
|
||||
}
|
||||
|
||||
window.customElements.define('chat-select', ChatSelect)
|
205
qortal-ui-plugins/plugins/core/components/ChatSideNavHeads.js
Normal file
@ -0,0 +1,205 @@
|
||||
import { LitElement, html, css } from 'lit'
|
||||
import { Epml } from '../../../epml.js'
|
||||
|
||||
import '@material/mwc-icon'
|
||||
|
||||
const parentEpml = new Epml({ type: 'WINDOW', source: window.parent })
|
||||
|
||||
class ChatSideNavHeads extends LitElement {
|
||||
static get properties() {
|
||||
return {
|
||||
selectedAddress: { type: Object },
|
||||
config: { type: Object },
|
||||
chatInfo: { type: Object },
|
||||
iconName: { type: String },
|
||||
activeChatHeadUrl: { type: String },
|
||||
isImageLoaded: { type: Boolean },
|
||||
setActiveChatHeadUrl: {attribute: false}
|
||||
}
|
||||
}
|
||||
|
||||
static get styles() {
|
||||
return css`
|
||||
ul {
|
||||
list-style-type: none;
|
||||
}
|
||||
li {
|
||||
padding: 10px 2px 10px 5px;
|
||||
cursor: pointer;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
box-sizing: border-box;
|
||||
font-size: 14px;
|
||||
transition: 0.2s background-color;
|
||||
}
|
||||
|
||||
li:hover {
|
||||
background-color: var(--lightChatHeadHover);
|
||||
}
|
||||
|
||||
.active {
|
||||
background: var(--menuactive);
|
||||
border-left: 4px solid #3498db;
|
||||
}
|
||||
|
||||
.img-icon {
|
||||
font-size:40px;
|
||||
color: var(--chat-group);
|
||||
}
|
||||
|
||||
.status {
|
||||
color: #92959e;
|
||||
}
|
||||
|
||||
.clearfix {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.clearfix:after {
|
||||
visibility: hidden;
|
||||
display: block;
|
||||
font-size: 0;
|
||||
content: " ";
|
||||
clear: both;
|
||||
height: 0;
|
||||
}
|
||||
`
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
this.selectedAddress = {}
|
||||
this.config = {
|
||||
user: {
|
||||
node: {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
this.chatInfo = {}
|
||||
this.iconName = ''
|
||||
this.activeChatHeadUrl = ''
|
||||
this.isImageLoaded = false
|
||||
this.imageFetches = 0
|
||||
}
|
||||
|
||||
createImage(imageUrl) {
|
||||
const imageHTMLRes = new Image();
|
||||
imageHTMLRes.src = imageUrl;
|
||||
imageHTMLRes.style= "width:30px; height:30px; float: left; border-radius:50%; font-size:14px";
|
||||
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;
|
||||
}, 500);
|
||||
} else {
|
||||
this.isImageLoaded = false
|
||||
}
|
||||
};
|
||||
return imageHTMLRes;
|
||||
}
|
||||
|
||||
render() {
|
||||
let avatarImg = ""
|
||||
if (this.chatInfo.name) {
|
||||
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.chatInfo.name}/qortal_avatar?async=true&apiKey=${myNode.apiKey}`;
|
||||
avatarImg = this.createImage(avatarUrl)
|
||||
}
|
||||
|
||||
return html`
|
||||
<li @click=${() => this.getUrl(this.chatInfo)} class="clearfix">
|
||||
${this.isImageLoaded ? html`${avatarImg}` : html``}
|
||||
${!this.isImageLoaded && !this.chatInfo.name && !this.chatInfo.groupName
|
||||
? html`<mwc-icon class="img-icon">account_circle</mwc-icon>`
|
||||
: html``}
|
||||
${!this.isImageLoaded && this.chatInfo.name
|
||||
? html`<div
|
||||
style="width:30px; height:30px; float: left; border-radius:50%; background: ${this.activeChatHeadUrl === this.chatInfo.url
|
||||
? "var(--chatHeadBgActive)"
|
||||
: "var(--chatHeadBg)"}; color: ${this.activeChatHeadUrl ===
|
||||
this.chatInfo.url
|
||||
? "var(--chatHeadTextActive)"
|
||||
: "var(--chatHeadText)"}; font-weight:bold; display: flex; justify-content: center; align-items: center; text-transform: capitalize"
|
||||
>
|
||||
${this.chatInfo.name.charAt(0)}
|
||||
</div>`
|
||||
: ""}
|
||||
${!this.isImageLoaded && this.chatInfo.groupName
|
||||
? html`<div
|
||||
style="width:30px; height:30px; float: left; border-radius:50%; background: ${this.activeChatHeadUrl === this.chatInfo.url
|
||||
? "var(--chatHeadBgActive)"
|
||||
: "var(--chatHeadBg)"}; color: ${this.activeChatHeadUrl === this.chatInfo.url
|
||||
? "var(--chatHeadTextActive)"
|
||||
: "var(--chatHeadText)"}; font-weight:bold; display: flex; justify-content: center; align-items: center; text-transform: capitalize"
|
||||
>
|
||||
${this.chatInfo.groupName.charAt(0)}
|
||||
</div>`
|
||||
: ""}
|
||||
<div>
|
||||
<div class="name">
|
||||
<span style="float:left; padding-left: 8px; color: var(--chat-group);">
|
||||
${this.chatInfo.groupName
|
||||
? this.chatInfo.groupName
|
||||
: this.chatInfo.name !== undefined
|
||||
? this.chatInfo.name
|
||||
: this.chatInfo.address.substr(0, 15)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
`
|
||||
}
|
||||
|
||||
firstUpdated() {
|
||||
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) {
|
||||
configLoaded = true
|
||||
}
|
||||
this.config = JSON.parse(c)
|
||||
})
|
||||
})
|
||||
parentEpml.imReady();
|
||||
}
|
||||
|
||||
shouldUpdate(changedProperties) {
|
||||
if(changedProperties.has('activeChatHeadUrl')){
|
||||
return true
|
||||
}
|
||||
if(changedProperties.has('chatInfo')){
|
||||
return true
|
||||
}
|
||||
if(changedProperties.has('isImageLoaded')){
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
getUrl(chatUrl) {
|
||||
this.setActiveChatHeadUrl(chatUrl)
|
||||
}
|
||||
|
||||
onPageNavigation(pageUrl) {
|
||||
parentEpml.request('setPageUrl', pageUrl)
|
||||
}
|
||||
}
|
||||
|
||||
window.customElements.define('chat-side-nav-heads', ChatSideNavHeads)
|
828
qortal-ui-plugins/plugins/core/components/ChatTextEditor copy.js
Normal file
@ -0,0 +1,828 @@
|
||||
import { LitElement, html, css } from "lit";
|
||||
import { get } from 'lit-translate';
|
||||
import { escape, unescape } from 'html-escaper';
|
||||
import { EmojiPicker } from 'emoji-picker-js';
|
||||
import { inputKeyCodes } from '../../utils/keyCodes.js';
|
||||
import { Epml } from '../../../epml.js';
|
||||
|
||||
const parentEpml = new Epml({ type: 'WINDOW', source: window.parent });
|
||||
class ChatTextEditor extends LitElement {
|
||||
static get properties() {
|
||||
return {
|
||||
isLoading: { type: Boolean },
|
||||
isLoadingMessages: { type: Boolean },
|
||||
_sendMessage: { attribute: false },
|
||||
placeholder: { type: String },
|
||||
imageFile: { type: Object },
|
||||
insertImage: { attribute: false },
|
||||
iframeHeight: { type: Number },
|
||||
editedMessageObj: { type: Object },
|
||||
chatEditor: { type: Object },
|
||||
setChatEditor: { attribute: false },
|
||||
iframeId: { type: String },
|
||||
hasGlobalEvents: { type: Boolean },
|
||||
chatMessageSize: { type: Number },
|
||||
isEditMessageOpen: { type: Boolean },
|
||||
theme: {
|
||||
type: String,
|
||||
reflect: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static get styles() {
|
||||
return css`
|
||||
:host {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: auto;
|
||||
overflow-y: hidden;
|
||||
width: 100%;
|
||||
}
|
||||
.chatbar-container {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
height: auto;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.chatbar-caption {
|
||||
border-bottom: 2px solid var(--mdc-theme-primary);
|
||||
}
|
||||
|
||||
.emoji-button {
|
||||
width: 45px;
|
||||
height: 40px;
|
||||
padding-top: 4px;
|
||||
border: none;
|
||||
outline: none;
|
||||
background: transparent;
|
||||
cursor: pointer;
|
||||
max-height: 40px;
|
||||
color: var(--black);
|
||||
}
|
||||
|
||||
.message-size-container {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.message-size {
|
||||
font-family: Roboto, sans-serif;
|
||||
font-size: 12px;
|
||||
color: black;
|
||||
}
|
||||
|
||||
.paperclip-icon {
|
||||
color: var(--paperclip-icon);
|
||||
width: 25px;
|
||||
}
|
||||
|
||||
.paperclip-icon:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.send-icon {
|
||||
width: 30px;
|
||||
margin-left: 5px;
|
||||
transition: all 0.1s ease-in-out;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.send-icon:hover {
|
||||
filter: brightness(1.1);
|
||||
}
|
||||
|
||||
.file-picker-container {
|
||||
position: relative;
|
||||
height: 25px;
|
||||
width: 25px;
|
||||
}
|
||||
|
||||
.file-picker-input-container {
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
bottom: 0px;
|
||||
left: 0px;
|
||||
right: 0px;
|
||||
z-index: 10;
|
||||
opacity: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
input[type=file]::-webkit-file-upload-button {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.chatbar-container textarea {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.chatbar-container .chat-editor {
|
||||
display: flex;
|
||||
max-height: -webkit-fill-available;
|
||||
width: 100%;
|
||||
border-color: transparent;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.checkmark-icon {
|
||||
width: 30px;
|
||||
color: var(--mdc-theme-primary);
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.checkmark-icon:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
`
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
this.isLoadingMessages = true
|
||||
this.isLoading = false
|
||||
this.getMessageSize = this.getMessageSize.bind(this)
|
||||
this.calculateIFrameHeight = this.calculateIFrameHeight.bind(this)
|
||||
this.resetIFrameHeight = this.resetIFrameHeight.bind(this)
|
||||
this.addGlobalEventListener = this.addGlobalEventListener.bind(this)
|
||||
this.sendMessageFunc = this.sendMessageFunc.bind(this)
|
||||
this.removeGlobalEventListener = this.removeGlobalEventListener.bind(this)
|
||||
this.initialChat = this.initialChat.bind(this)
|
||||
this.iframeHeight = 42
|
||||
this.chatMessageSize = 0
|
||||
this.userName = window.parent.reduxStore.getState().app.accountInfo.names[0]
|
||||
this.theme = localStorage.getItem('qortalTheme') ? localStorage.getItem('qortalTheme') : 'light'
|
||||
}
|
||||
|
||||
render() {
|
||||
let scrollHeightBool = false;
|
||||
try {
|
||||
if (this.chatMessageInput && this.chatMessageInput.contentDocument.body.scrollHeight > 60 && this.shadowRoot.querySelector(".chat-editor").contentDocument.body.querySelector("#chatbarId").innerHTML.trim() !== "") {
|
||||
scrollHeightBool = true;
|
||||
}
|
||||
} catch (error) {
|
||||
scrollHeightBool = false;
|
||||
}
|
||||
return html`
|
||||
<div
|
||||
class=${["chatbar-container", (this.iframeId === "newChat" || this.iframeId === "privateMessage") ? "chatbar-caption" : ""].join(" ")}
|
||||
style="${scrollHeightBool ? 'align-items: flex-end' : "align-items: center"}">
|
||||
<div
|
||||
style=${this.iframeId === "privateMessage" ? "display: none" : "display: block"}
|
||||
class="file-picker-container"
|
||||
@click=${(e) => {
|
||||
this.preventUserSendingImage(e)
|
||||
}}>
|
||||
<vaadin-icon
|
||||
class="paperclip-icon"
|
||||
icon="vaadin:paperclip"
|
||||
slot="icon"
|
||||
>
|
||||
</vaadin-icon>
|
||||
<div class="file-picker-input-container">
|
||||
<input
|
||||
@change="${e => {
|
||||
this.insertImage(e.target.files[0]);
|
||||
const filePickerInput = this.shadowRoot.getElementById('file-picker')
|
||||
if(filePickerInput){
|
||||
filePickerInput.value = ""
|
||||
}
|
||||
}
|
||||
}"
|
||||
id="file-picker"
|
||||
class="file-picker-input" type="file" name="myImage" accept="image/*" />
|
||||
</div>
|
||||
</div>
|
||||
<textarea style="color: var(--black);" tabindex='1' ?autofocus=${true} ?disabled=${this.isLoading || this.isLoadingMessages} id="messageBox" rows="1"></textarea>
|
||||
<iframe style=${(this.iframeId === "newChat" && this.iframeHeight > 42) && "height: 100%;"} id=${this.iframeId} class="chat-editor" tabindex="-1" height=${this.iframeHeight}></iframe>
|
||||
<button class="emoji-button" ?disabled=${this.isLoading || this.isLoadingMessages}>
|
||||
${html`<img class="emoji" draggable="false" alt="😀" src="/emoji/svg/1f600.svg" />`}
|
||||
</button>
|
||||
${this.editedMessageObj ? (
|
||||
html`
|
||||
<div>
|
||||
${this.isLoading === false ? html`
|
||||
<vaadin-icon
|
||||
class="checkmark-icon"
|
||||
icon="vaadin:check"
|
||||
slot="icon"
|
||||
@click=${() => {
|
||||
this.sendMessageFunc();
|
||||
}}
|
||||
>
|
||||
</vaadin-icon>
|
||||
` :
|
||||
html`
|
||||
<paper-spinner-lite active></paper-spinner-lite>
|
||||
`}
|
||||
</div>
|
||||
`
|
||||
) :
|
||||
html`
|
||||
<div
|
||||
style="${scrollHeightBool
|
||||
? 'margin-bottom: 5px;'
|
||||
: "margin-bottom: 0;"}
|
||||
${this.iframeId === 'newChat'
|
||||
? 'display: none;'
|
||||
: 'display: flex;'}">
|
||||
${this.isLoading === false ? html`
|
||||
<img
|
||||
src="/img/qchat-send-message-icon.svg"
|
||||
alt="send-icon"
|
||||
class="send-icon"
|
||||
@click=${() => {
|
||||
this.sendMessageFunc();
|
||||
}}
|
||||
/>
|
||||
` :
|
||||
html`
|
||||
<paper-spinner-lite active></paper-spinner-lite>
|
||||
`}
|
||||
</div>
|
||||
`
|
||||
}
|
||||
</div>
|
||||
${this.chatMessageSize >= 750 ?
|
||||
html`
|
||||
<div class="message-size-container" style=${this.imageFile && "margin-top: 10px;"}>
|
||||
<div class="message-size" style="${this.chatMessageSize > 1000 && 'color: #bd1515'}">
|
||||
${`Your message size is of ${this.chatMessageSize} bytes out of a maximum of 1000`}
|
||||
</div>
|
||||
</div>
|
||||
` :
|
||||
html``}
|
||||
</div>
|
||||
`
|
||||
}
|
||||
|
||||
preventUserSendingImage(e) {
|
||||
if (!this.userName) {
|
||||
e.preventDefault();
|
||||
parentEpml.request('showSnackBar', get("chatpage.cchange27"));
|
||||
};
|
||||
}
|
||||
|
||||
initialChat(e) {
|
||||
if (!this.chatEditor?.contentDiv.matches(':focus')) {
|
||||
// WARNING: Deprecated methods from KeyBoard Event
|
||||
if (e.code === "Space" || e.keyCode === 32 || e.which === 32) {
|
||||
this.chatEditor.insertText(' ');
|
||||
} else if (inputKeyCodes.includes(e.keyCode)) {
|
||||
this.chatEditor.insertText(e.key);
|
||||
return this.chatEditor.focus();
|
||||
} else {
|
||||
return this.chatEditor.focus();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
addGlobalEventListener(){
|
||||
document.addEventListener('keydown', this.initialChat);
|
||||
}
|
||||
|
||||
removeGlobalEventListener(){
|
||||
document.removeEventListener('keydown', this.initialChat);
|
||||
}
|
||||
|
||||
async firstUpdated() {
|
||||
if (this.hasGlobalEvents) {
|
||||
this.addGlobalEventListener();
|
||||
}
|
||||
|
||||
window.addEventListener('storage', () => {
|
||||
const checkTheme = localStorage.getItem('qortalTheme');
|
||||
const chatbar = this.shadowRoot.getElementById(this.iframeId).contentWindow.document.getElementById('chatbarId');
|
||||
if (checkTheme === 'dark') {
|
||||
this.theme = 'dark';
|
||||
chatbar.style.cssText = "color:#ffffff;"
|
||||
} else {
|
||||
this.theme = 'light';
|
||||
chatbar.style.cssText = "color:#080808;"
|
||||
}
|
||||
})
|
||||
|
||||
this.emojiPickerHandler = this.shadowRoot.querySelector('.emoji-button');
|
||||
this.mirrorChatInput = this.shadowRoot.getElementById('messageBox');
|
||||
this.chatMessageInput = this.shadowRoot.getElementById(this.iframeId);
|
||||
|
||||
this.emojiPicker = new EmojiPicker({
|
||||
style: "twemoji",
|
||||
twemojiBaseUrl: '/emoji/',
|
||||
showPreview: false,
|
||||
showVariants: false,
|
||||
showAnimation: false,
|
||||
position: 'top-start',
|
||||
boxShadow: 'rgba(4, 4, 5, 0.15) 0px 0px 0px 1px, rgba(0, 0, 0, 0.24) 0px 8px 16px 0px',
|
||||
zIndex: 100
|
||||
|
||||
});
|
||||
|
||||
this.emojiPicker.on('emoji', selection => {
|
||||
const emojiHtmlString = `<img class="emoji" draggable="false" alt="${selection.emoji}" src="${selection.url}">`;
|
||||
this.chatEditor.insertEmoji(emojiHtmlString);
|
||||
});
|
||||
|
||||
|
||||
this.emojiPickerHandler.addEventListener('click', () => this.emojiPicker.togglePicker(this.emojiPickerHandler));
|
||||
|
||||
await this.updateComplete;
|
||||
this.initChatEditor();
|
||||
}
|
||||
|
||||
async updated(changedProperties) {
|
||||
if (changedProperties && changedProperties.has('editedMessageObj')) {
|
||||
if (this.editedMessageObj) {
|
||||
this.chatEditor.insertText(this.editedMessageObj.message);
|
||||
this.getMessageSize(this.editedMessageObj.message);
|
||||
} else {
|
||||
this.chatEditor.insertText("");
|
||||
this.chatMessageSize = 0;
|
||||
}
|
||||
}
|
||||
if (changedProperties && changedProperties.has('placeholder')) {
|
||||
const captionEditor = this.shadowRoot.getElementById(this.iframeId).contentWindow.document.getElementById('chatbarId');
|
||||
captionEditor.setAttribute('data-placeholder', this.placeholder);
|
||||
}
|
||||
|
||||
if (changedProperties && changedProperties.has("imageFile")) {
|
||||
this.chatMessageInput = "newChat";
|
||||
}
|
||||
}
|
||||
|
||||
shouldUpdate(changedProperties) {
|
||||
// Only update element if prop1 changed.
|
||||
if(changedProperties.has('setChatEditor') && changedProperties.size === 1) return false
|
||||
return true
|
||||
}
|
||||
|
||||
sendMessageFunc(props) {
|
||||
if (this.chatMessageSize > 1000 ) {
|
||||
parentEpml.request('showSnackBar', get("chatpage.cchange29"));
|
||||
return;
|
||||
};
|
||||
this.chatMessageSize = 0;
|
||||
this.chatEditor.updateMirror();
|
||||
this._sendMessage(props);
|
||||
}
|
||||
|
||||
getMessageSize(message){
|
||||
try {
|
||||
const messageText = message;
|
||||
// Format and Sanitize Message
|
||||
const sanitizedMessage = messageText.replace(/ /gi, ' ').replace(/<br\s*[\/]?>/gi, '\n');
|
||||
const trimmedMessage = sanitizedMessage.trim();
|
||||
let messageObject = {};
|
||||
|
||||
if (this.repliedToMessageObj) {
|
||||
let chatReference = this.repliedToMessageObj.reference;
|
||||
if (this.repliedToMessageObj.chatReference) {
|
||||
chatReference = this.repliedToMessageObj.chatReference;
|
||||
}
|
||||
messageObject = {
|
||||
messageText: trimmedMessage,
|
||||
images: [''],
|
||||
repliedTo: chatReference,
|
||||
version: 1
|
||||
}
|
||||
} else if (this.editedMessageObj) {
|
||||
let message = "";
|
||||
try {
|
||||
const parsedMessageObj = JSON.parse(this.editedMessageObj.decodedMessage);
|
||||
message = parsedMessageObj;
|
||||
} catch (error) {
|
||||
message = this.messageObj.decodedMessage
|
||||
}
|
||||
messageObject = {
|
||||
...message,
|
||||
messageText: trimmedMessage,
|
||||
}
|
||||
} else if(this.imageFile && this.iframeId === 'newChat') {
|
||||
messageObject = {
|
||||
messageText: trimmedMessage,
|
||||
images: [{
|
||||
service: "QCHAT_IMAGE",
|
||||
name: '123456789123456789123456789',
|
||||
identifier: '123456'
|
||||
}],
|
||||
repliedTo: '',
|
||||
version: 1
|
||||
};
|
||||
} else {
|
||||
messageObject = {
|
||||
messageText: trimmedMessage,
|
||||
images: [''],
|
||||
repliedTo: '',
|
||||
version: 1
|
||||
};
|
||||
}
|
||||
|
||||
const stringified = JSON.stringify(messageObject);
|
||||
const size = new Blob([stringified]).size;
|
||||
this.chatMessageSize = size;
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
calculateIFrameHeight(height) {
|
||||
setTimeout(()=> {
|
||||
const editorTest = this.shadowRoot.getElementById(this.iframeId).contentWindow.document.getElementById('chatbarId').scrollHeight;
|
||||
this.iframeHeight = editorTest + 20;
|
||||
}, 50)
|
||||
}
|
||||
resetIFrameHeight(height) {
|
||||
this.iframeHeight = 42;
|
||||
}
|
||||
initChatEditor() {
|
||||
const ChatEditor = function (editorConfig) {
|
||||
const ChatEditor = function () {
|
||||
const editor = this;
|
||||
editor.init();
|
||||
};
|
||||
|
||||
ChatEditor.prototype.getValue = function () {
|
||||
const editor = this;
|
||||
|
||||
if (editor.contentDiv) {
|
||||
return editor.contentDiv.innerHTML;
|
||||
}
|
||||
};
|
||||
|
||||
ChatEditor.prototype.setValue = function (value) {
|
||||
const editor = this;
|
||||
|
||||
if (value) {
|
||||
editor.contentDiv.innerHTML = value;
|
||||
editor.updateMirror();
|
||||
}
|
||||
|
||||
editor.focus();
|
||||
};
|
||||
|
||||
ChatEditor.prototype.resetValue = function () {
|
||||
const editor = this;
|
||||
editor.contentDiv.innerHTML = '';
|
||||
editor.updateMirror();
|
||||
editor.focus();
|
||||
editorConfig.resetIFrameHeight()
|
||||
};
|
||||
|
||||
ChatEditor.prototype.styles = function () {
|
||||
const editor = this;
|
||||
|
||||
editor.styles = document.createElement('style');
|
||||
editor.styles.setAttribute('type', 'text/css');
|
||||
editor.styles.innerText = `
|
||||
html {
|
||||
cursor: text;
|
||||
}
|
||||
|
||||
.chatbar-body {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.chatbar-body::-webkit-scrollbar-track {
|
||||
background-color: whitesmoke;
|
||||
border-radius: 7px;
|
||||
}
|
||||
|
||||
.chatbar-body::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
border-radius: 7px;
|
||||
background-color: whitesmoke;
|
||||
}
|
||||
|
||||
.chatbar-body::-webkit-scrollbar-thumb {
|
||||
background-color: rgb(180, 176, 176);
|
||||
border-radius: 7px;
|
||||
transition: all 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
.chatbar-body::-webkit-scrollbar-thumb:hover {
|
||||
background-color: rgb(148, 146, 146);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
div {
|
||||
font-size: 1rem;
|
||||
line-height: 1.38rem;
|
||||
font-weight: 400;
|
||||
font-family: "Open Sans", helvetica, sans-serif;
|
||||
padding-right: 3px;
|
||||
text-align: left;
|
||||
white-space: break-spaces;
|
||||
word-break: break-word;
|
||||
outline: none;
|
||||
min-height: 20px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
div[contentEditable=true]:empty:before {
|
||||
content: attr(data-placeholder);
|
||||
display: block;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
user-select: none;
|
||||
white-space: nowrap;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
div[contentEditable=false]{
|
||||
background: rgba(0,0,0,0.1);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
img.emoji {
|
||||
width: 1.7em;
|
||||
height: 1.5em;
|
||||
margin-bottom: -2px;
|
||||
vertical-align: bottom;
|
||||
}
|
||||
`;
|
||||
editor.content.head.appendChild(editor.styles);
|
||||
};
|
||||
|
||||
ChatEditor.prototype.enable = function () {
|
||||
const editor = this;
|
||||
|
||||
editor.contentDiv.setAttribute('contenteditable', 'true');
|
||||
editor.focus();
|
||||
};
|
||||
|
||||
ChatEditor.prototype.getMirrorElement = function (){
|
||||
return editor.mirror
|
||||
}
|
||||
|
||||
ChatEditor.prototype.disable = function () {
|
||||
const editor = this;
|
||||
|
||||
editor.contentDiv.setAttribute('contenteditable', 'false');
|
||||
};
|
||||
|
||||
ChatEditor.prototype.state = function () {
|
||||
const editor = this;
|
||||
|
||||
return editor.contentDiv.getAttribute('contenteditable');
|
||||
};
|
||||
|
||||
ChatEditor.prototype.focus = function () {
|
||||
const editor = this;
|
||||
|
||||
editor.contentDiv.focus();
|
||||
};
|
||||
|
||||
ChatEditor.prototype.clearSelection = function () {
|
||||
const editor = this;
|
||||
|
||||
let selection = editor.content.getSelection().toString();
|
||||
if (!/^\s*$/.test(selection)) editor.content.getSelection().removeAllRanges();
|
||||
};
|
||||
|
||||
ChatEditor.prototype.insertEmoji = function (emojiImg) {
|
||||
const editor = this;
|
||||
|
||||
const doInsert = () => {
|
||||
|
||||
if (editor.content.queryCommandSupported("InsertHTML")) {
|
||||
editor.content.execCommand("insertHTML", false, emojiImg);
|
||||
editor.updateMirror();
|
||||
}
|
||||
};
|
||||
|
||||
editor.focus();
|
||||
return doInsert();
|
||||
};
|
||||
|
||||
ChatEditor.prototype.insertText = function (text) {
|
||||
const editor = this;
|
||||
|
||||
const parsedText = editorConfig.emojiPicker.parse(text);
|
||||
const doPaste = () => {
|
||||
|
||||
if (editor.content.queryCommandSupported("InsertHTML")) {
|
||||
editor.content.execCommand("insertHTML", false, parsedText);
|
||||
editor.updateMirror();
|
||||
}
|
||||
};
|
||||
|
||||
editor.focus();
|
||||
return doPaste();
|
||||
};
|
||||
|
||||
ChatEditor.prototype.updateMirror = function () {
|
||||
const editor = this;
|
||||
|
||||
const chatInputValue = editor.getValue();
|
||||
const filteredValue = chatInputValue.replace(/<img.*?alt=".*?/g, '').replace(/".?src=.*?>/g, '');
|
||||
|
||||
let unescapedValue = editorConfig.unescape(filteredValue);
|
||||
editor.mirror.value = unescapedValue;
|
||||
};
|
||||
|
||||
ChatEditor.prototype.listenChanges = function () {
|
||||
|
||||
const editor = this;
|
||||
|
||||
const events = ['drop', 'contextmenu', 'mouseup', 'click', 'touchend', 'keydown', 'blur', 'paste']
|
||||
|
||||
for (let i = 0; i < events.length; i++) {
|
||||
const event = events[i]
|
||||
editor.content.body.addEventListener(event, async function (e) {
|
||||
|
||||
if (e.type === 'click') {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
|
||||
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
|
||||
});
|
||||
|
||||
editorConfig.insertImage(file)
|
||||
} else {
|
||||
navigator.clipboard.readText()
|
||||
.then(clipboardText => {
|
||||
let escapedText = editorConfig.escape(clipboardText);
|
||||
editor.insertText(escapedText);
|
||||
})
|
||||
.then(() => {
|
||||
editorConfig.getMessageSize(editorConfig.editableElement.contentDocument.body.querySelector("#chatbarId").innerHTML);
|
||||
})
|
||||
.catch(err => {
|
||||
// Fallback if everything fails...
|
||||
let textData = (e.originalEvent || e).clipboardData.getData('text/plain');
|
||||
editor.insertText(textData);
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (e.type === 'contextmenu') {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (e.type === 'keydown') {
|
||||
await new Promise((res, rej) => {
|
||||
setTimeout(() => {
|
||||
editorConfig.calculateIFrameHeight(editorConfig.editableElement.contentDocument.body.scrollHeight);
|
||||
editorConfig.getMessageSize(editorConfig.editableElement.contentDocument.body.querySelector("#chatbarId").innerHTML);
|
||||
}, 0);
|
||||
res();
|
||||
})
|
||||
|
||||
// Handle Enter
|
||||
if (e.keyCode === 13 && !e.shiftKey) {
|
||||
|
||||
|
||||
if (editor.state() === 'false') return false;
|
||||
if (editorConfig.iframeId === 'newChat') {
|
||||
editorConfig.sendFunc(
|
||||
{
|
||||
type: 'image',
|
||||
imageFile: editorConfig.imageFile,
|
||||
}
|
||||
);
|
||||
} else {
|
||||
editorConfig.sendFunc();
|
||||
}
|
||||
|
||||
e.preventDefault();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Handle Commands with CTR or CMD
|
||||
if (e.ctrlKey || e.metaKey) {
|
||||
switch (e.keyCode) {
|
||||
case 66:
|
||||
case 98: e.preventDefault();
|
||||
return false;
|
||||
case 73:
|
||||
case 105: e.preventDefault();
|
||||
return false;
|
||||
case 85:
|
||||
case 117: e.preventDefault();
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (e.type === 'blur') {
|
||||
editor.clearSelection();
|
||||
}
|
||||
|
||||
if (e.type === 'drop') {
|
||||
e.preventDefault();
|
||||
|
||||
let droppedText = e.dataTransfer.getData('text/plain')
|
||||
let escapedText = editorConfig.escape(droppedText)
|
||||
|
||||
editor.insertText(escapedText);
|
||||
return false;
|
||||
}
|
||||
|
||||
editor.updateMirror();
|
||||
});
|
||||
}
|
||||
|
||||
editor.content.addEventListener('click', function (event) {
|
||||
event.preventDefault();
|
||||
editor.focus();
|
||||
});
|
||||
};
|
||||
|
||||
ChatEditor.prototype.remove = function () {
|
||||
const editor = this;
|
||||
var old_element = editor.content.body;
|
||||
var new_element = old_element.cloneNode(true);
|
||||
editor.content.body.parentNode.replaceChild(new_element, old_element);
|
||||
while (editor.content.body.firstChild) {
|
||||
editor.content.body.removeChild(editor.content.body.lastChild);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
ChatEditor.prototype.init = function () {
|
||||
const editor = this;
|
||||
|
||||
editor.frame = editorConfig.editableElement;
|
||||
editor.mirror = editorConfig.mirrorElement;
|
||||
|
||||
editor.content = (editor.frame.contentDocument || editor.frame.document);
|
||||
editor.content.body.classList.add("chatbar-body");
|
||||
|
||||
let elemDiv = document.createElement('div');
|
||||
elemDiv.setAttribute('contenteditable', 'true');
|
||||
elemDiv.setAttribute('spellcheck', 'false');
|
||||
elemDiv.setAttribute('data-placeholder', editorConfig.placeholder);
|
||||
elemDiv.style.cssText = `width:100%; ${editorConfig.theme === "dark" ? "color:#ffffff;" : "color: #080808"}`;
|
||||
elemDiv.id = 'chatbarId';
|
||||
editor.content.body.appendChild(elemDiv);
|
||||
editor.contentDiv = editor.frame.contentDocument.body.firstChild;
|
||||
editor.styles();
|
||||
editor.listenChanges();
|
||||
|
||||
};
|
||||
|
||||
|
||||
function doInit() {
|
||||
return new ChatEditor();
|
||||
}
|
||||
return doInit();
|
||||
};
|
||||
|
||||
const editorConfig = {
|
||||
getMessageSize: this.getMessageSize,
|
||||
calculateIFrameHeight: this.calculateIFrameHeight,
|
||||
mirrorElement: this.mirrorChatInput,
|
||||
editableElement: this.chatMessageInput,
|
||||
sendFunc: this.sendMessageFunc,
|
||||
emojiPicker: this.emojiPicker,
|
||||
escape: escape,
|
||||
unescape: unescape,
|
||||
placeholder: this.placeholder,
|
||||
imageFile: this.imageFile,
|
||||
requestUpdate: this.requestUpdate,
|
||||
insertImage: this.insertImage,
|
||||
chatMessageSize: this.chatMessageSize,
|
||||
addGlobalEventListener: this.addGlobalEventListener,
|
||||
removeGlobalEventListener: this.removeGlobalEventListener,
|
||||
iframeId: this.iframeId,
|
||||
theme: this.theme,
|
||||
resetIFrameHeight: this.resetIFrameHeight
|
||||
};
|
||||
const newChat = new ChatEditor(editorConfig);
|
||||
this.setChatEditor(newChat);
|
||||
}
|
||||
}
|
||||
|
||||
window.customElements.define("chat-text-editor", ChatTextEditor)
|
677
qortal-ui-plugins/plugins/core/components/ChatTextEditor.js
Normal file
@ -0,0 +1,677 @@
|
||||
import { LitElement, html, css } from "lit";
|
||||
import { get, translate } from 'lit-translate';
|
||||
|
||||
import { EmojiPicker } from 'emoji-picker-js';
|
||||
import { Epml } from '../../../epml.js';
|
||||
import '@material/mwc-icon'
|
||||
|
||||
const parentEpml = new Epml({ type: 'WINDOW', source: window.parent });
|
||||
class ChatTextEditor extends LitElement {
|
||||
static get properties() {
|
||||
return {
|
||||
isLoading: { type: Boolean },
|
||||
isLoadingMessages: { type: Boolean },
|
||||
_sendMessage: { attribute: false },
|
||||
placeholder: { type: String },
|
||||
imageFile: { type: Object },
|
||||
insertImage: { attribute: false },
|
||||
iframeHeight: { type: Number },
|
||||
editedMessageObj: { type: Object },
|
||||
repliedToMessageObj: {type: Object},
|
||||
setChatEditor: { attribute: false },
|
||||
iframeId: { type: String },
|
||||
hasGlobalEvents: { type: Boolean },
|
||||
chatMessageSize: { type: Number },
|
||||
isEditMessageOpen: { type: Boolean },
|
||||
editor: {type: Object},
|
||||
theme: {
|
||||
type: String,
|
||||
reflect: true
|
||||
},
|
||||
toggleEnableChatEnter: {attribute: false},
|
||||
isEnabledChatEnter: {type: Boolean}
|
||||
}
|
||||
}
|
||||
|
||||
static get styles() {
|
||||
return css`
|
||||
:host {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: auto;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
|
||||
}
|
||||
.chatbar-container {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
height: auto;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.chatbar-caption {
|
||||
border-bottom: 2px solid var(--mdc-theme-primary);
|
||||
}
|
||||
.privateMessageMargin {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.emoji-button {
|
||||
width: 45px;
|
||||
height: 40px;
|
||||
padding-top: 4px;
|
||||
border: none;
|
||||
outline: none;
|
||||
background: transparent;
|
||||
cursor: pointer;
|
||||
max-height: 40px;
|
||||
color: var(--black);
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.message-size-container {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.message-size {
|
||||
font-family: Roboto, sans-serif;
|
||||
font-size: 12px;
|
||||
color: black;
|
||||
}
|
||||
|
||||
.paperclip-icon {
|
||||
color: var(--paperclip-icon);
|
||||
width: 25px;
|
||||
}
|
||||
|
||||
.paperclip-icon:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.send-icon {
|
||||
width: 30px;
|
||||
margin-left: 5px;
|
||||
transition: all 0.1s ease-in-out;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.send-icon:hover {
|
||||
filter: brightness(1.1);
|
||||
}
|
||||
|
||||
.file-picker-container {
|
||||
position: relative;
|
||||
height: 25px;
|
||||
width: 25px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.file-picker-input-container {
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
bottom: 0px;
|
||||
left: 0px;
|
||||
right: 0px;
|
||||
z-index: 10;
|
||||
opacity: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
input[type=file]::-webkit-file-upload-button {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.chatbar-container textarea {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.chatbar-container .chat-editor {
|
||||
display: flex;
|
||||
max-height: -webkit-fill-available;
|
||||
width: 100%;
|
||||
border-color: transparent;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.checkmark-icon {
|
||||
width: 30px;
|
||||
color: var(--mdc-theme-primary);
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.checkmark-icon:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.element {
|
||||
width: 100%;
|
||||
max-height: 100%;
|
||||
overflow: auto;
|
||||
color: var(--black);
|
||||
padding: 0px 10px;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.element::-webkit-scrollbar-track {
|
||||
background-color: whitesmoke;
|
||||
border-radius: 7px;
|
||||
}
|
||||
|
||||
.element::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
border-radius: 7px;
|
||||
background-color: whitesmoke;
|
||||
}
|
||||
|
||||
.element::-webkit-scrollbar-thumb {
|
||||
background-color: rgb(180, 176, 176);
|
||||
border-radius: 7px;
|
||||
transition: all 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
.element::-webkit-scrollbar-thumb:hover {
|
||||
background-color: rgb(148, 146, 146);
|
||||
cursor: pointer;
|
||||
}
|
||||
.ProseMirror:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.is-active {
|
||||
background-color: var(--white)
|
||||
}
|
||||
|
||||
.ProseMirror > * + * {
|
||||
margin-top: 0.75em;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.ProseMirror ul,
|
||||
ol {
|
||||
padding: 0 1rem;
|
||||
}
|
||||
|
||||
.ProseMirror h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
.ProseMirror code {
|
||||
background-color: rgba(#616161, 0.1);
|
||||
color: #616161;
|
||||
}
|
||||
|
||||
.ProseMirror pre {
|
||||
background: #0D0D0D;
|
||||
color: #FFF;
|
||||
font-family: 'JetBrainsMono', monospace;
|
||||
padding: 0.75rem 1rem;
|
||||
border-radius: 0.5rem;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
.ProseMirror pre code {
|
||||
color: inherit;
|
||||
padding: 0;
|
||||
background: none;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
|
||||
.ProseMirror img {
|
||||
width: 1.7em;
|
||||
height: 1.5em;
|
||||
margin: 0px;
|
||||
|
||||
}
|
||||
|
||||
.ProseMirror blockquote {
|
||||
padding-left: 1rem;
|
||||
border-left: 2px solid rgba(#0D0D0D, 0.1);
|
||||
}
|
||||
|
||||
.ProseMirror hr {
|
||||
border: none;
|
||||
border-top: 2px solid rgba(#0D0D0D, 0.1);
|
||||
margin: 2rem 0;
|
||||
}
|
||||
.chatbar-button-single {
|
||||
background: var(--white);
|
||||
outline: none;
|
||||
border: none;
|
||||
color: var(--black);
|
||||
padding: 4px;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
margin-right: 2px;
|
||||
filter: brightness(100%);
|
||||
transition: all 0.2s;
|
||||
display: none;
|
||||
}
|
||||
.chatbar-button-single:hover {
|
||||
filter: brightness(120%);
|
||||
|
||||
}
|
||||
|
||||
.chatbar-buttons {
|
||||
margin-bottom: 5px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.show-chatbar-buttons {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
:host(:hover) .chatbar-button-single {
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.ProseMirror p.is-editor-empty:first-child::before {
|
||||
color: #adb5bd;
|
||||
content: attr(data-placeholder);
|
||||
float: left;
|
||||
height: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
.ProseMirror p {
|
||||
font-size: 18px;
|
||||
margin-block-start: 0px;
|
||||
margin-block-end: 0px;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.ProseMirror {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.ProseMirror mark {
|
||||
background-color: #ffe066;
|
||||
border-radius: 0.25em;
|
||||
box-decoration-break: clone;
|
||||
padding: 0.125em 0;
|
||||
}
|
||||
|
||||
.material-icons {
|
||||
font-family: 'Material Icons';
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
font-size: 24px;
|
||||
/* Preferred icon size */
|
||||
display: inline-block;
|
||||
line-height: 1;
|
||||
text-transform: none;
|
||||
letter-spacing: normal;
|
||||
word-wrap: normal;
|
||||
white-space: nowrap;
|
||||
direction: ltr;
|
||||
|
||||
}
|
||||
|
||||
.material-symbols-outlined {
|
||||
font-family: 'Material Symbols Outlined';
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
font-size: 18px; /* Preferred icon size */
|
||||
display: inline-block;
|
||||
line-height: 1;
|
||||
text-transform: none;
|
||||
letter-spacing: normal;
|
||||
word-wrap: normal;
|
||||
white-space: nowrap;
|
||||
direction: ltr;
|
||||
}
|
||||
.hide-styling {
|
||||
display: none;
|
||||
}
|
||||
|
||||
`
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
this.isLoadingMessages = true
|
||||
this.isLoading = false
|
||||
this.getMessageSize = this.getMessageSize.bind(this)
|
||||
this.sendMessageFunc = this.sendMessageFunc.bind(this)
|
||||
this.iframeHeight = 42
|
||||
this.chatMessageSize = 0
|
||||
this.userName = window.parent.reduxStore.getState().app.accountInfo.names[0]
|
||||
this.theme = localStorage.getItem('qortalTheme') ? localStorage.getItem('qortalTheme') : 'light'
|
||||
this.editor = null
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
return html`
|
||||
<div
|
||||
class=${["chatbar-container", "chatbar-buttons", this.iframeId !=="_chatEditorDOM" && 'hide-styling'].join(" ")}
|
||||
style="align-items: center;">
|
||||
|
||||
<button
|
||||
@click=${() => this.editor.chain().focus().toggleBold().run()}
|
||||
?disabled=${
|
||||
this.editor &&
|
||||
!this.editor.can()
|
||||
.chain()
|
||||
.focus()
|
||||
.toggleBold()
|
||||
.run()
|
||||
}
|
||||
class=${["chatbar-button-single", (this.editedMessageObj || this.repliedToMessageObj) && 'show-chatbar-buttons', this.editor && this.editor.isActive('bold') ? 'is-active' : ''].join(" ")}
|
||||
>
|
||||
<!-- <mwc-icon >format_bold</mwc-icon> -->
|
||||
<span class="material-symbols-outlined"></span>
|
||||
</button>
|
||||
<button
|
||||
@click=${() => this.editor.chain().focus().toggleItalic().run()}
|
||||
?disabled=${ this.editor &&
|
||||
!this.editor.can()
|
||||
.chain()
|
||||
.focus()
|
||||
.toggleItalic()
|
||||
.run()
|
||||
}
|
||||
class=${["chatbar-button-single", (this.editedMessageObj || this.repliedToMessageObj) && 'show-chatbar-buttons', this.editor && this.editor.isActive('italic') ? 'is-active' : ''].join(' ')}
|
||||
>
|
||||
<span class="material-symbols-outlined"></span>
|
||||
</button>
|
||||
|
||||
<button
|
||||
@click=${() => this.editor.chain().focus().toggleUnderline().run()}
|
||||
class=${["chatbar-button-single", (this.editedMessageObj || this.repliedToMessageObj) && 'show-chatbar-buttons', this.editor && this.editor.isActive('underline') ? 'is-active' : ''].join(' ')}
|
||||
>
|
||||
<span class="material-symbols-outlined"></span>
|
||||
</button>
|
||||
<button
|
||||
@click=${() => this.editor.chain().focus().toggleHighlight().run()}
|
||||
class=${["chatbar-button-single", (this.editedMessageObj || this.repliedToMessageObj) && 'show-chatbar-buttons', this.editor && this.editor.isActive('highlight') ? 'is-active' : ''].join(' ')}
|
||||
>
|
||||
<span class="material-symbols-outlined"></span>
|
||||
</button>
|
||||
<button
|
||||
@click=${() => this.editor.chain().focus().toggleCodeBlock().run()}
|
||||
class=${["chatbar-button-single",(this.editedMessageObj || this.repliedToMessageObj) && 'show-chatbar-buttons', this.editor && this.editor.isActive('codeBlock') ? 'is-active' : ''].join(' ')}
|
||||
>
|
||||
<span class="material-symbols-outlined"></span>
|
||||
</button>
|
||||
<button
|
||||
@click=${()=> this.toggleEnableChatEnter() }
|
||||
style="height: 26px; box-sizing: border-box;"
|
||||
class=${["chatbar-button-single",(this.editedMessageObj || this.repliedToMessageObj) && 'show-chatbar-buttons', this.editor && this.editor.isActive('codeBlock') ? 'is-active' : ''].join(' ')}
|
||||
>
|
||||
${this.isEnabledChatEnter ? html`
|
||||
${translate("chatpage.cchange63")}
|
||||
` : html`
|
||||
${translate("chatpage.cchange64")}
|
||||
`}
|
||||
|
||||
</button>
|
||||
|
||||
</div>
|
||||
<div
|
||||
class=${["chatbar-container", (this.iframeId === "newChat" || this.iframeId === "privateMessage") ? "chatbar-caption" : ""].join(" ")}
|
||||
style="align-items: flex-end; position: relative">
|
||||
|
||||
<div
|
||||
style=${this.iframeId === "privateMessage" ? "display: none" : "display: block"}
|
||||
class="file-picker-container"
|
||||
@click=${(e) => {
|
||||
this.preventUserSendingImage(e)
|
||||
}}>
|
||||
<vaadin-icon
|
||||
class="paperclip-icon"
|
||||
icon="vaadin:paperclip"
|
||||
slot="icon"
|
||||
>
|
||||
</vaadin-icon>
|
||||
<div class="file-picker-input-container">
|
||||
<input
|
||||
@change="${e => {
|
||||
this.insertImage(e.target.files[0]);
|
||||
const filePickerInput = this.shadowRoot.getElementById('file-picker')
|
||||
if(filePickerInput){
|
||||
filePickerInput.value = ""
|
||||
}
|
||||
}
|
||||
}"
|
||||
id="file-picker"
|
||||
class="file-picker-input" type="file" name="myImage" accept="image/*" />
|
||||
</div>
|
||||
</div>
|
||||
<textarea style="color: var(--black);" tabindex='1' ?autofocus=${true} ?disabled=${this.isLoading || this.isLoadingMessages} id="messageBox" rows="1"></textarea>
|
||||
<div id=${this.iframeId}
|
||||
class=${["element", this.iframeId === "privateMessage" ? "privateMessageMargin" : ""].join(" ")}
|
||||
></div>
|
||||
<button class="emoji-button" ?disabled=${this.isLoading || this.isLoadingMessages}>
|
||||
${html`<img class="emoji" draggable="false" alt="😀" src="/emoji/svg/1f600.svg" />`}
|
||||
</button>
|
||||
${this.editedMessageObj ? (
|
||||
html`
|
||||
<div style="margin-bottom: 10px">
|
||||
${this.isLoading === false ? html`
|
||||
<vaadin-icon
|
||||
class="checkmark-icon"
|
||||
icon="vaadin:check"
|
||||
slot="icon"
|
||||
@click=${() => {
|
||||
this.sendMessageFunc();
|
||||
}}
|
||||
>
|
||||
</vaadin-icon>
|
||||
` :
|
||||
html`
|
||||
<paper-spinner-lite active></paper-spinner-lite>
|
||||
`}
|
||||
</div>
|
||||
`
|
||||
) :
|
||||
html`
|
||||
<div
|
||||
style="margin-bottom: 10px;
|
||||
${this.iframeId === 'newChat'
|
||||
? 'display: none;'
|
||||
: 'display: flex;'}">
|
||||
${this.isLoading === false ? html`
|
||||
<img
|
||||
src="/img/qchat-send-message-icon.svg"
|
||||
alt="send-icon"
|
||||
class="send-icon"
|
||||
@click=${() => {
|
||||
this.sendMessageFunc();
|
||||
}}
|
||||
/>
|
||||
` :
|
||||
html`
|
||||
<paper-spinner-lite active></paper-spinner-lite>
|
||||
`}
|
||||
</div>
|
||||
`
|
||||
}
|
||||
</div>
|
||||
${this.chatMessageSize >= 750 ?
|
||||
html`
|
||||
<div class="message-size-container" style=${this.imageFile && "margin-top: 10px;"}>
|
||||
<div class="message-size" style="${this.chatMessageSize > 4000 && 'color: #bd1515'}">
|
||||
${`Your message size is of ${this.chatMessageSize} bytes out of a maximum of 4000`}
|
||||
</div>
|
||||
</div>
|
||||
` :
|
||||
html``}
|
||||
</div>
|
||||
`
|
||||
}
|
||||
|
||||
preventUserSendingImage(e) {
|
||||
if (!this.userName) {
|
||||
e.preventDefault();
|
||||
parentEpml.request('showSnackBar', get("chatpage.cchange27"));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
async firstUpdated() {
|
||||
|
||||
|
||||
|
||||
window.addEventListener('storage', () => {
|
||||
const checkTheme = localStorage.getItem('qortalTheme');
|
||||
const chatbar = this.shadowRoot.querySelector('.element')
|
||||
if (checkTheme === 'dark') {
|
||||
this.theme = 'dark';
|
||||
chatbar.style.cssText = "color:#ffffff;"
|
||||
|
||||
} else {
|
||||
this.theme = 'light';
|
||||
chatbar.style.cssText = "color:#080808;"
|
||||
|
||||
}
|
||||
})
|
||||
|
||||
this.emojiPickerHandler = this.shadowRoot.querySelector('.emoji-button');
|
||||
this.mirrorChatInput = this.shadowRoot.getElementById('messageBox');
|
||||
this.chatMessageInput = this.shadowRoot.querySelector('.element')
|
||||
|
||||
this.emojiPicker = new EmojiPicker({
|
||||
style: "twemoji",
|
||||
twemojiBaseUrl: '/emoji/',
|
||||
showPreview: false,
|
||||
showVariants: false,
|
||||
showAnimation: false,
|
||||
position: 'top-start',
|
||||
boxShadow: 'rgba(4, 4, 5, 0.15) 0px 0px 0px 1px, rgba(0, 0, 0, 0.24) 0px 8px 16px 0px',
|
||||
zIndex: 100
|
||||
|
||||
});
|
||||
|
||||
this.emojiPicker.on('emoji', selection => {
|
||||
|
||||
this.editor.commands.insertContent(selection.emoji, {
|
||||
parseOptions: {
|
||||
preserveWhitespace: false
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
this.emojiPickerHandler.addEventListener('click', () => this.emojiPicker.togglePicker(this.emojiPickerHandler));
|
||||
|
||||
await this.updateComplete;
|
||||
// this.initChatEditor();
|
||||
}
|
||||
|
||||
async updated(changedProperties) {
|
||||
if (changedProperties && changedProperties.has('editedMessageObj')) {
|
||||
if (this.editedMessageObj) {
|
||||
this.editor.commands.setContent(this.editedMessageObj.message)
|
||||
this.getMessageSize(this.editedMessageObj.message);
|
||||
} else {
|
||||
this.chatMessageSize = 0;
|
||||
}
|
||||
}
|
||||
if (changedProperties && changedProperties.has('placeholder') && this.updatePlaceholder && this.editor) {
|
||||
this.updatePlaceholder(this.editor, this.placeholder )
|
||||
}
|
||||
|
||||
if (changedProperties && changedProperties.has("imageFile")) {
|
||||
this.chatMessageInput = "newChat";
|
||||
}
|
||||
}
|
||||
|
||||
shouldUpdate(changedProperties) {
|
||||
// Only update element if prop1 changed.
|
||||
if(changedProperties.has('setChatEditor') && changedProperties.size === 1) return false
|
||||
return true
|
||||
}
|
||||
|
||||
sendMessageFunc(props) {
|
||||
if(this.editor.isEmpty && this.iframeId !== 'newChat') return
|
||||
this.getMessageSize(this.editor.getJSON())
|
||||
if (this.chatMessageSize > 4000 ) {
|
||||
parentEpml.request('showSnackBar', get("chatpage.cchange29"));
|
||||
return;
|
||||
}
|
||||
this.chatMessageSize = 0;
|
||||
this._sendMessage(props, this.editor.getJSON());
|
||||
}
|
||||
|
||||
getMessageSize(message){
|
||||
try {
|
||||
|
||||
const trimmedMessage = message
|
||||
let messageObject = {};
|
||||
|
||||
if (this.repliedToMessageObj) {
|
||||
let chatReference = this.repliedToMessageObj.reference;
|
||||
if (this.repliedToMessageObj.chatReference) {
|
||||
chatReference = this.repliedToMessageObj.chatReference;
|
||||
}
|
||||
messageObject = {
|
||||
messageText: trimmedMessage,
|
||||
images: [''],
|
||||
repliedTo: chatReference,
|
||||
version: 2
|
||||
}
|
||||
} else if (this.editedMessageObj) {
|
||||
let message = "";
|
||||
try {
|
||||
const parsedMessageObj = JSON.parse(this.editedMessageObj.decodedMessage);
|
||||
message = parsedMessageObj;
|
||||
} catch (error) {
|
||||
message = this.messageObj.decodedMessage
|
||||
}
|
||||
messageObject = {
|
||||
...message,
|
||||
messageText: trimmedMessage,
|
||||
}
|
||||
} else if(this.imageFile && this.iframeId === 'newChat') {
|
||||
messageObject = {
|
||||
messageText: trimmedMessage,
|
||||
images: [{
|
||||
service: "QCHAT_IMAGE",
|
||||
name: '123456789123456789123456789',
|
||||
identifier: '123456'
|
||||
}],
|
||||
repliedTo: '',
|
||||
version: 2
|
||||
};
|
||||
} else {
|
||||
messageObject = {
|
||||
messageText: trimmedMessage,
|
||||
images: [''],
|
||||
repliedTo: '',
|
||||
version: 2
|
||||
};
|
||||
}
|
||||
|
||||
const stringified = JSON.stringify(messageObject);
|
||||
const size = new Blob([stringified]).size;
|
||||
this.chatMessageSize = size;
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
window.customElements.define("chat-text-editor", ChatTextEditor)
|
@ -25,7 +25,8 @@ class ChatWelcomePage extends LitElement {
|
||||
btnDisable: { type: Boolean },
|
||||
isLoading: { type: Boolean },
|
||||
balance: { type: Number },
|
||||
theme: { type: String, reflect: true }
|
||||
theme: { type: String, reflect: true },
|
||||
setOpenPrivateMessage: { attribute: false }
|
||||
}
|
||||
}
|
||||
|
||||
@ -212,7 +213,14 @@ class ChatWelcomePage extends LitElement {
|
||||
<div class="center-box">
|
||||
<mwc-icon class="img-icon">chat</mwc-icon><br>
|
||||
<span style="font-size: 20px; color: var(--black);">${this.myAddress.address}</span>
|
||||
<div class="start-chat" @click=${() => this.shadowRoot.querySelector('#startSecondChatDialog').show()}>${translate("welcomepage.wcchange2")}</div>
|
||||
<div
|
||||
class="start-chat"
|
||||
@click="${() => this.setOpenPrivateMessage({
|
||||
name: "",
|
||||
open: true
|
||||
})}">
|
||||
${translate("welcomepage.wcchange2")}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -230,7 +238,11 @@ class ChatWelcomePage extends LitElement {
|
||||
<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="primaryAction" @click=${() => {
|
||||
this._sendMessage();
|
||||
}
|
||||
}>
|
||||
${translate("welcomepage.wcchange6")}</mwc-button>
|
||||
<mwc-button
|
||||
?disabled="${this.isLoading}"
|
||||
slot="secondaryAction"
|
||||
@ -319,90 +331,90 @@ class ChatWelcomePage extends LitElement {
|
||||
}
|
||||
|
||||
_sendMessage() {
|
||||
this.isLoading = true
|
||||
|
||||
const recipient = this.shadowRoot.getElementById('sendTo').value
|
||||
const messageBox = this.shadowRoot.getElementById('messageBox')
|
||||
const messageText = messageBox.value
|
||||
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
|
||||
this.isLoading = false;
|
||||
} else if (messageText.length === 0) {
|
||||
this.isLoading = false
|
||||
this.isLoading = false;
|
||||
} else {
|
||||
this.sendMessage()
|
||||
}
|
||||
this.sendMessage();
|
||||
}
|
||||
};
|
||||
|
||||
async sendMessage(e) {
|
||||
this.isLoading = true
|
||||
|
||||
const _recipient = this.shadowRoot.getElementById('sendTo').value
|
||||
const messageBox = this.shadowRoot.getElementById('messageBox')
|
||||
const messageText = messageBox.value
|
||||
let recipient
|
||||
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 myRes;
|
||||
let myNameRes = await parentEpml.request('apiCall', {
|
||||
type: 'api',
|
||||
url: `/names/${receiverName}`
|
||||
})
|
||||
|
||||
});
|
||||
if (myNameRes.error === 401) {
|
||||
myRes = false
|
||||
myRes = false;
|
||||
} else {
|
||||
myRes = myNameRes
|
||||
}
|
||||
myRes = myNameRes;
|
||||
};
|
||||
return myRes;
|
||||
};
|
||||
|
||||
return myRes
|
||||
}
|
||||
const myNameRes = await validateName(_recipient);
|
||||
|
||||
const myNameRes = await validateName(_recipient)
|
||||
if (!myNameRes) {
|
||||
|
||||
recipient = _recipient
|
||||
recipient = _recipient;
|
||||
} else {
|
||||
|
||||
recipient = myNameRes.owner
|
||||
}
|
||||
recipient = myNameRes.owner;
|
||||
};
|
||||
|
||||
let _reference = new Uint8Array(64);
|
||||
window.crypto.getRandomValues(_reference);
|
||||
|
||||
let sendTimestamp = Date.now()
|
||||
let sendTimestamp = Date.now();
|
||||
|
||||
let reference = window.parent.Base58.encode(_reference)
|
||||
let reference = window.parent.Base58.encode(_reference);
|
||||
|
||||
const getAddressPublicKey = async () => {
|
||||
let isEncrypted
|
||||
let _publicKey
|
||||
let isEncrypted;
|
||||
let _publicKey;
|
||||
|
||||
let addressPublicKey = await parentEpml.request('apiCall', {
|
||||
type: 'api',
|
||||
url: `/addresses/publickey/${recipient}`
|
||||
})
|
||||
|
||||
|
||||
if (addressPublicKey.error === 102) {
|
||||
_publicKey = false
|
||||
_publicKey = false;
|
||||
// Do something here...
|
||||
let err1string = get("welcomepage.wcchange7")
|
||||
parentEpml.request('showSnackBar', `${err1string}`)
|
||||
this.isLoading = false
|
||||
let err1string = get("welcomepage.wcchange7");
|
||||
parentEpml.request('showSnackBar', `${err1string}`);
|
||||
this.isLoading = false;
|
||||
} else if (addressPublicKey !== false) {
|
||||
isEncrypted = 1
|
||||
_publicKey = addressPublicKey
|
||||
sendMessageRequest(isEncrypted, _publicKey)
|
||||
isEncrypted = 1;
|
||||
_publicKey = addressPublicKey;
|
||||
sendMessageRequest(isEncrypted, _publicKey);
|
||||
} else {
|
||||
isEncrypted = 0
|
||||
_publicKey = this.selectedAddress.address
|
||||
sendMessageRequest(isEncrypted, _publicKey)
|
||||
}
|
||||
isEncrypted = 0;
|
||||
_publicKey = this.selectedAddress.address;
|
||||
sendMessageRequest(isEncrypted, _publicKey);
|
||||
};
|
||||
};
|
||||
|
||||
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,
|
||||
@ -411,14 +423,13 @@ class ChatWelcomePage extends LitElement {
|
||||
recipient: recipient,
|
||||
recipientPublicKey: _publicKey,
|
||||
hasChatReference: 0,
|
||||
message: messageText,
|
||||
message: stringifyMessageObject,
|
||||
lastReference: reference,
|
||||
proofOfWorkNonce: 0,
|
||||
isEncrypted: isEncrypted,
|
||||
isText: 1
|
||||
}
|
||||
})
|
||||
|
||||
_computePow(chatResponse)
|
||||
}
|
||||
|
||||
|
@ -48,32 +48,42 @@ class LevelFounder extends LitElement {
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.level {
|
||||
position: relative;
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.custom {
|
||||
--paper-tooltip-background: #03a9f4;
|
||||
--paper-tooltip-text-color: #fff;
|
||||
}
|
||||
|
||||
.level-img-tooltip {
|
||||
--paper-tooltip-background: #000000;
|
||||
--paper-tooltip-text-color: #fff;
|
||||
--paper-tooltip-delay-in: 300;
|
||||
--paper-tooltip-delay-out: 3000;
|
||||
}
|
||||
|
||||
.message-data {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.message-data-level {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
|
||||
.badge {
|
||||
align-items: center;
|
||||
background: #03a9f4;
|
||||
background: rgb(3, 169, 244);
|
||||
border: 1px solid transparent;
|
||||
border-radius: 99em;
|
||||
color: #fff;
|
||||
border-radius: 50%;
|
||||
color: rgb(255, 255, 255);
|
||||
display: flex;
|
||||
font-size: 10px;
|
||||
font-weight: 400;
|
||||
height: 12px;
|
||||
width: 12px;
|
||||
justify-content: center;
|
||||
line-height: 1;
|
||||
min-width: 12px;
|
||||
position: absolute;
|
||||
left: -16px;
|
||||
top: -12px;
|
||||
cursor: pointer;
|
||||
}
|
||||
`
|
||||
@ -87,7 +97,7 @@ class LevelFounder extends LitElement {
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<div class="level">
|
||||
<div class="message-data">
|
||||
${this.renderFounder()}
|
||||
${this.renderLevel()}
|
||||
</div>
|
||||
@ -135,21 +145,24 @@ class LevelFounder extends LitElement {
|
||||
}
|
||||
|
||||
renderFounder() {
|
||||
let adressfounder = this.memberInfo.flags
|
||||
let adressfounder = this.memberInfo.flags;
|
||||
if (adressfounder === 1) {
|
||||
return html `
|
||||
<span id="founderTooltip" class="badge">F</span>
|
||||
<paper-tooltip class="custom" for="founderTooltip" position="top">FOUNDER</paper-tooltip>
|
||||
`
|
||||
} else {
|
||||
return html ``
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
renderLevel() {
|
||||
let adresslevel = this.memberInfo.level
|
||||
let adresslevel = this.memberInfo.level;
|
||||
return html `
|
||||
<span id="levelTooltip">${translate("mintingpage.mchange27")} ${adresslevel}</span>
|
||||
<img id="level-img" src=${`/img/badges/level-${adresslevel}.png`} alt=${`badge-${adresslevel}`} class="message-data-level" />
|
||||
<paper-tooltip class="level-img-tooltip" for="level-img" position="top" >
|
||||
${translate("mintingpage.mchange27")} ${adresslevel}
|
||||
</paper-tooltip>
|
||||
`
|
||||
}
|
||||
|
||||
|
@ -232,7 +232,11 @@ class NameMenu extends LitElement {
|
||||
<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="primaryAction" @click=${() => {
|
||||
this._sendMessage();
|
||||
}
|
||||
}>
|
||||
${translate("welcomepage.wcchange6")}</mwc-button>
|
||||
<mwc-button
|
||||
?disabled="${this.isLoading}"
|
||||
slot="secondaryAction"
|
||||
@ -246,15 +250,15 @@ class NameMenu extends LitElement {
|
||||
}
|
||||
|
||||
firstUpdated() {
|
||||
this.getChatBlockedAdresses()
|
||||
this.getChatBlockedAdresses();
|
||||
|
||||
setInterval(() => {
|
||||
this.getChatBlockedAdresses();
|
||||
}, 60000)
|
||||
|
||||
window.addEventListener('storage', () => {
|
||||
const checkLanguage = localStorage.getItem('qortalLanguage')
|
||||
use(checkLanguage)
|
||||
const checkLanguage = localStorage.getItem('qortalLanguage');
|
||||
use(checkLanguage);
|
||||
})
|
||||
|
||||
window.onclick = function(event) {
|
||||
@ -521,7 +525,13 @@ class NameMenu 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,
|
||||
@ -530,7 +540,7 @@ class NameMenu extends LitElement {
|
||||
recipient: recipient,
|
||||
recipientPublicKey: _publicKey,
|
||||
hasChatReference: 0,
|
||||
message: messageText,
|
||||
message: stringifyMessageObject,
|
||||
lastReference: reference,
|
||||
proofOfWorkNonce: 0,
|
||||
isEncrypted: isEncrypted,
|
||||
|
85
qortal-ui-plugins/plugins/core/components/TipUser-css.js
Normal file
@ -0,0 +1,85 @@
|
||||
import { css } from 'lit'
|
||||
|
||||
export const tipUserStyles = css`
|
||||
.tip-user-header {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 12px;
|
||||
border-bottom: 1px solid whitesmoke;
|
||||
gap: 25px;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.tip-user-header-font {
|
||||
font-family: Montserrat, sans-serif;
|
||||
font-size: 20px;
|
||||
color: var(--chat-bubble-msg-color);
|
||||
margin: 0;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.tip-user-body {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 20px 10px;
|
||||
flex-direction: column;
|
||||
gap: 25px;
|
||||
}
|
||||
|
||||
.tip-input {
|
||||
width: 300px;
|
||||
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);
|
||||
}
|
||||
|
||||
.tip-input::selection {
|
||||
background-color: var(--mdc-theme-primary);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.tip-input::placeholder {
|
||||
opacity: 0.9;
|
||||
color: var(--black);
|
||||
}
|
||||
|
||||
.tip-available {
|
||||
font-family: Roboto, sans-serif;
|
||||
font-size: 17px;
|
||||
color: var(--chat-bubble-msg-color);
|
||||
font-weight: 300;
|
||||
letter-spacing: 0.3px;
|
||||
margin: 0;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.success-msg {
|
||||
font-family: Roboto, sans-serif;
|
||||
font-size: 18px;
|
||||
font-weight: 400;
|
||||
letter-spacing: 0.3px;
|
||||
margin: 0;
|
||||
user-select: none;
|
||||
color: #10880b;
|
||||
}
|
||||
|
||||
.error-msg {
|
||||
font-family: Roboto, sans-serif;
|
||||
font-size: 18px;
|
||||
font-weight: 400;
|
||||
letter-spacing: 0.3px;
|
||||
margin: 0;
|
||||
user-select: none;
|
||||
color: #f30000;
|
||||
}
|
||||
`
|
277
qortal-ui-plugins/plugins/core/components/TipUser.js
Normal file
@ -0,0 +1,277 @@
|
||||
import { LitElement, html } from 'lit';
|
||||
import { render } from 'lit/html.js';
|
||||
import { get, translate } from 'lit-translate';
|
||||
import { tipUserStyles } from './TipUser-css.js';
|
||||
import { Epml } from '../../../epml';
|
||||
import '@vaadin/button';
|
||||
import '@polymer/paper-progress/paper-progress.js';
|
||||
|
||||
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 },
|
||||
}
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
this.sendMoneyLoading = false
|
||||
this.btnDisable = false
|
||||
this.errorMessage = ""
|
||||
this.successMessage = ""
|
||||
this.myAddress = window.parent.reduxStore.getState().app.selectedAddress
|
||||
}
|
||||
|
||||
static styles = [tipUserStyles]
|
||||
|
||||
async firstUpdated() {
|
||||
await this.fetchWalletDetails();
|
||||
}
|
||||
|
||||
updated(changedProperties) {
|
||||
if (changedProperties && changedProperties.has("closeTipUser")) {
|
||||
if (this.closeTipUser) {
|
||||
this.shadowRoot.getElementById("amountInput").value = "";
|
||||
this.errorMessage = "";
|
||||
this.successMessage = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async getLastRef() {
|
||||
let myRef = await parentEpml.request("apiCall", {
|
||||
type: "api",
|
||||
url: `/addresses/lastreference/${this.myAddress.address}`,
|
||||
})
|
||||
return myRef;
|
||||
}
|
||||
|
||||
renderSuccessText() {
|
||||
return html`${translate("chatpage.cchange55")}`
|
||||
}
|
||||
|
||||
renderReceiverText() {
|
||||
return html`${translate("chatpage.cchange54")}`
|
||||
}
|
||||
|
||||
getApiKey() {
|
||||
const myNode = window.parent.reduxStore.getState().app.nodeConfig.knownNodes[window.parent.reduxStore.getState().app.nodeConfig.node];
|
||||
let apiKey = myNode.apiKey;
|
||||
return apiKey;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
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 (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)
|
||||
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;
|
||||
throw new Error(txnResponse);
|
||||
} else if (txnResponse.success === true && !txnResponse.data.error) {
|
||||
this.shadowRoot.getElementById('amountInput').value = '';
|
||||
this.errorMessage = '';
|
||||
this.successMessage = this.renderSuccessText();
|
||||
this.sendMoneyLoading = false;
|
||||
this.btnDisable = false;
|
||||
setTimeout(() => {
|
||||
this.setOpenTipUser(false);
|
||||
this.successMessage = "";
|
||||
}, 3000);
|
||||
} else {
|
||||
this.errorMessage = txnResponse.data.message;
|
||||
this.sendMoneyLoading = false;
|
||||
this.btnDisable = false;
|
||||
throw new Error(txnResponse);
|
||||
}
|
||||
}
|
||||
validateReceiver(recipient);
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<div class="tip-user-header">
|
||||
<img src="/img/qort.png" width="32" height="32">
|
||||
<p class="tip-user-header-font">${translate("chatpage.cchange43")} ${this.userName}</p>
|
||||
</div>
|
||||
<div class="tip-user-body">
|
||||
<p class="tip-available">${translate("chatpage.cchange47")}: ${this.walletBalance} QORT</p>
|
||||
<input id="amountInput" class="tip-input" type="number" placeholder="${translate("chatpage.cchange46")}" />
|
||||
<p class="tip-available">${translate("chatpage.cchange49")}: 0.001 QORT</p>
|
||||
${this.sendMoneyLoading ?
|
||||
html`
|
||||
<paper-progress indeterminate style="width: 100%; margin: 4px;">
|
||||
</paper-progress>`
|
||||
: html`
|
||||
<div style=${"text-align: center;"}>
|
||||
<vaadin-button
|
||||
?disabled=${this.btnDisable}
|
||||
theme="primary medium"
|
||||
style="width: 100%; cursor: pointer"
|
||||
@click=${() => this.sendQort()}>
|
||||
<vaadin-icon icon="vaadin:arrow-forward" slot="prefix"></vaadin-icon>
|
||||
${translate("chatpage.cchange50")} QORT
|
||||
</vaadin-button>
|
||||
</div>
|
||||
`}
|
||||
|
||||
${this.successMessage ?
|
||||
html`
|
||||
<p class="success-msg">
|
||||
${this.successMessage}
|
||||
</p>
|
||||
`
|
||||
: this.errorMessage ?
|
||||
html`
|
||||
<p class="error-msg">
|
||||
${this.errorMessage}
|
||||
</p>
|
||||
`
|
||||
: null}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
customElements.define('tip-user', TipUser);
|
@ -0,0 +1,69 @@
|
||||
import { css } from 'lit'
|
||||
|
||||
export const userInfoStyles = css`
|
||||
.user-info-header {
|
||||
font-family: Montserrat, sans-serif;
|
||||
text-align: center;
|
||||
font-size: 28px;
|
||||
color: var(--chat-bubble-msg-color);
|
||||
margin-bottom: 10px;
|
||||
padding: 10px 0;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.avatar-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.user-info-avatar {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
border-radius: 50%;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.user-info-no-avatar {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
text-transform: capitalize;
|
||||
font-size: 50px;
|
||||
font-family: Roboto, sans-serif;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
border-radius:50%;
|
||||
background: var(--chatHeadBg);
|
||||
color: var(--chatHeadText);
|
||||
}
|
||||
|
||||
.send-message-button {
|
||||
font-family: Roboto, sans-serif;
|
||||
letter-spacing: 0.3px;
|
||||
font-weight: 300;
|
||||
padding: 8px 5px;
|
||||
border-radius: 3px;
|
||||
text-align: center;
|
||||
color: var(--mdc-theme-primary);
|
||||
transition: all 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
.send-message-button:hover {
|
||||
cursor: pointer;
|
||||
background-color: #03a8f485;
|
||||
}
|
||||
|
||||
.close-icon {
|
||||
position: absolute;
|
||||
top: 3px;
|
||||
right: 5px;
|
||||
color: #676b71;
|
||||
width: 14px;
|
||||
transition: all 0.1s ease-in-out;
|
||||
}
|
||||
|
||||
.close-icon:hover {
|
||||
cursor: pointer;
|
||||
color: #494c50;
|
||||
}
|
||||
`
|
119
qortal-ui-plugins/plugins/core/components/UserInfo/UserInfo.js
Normal file
@ -0,0 +1,119 @@
|
||||
import { LitElement, html } from 'lit';
|
||||
import { render } from 'lit/html.js';
|
||||
import { translate } from 'lit-translate';
|
||||
import { userInfoStyles } from './UserInfo-css.js';
|
||||
import { Epml } from '../../../../epml';
|
||||
import '@vaadin/button';
|
||||
import '@polymer/paper-progress/paper-progress.js';
|
||||
import { cropAddress } from '../../../utils/cropAddress.js';
|
||||
|
||||
export class UserInfo extends LitElement {
|
||||
static get properties() {
|
||||
return {
|
||||
setOpenUserInfo: { attribute: false },
|
||||
setOpenTipUser: { attribute: false },
|
||||
setOpenPrivateMessage: { attribute: false },
|
||||
userName: { type: String },
|
||||
selectedHead: { type: Object },
|
||||
isImageLoaded: { type: Boolean }
|
||||
}
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
this.isImageLoaded = false
|
||||
this.selectedHead = {}
|
||||
this.imageFetches = 0
|
||||
}
|
||||
|
||||
static styles = [userInfoStyles]
|
||||
|
||||
createImage(imageUrl) {
|
||||
const imageHTMLRes = new Image();
|
||||
imageHTMLRes.src = imageUrl;
|
||||
imageHTMLRes.classList.add("user-info-avatar");
|
||||
imageHTMLRes.onload = () => {
|
||||
this.isImageLoaded = true;
|
||||
}
|
||||
imageHTMLRes.onerror = () => {
|
||||
if (this.imageFetches < 4) {
|
||||
setTimeout(() => {
|
||||
this.imageFetches = this.imageFetches + 1;
|
||||
imageHTMLRes.src = imageUrl;
|
||||
}, 500);
|
||||
} else {
|
||||
this.isImageLoaded = false
|
||||
}
|
||||
};
|
||||
return imageHTMLRes;
|
||||
}
|
||||
|
||||
|
||||
render() {
|
||||
let avatarImg = "";
|
||||
if (this.selectedHead && this.selectedHead.name) {
|
||||
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.selectedHead.name}/qortal_avatar?async=true&apiKey=${myNode.apiKey}`;
|
||||
avatarImg = this.createImage(avatarUrl);
|
||||
}
|
||||
return html`
|
||||
<div style=${"position: relative;"}>
|
||||
<vaadin-icon
|
||||
class="close-icon"
|
||||
icon="vaadin:close-big"
|
||||
slot="icon"
|
||||
@click=${() => {
|
||||
this.setOpenUserInfo(false)
|
||||
}}>
|
||||
</vaadin-icon>
|
||||
${this.isImageLoaded ?
|
||||
html`
|
||||
<div class="avatar-container">
|
||||
${avatarImg}
|
||||
</div>` :
|
||||
html``}
|
||||
${!this.isImageLoaded && this.selectedHead && this.selectedHead.name ?
|
||||
html`
|
||||
<div class="avatar-container">
|
||||
<div class="user-info-no-avatar">
|
||||
${this.selectedHead.name.charAt(0)}
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
${!this.isImageLoaded && this.selectedHead && !this.selectedHead.name ?
|
||||
html`
|
||||
<div class="avatar-container">
|
||||
<img src="/img/qortal-chat-logo.png" alt="avatar" />
|
||||
</div>`
|
||||
: ""}
|
||||
<div class="user-info-header">
|
||||
${this.selectedHead && this.selectedHead.name ? this.selectedHead.name : this.selectedHead ? cropAddress(this.selectedHead.address) : null}
|
||||
</div>
|
||||
<div
|
||||
class="send-message-button"
|
||||
@click="${() => {
|
||||
this.setOpenPrivateMessage({
|
||||
name: this.userName,
|
||||
open: true
|
||||
})
|
||||
this.setOpenUserInfo(false);
|
||||
}
|
||||
}">
|
||||
${translate("chatpage.cchange58")}
|
||||
</div>
|
||||
<div
|
||||
style=${"margin-top: 5px;"}
|
||||
class="send-message-button"
|
||||
@click=${() => {
|
||||
this.setOpenTipUser(true);
|
||||
this.setOpenUserInfo(false);
|
||||
}}>
|
||||
${translate("chatpage.cchange59")}
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
}
|
||||
customElements.define('user-info', UserInfo);
|
@ -0,0 +1,57 @@
|
||||
import { css } from 'lit'
|
||||
|
||||
export const wrapperModalStyles = css`
|
||||
.backdrop {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
background: rgb(186 186 186 / 26%);
|
||||
overflow: hidden;
|
||||
animation: backdrop_blur cubic-bezier(0.22, 1, 0.36, 1) 1s forwards;
|
||||
z-index: 50
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
height: auto;
|
||||
position: fixed;
|
||||
box-shadow: rgb(60 64 67 / 30%) 0px 1px 2px 0px, rgb(60 64 67 / 15%) 0px 2px 6px 2px;
|
||||
width: 500px;
|
||||
z-index: 5;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 15px;
|
||||
background-color: var(--white);
|
||||
left: 50%;
|
||||
top: 0px;
|
||||
transform: translate(-50%, 10%);
|
||||
border-radius: 12px;
|
||||
overflow-y: auto;
|
||||
animation: 1s cubic-bezier(0.22, 1, 0.36, 1) 0s 1 normal forwards running modal_transition;
|
||||
max-height: 80%;
|
||||
z-index: 60
|
||||
}
|
||||
|
||||
@keyframes backdrop_blur {
|
||||
0% {
|
||||
backdrop-filter: blur(0px);
|
||||
background: transparent;
|
||||
}
|
||||
100% {
|
||||
backdrop-filter: blur(5px);
|
||||
background: rgb(186 186 186 / 26%);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes modal_transition {
|
||||
0% {
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
}
|
||||
100% {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
`
|
33
qortal-ui-plugins/plugins/core/components/WrapperModal.js
Normal file
@ -0,0 +1,33 @@
|
||||
import { LitElement, html } from 'lit';
|
||||
import { render } from 'lit/html.js';
|
||||
import { wrapperModalStyles } from './WrapperModal-css.js'
|
||||
|
||||
export class WrapperModal extends LitElement {
|
||||
static get properties() {
|
||||
return {
|
||||
customStyle: {type: String},
|
||||
onClickFunc: { attribute: false },
|
||||
zIndex: {type: Number}
|
||||
}
|
||||
}
|
||||
|
||||
static styles = [wrapperModalStyles]
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<div>
|
||||
<div
|
||||
style="z-index: ${this.zIndex || 50}"
|
||||
class="backdrop"
|
||||
@click=${() => {
|
||||
this.onClickFunc();
|
||||
}}>
|
||||
</div>
|
||||
<div class="modal-body" style=${this.customStyle ? this.customStyle : ""}>
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
customElements.define('wrapper-modal', WrapperModal);
|
@ -0,0 +1,82 @@
|
||||
import { Sha256 } from 'asmcrypto.js'
|
||||
|
||||
|
||||
function sbrk(size, heap){
|
||||
let brk = 512 * 1024 // stack top
|
||||
let old = brk
|
||||
brk += size
|
||||
|
||||
if (brk > heap.length)
|
||||
throw new Error('heap exhausted')
|
||||
|
||||
return old
|
||||
}
|
||||
|
||||
|
||||
|
||||
self.addEventListener('message', async e => {
|
||||
const response = await computePow(e.data.chatBytes, e.data.path, e.data.difficulty)
|
||||
postMessage(response)
|
||||
|
||||
})
|
||||
|
||||
|
||||
const memory = new WebAssembly.Memory({ initial: 256, maximum: 256 })
|
||||
const heap = new Uint8Array(memory.buffer)
|
||||
|
||||
|
||||
|
||||
const computePow = async (chatBytes, path, difficulty) => {
|
||||
|
||||
let response = null
|
||||
|
||||
await new Promise((resolve, reject)=> {
|
||||
|
||||
const _chatBytesArray = Object.keys(chatBytes).map(function (key) { return chatBytes[key]; });
|
||||
const chatBytesArray = new Uint8Array(_chatBytesArray);
|
||||
const chatBytesHash = new Sha256().process(chatBytesArray).finish().result;
|
||||
const hashPtr = sbrk(32, heap);
|
||||
const hashAry = new Uint8Array(memory.buffer, hashPtr, 32);
|
||||
hashAry.set(chatBytesHash);
|
||||
|
||||
|
||||
const workBufferLength = 8 * 1024 * 1024;
|
||||
const workBufferPtr = sbrk(workBufferLength, heap);
|
||||
|
||||
|
||||
|
||||
const importObject = {
|
||||
env: {
|
||||
memory: memory
|
||||
},
|
||||
};
|
||||
|
||||
function loadWebAssembly(filename, imports) {
|
||||
// Fetch the file and compile it
|
||||
return fetch(filename)
|
||||
.then(response => response.arrayBuffer())
|
||||
.then(buffer => WebAssembly.compile(buffer))
|
||||
.then(module => {
|
||||
|
||||
// Create the instance.
|
||||
return new WebAssembly.Instance(module, importObject);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
loadWebAssembly(path)
|
||||
.then(wasmModule => {
|
||||
response = {
|
||||
nonce : wasmModule.exports.compute2(hashPtr, workBufferPtr, workBufferLength, difficulty),
|
||||
chatBytesArray
|
||||
}
|
||||
|
||||
resolve()
|
||||
|
||||
});
|
||||
|
||||
|
||||
})
|
||||
|
||||
return response
|
||||
}
|
@ -0,0 +1,92 @@
|
||||
import { Sha256 } from 'asmcrypto.js'
|
||||
|
||||
|
||||
|
||||
function sbrk(size, heap){
|
||||
let brk = 512 * 1024 // stack top
|
||||
let old = brk
|
||||
brk += size
|
||||
|
||||
if (brk > heap.length)
|
||||
throw new Error('heap exhausted')
|
||||
|
||||
return old
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
self.addEventListener('message', async e => {
|
||||
const response = await computePow(e.data.convertedBytes, e.data.path)
|
||||
postMessage(response)
|
||||
|
||||
})
|
||||
|
||||
|
||||
const memory = new WebAssembly.Memory({ initial: 256, maximum: 256 })
|
||||
const heap = new Uint8Array(memory.buffer)
|
||||
|
||||
|
||||
|
||||
const computePow = async (convertedBytes, path) => {
|
||||
|
||||
|
||||
let response = null
|
||||
|
||||
await new Promise((resolve, reject)=> {
|
||||
|
||||
const _convertedBytesArray = Object.keys(convertedBytes).map(
|
||||
function (key) {
|
||||
return convertedBytes[key]
|
||||
}
|
||||
)
|
||||
const convertedBytesArray = new Uint8Array(_convertedBytesArray)
|
||||
const convertedBytesHash = new Sha256()
|
||||
.process(convertedBytesArray)
|
||||
.finish().result
|
||||
const hashPtr = sbrk(32, heap)
|
||||
const hashAry = new Uint8Array(
|
||||
memory.buffer,
|
||||
hashPtr,
|
||||
32
|
||||
)
|
||||
|
||||
hashAry.set(convertedBytesHash)
|
||||
const difficulty = 14
|
||||
const workBufferLength = 8 * 1024 * 1024
|
||||
const workBufferPtr = sbrk(
|
||||
workBufferLength,
|
||||
heap
|
||||
)
|
||||
|
||||
const importObject = {
|
||||
env: {
|
||||
memory: memory
|
||||
},
|
||||
};
|
||||
|
||||
function loadWebAssembly(filename, imports) {
|
||||
return fetch(filename)
|
||||
.then(response => response.arrayBuffer())
|
||||
.then(buffer => WebAssembly.compile(buffer))
|
||||
.then(module => {
|
||||
return new WebAssembly.Instance(module, importObject);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
loadWebAssembly(path)
|
||||
.then(wasmModule => {
|
||||
response = {
|
||||
nonce : wasmModule.exports.compute2(hashPtr, workBufferPtr, workBufferLength, difficulty),
|
||||
|
||||
}
|
||||
resolve()
|
||||
|
||||
});
|
||||
|
||||
|
||||
})
|
||||
|
||||
return response
|
||||
}
|
@ -1820,6 +1820,7 @@ class GroupManagement extends LitElement {
|
||||
setTimeout(getGroupInvites, 1)
|
||||
configLoaded = true
|
||||
}
|
||||
console.log('parse', JSON.parse(c))
|
||||
this.config = JSON.parse(c)
|
||||
})
|
||||
parentEpml.subscribe('copy_menu_switch', async value => {
|
||||
|
@ -0,0 +1,82 @@
|
||||
import { Sha256 } from 'asmcrypto.js'
|
||||
|
||||
|
||||
function sbrk(size, heap){
|
||||
let brk = 512 * 1024 // stack top
|
||||
let old = brk
|
||||
brk += size
|
||||
|
||||
if (brk > heap.length)
|
||||
throw new Error('heap exhausted')
|
||||
|
||||
return old
|
||||
}
|
||||
|
||||
|
||||
|
||||
self.addEventListener('message', async e => {
|
||||
const response = await computePow(e.data.chatBytes, e.data.path, e.data.difficulty)
|
||||
postMessage(response)
|
||||
|
||||
})
|
||||
|
||||
|
||||
const memory = new WebAssembly.Memory({ initial: 256, maximum: 256 })
|
||||
const heap = new Uint8Array(memory.buffer)
|
||||
|
||||
|
||||
|
||||
const computePow = async (chatBytes, path, difficulty) => {
|
||||
|
||||
let response = null
|
||||
|
||||
await new Promise((resolve, reject)=> {
|
||||
|
||||
const _chatBytesArray = Object.keys(chatBytes).map(function (key) { return chatBytes[key]; });
|
||||
const chatBytesArray = new Uint8Array(_chatBytesArray);
|
||||
const chatBytesHash = new Sha256().process(chatBytesArray).finish().result;
|
||||
const hashPtr = sbrk(32, heap);
|
||||
const hashAry = new Uint8Array(memory.buffer, hashPtr, 32);
|
||||
hashAry.set(chatBytesHash);
|
||||
|
||||
|
||||
const workBufferLength = 8 * 1024 * 1024;
|
||||
const workBufferPtr = sbrk(workBufferLength, heap);
|
||||
|
||||
|
||||
|
||||
const importObject = {
|
||||
env: {
|
||||
memory: memory
|
||||
},
|
||||
};
|
||||
|
||||
function loadWebAssembly(filename, imports) {
|
||||
// Fetch the file and compile it
|
||||
return fetch(filename)
|
||||
.then(response => response.arrayBuffer())
|
||||
.then(buffer => WebAssembly.compile(buffer))
|
||||
.then(module => {
|
||||
|
||||
// Create the instance.
|
||||
return new WebAssembly.Instance(module, importObject);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
loadWebAssembly(path)
|
||||
.then(wasmModule => {
|
||||
response = {
|
||||
nonce : wasmModule.exports.compute2(hashPtr, workBufferPtr, workBufferLength, difficulty),
|
||||
chatBytesArray
|
||||
}
|
||||
|
||||
resolve()
|
||||
|
||||
});
|
||||
|
||||
|
||||
})
|
||||
|
||||
return response
|
||||
}
|
@ -0,0 +1,479 @@
|
||||
import { css } from 'lit'
|
||||
|
||||
export const qchatStyles = css`
|
||||
* {
|
||||
--mdc-theme-primary: rgb(3, 169, 244);
|
||||
--mdc-theme-secondary: var(--mdc-theme-primary);
|
||||
--paper-input-container-focus-color: var(--mdc-theme-primary);
|
||||
--mdc-theme-surface: var(--white);
|
||||
--mdc-dialog-content-ink-color: var(--black);
|
||||
--lumo-primary-text-color: rgb(0, 167, 245);
|
||||
--lumo-primary-color-50pct: rgba(0, 167, 245, 0.5);
|
||||
--lumo-primary-color-10pct: rgba(0, 167, 245, 0.1);
|
||||
--lumo-primary-color: hsl(199, 100%, 48%);
|
||||
--lumo-base-color: var(--white);
|
||||
--lumo-body-text-color: var(--black);
|
||||
--_lumo-grid-border-color: var(--border);
|
||||
--_lumo-grid-secondary-border-color: var(--border2);
|
||||
--mdc-dialog-min-width: 750px;
|
||||
}
|
||||
|
||||
paper-spinner-lite {
|
||||
height: 24px;
|
||||
width: 24px;
|
||||
--paper-spinner-color: var(--mdc-theme-primary);
|
||||
--paper-spinner-stroke-width: 2px;
|
||||
}
|
||||
|
||||
*,
|
||||
*:before,
|
||||
*:after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.container {
|
||||
margin: 0 auto;
|
||||
width: 100%;
|
||||
background: var(--white);
|
||||
}
|
||||
|
||||
.people-list {
|
||||
width: 20vw;
|
||||
float: left;
|
||||
height: 100vh;
|
||||
overflow-y: hidden;
|
||||
border-right: 3px #ddd solid;
|
||||
}
|
||||
|
||||
.people-list .blockedusers {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
width: 20vw;
|
||||
background: var(--white);
|
||||
border-top: 1px solid var(--border);
|
||||
border-right: 3px #ddd solid;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 15px;
|
||||
flex-direction: column;
|
||||
padding: 5px 30px 0 30px;
|
||||
}
|
||||
|
||||
.groups-button-container {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.groups-button {
|
||||
width: 100%;
|
||||
background-color: rgb(116, 69, 240);
|
||||
border: none;
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
font-family: 'Roboto';
|
||||
letter-spacing: 0.8px;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 5px;
|
||||
gap: 10px;
|
||||
padding: 5px 8px;
|
||||
transition: all 0.1s ease-in-out;
|
||||
}
|
||||
|
||||
.groups-button-notif {
|
||||
position: absolute;
|
||||
top: -10px;
|
||||
right: -8px;
|
||||
width: 25px;
|
||||
border-radius: 50%;
|
||||
height: 25px;
|
||||
font-weight: bold;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-family: Montserrat, sans-serif;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
color: black;
|
||||
background-color: rgb(51, 213, 0);
|
||||
user-select: none;
|
||||
transition: all 0.3s ease-in-out 0s;
|
||||
}
|
||||
|
||||
.groups-button-notif:hover {
|
||||
cursor: auto;
|
||||
box-shadow: rgba(99, 99, 99, 0.2) 0px 2px 8px 0px;
|
||||
}
|
||||
|
||||
.groups-button-notif:hover + .groups-button-notif-number {
|
||||
display: block;
|
||||
opacity: 1;
|
||||
animation: fadeIn 0.6s;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
top: -10px;
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
top: -60px;
|
||||
}
|
||||
}
|
||||
|
||||
.groups-button-notif-number {
|
||||
position: absolute;
|
||||
transform: translateX(-50%);
|
||||
left: 50%;
|
||||
width: 150px;
|
||||
text-align: center;
|
||||
border-radius: 3px;
|
||||
padding: 5px 10px;
|
||||
background-color: white;
|
||||
color: black;
|
||||
font-family: Roboto, sans-serif;
|
||||
letter-spacing: 0.3px;
|
||||
font-weight: 300;
|
||||
display: none;
|
||||
opacity: 0;
|
||||
top: -60px;
|
||||
box-shadow: rgb(216 216 216 / 25%) 0px 6px 12px -2px, rgb(0 0 0 / 30%) 0px 3px 7px -3px;
|
||||
}
|
||||
|
||||
.groups-button:hover {
|
||||
cursor: pointer;
|
||||
filter: brightness(120%);
|
||||
}
|
||||
|
||||
.people-list .search {
|
||||
padding-top: 20px;
|
||||
padding-left: 20px;
|
||||
padding-right: 20px;
|
||||
}
|
||||
|
||||
.center {
|
||||
margin: 0;
|
||||
position: absolute;
|
||||
padding-top: 12px;
|
||||
left: 50%;
|
||||
-ms-transform: translateX(-50%);
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
.people-list .create-chat {
|
||||
border-radius: 5px;
|
||||
border: none;
|
||||
display: inline-block;
|
||||
padding: 14px;
|
||||
color: #fff;
|
||||
background: var(--tradehead);
|
||||
width: 100%;
|
||||
font-size: 15px;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.people-list .create-chat:hover {
|
||||
opacity: .8;
|
||||
box-shadow: 0 3px 5px rgba(0, 0, 0, .2);
|
||||
}
|
||||
|
||||
.people-list ul {
|
||||
padding: 0px 0px 60px 0px;
|
||||
height: 85vh;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.people-list ul::-webkit-scrollbar-track {
|
||||
background-color: whitesmoke;
|
||||
border-radius: 7px;
|
||||
}
|
||||
|
||||
.people-list ul::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
border-radius: 7px;
|
||||
background-color: whitesmoke;
|
||||
}
|
||||
|
||||
.people-list ul::-webkit-scrollbar-thumb {
|
||||
background-color: rgb(180, 176, 176);
|
||||
border-radius: 7px;
|
||||
transition: all 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
.chat {
|
||||
width: 80vw;
|
||||
height: 100vh;
|
||||
float: left;
|
||||
background: var(--white);
|
||||
border-top-right-radius: 5px;
|
||||
border-bottom-right-radius: 5px;
|
||||
color: #434651;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.chat .new-message-bar {
|
||||
display: flex;
|
||||
flex: 0 1 auto;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0px 25px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
top: 0;
|
||||
position: absolute;
|
||||
left: 20vw;
|
||||
right: 0;
|
||||
z-index: 5;
|
||||
background: var(--tradehead);
|
||||
color: var(--white);
|
||||
border-radius: 0 0 8px 8px;
|
||||
min-height: 25px;
|
||||
transition: opacity .15s;
|
||||
text-transform: capitalize;
|
||||
opacity: .85;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.chat .new-message-bar:hover {
|
||||
opacity: .75;
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 3px 7px rgba(0, 0, 0, .2);
|
||||
}
|
||||
|
||||
.hide-new-message-bar {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.chat .chat-history {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 100%;
|
||||
left: 20vw;
|
||||
border-bottom: 2px solid var(--white);
|
||||
overflow-y: hidden;
|
||||
height: 100vh;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.chat .chat-message {
|
||||
padding: 10px;
|
||||
height: 10%;
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
background-color: #eee;
|
||||
}
|
||||
|
||||
.chat .chat-message textarea {
|
||||
width: 90%;
|
||||
border: none;
|
||||
font-size: 16px;
|
||||
padding: 10px 20px;
|
||||
border-radius: 5px;
|
||||
resize: none;
|
||||
}
|
||||
|
||||
.chat .chat-message button {
|
||||
float: right;
|
||||
color: #94c2ed;
|
||||
font-size: 16px;
|
||||
text-transform: uppercase;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
font-weight: bold;
|
||||
background: #f2f5f8;
|
||||
padding: 10px;
|
||||
margin-top: 4px;
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.chat .chat-message button:hover {
|
||||
color: #75b1e8;
|
||||
}
|
||||
|
||||
.online,
|
||||
.offline,
|
||||
.me {
|
||||
margin-right: 3px;
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.clearfix:after {
|
||||
visibility: hidden;
|
||||
display: block;
|
||||
font-size: 0;
|
||||
content: " ";
|
||||
clear: both;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.red {
|
||||
--mdc-theme-primary: red;
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin:0;
|
||||
}
|
||||
|
||||
h2, h3, h4, h5 {
|
||||
color: var(--black);
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
[hidden] {
|
||||
display: hidden !important;
|
||||
visibility: none !important;
|
||||
}
|
||||
|
||||
.details {
|
||||
display: flex;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-weight:600;
|
||||
font-size:12px;
|
||||
line-height: 32px;
|
||||
opacity: 0.66;
|
||||
}
|
||||
|
||||
.textarea {
|
||||
width: 100%;
|
||||
border: none;
|
||||
display: inline-block;
|
||||
font-size: 16px;
|
||||
padding: 10px 20px;
|
||||
border-radius: 5px;
|
||||
height: 120px;
|
||||
resize: none;
|
||||
background: #eee;
|
||||
}
|
||||
|
||||
.dialog-container {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
padding: 0 10px;
|
||||
gap: 10px;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.dialog-header {
|
||||
color: var(--chat-bubble-msg-color);
|
||||
}
|
||||
|
||||
.dialog-subheader {
|
||||
color: var(--chat-bubble-msg-color);
|
||||
}
|
||||
|
||||
.modal-button-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.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%;
|
||||
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-field {
|
||||
width: 100%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.search-icon {
|
||||
position: absolute;
|
||||
right: 3px;
|
||||
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;
|
||||
}
|
||||
|
||||
.search-results-div {
|
||||
position: absolute;
|
||||
top: 25px;
|
||||
right: 25px;
|
||||
}
|
||||
|
||||
.user-verified {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 5px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
color: #04aa2e;
|
||||
font-size: 13px;
|
||||
}
|
||||
`
|
@ -272,7 +272,7 @@ class NameRegistration extends LitElement {
|
||||
const myNode = window.parent.reduxStore.getState().app.nodeConfig.knownNodes[window.parent.reduxStore.getState().app.nodeConfig.node]
|
||||
const nodeUrl = myNode.protocol + '://' + myNode.domain + ':' + myNode.port
|
||||
const url = `${nodeUrl}/arbitrary/THUMBNAIL/${name}/qortal_avatar?async=true&apiKey=${this.getApiKey()}`;
|
||||
return html`<img src="${url}" onerror="this.onerror=null; this.src='/img/incognito.png';">`
|
||||
return html`<img src="${url}" onerror="this.onerror=null; this.src='/img/qortal-chat-logo.png';">`
|
||||
}
|
||||
|
||||
renderAvatarButton(nameObj) {
|
||||
|
@ -703,7 +703,7 @@ class Websites extends LitElement {
|
||||
const myNode = window.parent.reduxStore.getState().app.nodeConfig.knownNodes[window.parent.reduxStore.getState().app.nodeConfig.node]
|
||||
const nodeUrl = myNode.protocol + '://' + myNode.domain + ':' + myNode.port
|
||||
const url = `${nodeUrl}/arbitrary/THUMBNAIL/${name}/qortal_avatar?async=true&apiKey=${this.getApiKey()}`
|
||||
return html`<a class="visitSite" href="browser/index.html?name=${name}&service=${this.service}"><img src="${url}" onerror="this.src='/img/incognito.png';"></a>`
|
||||
return html`<a class="visitSite" href="browser/index.html?name=${name}&service=${this.service}"><img src="${url}" onerror="this.src='/img/qortal-chat-logo.png';"></a>`
|
||||
}
|
||||
|
||||
renderRelayModeText() {
|
||||
|
@ -534,7 +534,7 @@ class SponsorshipList extends LitElement {
|
||||
${sponsorship?.name ? html`
|
||||
<img src=${sponsorship.url}
|
||||
class="avatar-img"
|
||||
onerror="this.src='/img/incognito.png'"
|
||||
onerror="this.src='/img/qortal-chat-logo.png'"
|
||||
/>
|
||||
` : ''}
|
||||
${sponsorship?.name || sponsorship.address}
|
||||
|
8
qortal-ui-plugins/plugins/utils/cropAddress.js
Normal file
@ -0,0 +1,8 @@
|
||||
export function cropAddress(string = "", range = 5) {
|
||||
const [start, end] = [
|
||||
string?.substring(0, range),
|
||||
string?.substring(string?.length - range, string?.length),
|
||||
//
|
||||
];
|
||||
return start + "..." + end;
|
||||
}
|
21
qortal-ui-plugins/plugins/utils/getUserNameFromAddress.js
Normal file
@ -0,0 +1,21 @@
|
||||
import { Epml } from '../../epml.js';
|
||||
import { cropAddress } from './cropAddress.js';
|
||||
|
||||
const parentEpml = new Epml({ type: 'WINDOW', source: window.parent })
|
||||
|
||||
export const getUserNameFromAddress = async (address) => {
|
||||
try {
|
||||
const getNames = await parentEpml.request("apiCall", {
|
||||
type: "api",
|
||||
url: `/names/address/${address}`,
|
||||
});
|
||||
|
||||
if (Array.isArray(getNames) && getNames.length > 0 ) {
|
||||
return getNames[0].name;
|
||||
} else {
|
||||
return address;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
143
qortal-ui-plugins/plugins/utils/publish-image.js
Normal file
@ -0,0 +1,143 @@
|
||||
const getApiKey = () => {
|
||||
const myNode =
|
||||
window.parent.reduxStore.getState().app.nodeConfig.knownNodes[
|
||||
window.parent.reduxStore.getState().app.nodeConfig.node
|
||||
]
|
||||
let apiKey = myNode.apiKey
|
||||
return apiKey
|
||||
}
|
||||
|
||||
export const publishData = async ({
|
||||
registeredName,
|
||||
path,
|
||||
file,
|
||||
service,
|
||||
identifier,
|
||||
parentEpml,
|
||||
uploadType,
|
||||
selectedAddress,
|
||||
worker
|
||||
}) => {
|
||||
const validateName = async (receiverName) => {
|
||||
let nameRes = await parentEpml.request("apiCall", {
|
||||
type: "api",
|
||||
url: `/names/${receiverName}`,
|
||||
})
|
||||
|
||||
return nameRes
|
||||
}
|
||||
|
||||
const convertBytesForSigning = async (transactionBytesBase58) => {
|
||||
let convertedBytes = await parentEpml.request("apiCall", {
|
||||
type: "api",
|
||||
method: "POST",
|
||||
url: `/transactions/convert`,
|
||||
body: `${transactionBytesBase58}`,
|
||||
})
|
||||
return convertedBytes
|
||||
}
|
||||
|
||||
const signAndProcess = async (transactionBytesBase58) => {
|
||||
let convertedBytesBase58 = await convertBytesForSigning(
|
||||
transactionBytesBase58
|
||||
)
|
||||
if (convertedBytesBase58.error) {
|
||||
return
|
||||
}
|
||||
|
||||
const convertedBytes =
|
||||
window.parent.Base58.decode(convertedBytesBase58)
|
||||
let nonce = null
|
||||
const computPath =window.parent.location.origin + '/memory-pow/memory-pow.wasm.full'
|
||||
await new Promise((res, rej) => {
|
||||
|
||||
worker.postMessage({convertedBytes, path: computPath});
|
||||
|
||||
worker.onmessage = e => {
|
||||
|
||||
worker.terminate()
|
||||
|
||||
nonce = e.data.nonce
|
||||
res()
|
||||
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
let response = await parentEpml.request("sign_arbitrary", {
|
||||
nonce: selectedAddress.nonce,
|
||||
arbitraryBytesBase58: transactionBytesBase58,
|
||||
arbitraryBytesForSigningBase58: convertedBytesBase58,
|
||||
arbitraryNonce: nonce,
|
||||
})
|
||||
let myResponse = { error: "" }
|
||||
if (response === false) {
|
||||
return
|
||||
} else {
|
||||
myResponse = response
|
||||
}
|
||||
|
||||
return myResponse
|
||||
}
|
||||
|
||||
const validate = async () => {
|
||||
let validNameRes = await validateName(registeredName)
|
||||
if (validNameRes.error) {
|
||||
return
|
||||
}
|
||||
let transactionBytes = await uploadData(registeredName, path, file)
|
||||
if (transactionBytes.error) {
|
||||
return
|
||||
} else if (
|
||||
transactionBytes.includes("Error 500 Internal Server Error")
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
let signAndProcessRes = await signAndProcess(transactionBytes)
|
||||
if (signAndProcessRes.error) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
const uploadData = async (registeredName, path, file) => {
|
||||
if (identifier != null && identifier.trim().length > 0) {
|
||||
let postBody = path
|
||||
let urlSuffix = ""
|
||||
if (file != null) {
|
||||
// If we're sending zipped data, make sure to use the /zip version of the POST /arbitrary/* API
|
||||
if (uploadType === "zip") {
|
||||
urlSuffix = "/zip"
|
||||
}
|
||||
// If we're sending file data, use the /base64 version of the POST /arbitrary/* API
|
||||
else if (uploadType === "file") {
|
||||
urlSuffix = "/base64"
|
||||
}
|
||||
|
||||
// Base64 encode the file to work around compatibility issues between javascript and java byte arrays
|
||||
let fileBuffer = new Uint8Array(await file.arrayBuffer())
|
||||
postBody = Buffer.from(fileBuffer).toString("base64")
|
||||
}
|
||||
|
||||
|
||||
|
||||
let uploadDataUrl = `/arbitrary/${service}/${registeredName}${urlSuffix}?apiKey=${getApiKey()}`
|
||||
if (identifier != null && identifier.trim().length > 0) {
|
||||
uploadDataUrl = `/arbitrary/${service}/${registeredName}/${identifier}${urlSuffix}?apiKey=${getApiKey()}`
|
||||
}
|
||||
let uploadDataRes = await parentEpml.request("apiCall", {
|
||||
type: "api",
|
||||
method: "POST",
|
||||
url: `${uploadDataUrl}`,
|
||||
body: `${postBody}`,
|
||||
})
|
||||
return uploadDataRes
|
||||
}
|
||||
}
|
||||
try {
|
||||
await validate()
|
||||
} catch (error) {
|
||||
throw new Error(error.message)
|
||||
}
|
||||
|
||||
}
|
92
qortal-ui-plugins/plugins/utils/replace-messages-edited.js
Normal file
@ -0,0 +1,92 @@
|
||||
export const replaceMessagesEdited = async ({
|
||||
decodedMessages,
|
||||
parentEpml,
|
||||
isReceipient,
|
||||
decodeMessageFunc,
|
||||
_publicKey
|
||||
}) => {
|
||||
const findNewMessages = decodedMessages.map(async (msg) => {
|
||||
let msgItem = msg
|
||||
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.reference}&reverse=true${msgQuery}`,
|
||||
})
|
||||
|
||||
if (response && Array.isArray(response) && response.length !== 0) {
|
||||
let responseItem = { ...response[0] }
|
||||
const decodeResponseItem = decodeMessageFunc(responseItem, isReceipient, _publicKey)
|
||||
delete decodeResponseItem.timestamp
|
||||
msgItem = {
|
||||
...msg,
|
||||
...decodeResponseItem,
|
||||
editedTimestamp: response[0].timestamp,
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
|
||||
return msgItem
|
||||
})
|
||||
const updateMessages = await Promise.all(findNewMessages)
|
||||
const findNewMessages2 = updateMessages.map(async (msg) => {
|
||||
let parsedMessageObj = msg
|
||||
try {
|
||||
parsedMessageObj = JSON.parse(msg.decodedMessage)
|
||||
} catch (error) {
|
||||
console.log('error')
|
||||
return msg
|
||||
}
|
||||
let msgItem = msg
|
||||
try {
|
||||
let msgQuery = `&involving=${msg.recipient}&involving=${msg.sender}`
|
||||
if (!isReceipient) {
|
||||
msgQuery = `&txGroupId=${msg.txGroupId}`
|
||||
}
|
||||
if (parsedMessageObj.repliedTo) {
|
||||
const response = await parentEpml.request("apiCall", {
|
||||
type: "api",
|
||||
url: `/chat/messages?chatreference=${parsedMessageObj.repliedTo}&reverse=true${msgQuery}`,
|
||||
})
|
||||
if (
|
||||
response &&
|
||||
Array.isArray(response) &&
|
||||
response.length !== 0
|
||||
) {
|
||||
msgItem = {
|
||||
...msg,
|
||||
repliedToData: decodeMessageFunc(response[0], isReceipient, _publicKey),
|
||||
}
|
||||
} else {
|
||||
const response2 = await parentEpml.request("apiCall", {
|
||||
type: "api",
|
||||
url: `/chat/messages?reference=${parsedMessageObj.repliedTo}&reverse=true${msgQuery}`,
|
||||
})
|
||||
|
||||
if (
|
||||
response2 &&
|
||||
Array.isArray(response2) &&
|
||||
response2.length !== 0
|
||||
) {
|
||||
msgItem = {
|
||||
...msg,
|
||||
repliedToData: decodeMessageFunc(response2[0], isReceipient, _publicKey),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
|
||||
return msgItem
|
||||
})
|
||||
const updateMessages2 = await Promise.all(findNewMessages2)
|
||||
|
||||
return updateMessages2
|
||||
}
|