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

added list for friends

This commit is contained in:
PhilReact 2023-10-06 01:36:01 -05:00
parent 369fa15224
commit a2f388a7b8
14 changed files with 1350 additions and 52 deletions

View File

@ -1176,5 +1176,18 @@
"notify1": "Confirming transaction",
"notify2": "Transaction confirmed",
"explanation": "Your transaction is getting confirmed. To track its progress, click on the bell icon."
},
"friends": {
"friend1": "Add name",
"friend2": "Add friend",
"friend3": "Adding a friend allows you to connect easily with that person. Be sure to also follow that user to support the hosting of their published resources.",
"friend4": "Notes",
"friend5": "Follow name",
"friend6": "Alias",
"friend7": "Add an alias to better remember your friend (Optional)",
"friend8": "Send a Q-Chat message",
"friend9": "Send a Q-Mail",
"friend10": "Edit friend",
"friend11": "Following"
}
}

View File

@ -43,6 +43,7 @@ import '../functional-components/side-menu-item.js'
import './start-minting.js'
import './notification-view/notification-bell.js'
import './notification-view/notification-bell-general.js'
import './friends-view/friends-side-panel-parent.js'
const parentEpml = new Epml({ type: 'WINDOW', source: window.parent })
@ -559,8 +560,10 @@ class AppView extends connect(store)(LitElement) {
</span>
</div>
<div style="display:flex;align-items:center;gap:20px">
<friends-side-panel-parent></friends-side-panel-parent>
<notification-bell></notification-bell>
<notification-bell-general></notification-bell-general>
</div>
<div style="display: inline;">
<span>
@ -647,6 +650,8 @@ class AppView extends connect(store)(LitElement) {
<mwc-button dense unelevated label="${translate("login.lp7")}" icon="lock_open" @click="${() => this.closeLockScreenActive()}"></mwc-button>
</div>
</paper-dialog>
<div id="portal-target"></div>
`
}

View File

@ -0,0 +1,222 @@
import { LitElement, html, css } from 'lit'
import { render } from 'lit/html.js'
import { use, get, translate, translateUnsafeHTML, registerTranslateConfig } from 'lit-translate'
import '@material/mwc-icon'
import '@vaadin/tooltip';
import './friend-item-actions'
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},
openEditFriend: {attribute: false}
}
}
static get styles() {
return css`
:host {
width: 100%;
}
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 style="display:flex; justify-content: space-between; align-items: center" @click=${(e) => {
const target = e.target
const popover =
this.shadowRoot.querySelector('friend-item-actions');
if (popover) {
popover.openPopover(target);
}
}} class="clearfix" id=${`friend-item-parent-${this.chatInfo.name}`}>
<div style="display:flex; flex-grow: 1; align-items: center">
${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>
</div>
<div style="display:flex; align-items: center">
${this.chatInfo.willFollow ? html`
<mwc-icon id="willFollowIcon" style="color: var(--black)">connect_without_contact</mwc-icon>
<vaadin-tooltip
for="willFollowIcon"
position="top"
hover-delay=${200}
hide-delay=${1}
text=${get('friends.friend11')}>
</vaadin-tooltip>
` : ''}
</div>
</li>
<friend-item-actions
for=${`friend-item-parent-${this.chatInfo.name}`}
message=${get('notifications.explanation')}
.openEditFriend=${()=> {
this.openEditFriend(this.chatInfo)
}}
name=${this.chatInfo.name}
></friend-item-actions>
`
}
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)
}
}
window.customElements.define('chat-side-nav-heads', ChatSideNavHeads)

View File

@ -0,0 +1,253 @@
import { LitElement, html, css } from 'lit';
import { render } from 'lit/html.js';
import {
use,
get,
translate,
translateUnsafeHTML,
registerTranslateConfig,
} from 'lit-translate';
import '@material/mwc-button';
import '@material/mwc-dialog';
import '@material/mwc-checkbox';
class AddFriendsModal extends LitElement {
static get properties() {
return {
isOpen: { type: Boolean },
setIsOpen: { attribute: false },
isLoading: { type: Boolean },
userSelected: { type: Object },
alias: { type: String },
willFollow: { type: Boolean },
notes: { type: String },
onSubmit: {attribute: false},
editContent: {type: Object},
onClose: {attribute: false}
};
}
constructor() {
super();
this.isOpen = false;
this.isLoading = false;
this.alias = '';
this.willFollow = true;
this.notes = '';
}
static get styles() {
return css`
* {
--mdc-theme-primary: rgb(3, 169, 244);
--mdc-theme-secondary: var(--mdc-theme-primary);
--mdc-theme-surface: var(--white);
--mdc-dialog-content-ink-color: var(--black);
--mdc-dialog-min-width: 400px;
--mdc-dialog-max-width: 1024px;
}
.input {
width: 90%;
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;
}
.input::selection {
background-color: var(--mdc-theme-primary);
color: white;
}
.input::placeholder {
opacity: 0.6;
color: var(--black);
}
.close-button {
display: block;
--mdc-theme-primary: red;
}
.checkbox-row {
position: relative;
display: flex;
align-items: center;
align-content: center;
font-family: Montserrat, sans-serif;
font-weight: 600;
color: var(--black);
}
.modal-overlay {
display: block;
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
background-color: rgba(0, 0, 0, 0.5); /* Semi-transparent backdrop */
z-index: 1000;
}
.modal-content {
position: fixed;
top: 50vh;
left: 50vw;
transform: translate(-50%, -50%);
background-color: var(--mdc-theme-surface);
width: 80vw;
max-width: 600px;
padding: 20px;
box-shadow: rgba(0, 0, 0, 0.1) 0px 4px 6px;
z-index: 1001;
border-radius: 5px;
max-height: 80vh;
overflow: auto;
}
.modal-overlay.hidden {
display: none;
}
`;
}
firstUpdated() {}
clearFields(){
this.alias = '';
this.willFollow = true;
this.notes = '';
}
addFriend(){
this.onSubmit({
name: this.userSelected.name,
alias: this.alias,
notes: this.notes,
willFollow: this.willFollow
})
this.clearFields()
this.onClose()
}
async updated(changedProperties) {
if (changedProperties && changedProperties.has('editContent') && this.editContent) {
console.log('this.editedContent', this.editContent)
this.userSelected = {
name: this.editContent.name ?? '',
}
this.notes = this.editContent.notes ?? ''
this.willFollow = this.editContent.willFollow ?? true
this.alias = this.editContent.alias ?? ''
}
}
render() {
console.log('hello2', this.editContent)
return html`
<div class="modal-overlay ${this.isOpen ? '' : 'hidden'}">
<div class="modal-content">
<div style="text-align:center">
<h1>${this.editContent ? translate('friends.friend10') : translate('friends.friend2')}</h1>
<hr />
</div>
<p>${translate('friends.friend3')}</p>
<div class="checkbox-row">
<label
for="willFollow"
id="willFollowLabel"
style="color: var(--black);"
>
${get('friends.friend5')}
</label>
<mwc-checkbox
style="margin-right: -15px;"
id="willFollow"
@change=${(e) => {
this.willFollow = e.target.checked;
}}
?checked=${this.willFollow}
></mwc-checkbox>
</div>
<div style="height: 15px"></div>
<div style="display: flex;flex-direction: column;">
<label
for="name"
id="nameLabel"
style="color: var(--black);"
>
${get('login.name')}
</label>
<input
id="name"
class="input"
?disabled=${true}
value=${this.userSelected ? this.userSelected.name : ''}
/>
</div>
<div style="height: 15px"></div>
<div style="display: flex;flex-direction: column;">
<label
for="alias"
id="aliasLabel"
style="color: var(--black);"
>
${get('friends.friend6')}
</label>
<input
id="alias"
placeholder=${translate('friends.friend7')}
class="input"
value=${this.alias}
@change=${(e) => (this.alias = e.target.value)}
/>
</div>
<div style="height: 15px"></div>
<div style="margin-bottom:0;">
<textarea
class="input"
@change=${(e) => (this.notes = e.target.value)}
value=${this.notes}
?disabled=${this.isLoading}
id="messageBoxAddFriend"
placeholder="${translate('friends.friend4')}"
rows="3"
></textarea>
</div>
<div style="display:flex;justify-content:space-between;align-items:center;margin-top:20px">
<mwc-button
?disabled="${this.isLoading}"
slot="secondaryAction"
@click="${() => {
this.setIsOpen(false)
this.clearFields()
this.onClose()
} }"
class="close-button"
>
${translate('general.close')}
</mwc-button>
<mwc-button
?disabled="${this.isLoading}"
slot="primaryAction"
@click=${() => {
this.addFriend();
}}
>${this.editContent ? translate('friends.friend10') : translate('friends.friend2')}
</mwc-button>
</div>
</div>
</div>
`;
}
}
customElements.define('add-friends-modal', AddFriendsModal);

View File

@ -0,0 +1,224 @@
// popover-component.js
import { LitElement, html, css } from 'lit';
import { createPopper } from '@popperjs/core';
import '@material/mwc-icon';
import { use, get, translate } from 'lit-translate';
import { store } from '../../store';
import { connect } from 'pwa-helpers';
import { setNewTab, setSideEffectAction } from '../../redux/app/app-actions';
import ShortUniqueId from 'short-unique-id';
export class FriendItemActions extends connect(store)(LitElement) {
static styles = css`
:host {
display: none;
position: absolute;
background-color: var(--white);
border: 1px solid #ddd;
padding: 8px;
z-index: 10;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
color: var(--black);
max-width: 250px;
}
.close-icon {
cursor: pointer;
float: right;
margin-left: 10px;
color: var(--black);
}
.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;
}
.action-parent {
display: flex;
flex-direction: column;
width: 100%;
}
div[tabindex='0']:focus {
outline: none;
}
`;
static get properties() {
return {
for: { type: String, reflect: true },
message: { type: String },
openEditFriend: { attribute: false },
name: { type: String },
};
}
constructor() {
super();
this.message = '';
this.nodeUrl = this.getNodeUrl();
this.uid = new ShortUniqueId();
this.getUserAddress = this.getUserAddress.bind(this)
}
getNodeUrl() {
const myNode =
store.getState().app.nodeConfig.knownNodes[
window.parent.reduxStore.getState().app.nodeConfig.node
];
const nodeUrl =
myNode.protocol + '://' + myNode.domain + ':' + myNode.port;
return nodeUrl;
}
firstUpdated() {
// We'll defer the popper attachment to the openPopover() method to ensure target availability
}
attachToTarget(target) {
console.log({ target });
if (!this.popperInstance && target) {
this.popperInstance = createPopper(target, this, {
placement: 'bottom',
strategy: 'fixed',
});
}
}
openPopover(target) {
this.attachToTarget(target);
this.style.display = 'block';
setTimeout(() => {
this.shadowRoot.getElementById('parent-div').focus();
}, 50);
}
closePopover() {
this.style.display = 'none';
if (this.popperInstance) {
this.popperInstance.destroy();
this.popperInstance = null;
}
this.requestUpdate();
}
handleBlur() {
setTimeout(() => {
this.closePopover();
}, 0);
}
async getUserAddress() {
try {
const url = `${this.nodeUrl}/names/${this.name}`;
const res = await fetch(url);
const result = await res.json();
if (result.error === 401) {
return '';
} else {
return result.owner;
}
} catch (error) {
return '';
}
}
render() {
return html`
<div id="parent-div" tabindex="0" @blur=${this.handleBlur}>
<span class="close-icon" @click="${this.closePopover}"
><mwc-icon style="color: var(--black)"
>close</mwc-icon
></span
>
<div class="action-parent">
<div
class="send-message-button"
@click="${() => {
this.openEditFriend();
this.closePopover();
}}"
>
${translate('friends.friend10')}
</div>
<div
class="send-message-button"
@click="${async () => {
const address = await this.getUserAddress();
if (!address) return;
store.dispatch(
setNewTab({
url: `q-chat`,
id: this.uid.rnd(),
myPlugObj: {
url: 'q-chat',
domain: 'core',
page: 'messaging/q-chat/index.html',
title: 'Q-Chat',
icon: 'vaadin:chat',
mwcicon: 'forum',
pluginNumber: 'plugin-qhsyOnpRhT',
menus: [],
parent: false,
},
openExisting: true,
})
);
store.dispatch(
setSideEffectAction({
type: 'openPrivateChat',
data: {
address,
name: this.name
},
})
);
this.closePopover();
}}"
>
${translate('friends.friend8')}
</div>
<div
class="send-message-button"
@click="${() => {
const query = `?service=APP&name=Q-Mail/to/${this.name}`;
store.dispatch(
setNewTab({
url: `qdn/browser/index.html${query}`,
id: this.uid.rnd(),
myPlugObj: {
url: 'myapp',
domain: 'core',
page: `qdn/browser/index.html${query}`,
title: 'Q-Mail',
icon: 'vaadin:mailbox',
mwcicon: 'mail_outline',
menus: [],
parent: false,
},
openExisting: true,
})
);
this.closePopover();
}}"
>
${translate('friends.friend9')}
</div>
</div>
</div>
`;
}
}
customElements.define('friend-item-actions', FriendItemActions);

View File

@ -0,0 +1,61 @@
import { LitElement, html, css } from 'lit';
import '@material/mwc-icon';
import './friends-side-panel.js';
class FriendsSidePanelParent extends LitElement {
static get properties() {
return {
isOpen: {type: Boolean}
};
}
constructor() {
super();
this.isOpen = false
}
static styles = css`
.header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 16px;
border-bottom: 1px solid #e0e0e0;
}
.content {
padding: 16px;
}
.close {
visibility: hidden;
position: fixed;
z-index: -100;
right: -1000px;
}
.parent-side-panel {
transform: translateX(100%); /* start from outside the right edge */
transition: transform 0.3s ease-in-out;
}
.parent-side-panel.open {
transform: translateX(0); /* slide in to its original position */
}
`;
render() {
return html`
<mwc-icon @click=${()=> {
this.isOpen = !this.isOpen
}} style="color: var(--black); cursor:pointer"
>group</mwc-icon
>
<friends-side-panel ?isOpen=${this.isOpen} .setIsOpen=${(val)=> this.isOpen = val}></friends-side-panel>
`;
}
}
customElements.define('friends-side-panel-parent', FriendsSidePanelParent);

View File

@ -0,0 +1,61 @@
import { LitElement, html, css } from 'lit';
import '@material/mwc-icon';
import './friends-view'
class FriendsSidePanel extends LitElement {
static get properties() {
return {
setIsOpen: { attribute: false},
isOpen: {type: Boolean}
};
}
static styles = css`
:host {
display: block;
position: fixed;
top: 55px;
right: 0px;
width: 420px;
max-width: 95%;
height: calc(100vh - 55px);
background-color: var(--white);
border-left: 1px solid rgb(224, 224, 224);
overflow-y: auto;
z-index: 1;
transform: translateX(100%); /* start from outside the right edge */
transition: transform 0.3s ease-in-out;
}
:host([isOpen]) {
transform: unset; /* slide in to its original position */
}
.header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 16px;
border-bottom: 1px solid #e0e0e0;
}
.content {
padding: 16px;
}
`;
render() {
return html`
<div class="header">
<span>Panel Title</span>
<mwc-icon style="cursor:pointer" @click=${()=> {
this.setIsOpen(false)
}}>close</mwc-icon>
</div>
<div class="content">
<friends-view></friends-view></div>
</div>
`;
}
}
customElements.define('friends-side-panel', FriendsSidePanel);

View File

@ -0,0 +1,178 @@
import { css } from 'lit'
export const friendsViewStyles = 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;
margin-top: 5px;
padding: 0px 6px;
box-sizing: border-box;
align-items: center;
}
.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);
}
.search-results-div {
position: absolute;
top: 25px;
right: 25px;
}
.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;
}
`

