Browse Source

started group features - leave group

q-apps
Phillip Lang Martinez 2 years ago
parent
commit
7639c8b211
  1. 2
      qortal-ui-core/font/switch-theme.css
  2. 2
      qortal-ui-core/src/components/app-view.js
  3. 1
      qortal-ui-plugins/package.json
  4. 247
      qortal-ui-plugins/plugins/core/components/ChatLeaveGroup.js
  5. 181
      qortal-ui-plugins/plugins/core/components/ChatPage.js
  6. 2
      qortal-ui-plugins/plugins/core/components/ChatSelect.js
  7. 180
      qortal-ui-plugins/plugins/core/components/ChatSideNavHeads.js
  8. 2
      qortal-ui-plugins/plugins/core/messaging/q-chat/q-chat.src.js

2
qortal-ui-core/font/switch-theme.css

@ -47,6 +47,7 @@ html {
--chatHeadBgActive: #ebebeb;
--chatHeadText: #080808;
--chatHeadTextActive: #080808;
--lightChatHeadHover: #1e1f201a;
}
html[theme="dark"] {
@ -98,4 +99,5 @@ html[theme="dark"] {
--chatHeadBgActive: #0f1a2e;
--chatHeadText: #ffffff;
--chatHeadTextActive: #ffffff;
--lightChatHeadHover: #e0e1e31a;
}

2
qortal-ui-core/src/components/app-view.js

@ -55,6 +55,8 @@ class AppView extends connect(store)(LitElement) {
background: var(--sidetopbar);
color: var(--black);
border-top: var(--border);
height: 48px;
padding: 3px;
}
#sideBar {

1
qortal-ui-plugins/package.json

@ -17,6 +17,7 @@
"author": "QORTAL <[email protected]>",
"license": "GPL-3.0",
"dependencies": {
"@lit-labs/motion": "^1.0.3",
"@material/mwc-list": "0.27.0",
"@material/mwc-select": "0.27.0",
"asmcrypto.js": "2.3.2",

247
qortal-ui-plugins/plugins/core/components/ChatLeaveGroup.js

@ -0,0 +1,247 @@
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}
}
}
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 _leaveGroup(groupId, groupName) {
// 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)
}
// 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")} &nbsp;
<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);

181
qortal-ui-plugins/plugins/core/components/ChatPage.js

