mirror of
https://github.com/Qortal/qortal-ui.git
synced 2025-02-12 02:05:51 +00:00
Merge remote-tracking branch 'justin/justin-groups-features' into feature/group-features
This commit is contained in:
commit
c2eaa62d4a
2
.gitignore
vendored
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/
|
||||
|
@ -548,8 +548,24 @@
|
||||
"cchange39": "Cannot send an encrypted message to this user since they do not have their publickey on chain.",
|
||||
"cchange40": "IMAGE (click to view)",
|
||||
"cchange41": "Your Balance Is Under 4.20 QORT",
|
||||
"cchange42":"Out of the need to combat spam, accounts with under 4.20 Qort balance will take a long time to SEND messages in Q-Chat. If you wish to immediately increase the send speed for Q-Chat messages, obtain over 4.20 QORT to your address. This can be done with trades in the Trade Portal, or by way of another Qortian giving you the QORT. Once you have over 4.20 QORT in your account, Q-Chat messages will be instant and this dialog will no more show. Thank you for your understanding of this necessary spam prevention method, and we hope you enjoy Qortal!"
|
||||
|
||||
"cchange42": "Out of the need to combat spam, accounts with under 4.20 Qort balance will take a long time to SEND messages in Q-Chat. If you wish to immediately increase the send speed for Q-Chat messages, obtain over 4.20 QORT to your address. This can be done with trades in the Trade Portal, or by way of another Qortian giving you the QORT. Once you have over 4.20 QORT in your account, Q-Chat messages will be instant and this dialog will no more show. Thank you for your understanding of this necessary spam prevention method, and we hope you enjoy Qortal!",
|
||||
"cchange43": "Tip QORT to",
|
||||
"cchange44": "SEND MESSAGE",
|
||||
"cchange45": "TIP USER",
|
||||
"cchange46": "Tip Amount",
|
||||
"cchange47": "Available Balance",
|
||||
"cchange48": "Failed to Fetch QORT Balance. Try again!",
|
||||
"cchange49": "Current static fee",
|
||||
"cchange50": "Send",
|
||||
"cchange51": "Insufficient Funds!",
|
||||
"cchange52": "Invalid Amount!",
|
||||
"cchange53": "Receiver cannot be empty!",
|
||||
"cchange54": "Invalid Receiver!",
|
||||
"cchange55": "Transaction Successful!",
|
||||
"cchange56": "Transaction Failed!",
|
||||
"cchange57": "User Info",
|
||||
"cchange58": "SEND MESSAGE",
|
||||
"cchange59": "TIP USER"
|
||||
},
|
||||
"welcomepage": {
|
||||
"wcchange1": "Welcome to Q-Chat",
|
||||
@ -579,7 +595,8 @@
|
||||
"bcchange14": "Forward",
|
||||
"bcchange15": "Message Forwarded",
|
||||
"bcchange16": "Choose Recipient or Search for One Below",
|
||||
"bcchange17": "FORWARDED"
|
||||
"bcchange17": "FORWARDED",
|
||||
"bcchange18": "Tip User"
|
||||
},
|
||||
"grouppage": {
|
||||
"gchange1": "Qortal Groups",
|
||||
|
@ -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 {
|
||||
@ -190,19 +188,21 @@ class AppView extends connect(store)(LitElement) {
|
||||
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 {
|
||||
@ -325,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;
|
||||
}
|
||||
`
|
||||
]
|
||||
}
|
||||
|
@ -107,6 +107,7 @@ class ChatPage extends LitElement {
|
||||
flex-direction: column;
|
||||
height: 50vh;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@ -289,7 +290,6 @@ class ChatPage extends LitElement {
|
||||
|
||||
.chat-container {
|
||||
display: grid;
|
||||
grid-template-rows: minmax(6%, 92vh) minmax(40px, auto);
|
||||
max-height: 100%;
|
||||
}
|
||||
|
||||
@ -598,13 +598,6 @@ class ChatPage extends LitElement {
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.chat-container {
|
||||
display: grid;
|
||||
grid-template-rows: minmax(40px, auto) minmax(6%, 92vh) minmax(40px, auto);
|
||||
max-height: 100%;
|
||||
flex: 3;
|
||||
}
|
||||
|
||||
.chat-right-panel {
|
||||
flex: 0;
|
||||
border-left: 3px solid rgb(221, 221, 221);
|
||||
@ -869,8 +862,11 @@ class ChatPage extends LitElement {
|
||||
render() {
|
||||
return html`
|
||||
<div class="main-container">
|
||||
<div class="chat-container">
|
||||
${(!this.isReceipient && +this._chatId !== 0) ? html`
|
||||
<div
|
||||
class="chat-container"
|
||||
style=${(!this.isReceipient && +this._chatId !== 0) ? "grid-template-rows: minmax(40px, auto) minmax(6%, 92vh) minmax(40px, auto); flex: 3;" : "grid-template-rows: minmax(6%, 92vh) minmax(40px, auto); flex: 2;"}>
|
||||
${(!this.isReceipient && +this._chatId !== 0) ?
|
||||
html`
|
||||
<div class="group-nav-container">
|
||||
<div @click=${this._toggle} style="height: 100%; display: flex; align-items: center;flex-grow: 1; cursor: pointer; cursor: pointer; user-select: none">
|
||||
<p class="group-name">${this.groupInfo && this.groupInfo.groupName}</p>
|
||||
@ -883,7 +879,7 @@ class ChatPage extends LitElement {
|
||||
<!-- <chat-leave-group .chatHeads=${this.chatHeads} .selectedAddress=${this.selectedAddress} .leaveGroupObj=${this.groupInfo} .setActiveChatHeadUrl=${(val)=> this.setActiveChatHeadUrl(val)}></chat-leave-group> -->
|
||||
</div>
|
||||
</div>
|
||||
` : html`<div></div>`}
|
||||
` : null}
|
||||
|
||||
<div>
|
||||
${this.isLoadingMessages ?
|
||||
@ -1177,8 +1173,16 @@ class ChatPage extends LitElement {
|
||||
</wrapper-modal>
|
||||
</div>
|
||||
<div class="chat-right-panel ${this.shifted ? "movedin" : "movedout"}" ${animate()}>
|
||||
<chat-right-panel .getMoreMembers=${(val)=> this.getMoreMembers(val)} .toggle=${(val)=> this._toggle(val)} .selectedAddress=${this.selectedAddress} .groupMembers=${this.groupMembers} .groupAdmin=${this.groupAdmin} .leaveGroupObj=${this.groupInfo}></chat-right-panel>
|
||||
|
||||
<chat-right-panel
|
||||
.getMoreMembers=${(val)=> this.getMoreMembers(val)}
|
||||
.toggle=${(val)=> this._toggle(val)}
|
||||
.selectedAddress=${this.selectedAddress}
|
||||
.groupMembers=${this.groupMembers}
|
||||
.groupAdmin=${this.groupAdmin}
|
||||
.leaveGroupObj=${this.groupInfo}
|
||||
.setOpenPrivateMessage=${(val) => this.setOpenPrivateMessage(val)}
|
||||
.chatEditor=${this.chatEditor}>
|
||||
</chat-right-panel>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
@ -1458,12 +1462,10 @@ class ChatPage extends LitElement {
|
||||
async updated(changedProperties) {
|
||||
if (changedProperties && changedProperties.has('userLanguage')) {
|
||||
const userLang = changedProperties.get('userLanguage')
|
||||
|
||||
if (userLang) {
|
||||
await new Promise(r => setTimeout(r, 100));
|
||||
this.chatEditorPlaceholder = this.isReceipient === true ? `Message ${this._chatId}` : `${get("chatpage.cchange8")}`;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (changedProperties && changedProperties.has('chatId') && changedProperties.get('chatId')) {
|
||||
@ -1475,7 +1477,6 @@ class ChatPage extends LitElement {
|
||||
this.chatEditor.disable();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
async getName (recipient) {
|
||||
@ -1529,6 +1530,7 @@ async getName (recipient) {
|
||||
chatId=${this.chatId}
|
||||
.messages=${this.messagesRendered}
|
||||
.escapeHTML=${escape}
|
||||
.chatEditor=${this.chatEditor}
|
||||
.getOldMessage=${this.getOldMessage}
|
||||
.setRepliedToMessageObj=${(val) => this.setRepliedToMessageObj(val)}
|
||||
.setEditedMessageObj=${(val) => this.setEditedMessageObj(val)}
|
||||
|
@ -1,47 +1,64 @@
|
||||
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 })
|
||||
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 {
|
||||
isLoading: { type: Boolean },
|
||||
isOpenLeaveModal: { type: Boolean },
|
||||
openUserInfo: { type: Boolean },
|
||||
leaveGroupObj: { type: Object },
|
||||
error: { type: Boolean },
|
||||
message: { type: String },
|
||||
chatHeads: { type: Array },
|
||||
groupAdmin: { attribute: false },
|
||||
groupMembers: { attribute: false },
|
||||
selectedHead: { type: Object },
|
||||
toggle: { attribute: false },
|
||||
getMoreMembers:{ attribute: false }
|
||||
getMoreMembers:{ attribute: false },
|
||||
setOpenPrivateMessage: { attribute: false },
|
||||
openTipUser: { type: Boolean },
|
||||
userName: { type: String },
|
||||
chatEditor: { type: Object },
|
||||
walletBalance: { type: Number },
|
||||
sendMoneyLoading: { type: Boolean },
|
||||
btnDisable: { type: Boolean },
|
||||
errorMessage: { type: String },
|
||||
successMessage: { type: String }
|
||||
}
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
this.isLoading = false
|
||||
this.isOpenLeaveModal = false
|
||||
this.openUserInfo = false
|
||||
this.leaveGroupObj = {}
|
||||
this.leaveFee = 0.001
|
||||
this.error = false
|
||||
this.message = ""
|
||||
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.openTipUser = false
|
||||
this.userName = ""
|
||||
this.sendMoneyLoading = false
|
||||
this.btnDisable = false
|
||||
this.errorMessage = ""
|
||||
this.successMessage = ""
|
||||
this.setOpenTipUser = this.setOpenTipUser.bind(this);
|
||||
this.setOpenUserInfo = this.setOpenUserInfo.bind(this);
|
||||
}
|
||||
|
||||
static get styles() {
|
||||
@ -180,234 +197,13 @@ return css`
|
||||
this.elementObserver();
|
||||
}
|
||||
|
||||
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
|
||||
async updated(changedProperties) {
|
||||
if (changedProperties && changedProperties.has('selectedHead')) {
|
||||
if (this.selectedHead !== {}) {
|
||||
const userName = await getUserNameFromAddress(this.selectedHead.address);
|
||||
this.userName = userName;
|
||||
}
|
||||
}
|
||||
interval = setInterval(getAnswer, 5000)
|
||||
}
|
||||
|
||||
async unitFee(txType) {
|
||||
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=${txType}`;
|
||||
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;
|
||||
}
|
||||
|
||||
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) {
|
||||
this.resetDefaultSettings()
|
||||
|
||||
const leaveFeeInput = await this.unitFee('ADD_GROUP_ADMIN')
|
||||
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: 24,
|
||||
nonce: this.selectedAddress.nonce,
|
||||
params: {
|
||||
_groupId: groupId,
|
||||
fee: leaveFeeInput,
|
||||
member: this.selectedHead.address,
|
||||
lastReference: lastRef
|
||||
}
|
||||
})
|
||||
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()
|
||||
}
|
||||
|
||||
async _removeAdmin(groupId) {
|
||||
this.resetDefaultSettings()
|
||||
|
||||
const leaveFeeInput = await this.unitFee('REMOVE_GROUP_ADMIN')
|
||||
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: 25,
|
||||
nonce: this.selectedAddress.nonce,
|
||||
params: {
|
||||
_groupId: groupId,
|
||||
fee: leaveFeeInput,
|
||||
member: this.selectedHead.address,
|
||||
lastReference: lastRef
|
||||
}
|
||||
})
|
||||
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()
|
||||
}
|
||||
|
||||
elementObserver() {
|
||||
@ -424,6 +220,7 @@ return css`
|
||||
// passing it the element to observe, and the options object
|
||||
observer.observe(elementToObserve);
|
||||
}
|
||||
|
||||
observerHandler(entries) {
|
||||
if (!entries[0].isIntersecting) {
|
||||
return
|
||||
@ -435,9 +232,16 @@ return css`
|
||||
this.getMoreMembers(this.leaveGroupObj.groupId)
|
||||
}
|
||||
}
|
||||
|
||||
setOpenTipUser(props) {
|
||||
this.openTipUser = props
|
||||
}
|
||||
|
||||
setOpenUserInfo(props) {
|
||||
this.openUserInfo = props
|
||||
}
|
||||
|
||||
render() {
|
||||
console.log('this.groupMembers', this.groupMembers);
|
||||
console.log(5, "Chat Right Panel Here");
|
||||
const owner = this.groupAdmin.filter((admin)=> admin.address === this.leaveGroupObj.owner)
|
||||
return html`
|
||||
<div class="container">
|
||||
@ -457,7 +261,12 @@ return css`
|
||||
${owner.map((item) => {
|
||||
return html`<chat-side-nav-heads
|
||||
activeChatHeadUrl=""
|
||||
.setActiveChatHeadUrl=${(val) => {}}
|
||||
.setActiveChatHeadUrl=${(val) => {
|
||||
if (val.address === this.myAddress) return;
|
||||
console.log({ val });
|
||||
this.selectedHead = val;
|
||||
this.openUserInfo = true;
|
||||
}}
|
||||
chatInfo=${JSON.stringify(item)}
|
||||
></chat-side-nav-heads>`
|
||||
})}
|
||||
@ -465,7 +274,12 @@ return css`
|
||||
${this.groupAdmin.map((item) => {
|
||||
return html`<chat-side-nav-heads
|
||||
activeChatHeadUrl=""
|
||||
.setActiveChatHeadUrl=${(val) => {}}
|
||||
.setActiveChatHeadUrl=${(val) => {
|
||||
if (val.address === this.myAddress) return;
|
||||
console.log({ val });
|
||||
this.selectedHead = val;
|
||||
this.openUserInfo = true;
|
||||
}}
|
||||
chatInfo=${JSON.stringify(item)}
|
||||
></chat-side-nav-heads>`
|
||||
})}
|
||||
@ -474,9 +288,10 @@ return css`
|
||||
return html`<chat-side-nav-heads
|
||||
activeChatHeadUrl=""
|
||||
.setActiveChatHeadUrl=${(val) => {
|
||||
console.log({ val })
|
||||
this.selectedHead = val
|
||||
this.isOpenLeaveModal = true
|
||||
if (val.address === this.myAddress) return;
|
||||
console.log({ val });
|
||||
this.selectedHead = val;
|
||||
this.openUserInfo = true;
|
||||
}}
|
||||
chatInfo=${JSON.stringify(item)}
|
||||
></chat-side-nav-heads>`
|
||||
@ -485,55 +300,36 @@ return css`
|
||||
</div>
|
||||
|
||||
<wrapper-modal
|
||||
.removeImage=${() => {
|
||||
if (this.isLoading) return
|
||||
this.isOpenLeaveModal = false
|
||||
.onClickFunc=${() => {
|
||||
this.openUserInfo = false;
|
||||
this.userName = "";
|
||||
this.shadowRoot.querySelector("tip-user").shadowRoot.getElementById('amountInput').value = "";
|
||||
}}
|
||||
style=${
|
||||
this.isOpenLeaveModal ? "display: block" : "display: none"
|
||||
this.openUserInfo ? "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
|
||||
<user-info
|
||||
.setOpenUserInfo=${(val) => this.setOpenUserInfo(val)}
|
||||
.setOpenTipUser=${(val) => this.setOpenTipUser(val)}
|
||||
.setOpenPrivateMessage=${(val) => this.setOpenPrivateMessage(val)}
|
||||
.chatEditor=${this.chatEditor}
|
||||
.userName=${this.userName}
|
||||
.selectedHead=${this.selectedHead}
|
||||
></user-info>
|
||||
</wrapper-modal>
|
||||
<wrapper-modal
|
||||
.onClickFunc=${() => {
|
||||
this.openTipUser = false;
|
||||
this.chatEditor.enable();
|
||||
}}
|
||||
class="modal-button"
|
||||
?disabled="${this.isLoading}"
|
||||
|
||||
style=${this.openTipUser ? "display: block" : "display: none"}>
|
||||
<tip-user
|
||||
.closeTipUser=${this.openUserInfo}
|
||||
.chatEditor=${this.chatEditor}
|
||||
.userName=${this.userName}
|
||||
.setOpenTipUser=${(val) => this.setOpenTipUser(val)}
|
||||
>
|
||||
${translate("general.close")}
|
||||
</button>
|
||||
</tip-user>
|
||||
</wrapper-modal>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -63,12 +63,19 @@ export const chatStyles = css`
|
||||
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;
|
||||
|
@ -5,16 +5,19 @@ 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 { EmojiPicker } from 'emoji-picker-js';
|
||||
import { cropAddress } from "../../utils/cropAddress";
|
||||
import './LevelFounder.js';
|
||||
import './NameMenu.js';
|
||||
import './ChatModals.js';
|
||||
import './WrapperModal';
|
||||
import './TipUser'
|
||||
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 { cropAddress } from "../../utils/cropAddress";
|
||||
|
||||
const parentEpml = new Epml({ type: 'WINDOW', source: window.parent })
|
||||
let toggledMessage = {}
|
||||
@ -31,13 +34,18 @@ class ChatScroller extends LitElement {
|
||||
focusChatEditor: { attribute: false },
|
||||
sendMessage: { attribute: false },
|
||||
sendMessageForward: { attribute: false },
|
||||
showLastMessageRefScroller: { type: Function },
|
||||
showLastMessageRefScroller: { attribute: false },
|
||||
emojiPicker: { attribute: false },
|
||||
isLoadingMessages: { type: Boolean},
|
||||
setIsLoadingMessages: { attribute: false },
|
||||
chatId: { type: String },
|
||||
chatEditor: { type: Object },
|
||||
setForwardProperties: { attribute: false },
|
||||
setOpenPrivateMessage: { attribute: false },
|
||||
openTipUser: { type: Boolean },
|
||||
openUserInfo: { type: Boolean },
|
||||
userName: { type: String },
|
||||
selectedHead: { type: Object }
|
||||
}
|
||||
}
|
||||
|
||||
@ -48,12 +56,18 @@ class ChatScroller extends LitElement {
|
||||
this.messages = []
|
||||
this._upObserverhandler = this._upObserverhandler.bind(this)
|
||||
this._downObserverHandler = this._downObserverHandler.bind(this)
|
||||
this.setOpenTipUser = this.setOpenTipUser.bind(this)
|
||||
this.setOpenUserInfo = this.setOpenUserInfo.bind(this)
|
||||
this.setUserName = this.setUserName.bind(this)
|
||||
this.myAddress = window.parent.reduxStore.getState().app.selectedAddress.address
|
||||
this.hideMessages = JSON.parse(localStorage.getItem("MessageBlockedAddresses") || "[]")
|
||||
this.openTipUser = false;
|
||||
this.openUserInfo = false;
|
||||
this.userName = "";
|
||||
}
|
||||
|
||||
|
||||
render() {
|
||||
console.log(9, "chat scroller here");
|
||||
let formattedMessages = this.messages.reduce((messageArray, message, index) => {
|
||||
const lastGroupedMessage = messageArray[messageArray.length - 1];
|
||||
let timestamp;
|
||||
@ -117,12 +131,47 @@ class ChatScroller extends LitElement {
|
||||
.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)}>
|
||||
</message-template>`
|
||||
)
|
||||
})}
|
||||
<div id='downObserver'></div>
|
||||
</ul>
|
||||
<wrapper-modal
|
||||
.onClickFunc=${() => {
|
||||
this.openTipUser = false;
|
||||
this.chatEditor.enable();
|
||||
}}
|
||||
style=${this.openTipUser ? "display: block;" : "display: none;"}>
|
||||
<tip-user
|
||||
.closeTipUser=${!this.openTipUser}
|
||||
.chatEditor=${this.chatEditor}
|
||||
.focusChatEditor=${this.focusChatEditor}
|
||||
.userName=${this.userName}
|
||||
.setOpenTipUser=${(val) => this.setOpenTipUser(val)}>
|
||||
</tip-user>
|
||||
</wrapper-modal>
|
||||
<wrapper-modal
|
||||
.onClickFunc=${() => {
|
||||
this.openUserInfo = false;
|
||||
this.chatEditor.enable();
|
||||
this.userName = "";
|
||||
this.selectedHead = {};
|
||||
}}
|
||||
style=${
|
||||
this.openUserInfo ? "display: block" : "display: none"
|
||||
}>
|
||||
<user-info
|
||||
.setOpenUserInfo=${(val) => this.setOpenUserInfo(val)}
|
||||
.setOpenTipUser=${(val) => this.setOpenTipUser(val)}
|
||||
.setOpenPrivateMessage=${(val) => this.setOpenPrivateMessage(val)}
|
||||
.chatEditor=${this.chatEditor}
|
||||
.userName=${this.userName}
|
||||
.selectedHead=${this.selectedHead}
|
||||
></user-info>
|
||||
</wrapper-modal>
|
||||
`
|
||||
}
|
||||
|
||||
@ -133,6 +182,12 @@ class ChatScroller extends LitElement {
|
||||
if(changedProperties.has('chatId') && changedProperties.get('chatId')){
|
||||
return true
|
||||
}
|
||||
if(changedProperties.has('openTipUser')){
|
||||
return true
|
||||
}
|
||||
if(changedProperties.has('openUserInfo')){
|
||||
return true
|
||||
}
|
||||
// Only update element if prop1 changed.
|
||||
return changedProperties.has('messages');
|
||||
}
|
||||
@ -148,6 +203,25 @@ class ChatScroller extends LitElement {
|
||||
toggledMessage = message;
|
||||
}
|
||||
|
||||
setOpenTipUser(props) {
|
||||
this.openTipUser = props;
|
||||
this.chatEditor.disable();
|
||||
}
|
||||
|
||||
setOpenUserInfo(props) {
|
||||
this.openUserInfo = props;
|
||||
this.chatEditor.disable();
|
||||
}
|
||||
|
||||
setUserName(props) {
|
||||
this.userName = props.senderName ? props.senderName : props.sender;
|
||||
this.selectedHead = {
|
||||
...this.selectedHead,
|
||||
address: props.sender,
|
||||
name: props.senderName,
|
||||
};
|
||||
}
|
||||
|
||||
async firstUpdated() {
|
||||
this.emojiPicker.on('emoji', selection => {
|
||||
|
||||
@ -243,7 +317,10 @@ class MessageTemplate extends LitElement {
|
||||
setToggledMessage: { attribute: false },
|
||||
setForwardProperties: { attribute: false },
|
||||
viewImage: { type: Boolean },
|
||||
setOpenPrivateMessage : { attribute: false }
|
||||
setOpenPrivateMessage : { attribute: false },
|
||||
setOpenTipUser: { attribute: false },
|
||||
setOpenUserInfo: { attribute: false },
|
||||
setUserName: { attribute: false }
|
||||
}
|
||||
}
|
||||
|
||||
@ -413,7 +490,13 @@ class MessageTemplate extends LitElement {
|
||||
(this.isSingleMessageInGroup === true && this.isLastMessageInGroup === true))
|
||||
? (
|
||||
html`
|
||||
<div class="message-data-avatar">
|
||||
<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>
|
||||
`
|
||||
@ -440,7 +523,14 @@ class MessageTemplate extends LitElement {
|
||||
<div class="message-user-info">
|
||||
${this.isFirstMessage ?
|
||||
html`
|
||||
<span class="message-data-name">
|
||||
<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>
|
||||
`
|
||||
@ -448,7 +538,7 @@ class MessageTemplate extends LitElement {
|
||||
}
|
||||
${isForwarded ?
|
||||
html`
|
||||
<span class="message-data-name">
|
||||
<span class="forwarded-text">
|
||||
${forwarded}
|
||||
</span>
|
||||
`
|
||||
@ -462,7 +552,14 @@ class MessageTemplate extends LitElement {
|
||||
</div>
|
||||
${repliedToData && html`
|
||||
<div class="original-message">
|
||||
<p class="original-message-sender">
|
||||
<p
|
||||
style=${this.myAddress === repliedToData.sender ? "cursor: auto;" : "cursor: pointer;"}
|
||||
@click=${() => {
|
||||
if (this.myAddress === repliedToData.sender) return;
|
||||
this.setOpenUserInfo(true);
|
||||
this.setUserName(repliedToData)
|
||||
}}
|
||||
class="original-message-sender">
|
||||
${repliedToData.senderName ?? cropAddress(repliedToData.sender)}
|
||||
</p>
|
||||
<p class="replied-message">
|
||||
@ -538,6 +635,8 @@ class MessageTemplate extends LitElement {
|
||||
.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>
|
||||
@ -643,7 +742,9 @@ class ChatMenu extends LitElement {
|
||||
sendMessageForward: { attribute: false },
|
||||
setForwardProperties: { attribute: false },
|
||||
firstMessageInChat: { type: Boolean },
|
||||
setOpenPrivateMessage: { attribute: false }
|
||||
setOpenPrivateMessage: { attribute: false },
|
||||
setOpenTipUser: { attribute: false },
|
||||
setUserName: { attribute: false },
|
||||
}
|
||||
}
|
||||
|
||||
@ -789,6 +890,19 @@ class ChatMenu extends LitElement {
|
||||
</div>
|
||||
`
|
||||
) : html`<div></div>`}
|
||||
${this.myAddress !== this.originalMessage.sender ? (
|
||||
html`
|
||||
<div
|
||||
class=${`menu-icon ${!this.firstMessageInChat ? "tooltip" : ""}`}
|
||||
data-text="${translate("blockpage.bcchange18")}"
|
||||
@click=${() => {
|
||||
this.setOpenTipUser(true);
|
||||
this.setUserName(this.originalMessage);
|
||||
}}>
|
||||
<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>
|
||||
|
@ -101,8 +101,6 @@ class ChatSideNavHeads extends LitElement {
|
||||
imageHTMLRes.src = imageUrl;
|
||||
}, 500);
|
||||
} else {
|
||||
|
||||
|
||||
this.isImageLoaded = false
|
||||
}
|
||||
};
|
||||
@ -110,24 +108,53 @@ class ChatSideNavHeads extends LitElement {
|
||||
}
|
||||
|
||||
render() {
|
||||
let avatarImg = '';
|
||||
let backupAvatarImg = ''
|
||||
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>`: ''}
|
||||
${!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 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>
|
||||
`
|
||||
@ -149,9 +176,7 @@ class ChatSideNavHeads extends LitElement {
|
||||
this.config = JSON.parse(c)
|
||||
})
|
||||
})
|
||||
parentEpml.imReady()
|
||||
|
||||
|
||||
parentEpml.imReady();
|
||||
}
|
||||
|
||||
shouldUpdate(changedProperties) {
|
||||
@ -161,6 +186,9 @@ class ChatSideNavHeads extends LitElement {
|
||||
if(changedProperties.has('chatInfo')){
|
||||
return true
|
||||
}
|
||||
if(changedProperties.has('isImageLoaded')){
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
85
qortal-ui-plugins/plugins/core/components/TipUser-css.js
Normal file
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;
|
||||
}
|
||||
`
|
282
qortal-ui-plugins/plugins/core/components/TipUser.js
Normal file
282
qortal-ui-plugins/plugins/core/components/TipUser.js
Normal file
@ -0,0 +1,282 @@
|
||||
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 },
|
||||
chatEditor: { type: Object },
|
||||
walletBalance: { type: Number },
|
||||
sendMoneyLoading: { type: Boolean },
|
||||
closeTipUser: { type: Boolean },
|
||||
btnDisable: { type: Boolean },
|
||||
errorMessage: { type: String },
|
||||
successMessage: { type: String },
|
||||
setOpenTipUser: { attribute: false },
|
||||
focusChatEditor: { 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.userName = '';
|
||||
this.errorMessage = '';
|
||||
this.successMessage = this.renderSuccessText();
|
||||
this.sendMoneyLoading = false;
|
||||
this.btnDisable = false;
|
||||
setTimeout(() => {
|
||||
this.setOpenTipUser(false);
|
||||
this.chatEditor.enable();
|
||||
this.focusChatEditor();
|
||||
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;
|
||||
}
|
||||
`
|
134
qortal-ui-plugins/plugins/core/components/UserInfo/UserInfo.js
Normal file
134
qortal-ui-plugins/plugins/core/components/UserInfo/UserInfo.js
Normal file
@ -0,0 +1,134 @@
|
||||
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';
|
||||
|
||||
// const parentEpml = new Epml({ type: "WINDOW", source: window.parent });
|
||||
|
||||
export class UserInfo extends LitElement {
|
||||
static get properties() {
|
||||
return {
|
||||
setOpenUserInfo: { attribute: false },
|
||||
setOpenTipUser: { attribute: false },
|
||||
setOpenPrivateMessage: { attribute: false },
|
||||
chatEditor: { type: Object },
|
||||
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.style= "width:30px; height:30px; float: left; border-radius:50%; font-size:14px";
|
||||
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;
|
||||
}
|
||||
|
||||
updated(changedProperties) {
|
||||
if (changedProperties && changedProperties.has('selectedHead')) {
|
||||
if (this.selectedHead) {
|
||||
console.log(this.selectedHead, "selected head")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
let avatarImg = "";
|
||||
if (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)
|
||||
this.chatEditor.enable();
|
||||
}}>
|
||||
</vaadin-icon>
|
||||
${this.isImageLoaded ?
|
||||
html`
|
||||
<div class="avatar-container">
|
||||
${avatarImg}
|
||||
</div>` :
|
||||
html``}
|
||||
${!this.isImageLoaded && 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.name ?
|
||||
html`
|
||||
<div
|
||||
class="avatar-container"
|
||||
>
|
||||
<img src="/img/qortal-chat-logo.png" alt="avatar" />
|
||||
</div>`
|
||||
: ""}
|
||||
<div class="user-info-header">
|
||||
${this.selectedHead.name ? this.selectedHead.name : cropAddress(this.selectedHead.address)}
|
||||
</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);
|
||||
this.chatEditor.disable();
|
||||
}}>
|
||||
${translate("chatpage.cchange59")}
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
}
|
||||
customElements.define('user-info', UserInfo);
|
21
qortal-ui-plugins/plugins/utils/getUserNameFromAddress.js
Normal file
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);
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user