View File

@ -0,0 +1,303 @@
import { LitElement, html, css } from 'lit';
import { render } from 'lit/html.js';
import { connect } from 'pwa-helpers';
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/icon'
import '@vaadin/icons'
import '@vaadin/button';
import './ChatSideNavHeads';
import '../../../../plugins/plugins/core/components/ChatSearchResults'
import './add-friends-modal'
import {
use,
get,
translate,
translateUnsafeHTML,
registerTranslateConfig,
} from 'lit-translate';
import { store } from '../../store';
import { friendsViewStyles } from './friends-view-css';
import { parentEpml } from '../show-plugin';
class FriendsView extends connect(store)(LitElement) {
static get properties() {
return {
error: { type: Boolean },
toggle: { attribute: false },
userName: { type: String },
errorMessage: { type: String },
successMessage: { type: String },
setUserName: { attribute: false },
friendList: { type: Array },
userSelected: { type: Object },
isLoading: {type: Boolean},
userFoundModalOpen: {type: Boolean},
userFound: { type: Array},
isOpenAddFriendsModal: {type: Boolean},
editContent: {type: Object}
};
}
static get styles() {
return [friendsViewStyles];
}
constructor() {
super();
this.error = false;
this.observerHandler = this.observerHandler.bind(this);
this.viewElement = '';
this.downObserverElement = '';
this.myAddress =
window.parent.reduxStore.getState().app.selectedAddress.address;
this.errorMessage = '';
this.successMessage = '';
this.friendList = [{
name: "Phil"
}];
this.userSelected = {};
this.isLoading = false;
this.userFoundModalOpen = false
this.userFound = [];
this.nodeUrl = this.getNodeUrl();
this.myNode = this.getMyNode();
this.isOpenAddFriendsModal = false
this.editContent = null
this.addToFriendList = this.addToFriendList.bind(this)
}
getNodeUrl() {
const myNode =
store.getState().app.nodeConfig.knownNodes[
window.parent.reduxStore.getState().app.nodeConfig.node
];
const nodeUrl =
myNode.protocol + '://' + myNode.domain + ':' + myNode.port;
return nodeUrl;
}
getMyNode() {
const myNode =
store.getState().app.nodeConfig.knownNodes[
window.parent.reduxStore.getState().app.nodeConfig.node
];
return myNode;
}
getMoreFriends() {}
firstUpdated() {
this.viewElement = this.shadowRoot.getElementById('viewElement');
this.downObserverElement =
this.shadowRoot.getElementById('downObserver');
this.elementObserver();
}
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.friendList.length < 20) {
return;
}
this.getMoreFriends();
}
}
async userSearch() {
const nameValue = this.shadowRoot.getElementById('sendTo').value
if(!nameValue) {
this.userFound = []
this.userFoundModalOpen = true
return;
}
try {
const url = `${this.nodeUrl}/names/${nameValue}`
const res = await fetch(url)
const result = await res.json()
if (result.error === 401) {
this.userFound = []
} else {
this.userFound = [
...this.userFound,
result,
];
}
this.userFoundModalOpen = true;
} catch (error) {
// let err4string = get("chatpage.cchange35");
// parentEpml.request('showSnackBar', `${err4string}`)
}
}
getApiKey() {
const apiNode = window.parent.reduxStore.getState().app.nodeConfig.knownNodes[window.parent.reduxStore.getState().app.nodeConfig.node]
let apiKey = apiNode.apiKey
return apiKey
}
async myFollowName(name) {
let items = [
name
]
let namesJsonString = JSON.stringify({ "items": items })
let ret = await parentEpml.request('apiCall', {
url: `/lists/followedNames?apiKey=${this.getApiKey()}`,
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: `${namesJsonString}`
})
if (ret === true) {
this.myFollowedNames = this.myFollowedNames.filter(item => item != name)
this.myFollowedNames.push(name)
} else {
let err3string = get("appspage.schange22")
parentEpml.request('showSnackBar', `${err3string}`)
}
return ret
}
addToFriendList(val){
if(this.editContent){
const findFriend = this.friendList.findIndex(item=> item.name === val.name)
if(findFriend !== -1){
const copyList = [...this.friendList]
copyList[findFriend] = val
this.friendList = copyList
}
} else {
this.friendList = [...this.friendList, val]
}
if(val.willFollow){
this.myFollowName(val.name)
}
this.userSelected = {};
this.isLoading = false;
this.isOpenAddFriendsModal = false
this.editContent = null
}
openEditFriend(val){
this.isOpenAddFriendsModal = true
this.userSelected = val
this.editContent = val
}
onClose(){
console.log('hello100')
this.isLoading = false;
this.isOpenAddFriendsModal = false
this.editContent = null
this.userSelected = {}
}
render() {
console.log('friends', this.userSelected);
return html`
<div class="container">
<div id="viewElement" class="container-body" style=${"position: relative"}>
<p class="group-name">My Friends</p>
<div class="search-field">
<input
type="text"
class="name-input"
?disabled=${this.isLoading}
id="sendTo"
placeholder="${translate("friends.friend1")}"
value=${this.userSelected.name ? this.userSelected.name: ''}
@keypress=${(e) => {
if(e.key === 'Enter'){
this.userSearch()
}
}}
/>
<vaadin-icon
@click=${this.userSearch}
slot="icon"
icon="vaadin:open-book"
class="search-icon">
</vaadin-icon>
</div>
<div class="search-results-div">
<chat-search-results
.onClickFunc=${(result) => {
this.userSelected = result;
this.isOpenAddFriendsModal = true
console.log({result});
this.userFound = [];
this.userFoundModalOpen = false;
}}
.closeFunc=${() => {
this.userFoundModalOpen = false;
this.userFound = [];
}}
.searchResults=${this.userFound}
?isOpen=${this.userFoundModalOpen}
?loading=${this.isLoading}>
</chat-search-results>
</div>
<br />
${this.friendList.map((item) => {
return html`<chat-side-nav-heads
activeChatHeadUrl=""
.setActiveChatHeadUrl=${(val) => {
}}
.chatInfo=${item}
.openEditFriend=${(val)=> this.openEditFriend(val)}
></chat-side-nav-heads>`;
})}
<div id="downObserver"></div>
</div>
</div>
<add-friends-modal
?isOpen=${this.isOpenAddFriendsModal}
.setIsOpen=${(val)=> {
this.isOpenAddFriendsModal = val
}}
.userSelected=${this.userSelected}
.onSubmit=${(val)=> this.addToFriendList(val)}
.editContent=${this.editContent}
.onClose=${()=> this.onClose()}
>
</add-friends-modal>
`;
}
}
customElements.define('friends-view', FriendsView);