@ -1,5 +1,6 @@
import { LitElement, html, css } from 'lit';
import { render } from 'lit/html.js';
import {animate} from '@lit-labs/motion';
import { Epml } from '../../../epml.js';
import { use, get, translate, registerTranslateConfig } from 'lit-translate';
// import localForage from "localforage";
@ -17,6 +18,8 @@ import './TimeAgo.js';
import './ChatTextEditor';
import './WrapperModal';
import './ChatSelect.js'
import './ChatSideNavHeads.js'
import './ChatLeaveGroup.js'
import '@polymer/paper-spinner/paper-spinner-lite.js';
import '@material/mwc-button';
import '@material/mwc-dialog';
@ -72,7 +75,12 @@ class ChatPage extends LitElement {
webSocket: {attribute: false},
chatHeads: {type: Array},
forwardActiveChatHeadUrl: {type: String},
openForwardOpen: {type: Boolean}
openForwardOpen: {type: Boolean},
groupAdmin: {type: Array},
groupMembers: {type: Array},
shifted: {type: Boolean},
groupInfo: {type: Object},
setActiveChatHeadUrl: {attribute: false}
}
}
@ -81,7 +89,7 @@ class ChatPage extends LitElement {
html {
scroll-behavior: smooth;
}
.chat-head-container {
display: flex;
justify-content: flex-start;
@ -91,11 +99,7 @@ class ChatPage extends LitElement {
width: 100%;
}
.chat-container {
display: grid;
grid-template-rows: minmax(6%, 92vh) minmax(40px, auto);
max-height: 100%;
}
.chat-text-area {
display: flex;
@ -524,6 +528,43 @@ class ChatPage extends LitElement {
cursor: pointer;
background-color: #03a8f475;
}
.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);
height: 100%;
overflow-y: auto;
background: transparent;
}
.movedin {
flex: 1 !important;
background: transparent;
}
.chat-right-panel-label {
color: white;
padding: 5px;
font-size: 16px;
user-select: none;
}
.main-container {
display: flex;
height: 100%;
}
.top-bar-icon {
cursor: pointer;
height: 18px;
width: 18px;
transition: .2s all;
}
.top-bar-icon:hover {
color: var(--black)
}
`
}
@ -570,13 +611,33 @@ class ChatPage extends LitElement {
boxShadow: 'rgba(4, 4, 5, 0.15) 0px 0px 0px 1px, rgba(0, 0, 0, 0.24) 0px 8px 16px 0px'
});
this.openForwardOpen = false
this.groupAdmin = []
this.groupMembers = []
this.shifted = false
this.groupInfo = {}
}
_toggle() {
this.shifted = !this.shifted;
}
render() {
console.log('this.chatHeads', this.chatHeads)
return html`
<div class="main-container">
<div class="chat-container">
<div style="display:flex; height:40px; padding:3px; margin:0px;background-color: var(--chat-bubble-bg); box-sizing: border-box; align-items: center;justify-content: space-between">
<div>
<p style="color: var(--black);margin:0px;padding:0px">Name</p>
</div>
<div style="display:flex;height:100%;align-items:center">
<vaadin-icon class="top-bar-icon" @click=${this._toggle} style="margin: 0px 10px" icon="vaadin:info" slot="icon"></vaadin-icon>
<vaadin-icon class="top-bar-icon" style="margin: 0px 20px" icon="vaadin:cog" slot="icon"></vaadin-icon>
<vaadin-icon class="top-bar-icon" style="margin: 0px 20px" icon="vaadin:search" slot="icon"></vaadin-icon>
<!-- <vaadin-icon class="top-bar-icon" style="margin: 0px 20px" icon="vaadin:exit" slot="icon"></vaadin-icon> -->
<chat-leave-group .chatHeads=${this.chatHeads} .selectedAddress=${this.selectedAddress} .leaveGroupObj=${this.groupInfo} .setActiveChatHeadUrl=${(val)=> this.setActiveChatHeadUrl(val)}></chat-leave-group>
</div>
</div>
<div>
${this.isLoadingMessages ?
html`
@ -595,6 +656,7 @@ class ChatPage extends LitElement {
this.renderChatScroller()}
</div>
<div class="chat-text-area" style="${`${(this.repliedToMessageObj || this.editedMessageObj) && "min-height: 120px"}`}">
<div
class='last-message-ref'
style=${(this.lastMessageRefVisible && !this.imageFile) ? 'opacity: 1;' : 'opacity: 0;'}>
@ -661,6 +723,7 @@ class ChatPage extends LitElement {
</div>
</div>
</div>
${(this.isUploadingImage || this.isDeletingImage) ? html`
<div class="dialogCustom">
<div class="dialogCustomInner">
@ -766,6 +829,33 @@ class ChatPage extends LitElement {
</div>
</wrapper-modal>
</div>
<div class="chat-right-panel ${this.shifted ? "movedin" : "movedout"}" ${animate()}>
<p>Exit Icon</p>
<span>Group Avatar</span> <p>Group Name</p>
<p>Group owner</p>
<p>100 Members</p>
<p>Description</p>
<p>date created</p>
<p>private / public</p>
<p>approvalThreshold</p>
<p>"minimumBlockDelay": 0, "maximumBlockDelay": 0</p>
<p class="chat-right-panel-label">Admins</p>
${this.groupAdmin.map((item)=> {
return html`<chat-side-nav-heads activeChatHeadUrl="" setActiveChatHeadUrl=${(val)=> {
}} chatInfo=${JSON.stringify(item)}></chat-side-nav-heads>`
})}
<p class="chat-right-panel-label">Members</p>
${this.groupAdmin.map((item)=> {
return html`<chat-side-nav-heads activeChatHeadUrl="" setActiveChatHeadUrl=${(val)=> {
}} chatInfo=${JSON.stringify(item)}></chat-side-nav-heads>`
})}
</div>
</div>
`
}
@ -908,6 +998,63 @@ class ChatPage extends LitElement {
// this.initChatEditor();
}, 100)
const isRecipient = this.chatId.includes('direct') === true ? true : false;
const groupId = this.chatId.split('/')[1];
if(!isRecipient && groupId !== 0){
try {
const getMembers = await parentEpml.request("apiCall", {
type: "api",
url: `/groups/members/${groupId}?onlyAdmins=false&limit=20`,
});
const getMembersAdmins = await parentEpml.request("apiCall", {
type: "api",
url: `/groups/members/${groupId}?onlyAdmins=true&limit=20`,
});
const getGroupInfo = await parentEpml.request("apiCall", {
type: "api",
url: `/groups/${groupId}`,
});
const getMembersAdminsWithName = (getMembersAdmins.members || []).map(async (member) => {
let memberItem = member
try {
const name = await this.getName(member.member)
memberItem = {
address: member.member,
name: name ? name : undefined
}
} catch (error) {
console.log(error)
}
return memberItem
})
const membersAdminsWithName = await Promise.all(getMembersAdminsWithName)
const getMembersWithName = (getMembers.members || []).map(async (member) => {
let memberItem = member
try {
const name = await this.getName(member.member)
memberItem = {
address: member.member,
name: name ? name : undefined
}
} catch (error) {
console.log(error)
}
return memberItem
})
const membersWithName = await Promise.all(getMembersWithName)
this.groupAdmin = membersAdminsWithName
this.groupMembers = membersWithName
this.groupInfo = getGroupInfo
console.log({membersAdminsWithName})
} catch (error) {
console.error(error)
}
}
}
@ -961,6 +1108,24 @@ class ChatPage extends LitElement {
}
async getName (recipient) {
try {
const getNames = await parentEpml.request("apiCall", {
type: "api",
url: `/names/address/${recipient}`,
});
if (Array.isArray(getNames) && getNames.length > 0 ) {
return getNames[0].name
} else {
return ''
}
} catch (error) {
return ""
}
}
async renderPlaceholder() {
const getName = async (recipient)=> {
try {

2
qortal-ui-plugins/plugins/core/components/ChatSelect.js

@ -151,6 +151,8 @@ class ChatSelect extends LitElement {
})
})
parentEpml.imReady()
}
shouldUpdate(changedProperties) {

180
qortal-ui-plugins/plugins/core/components/ChatSideNavHeads.js

@ -0,0 +1,180 @@
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);
}
.about {
margin-top: 8px;
}
.about {
padding-left: 8px;
}
.status {
color: #92959e;
}
.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 = '';
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.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 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-side-nav-heads', ChatSideNavHeads)

2
qortal-ui-plugins/plugins/core/messaging/q-chat/q-chat.src.js

@ -702,7 +702,7 @@ class Chat extends LitElement {
// Else render Welcome to Q-CHat
// TODO: DONE: Do the above in the ChatPage
return html`<chat-page .chatHeads=${this.chatHeads} .hideNewMesssageBar=${this.hideNewMesssageBar} .showNewMesssageBar=${this.showNewMesssageBar} myAddress=${window.parent.reduxStore.getState().app.selectedAddress.address} chatId=${this.activeChatHeadUrl}></chat-page>`
return html`<chat-page .chatHeads=${this.chatHeads} .hideNewMesssageBar=${this.hideNewMesssageBar} .showNewMesssageBar=${this.showNewMesssageBar} myAddress=${window.parent.reduxStore.getState().app.selectedAddress.address} chatId=${this.activeChatHeadUrl} .setActiveChatHeadUrl=${(val)=> this.setActiveChatHeadUrl(val)}></chat-page>`
}
setChatHeads(chatObj) {

Loading…
Cancel
Save