View File

@ -111,13 +111,13 @@ class NotificationBellGeneral extends connect(store)(LitElement) {
>
${hasOngoing
? html`
<mwc-icon style="color: green;cursor:pointer"
<mwc-icon style="color: green;cursor:pointer;user-select:none"
>notifications</mwc-icon
>
`
: html`
<mwc-icon
style="color: var(--black); cursor:pointer"
style="color: var(--black); cursor:pointer;user-select:none"
>notifications</mwc-icon
>
`}

View File

@ -26,11 +26,9 @@ import '@vaadin/grid'
import '@vaadin/text-field'
import '../custom-elements/frag-file-input.js'
const chatLastSeen = localForage.createInstance({
name: "chat-last-seen",
})
const parentEpml = new Epml({ type: 'WINDOW', source: window.parent })
export const parentEpml = new Epml({ type: 'WINDOW', source: window.parent })
class ShowPlugin extends connect(store)(LitElement) {
static get properties() {

43
package-lock.json generated
View File

@ -7407,15 +7407,6 @@
"node": ">=0.10.0"
}
},
"node_modules/is-path-inside": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz",
"integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==",
"dev": true,
"engines": {
"node": ">=8"
}
},
"node_modules/is-plain-object": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz",
@ -7462,15 +7453,6 @@
"is-potential-custom-element-name": "^1.0.0"
}
},
"node_modules/is-valid-element-name": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-valid-element-name/-/is-valid-element-name-1.0.0.tgz",
"integrity": "sha512-GZITEJY2LkSjQfaIPBha7eyZv+ge0PhBR7KITeCCWvy7VBQrCUdFkvpI+HrAPQjVtVjy1LvlEkqQTHckoszruw==",
"dev": true,
"dependencies": {
"is-potential-custom-element-name": "^1.0.0"
}
},
"node_modules/is-wsl": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz",
@ -7758,19 +7740,6 @@
"node": ">= 0.8.0"
}
},
"node_modules/levn": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
"integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
"dev": true,
"dependencies": {
"prelude-ls": "^1.2.1",
"type-check": "~0.4.0"
},
"engines": {
"node": ">= 0.8.0"
}
},
"node_modules/lie": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/lie/-/lie-3.1.1.tgz",
@ -9746,18 +9715,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/strip-json-comments": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
"integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
"dev": true,
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/strip-outer": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/strip-outer/-/strip-outer-1.0.1.tgz",

View File

@ -1,12 +1,10 @@
import { LitElement, html } from 'lit'
import { render } from 'lit/html.js'
import { Epml } from '../../../epml.js'
import { chatSearchResultsStyles } from './ChatSearchResults-css.js'
import { use, get, translate, translateUnsafeHTML, registerTranslateConfig } from 'lit-translate'
import '@vaadin/icon'
import '@vaadin/icons'
const parentEpml = new Epml({ type: 'WINDOW', source: window.parent })
export class ChatSearchResults extends LitElement {
static get properties() {
@ -19,7 +17,10 @@ export class ChatSearchResults extends LitElement {
}
}
static styles = [chatSearchResultsStyles]
static get styles() {
return [chatSearchResultsStyles];
}
render() {
return html`

View File

@ -221,6 +221,8 @@ class Chat extends LitElement {
}
render() {
console.log('chatHeads', this.chatHeads)
console.log('chatHeadsObj', this.chatHeadsObj)
return html`
<div class="container clearfix">
<div class="people-list" id="people-list">
@ -516,6 +518,26 @@ class Chat extends LitElement {
chatHeads = JSON.parse(chatHeads)
this.getChatHeadFromState(chatHeads)
})
parentEpml.subscribe('side_effect_action', async sideEffectActionParam => {
const sideEffectAction = JSON.parse(sideEffectActionParam)
if(sideEffectAction && sideEffectAction.type === 'openPrivateChat'){
const name = sideEffectAction.data.name
const address = sideEffectAction.data.address
console.log({address}, this.chatHeadsObj)
if(this.chatHeadsObj.direct && this.chatHeadsObj.direct.find(item=> item.address === address)){
this.setActiveChatHeadUrl(`direct/${address}`)
window.parent.reduxStore.dispatch(
window.parent.reduxAction.setSideEffectAction(null))
} else {
this.setOpenPrivateMessage({
open: true,
name: name
})
}
}
})
parentEpml.request('apiCall', {
url: `/addresses/balance/${window.parent.reduxStore.getState().app.selectedAddress.address}`
}).then(res => {