mirror of
https://github.com/Qortal/qortal-ui.git
synced 2025-05-04 08:47:52 +00:00
Merge pull request #208 from Philreact/feature/friends-list
Feature/friends list
This commit is contained in:
commit
ac3a097b29
31
blog-test.json
Normal file
31
blog-test.json
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
{
|
||||||
|
"name": "Q-Blog",
|
||||||
|
"defaultFeedIndex": 0,
|
||||||
|
"feed": [
|
||||||
|
{
|
||||||
|
"id": "post-creation",
|
||||||
|
"version": 1,
|
||||||
|
"updated": 1696646223261,
|
||||||
|
"title": "Q-Blog Post creations",
|
||||||
|
"description": "blablabla",
|
||||||
|
"search": {
|
||||||
|
"query": "-post-",
|
||||||
|
"identifier": "q-blog-",
|
||||||
|
"service": "BLOG_POST",
|
||||||
|
"exactmatchnames": true
|
||||||
|
},
|
||||||
|
"click": "qortal://APP/Q-Blog/$${resource.name}$$/$${customParams.blogId}$$/$${customParams.shortIdentifier}$$",
|
||||||
|
"display": {
|
||||||
|
"title": "$${rawdata.title}$$"
|
||||||
|
},
|
||||||
|
"customParams": {
|
||||||
|
"blogId": "**methods.getBlogId(resource)**",
|
||||||
|
"shortIdentifier": "**methods.getShortId(resource)**"
|
||||||
|
},
|
||||||
|
"methods": {
|
||||||
|
"getShortId": "return resource.identifier.split('-post-')[1];",
|
||||||
|
"getBlogId": "const arr = resource.identifier.split('-post-'); const id = arr[0]; return id.startsWith('q-blog-') ? id.substring(7) : id;"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
@ -1182,6 +1182,36 @@
|
|||||||
"notifications": {
|
"notifications": {
|
||||||
"notify1": "Confirming transaction",
|
"notify1": "Confirming transaction",
|
||||||
"notify2": "Transaction confirmed",
|
"notify2": "Transaction confirmed",
|
||||||
"explanation": "Your transaction is getting confirmed. To track its progress, click on the bell icon."
|
"explanation": "Your transaction is getting confirmed. To track its progress, click on the bell icon.",
|
||||||
|
"status1": "Fully synced",
|
||||||
|
"status2": "Not synced",
|
||||||
|
"notify3": "No notifications",
|
||||||
|
"notify4": "Tx notifications"
|
||||||
|
},
|
||||||
|
"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 Q-Chat",
|
||||||
|
"friend9": "Send Q-Mail",
|
||||||
|
"friend10": "Edit friend",
|
||||||
|
"friend11": "Following",
|
||||||
|
"friend12": "Friends",
|
||||||
|
"friend13": "Feed",
|
||||||
|
"friend14": "Remove friend",
|
||||||
|
"friend15": "Feed settings",
|
||||||
|
"friend16": "Select the Q-Apps you want updates from, especially those related to your friends.",
|
||||||
|
"friends17": "Friends",
|
||||||
|
"friends18": "No items in your feed"
|
||||||
|
},
|
||||||
|
"save": {
|
||||||
|
"saving1": "Unable to fetch saved settings",
|
||||||
|
"saving2": "Nothing to save",
|
||||||
|
"saving3": "Save unsaved changes",
|
||||||
|
"saving4": "Undo changes"
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -43,8 +43,9 @@ import '../functional-components/side-menu-item.js'
|
|||||||
import './start-minting.js'
|
import './start-minting.js'
|
||||||
import './notification-view/notification-bell.js'
|
import './notification-view/notification-bell.js'
|
||||||
import './notification-view/notification-bell-general.js'
|
import './notification-view/notification-bell-general.js'
|
||||||
|
import './friends-view/friends-side-panel-parent.js'
|
||||||
|
import './friends-view/save-settings-qdn.js'
|
||||||
|
import './friends-view/core-sync-status.js'
|
||||||
const parentEpml = new Epml({ type: 'WINDOW', source: window.parent })
|
const parentEpml = new Epml({ type: 'WINDOW', source: window.parent })
|
||||||
|
|
||||||
class AppView extends connect(store)(LitElement) {
|
class AppView extends connect(store)(LitElement) {
|
||||||
@ -583,8 +584,10 @@ class AppView extends connect(store)(LitElement) {
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div style="display:flex;align-items:center;gap:20px">
|
<div style="display:flex;align-items:center;gap:20px">
|
||||||
|
<friends-side-panel-parent></friends-side-panel-parent>
|
||||||
<notification-bell></notification-bell>
|
<notification-bell></notification-bell>
|
||||||
<notification-bell-general></notification-bell-general>
|
<notification-bell-general></notification-bell-general>
|
||||||
|
<save-settings-qdn></save-settings-qdn>
|
||||||
</div>
|
</div>
|
||||||
<div style="display: inline;">
|
<div style="display: inline;">
|
||||||
<span>
|
<span>
|
||||||
@ -671,6 +674,8 @@ class AppView extends connect(store)(LitElement) {
|
|||||||
<mwc-button dense unelevated label="${translate("login.lp7")}" icon="lock_open" @click="${() => this.closeLockScreenActive()}"></mwc-button>
|
<mwc-button dense unelevated label="${translate("login.lp7")}" icon="lock_open" @click="${() => this.closeLockScreenActive()}"></mwc-button>
|
||||||
</div>
|
</div>
|
||||||
</paper-dialog>
|
</paper-dialog>
|
||||||
|
<div id="portal-target"></div>
|
||||||
|
|
||||||
`
|
`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
224
core/src/components/friends-view/ChatSideNavHeads.js
Normal file
224
core/src/components/friends-view/ChatSideNavHeads.js
Normal file
@ -0,0 +1,224 @@
|
|||||||
|
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},
|
||||||
|
closeSidePanel: {attribute: false, type: Object}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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.alias || 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}
|
||||||
|
.closeSidePanel=${this.closeSidePanel}
|
||||||
|
></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)
|
496
core/src/components/friends-view/add-friends-modal.js
Normal file
496
core/src/components/friends-view/add-friends-modal.js
Normal file
@ -0,0 +1,496 @@
|
|||||||
|
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';
|
||||||
|
import { connect } from 'pwa-helpers';
|
||||||
|
import { store } from '../../store';
|
||||||
|
import '@polymer/paper-spinner/paper-spinner-lite.js'
|
||||||
|
|
||||||
|
class AddFriendsModal extends connect(store)(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 },
|
||||||
|
mySelectedFeeds: { type: Array },
|
||||||
|
availableFeeedSchemas: {type: Array},
|
||||||
|
isLoadingSchemas: {type: Boolean}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.isOpen = false;
|
||||||
|
this.isLoading = false;
|
||||||
|
this.alias = '';
|
||||||
|
this.willFollow = true;
|
||||||
|
this.notes = '';
|
||||||
|
this.nodeUrl = this.getNodeUrl();
|
||||||
|
this.myNode = this.getMyNode();
|
||||||
|
this.mySelectedFeeds = [];
|
||||||
|
this.availableFeeedSchemas = [];
|
||||||
|
this.isLoadingSchemas= false;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
box-sizing:border-box;
|
||||||
|
}
|
||||||
|
.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);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-button {
|
||||||
|
font-family: Roboto, sans-serif;
|
||||||
|
font-size: 16px;
|
||||||
|
color: var(--mdc-theme-primary);
|
||||||
|
background-color: transparent;
|
||||||
|
padding: 8px 10px;
|
||||||
|
border-radius: 5px;
|
||||||
|
border: none;
|
||||||
|
transition: all 0.3s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-button-red {
|
||||||
|
font-family: Roboto, sans-serif;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #f44336;
|
||||||
|
background-color: transparent;
|
||||||
|
padding: 8px 10px;
|
||||||
|
border-radius: 5px;
|
||||||
|
border: none;
|
||||||
|
transition: all 0.3s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-button-red:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
background-color: #f4433663;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-button:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
background-color: #03a8f475;
|
||||||
|
}
|
||||||
|
.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;
|
||||||
|
display: flex;
|
||||||
|
flex-direction:column;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.modal-overlay.hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.avatar {
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-name {
|
||||||
|
display: flex;
|
||||||
|
gap: 20px;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 5px;
|
||||||
|
border-radius: 5px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
.inner-content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
max-height: 75vh;
|
||||||
|
flex-grow: 1;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inner-content::-webkit-scrollbar-track {
|
||||||
|
background-color: whitesmoke;
|
||||||
|
border-radius: 7px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inner-content::-webkit-scrollbar {
|
||||||
|
width: 12px;
|
||||||
|
border-radius: 7px;
|
||||||
|
background-color: whitesmoke;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inner-content::-webkit-scrollbar-thumb {
|
||||||
|
background-color: rgb(180, 176, 176);
|
||||||
|
border-radius: 7px;
|
||||||
|
transition: all 0.3s ease-in-out;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
firstUpdated() {}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
clearFields() {
|
||||||
|
this.alias = '';
|
||||||
|
this.willFollow = true;
|
||||||
|
this.notes = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
addFriend() {
|
||||||
|
this.onSubmit({
|
||||||
|
name: this.userSelected.name,
|
||||||
|
alias: this.alias,
|
||||||
|
notes: this.notes,
|
||||||
|
willFollow: this.willFollow,
|
||||||
|
mySelectedFeeds: this.mySelectedFeeds
|
||||||
|
|
||||||
|
});
|
||||||
|
this.clearFields();
|
||||||
|
this.onClose();
|
||||||
|
}
|
||||||
|
|
||||||
|
removeFriend() {
|
||||||
|
this.onSubmit(
|
||||||
|
{
|
||||||
|
name: this.userSelected.name,
|
||||||
|
alias: this.alias,
|
||||||
|
notes: this.notes,
|
||||||
|
willFollow: this.willFollow,
|
||||||
|
mySelectedFeeds: this.mySelectedFeeds
|
||||||
|
},
|
||||||
|
true
|
||||||
|
);
|
||||||
|
this.clearFields();
|
||||||
|
this.onClose();
|
||||||
|
}
|
||||||
|
|
||||||
|
async updated(changedProperties) {
|
||||||
|
if (
|
||||||
|
changedProperties &&
|
||||||
|
changedProperties.has('editContent') &&
|
||||||
|
this.editContent
|
||||||
|
) {
|
||||||
|
this.userSelected = {
|
||||||
|
name: this.editContent.name ?? '',
|
||||||
|
};
|
||||||
|
this.notes = this.editContent.notes ?? '';
|
||||||
|
this.willFollow = this.editContent.willFollow ?? true;
|
||||||
|
this.alias = this.editContent.alias ?? '';
|
||||||
|
this.requestUpdate()
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
changedProperties &&
|
||||||
|
changedProperties.has('isOpen') && this.isOpen
|
||||||
|
) {
|
||||||
|
this.getAvailableFeedSchemas()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
async getAvailableFeedSchemas() {
|
||||||
|
try {
|
||||||
|
this.isLoadingSchemas= true
|
||||||
|
const url = `${this.nodeUrl}/arbitrary/resources/search?service=DOCUMENT&identifier=ui_schema_feed&prefix=true`;
|
||||||
|
const res = await fetch(url);
|
||||||
|
const data = await res.json();
|
||||||
|
if (data.error === 401) {
|
||||||
|
this.availableFeeedSchemas = [];
|
||||||
|
} else {
|
||||||
|
const result = data.filter(
|
||||||
|
(item) => item.identifier === 'ui_schema_feed'
|
||||||
|
);
|
||||||
|
|
||||||
|
this.availableFeeedSchemas = result;
|
||||||
|
}
|
||||||
|
this.userFoundModalOpen = true;
|
||||||
|
} catch (error) {} finally {
|
||||||
|
this.isLoadingSchemas= false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return html`
|
||||||
|
<div class="modal-overlay ${this.isOpen ? '' : 'hidden'}">
|
||||||
|
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="inner-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="height:15px"></div>
|
||||||
|
<h2>${translate('friends.friend15')}</h2>
|
||||||
|
<div style="margin-bottom:0;">
|
||||||
|
<p>${translate('friends.friend16')}</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
${this.isLoadingSchemas ? html`
|
||||||
|
<div style="width:100%;display: flex; justify-content:center">
|
||||||
|
<paper-spinner-lite active></paper-spinner-lite>
|
||||||
|
</div>
|
||||||
|
` : ''}
|
||||||
|
${this.availableFeeedSchemas.map((schema) => {
|
||||||
|
const isAlreadySelected = this.mySelectedFeeds.find(
|
||||||
|
(item) => item.name === schema.name
|
||||||
|
);
|
||||||
|
let avatarImgApp;
|
||||||
|
const avatarUrl2 = `${this.nodeUrl}/arbitrary/THUMBNAIL/${schema.name}/qortal_avatar?async=true&apiKey=${this.myNode.apiKey}`;
|
||||||
|
avatarImgApp = html`<img
|
||||||
|
src="${avatarUrl2}"
|
||||||
|
style="max-width:100%; max-height:100%;"
|
||||||
|
onerror="this.onerror=null; this.src='/img/incognito.png';"
|
||||||
|
/>`;
|
||||||
|
return html`
|
||||||
|
<div
|
||||||
|
class="app-name"
|
||||||
|
style="background:${isAlreadySelected ? 'lightblue': ''}"
|
||||||
|
@click=${() => {
|
||||||
|
const copymySelectedFeeds = [
|
||||||
|
...this.mySelectedFeeds,
|
||||||
|
];
|
||||||
|
const findIndex =
|
||||||
|
copymySelectedFeeds.findIndex(
|
||||||
|
(item) =>
|
||||||
|
item.name === schema.name
|
||||||
|
);
|
||||||
|
if (findIndex === -1) {
|
||||||
|
if(this.mySelectedFeeds.length > 4) return
|
||||||
|
copymySelectedFeeds.push({
|
||||||
|
name: schema.name,
|
||||||
|
identifier: schema.identifier,
|
||||||
|
service: schema.service,
|
||||||
|
});
|
||||||
|
this.mySelectedFeeds =
|
||||||
|
copymySelectedFeeds;
|
||||||
|
} else {
|
||||||
|
this.mySelectedFeeds =
|
||||||
|
copymySelectedFeeds.filter(
|
||||||
|
(item) =>
|
||||||
|
item.name !==
|
||||||
|
schema.name
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div class="avatar">${avatarImgApp}</div>
|
||||||
|
<span
|
||||||
|
style="color:${isAlreadySelected ? 'var(--white)': 'var(--black)'};font-size:16px"
|
||||||
|
>${schema.name}</span
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
style="display:flex;justify-content:space-between;align-items:center;margin-top:20px"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="modal-button-red"
|
||||||
|
?disabled="${this.isLoading}"
|
||||||
|
@click="${() => {
|
||||||
|
this.setIsOpen(false);
|
||||||
|
this.clearFields();
|
||||||
|
this.onClose();
|
||||||
|
}}"
|
||||||
|
>
|
||||||
|
${translate('general.close')}
|
||||||
|
</button>
|
||||||
|
${this.editContent
|
||||||
|
? html`
|
||||||
|
<button
|
||||||
|
?disabled="${this.isLoading}"
|
||||||
|
class="modal-button-red"
|
||||||
|
@click=${() => {
|
||||||
|
this.removeFriend();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
${translate('friends.friend14')}
|
||||||
|
</button>
|
||||||
|
`
|
||||||
|
: ''}
|
||||||
|
|
||||||
|
<button
|
||||||
|
?disabled="${this.isLoading}"
|
||||||
|
class="modal-button"
|
||||||
|
@click=${() => {
|
||||||
|
this.addFriend();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
${this.editContent
|
||||||
|
? translate('friends.friend10')
|
||||||
|
: translate('friends.friend2')}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
customElements.define('add-friends-modal', AddFriendsModal);
|
92
core/src/components/friends-view/computePowWorkerFile.src.js
Normal file
92
core/src/components/friends-view/computePowWorkerFile.src.js
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
import { Sha256 } from 'asmcrypto.js'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function sbrk(size, heap){
|
||||||
|
let brk = 512 * 1024 // stack top
|
||||||
|
let old = brk
|
||||||
|
brk += size
|
||||||
|
|
||||||
|
if (brk > heap.length)
|
||||||
|
throw new Error('heap exhausted')
|
||||||
|
|
||||||
|
return old
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
self.addEventListener('message', async e => {
|
||||||
|
const response = await computePow(e.data.convertedBytes, e.data.path)
|
||||||
|
postMessage(response)
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
const memory = new WebAssembly.Memory({ initial: 256, maximum: 256 })
|
||||||
|
const heap = new Uint8Array(memory.buffer)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const computePow = async (convertedBytes, path) => {
|
||||||
|
|
||||||
|
|
||||||
|
let response = null
|
||||||
|
|
||||||
|
await new Promise((resolve, reject)=> {
|
||||||
|
|
||||||
|
const _convertedBytesArray = Object.keys(convertedBytes).map(
|
||||||
|
function (key) {
|
||||||
|
return convertedBytes[key]
|
||||||
|
}
|
||||||
|
)
|
||||||
|
const convertedBytesArray = new Uint8Array(_convertedBytesArray)
|
||||||
|
const convertedBytesHash = new Sha256()
|
||||||
|
.process(convertedBytesArray)
|
||||||
|
.finish().result
|
||||||
|
const hashPtr = sbrk(32, heap)
|
||||||
|
const hashAry = new Uint8Array(
|
||||||
|
memory.buffer,
|
||||||
|
hashPtr,
|
||||||
|
32
|
||||||
|
)
|
||||||
|
|
||||||
|
hashAry.set(convertedBytesHash)
|
||||||
|
const difficulty = 14
|
||||||
|
const workBufferLength = 8 * 1024 * 1024
|
||||||
|
const workBufferPtr = sbrk(
|
||||||
|
workBufferLength,
|
||||||
|
heap
|
||||||
|
)
|
||||||
|
|
||||||
|
const importObject = {
|
||||||
|
env: {
|
||||||
|
memory: memory
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
function loadWebAssembly(filename, imports) {
|
||||||
|
return fetch(filename)
|
||||||
|
.then(response => response.arrayBuffer())
|
||||||
|
.then(buffer => WebAssembly.compile(buffer))
|
||||||
|
.then(module => {
|
||||||
|
return new WebAssembly.Instance(module, importObject);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
loadWebAssembly(path)
|
||||||
|
.then(wasmModule => {
|
||||||
|
response = {
|
||||||
|
nonce : wasmModule.exports.compute2(hashPtr, workBufferPtr, workBufferLength, difficulty),
|
||||||
|
|
||||||
|
}
|
||||||
|
resolve()
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
return response
|
||||||
|
}
|
78
core/src/components/friends-view/core-sync-status.js
Normal file
78
core/src/components/friends-view/core-sync-status.js
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
import { LitElement, html, css } from 'lit';
|
||||||
|
import '@material/mwc-icon';
|
||||||
|
import { store } from '../../store';
|
||||||
|
import { connect } from 'pwa-helpers';
|
||||||
|
import '@vaadin/tooltip';
|
||||||
|
import { get } from 'lit-translate';
|
||||||
|
class CoreSyncStatus extends connect(store)(LitElement) {
|
||||||
|
static get properties() {
|
||||||
|
return {
|
||||||
|
nodeStatus: {type: Object}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.nodeStatus = {
|
||||||
|
isMintingPossible:false,
|
||||||
|
isSynchronizing:true,
|
||||||
|
syncPercent:undefined,
|
||||||
|
numberOfConnections:undefined,
|
||||||
|
height:undefined,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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 */
|
||||||
|
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
stateChanged(state) {
|
||||||
|
this.nodeStatus = state.app.nodeStatus
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return html`
|
||||||
|
<mwc-icon id="icon" style="color: ${this.nodeStatus.syncPercent === 100 ? 'green': 'red'};user-select:none;margin-right:20px"
|
||||||
|
>lightbulb</mwc-icon
|
||||||
|
>
|
||||||
|
<vaadin-tooltip
|
||||||
|
for="icon"
|
||||||
|
position="bottom"
|
||||||
|
hover-delay=${400}
|
||||||
|
hide-delay=${1}
|
||||||
|
text=${this.nodeStatus.syncPercent === 100 ? get('notifications.status1'): get('notifications.status2')}>
|
||||||
|
</vaadin-tooltip>
|
||||||
|
|
||||||
|
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
customElements.define('core-sync-status', CoreSyncStatus);
|
516
core/src/components/friends-view/feed-item.js
Normal file
516
core/src/components/friends-view/feed-item.js
Normal file
@ -0,0 +1,516 @@
|
|||||||
|
import { LitElement, html, css } from 'lit';
|
||||||
|
import {
|
||||||
|
get,
|
||||||
|
translate,
|
||||||
|
} from 'lit-translate';
|
||||||
|
import axios from 'axios'
|
||||||
|
import '@material/mwc-menu';
|
||||||
|
import '@material/mwc-list/mwc-list-item.js'
|
||||||
|
import { RequestQueueWithPromise } from '../../../../plugins/plugins/utils/queue';
|
||||||
|
import '../../../../plugins/plugins/core/components/TimeAgo'
|
||||||
|
import { connect } from 'pwa-helpers';
|
||||||
|
import { store } from '../../store';
|
||||||
|
import { setNewTab } from '../../redux/app/app-actions';
|
||||||
|
import ShortUniqueId from 'short-unique-id';
|
||||||
|
|
||||||
|
const requestQueue = new RequestQueueWithPromise(3);
|
||||||
|
const requestQueueRawData = new RequestQueueWithPromise(3);
|
||||||
|
const requestQueueStatus = new RequestQueueWithPromise(3);
|
||||||
|
|
||||||
|
|
||||||
|
export class FeedItem extends connect(store)(LitElement) {
|
||||||
|
static get properties() {
|
||||||
|
return {
|
||||||
|
resource: { type: Object },
|
||||||
|
isReady: { type: Boolean},
|
||||||
|
status: {type: Object},
|
||||||
|
feedItem: {type: Object},
|
||||||
|
appName: {type: String},
|
||||||
|
link: {type: String}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static get styles() {
|
||||||
|
return css`
|
||||||
|
* {
|
||||||
|
--mdc-theme-text-primary-on-background: var(--black);
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
:host {
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
img {
|
||||||
|
width:100%;
|
||||||
|
max-height:30vh;
|
||||||
|
border-radius: 5px;
|
||||||
|
cursor: pointer;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.smallLoading,
|
||||||
|
.smallLoading:after {
|
||||||
|
border-radius: 50%;
|
||||||
|
width: 2px;
|
||||||
|
height: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.smallLoading {
|
||||||
|
border-width: 0.8em;
|
||||||
|
border-style: solid;
|
||||||
|
border-color: rgba(3, 169, 244, 0.2) rgba(3, 169, 244, 0.2)
|
||||||
|
rgba(3, 169, 244, 0.2) rgb(3, 169, 244);
|
||||||
|
font-size: 30px;
|
||||||
|
position: relative;
|
||||||
|
text-indent: -9999em;
|
||||||
|
transform: translateZ(0px);
|
||||||
|
animation: 1.1s linear 0s infinite normal none running loadingAnimation;
|
||||||
|
}
|
||||||
|
|
||||||
|
.defaultSize {
|
||||||
|
width: 100%;
|
||||||
|
height: 160px;
|
||||||
|
}
|
||||||
|
.parent-feed-item {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
background-color: var(--chat-bubble-bg);
|
||||||
|
flex-grow: 0;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
justify-content: center;
|
||||||
|
border-radius: 5px;
|
||||||
|
padding: 12px 15px 4px 15px;
|
||||||
|
min-width: 150px;
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
.avatar {
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
border-radius:50%;
|
||||||
|
overflow: hidden;
|
||||||
|
display:flex;
|
||||||
|
align-items:center;
|
||||||
|
}
|
||||||
|
.avatarApp {
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
border-radius:50%;
|
||||||
|
overflow: hidden;
|
||||||
|
display:flex;
|
||||||
|
align-items:center;
|
||||||
|
}
|
||||||
|
.feed-item-name {
|
||||||
|
user-select: none;
|
||||||
|
color: #03a9f4;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-name {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
mwc-menu {
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@-webkit-keyframes loadingAnimation {
|
||||||
|
0% {
|
||||||
|
-webkit-transform: rotate(0deg);
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
-webkit-transform: rotate(360deg);
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes loadingAnimation {
|
||||||
|
0% {
|
||||||
|
-webkit-transform: rotate(0deg);
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
-webkit-transform: rotate(360deg);
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.resource = {
|
||||||
|
identifier: "",
|
||||||
|
name: "",
|
||||||
|
service: ""
|
||||||
|
}
|
||||||
|
this.status = {
|
||||||
|
status: ''
|
||||||
|
}
|
||||||
|
this.isReady = false
|
||||||
|
this.nodeUrl = this.getNodeUrl()
|
||||||
|
this.myNode = this.getMyNode()
|
||||||
|
this.hasCalledWhenDownloaded = false
|
||||||
|
this.isFetching = false
|
||||||
|
this.uid = new ShortUniqueId()
|
||||||
|
|
||||||
|
this.observer = new IntersectionObserver(entries => {
|
||||||
|
for (const entry of entries) {
|
||||||
|
if (entry.isIntersecting && this.status.status !== 'READY') {
|
||||||
|
this._fetchImage();
|
||||||
|
// Stop observing after the image has started loading
|
||||||
|
this.observer.unobserve(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.feedItem = null
|
||||||
|
}
|
||||||
|
getNodeUrl(){
|
||||||
|
const myNode = window.parent.reduxStore.getState().app.nodeConfig.knownNodes[window.parent.reduxStore.getState().app.nodeConfig.node]
|
||||||
|
|
||||||
|
const nodeUrl = myNode.protocol + '://' + myNode.domain + ':' + myNode.port
|
||||||
|
return nodeUrl
|
||||||
|
}
|
||||||
|
getMyNode(){
|
||||||
|
const myNode = window.parent.reduxStore.getState().app.nodeConfig.knownNodes[window.parent.reduxStore.getState().app.nodeConfig.node]
|
||||||
|
|
||||||
|
return myNode
|
||||||
|
}
|
||||||
|
|
||||||
|
getApiKey() {
|
||||||
|
const myNode =
|
||||||
|
window.parent.reduxStore.getState().app.nodeConfig.knownNodes[
|
||||||
|
window.parent.reduxStore.getState().app.nodeConfig.node
|
||||||
|
];
|
||||||
|
let apiKey = myNode.apiKey;
|
||||||
|
return apiKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
async fetchResource() {
|
||||||
|
try {
|
||||||
|
if(this.isFetching) return
|
||||||
|
this.isFetching = true
|
||||||
|
await axios.get(`${this.nodeUrl}/arbitrary/resource/properties/${this.resource.service}/${this.resource.name}/${this.resource.identifier}?apiKey=${this.myNode.apiKey}`)
|
||||||
|
this.isFetching = false
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
this.isFetching = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fetchVideoUrl() {
|
||||||
|
|
||||||
|
this.fetchResource()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
async getRawData(){
|
||||||
|
const url = `${this.nodeUrl}/arbitrary/${this.resource.service}/${this.resource.name}/${this.resource.identifier}?apiKey=${this.myNode.apiKey}`
|
||||||
|
return await requestQueueRawData.enqueue(()=> {
|
||||||
|
return axios.get(url)
|
||||||
|
})
|
||||||
|
// const response2 = await fetch(url, {
|
||||||
|
// method: 'GET',
|
||||||
|
// headers: {
|
||||||
|
// 'Content-Type': 'application/json'
|
||||||
|
// }
|
||||||
|
// })
|
||||||
|
|
||||||
|
// const responseData2 = await response2.json()
|
||||||
|
// return responseData2
|
||||||
|
}
|
||||||
|
|
||||||
|
updateDisplayWithPlaceholders(display, resource, rawdata) {
|
||||||
|
const pattern = /\$\$\{([a-zA-Z0-9_\.]+)\}\$\$/g;
|
||||||
|
|
||||||
|
for (const key in display) {
|
||||||
|
const value = display[key];
|
||||||
|
|
||||||
|
display[key] = value.replace(pattern, (match, p1) => {
|
||||||
|
if (p1.startsWith('rawdata.')) {
|
||||||
|
const dataKey = p1.split('.')[1];
|
||||||
|
if (rawdata[dataKey] === undefined) {
|
||||||
|
console.error("rawdata key not found:", dataKey);
|
||||||
|
}
|
||||||
|
return rawdata[dataKey] || match;
|
||||||
|
} else if (p1.startsWith('resource.')) {
|
||||||
|
const resourceKey = p1.split('.')[1];
|
||||||
|
if (resource[resourceKey] === undefined) {
|
||||||
|
console.error("resource key not found:", resourceKey);
|
||||||
|
}
|
||||||
|
return resource[resourceKey] || match;
|
||||||
|
}
|
||||||
|
return match;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
async fetchStatus(){
|
||||||
|
let isCalling = false
|
||||||
|
let percentLoaded = 0
|
||||||
|
let timer = 24
|
||||||
|
const response = await requestQueueStatus.enqueue(()=> {
|
||||||
|
return axios.get(`${this.nodeUrl}/arbitrary/resource/status/${this.resource.service}/${this.resource.name}/${this.resource.identifier}?apiKey=${this.myNode.apiKey}`)
|
||||||
|
})
|
||||||
|
if(response && response.data && response.data.status === 'READY'){
|
||||||
|
const rawData = await this.getRawData()
|
||||||
|
const object = {
|
||||||
|
...this.resource.schema.display
|
||||||
|
}
|
||||||
|
this.updateDisplayWithPlaceholders(object, {},rawData.data)
|
||||||
|
this.feedItem = object
|
||||||
|
this.status = response.data
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const intervalId = setInterval(async () => {
|
||||||
|
if (isCalling) return
|
||||||
|
isCalling = true
|
||||||
|
|
||||||
|
const data = await requestQueue.enqueue(() => {
|
||||||
|
return axios.get(`${this.nodeUrl}/arbitrary/resource/status/${this.resource.service}/${this.resource.name}/${this.resource.identifier}?apiKey=${this.myNode.apiKey}`)
|
||||||
|
});
|
||||||
|
const res = data.data
|
||||||
|
|
||||||
|
isCalling = false
|
||||||
|
if (res.localChunkCount) {
|
||||||
|
if (res.percentLoaded) {
|
||||||
|
if (
|
||||||
|
res.percentLoaded === percentLoaded &&
|
||||||
|
res.percentLoaded !== 100
|
||||||
|
) {
|
||||||
|
timer = timer - 5
|
||||||
|
} else {
|
||||||
|
timer = 24
|
||||||
|
}
|
||||||
|
if (timer < 0) {
|
||||||
|
timer = 24
|
||||||
|
isCalling = true
|
||||||
|
this.status = {
|
||||||
|
...res,
|
||||||
|
status: 'REFETCHING'
|
||||||
|
}
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
isCalling = false
|
||||||
|
this.fetchResource()
|
||||||
|
}, 25000)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
percentLoaded = res.percentLoaded
|
||||||
|
}
|
||||||
|
|
||||||
|
this.status = res
|
||||||
|
if(this.status.status === 'DOWNLOADED'){
|
||||||
|
this.fetchResource()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if progress is 100% and clear interval if true
|
||||||
|
if (res.status === 'READY') {
|
||||||
|
const rawData = await this.getRawData()
|
||||||
|
const object = {
|
||||||
|
...this.resource.schema.display
|
||||||
|
}
|
||||||
|
this.updateDisplayWithPlaceholders(object, {},rawData.data)
|
||||||
|
this.feedItem = object
|
||||||
|
clearInterval(intervalId)
|
||||||
|
this.status = res
|
||||||
|
this.isReady = true
|
||||||
|
}
|
||||||
|
}, 5000) // 1 second interval
|
||||||
|
}
|
||||||
|
|
||||||
|
async _fetchImage() {
|
||||||
|
try {
|
||||||
|
this.fetchVideoUrl()
|
||||||
|
this.fetchStatus()
|
||||||
|
} catch (error) { /* empty */ }
|
||||||
|
}
|
||||||
|
|
||||||
|
firstUpdated(){
|
||||||
|
this.observer.observe(this);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
async goToFeedLink(){
|
||||||
|
try {
|
||||||
|
let newQuery = this.link
|
||||||
|
if (newQuery.endsWith('/')) {
|
||||||
|
newQuery = newQuery.slice(0, -1)
|
||||||
|
}
|
||||||
|
const res = await this.extractComponents(newQuery)
|
||||||
|
if (!res) return
|
||||||
|
const { service, name, identifier, path } = res
|
||||||
|
let query = `?service=${service}`
|
||||||
|
if (name) {
|
||||||
|
query = query + `&name=${name}`
|
||||||
|
}
|
||||||
|
if (identifier) {
|
||||||
|
query = query + `&identifier=${identifier}`
|
||||||
|
}
|
||||||
|
if (path) {
|
||||||
|
query = query + `&path=${path}`
|
||||||
|
}
|
||||||
|
|
||||||
|
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": name,
|
||||||
|
"icon": 'vaadin:external-browser',
|
||||||
|
"mwcicon": 'open_in_browser',
|
||||||
|
"menus": [],
|
||||||
|
"parent": false
|
||||||
|
},
|
||||||
|
openExisting: true
|
||||||
|
}))
|
||||||
|
} catch (error) {
|
||||||
|
console.log({error})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
async extractComponents(url) {
|
||||||
|
if (!url.startsWith("qortal://")) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
url = url.replace(/^(qortal\:\/\/)/, "")
|
||||||
|
if (url.includes("/")) {
|
||||||
|
let parts = url.split("/")
|
||||||
|
const service = parts[0].toUpperCase()
|
||||||
|
parts.shift()
|
||||||
|
const name = parts[0]
|
||||||
|
parts.shift()
|
||||||
|
let identifier
|
||||||
|
|
||||||
|
if (parts.length > 0) {
|
||||||
|
identifier = parts[0] // Do not shift yet
|
||||||
|
// Check if a resource exists with this service, name and identifier combination
|
||||||
|
const myNode = store.getState().app.nodeConfig.knownNodes[store.getState().app.nodeConfig.node]
|
||||||
|
const nodeUrl = myNode.protocol + '://' + myNode.domain + ':' + myNode.port
|
||||||
|
const url = `${nodeUrl}/arbitrary/resource/status/${service}/${name}/${identifier}?apiKey=${myNode.apiKey}}`
|
||||||
|
|
||||||
|
const res = await fetch(url);
|
||||||
|
const data = await res.json();
|
||||||
|
if (data.totalChunkCount > 0) {
|
||||||
|
// Identifier exists, so don't include it in the path
|
||||||
|
parts.shift()
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
identifier = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const path = parts.join("/")
|
||||||
|
|
||||||
|
const components = {}
|
||||||
|
components["service"] = service
|
||||||
|
components["name"] = name
|
||||||
|
components["identifier"] = identifier
|
||||||
|
components["path"] = path
|
||||||
|
return components
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
render() {
|
||||||
|
let avatarImg
|
||||||
|
const avatarUrl = `${this.nodeUrl}/arbitrary/THUMBNAIL/${this.resource.name}/qortal_avatar?async=true&apiKey=${this.myNode.apiKey}`;
|
||||||
|
avatarImg = html`<img
|
||||||
|
src="${avatarUrl}"
|
||||||
|
style="width:100%; height:100%;"
|
||||||
|
onerror="this.onerror=null; this.src='/img/incognito.png';"
|
||||||
|
/>`;
|
||||||
|
let avatarImgApp
|
||||||
|
const avatarUrl2 = `${this.nodeUrl}/arbitrary/THUMBNAIL/${this.appName}/qortal_avatar?async=true&apiKey=${this.myNode.apiKey}`;
|
||||||
|
avatarImgApp = html`<img
|
||||||
|
src="${avatarUrl2}"
|
||||||
|
style="width:100%; height:100%;"
|
||||||
|
onerror="this.onerror=null; this.src='/img/incognito.png';"
|
||||||
|
/>`;
|
||||||
|
return html`
|
||||||
|
<div
|
||||||
|
class=${[
|
||||||
|
`image-container`,
|
||||||
|
this.status.status !== 'READY'
|
||||||
|
? 'defaultSize'
|
||||||
|
: '',
|
||||||
|
this.status.status !== 'READY'
|
||||||
|
? 'hideImg'
|
||||||
|
: '',
|
||||||
|
].join(' ')}
|
||||||
|
style=" box-sizing: border-box;"
|
||||||
|
>
|
||||||
|
${
|
||||||
|
this.status.status !== 'READY'
|
||||||
|
? html`
|
||||||
|
<div
|
||||||
|
style="display:flex;flex-direction:column;width:100%;height:100%;justify-content:center;align-items:center; box-sizing: border-box;"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class=${`smallLoading`}
|
||||||
|
></div>
|
||||||
|
<p style="color: var(--black)">${`${Math.round(this.status.percentLoaded || 0
|
||||||
|
).toFixed(0)}% `}${translate('chatpage.cchange94')}</p>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
: ''
|
||||||
|
}
|
||||||
|
${this.status.status === 'READY' && this.feedItem ? html`
|
||||||
|
<div class="parent-feed-item" style="position:relative" @click=${this.goToFeedLink}>
|
||||||
|
<div style="display:flex;gap:10px;margin-bottom:5px">
|
||||||
|
<div class="avatar">
|
||||||
|
${avatarImg}</div> <span class="feed-item-name">${this.resource.name}</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p>${this.feedItem.title}</p>
|
||||||
|
</div>
|
||||||
|
<div class="app-name">
|
||||||
|
<div class="avatarApp">
|
||||||
|
${avatarImgApp}
|
||||||
|
</div>
|
||||||
|
<message-time
|
||||||
|
timestamp=${this
|
||||||
|
.resource
|
||||||
|
.created}
|
||||||
|
></message-time>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
` : ''}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
`
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
customElements.define('feed-item', FeedItem);
|
237
core/src/components/friends-view/friend-item-actions.js
Normal file
237
core/src/components/friends-view/friend-item-actions.js
Normal file
@ -0,0 +1,237 @@
|
|||||||
|
// 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;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px
|
||||||
|
}
|
||||||
|
|
||||||
|
.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 },
|
||||||
|
closeSidePanel: {attribute: false, type: Object}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
if (!this.popperInstance && target) {
|
||||||
|
this.popperInstance = createPopper(target, this, {
|
||||||
|
placement: 'bottom'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
}}"
|
||||||
|
>
|
||||||
|
<mwc-icon style="color: var(--black)"
|
||||||
|
>edit</mwc-icon
|
||||||
|
>
|
||||||
|
${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();
|
||||||
|
this.closeSidePanel()
|
||||||
|
}}"
|
||||||
|
>
|
||||||
|
<mwc-icon style="color: var(--black)"
|
||||||
|
>send</mwc-icon
|
||||||
|
>
|
||||||
|
${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();
|
||||||
|
this.closeSidePanel()
|
||||||
|
}}"
|
||||||
|
>
|
||||||
|
<mwc-icon style="color: var(--black)"
|
||||||
|
>mail</mwc-icon
|
||||||
|
>
|
||||||
|
${translate('friends.friend9')}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
customElements.define('friend-item-actions', FriendItemActions);
|
471
core/src/components/friends-view/friends-feed.js
Normal file
471
core/src/components/friends-view/friends-feed.js
Normal file
@ -0,0 +1,471 @@
|
|||||||
|
import { LitElement, html, css } from 'lit';
|
||||||
|
import '@material/mwc-icon';
|
||||||
|
import './friends-view'
|
||||||
|
import { friendsViewStyles } from './friends-view-css';
|
||||||
|
import { connect } from 'pwa-helpers';
|
||||||
|
import { store } from '../../store';
|
||||||
|
import './feed-item'
|
||||||
|
import { translate } from 'lit-translate';
|
||||||
|
|
||||||
|
import '@polymer/paper-spinner/paper-spinner-lite.js'
|
||||||
|
|
||||||
|
|
||||||
|
const perEndpointCount = 20;
|
||||||
|
const totalDesiredCount = 100;
|
||||||
|
const maxResultsInMemory = 300;
|
||||||
|
class FriendsFeed extends connect(store)(LitElement) {
|
||||||
|
static get properties() {
|
||||||
|
return {
|
||||||
|
feed: {type: Array},
|
||||||
|
setHasNewFeed: {attribute:false},
|
||||||
|
isLoading: {type: Boolean},
|
||||||
|
hasFetched: {type: Boolean},
|
||||||
|
mySelectedFeeds: {type: Array}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
constructor(){
|
||||||
|
super()
|
||||||
|
this.feed = []
|
||||||
|
this.feedToRender = []
|
||||||
|
this.nodeUrl = this.getNodeUrl();
|
||||||
|
this.myNode = this.getMyNode();
|
||||||
|
this.endpoints = []
|
||||||
|
this.endpointOffsets = [] // Initialize offsets for each endpoint to 0
|
||||||
|
|
||||||
|
this.loadAndMergeData = this.loadAndMergeData.bind(this)
|
||||||
|
this.hasInitialFetch = false
|
||||||
|
this.observerHandler = this.observerHandler.bind(this);
|
||||||
|
this.elementObserver = this.elementObserver.bind(this)
|
||||||
|
this.mySelectedFeeds = []
|
||||||
|
this.getSchemas = this.getSchemas.bind(this)
|
||||||
|
this.hasFetched = false
|
||||||
|
this._updateFeeds = this._updateFeeds.bind(this)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static get styles() {
|
||||||
|
return [friendsViewStyles];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
_updateFeeds(event) {
|
||||||
|
const detail = event.detail
|
||||||
|
this.mySelectedFeeds = detail
|
||||||
|
this.reFetchFeedData()
|
||||||
|
this.requestUpdate()
|
||||||
|
}
|
||||||
|
|
||||||
|
connectedCallback() {
|
||||||
|
super.connectedCallback()
|
||||||
|
window.addEventListener('friends-my-selected-feeds-event', this._updateFeeds) }
|
||||||
|
|
||||||
|
disconnectedCallback() {
|
||||||
|
window.removeEventListener('friends-my-selected-feeds-event', this._updateFeeds)
|
||||||
|
super.disconnectedCallback()
|
||||||
|
}
|
||||||
|
|
||||||
|
async getSchemas(){
|
||||||
|
this.mySelectedFeeds = JSON.parse(localStorage.getItem('friends-my-selected-feeds') || "[]")
|
||||||
|
const schemas = this.mySelectedFeeds
|
||||||
|
const getAllSchemas = (schemas || []).map(
|
||||||
|
async (schema) => {
|
||||||
|
try {
|
||||||
|
const url = `${this.nodeUrl}/arbitrary/${schema.service}/${schema.name}/${schema.identifier}`;
|
||||||
|
const res = await fetch(url)
|
||||||
|
const data = await res.json()
|
||||||
|
if(data.error) return false
|
||||||
|
return data
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const res = await Promise.all(getAllSchemas);
|
||||||
|
return res.filter((item)=> !!item)
|
||||||
|
}
|
||||||
|
|
||||||
|
getFeedOnInterval(){
|
||||||
|
let interval = null;
|
||||||
|
let stop = false;
|
||||||
|
const getAnswer = async () => {
|
||||||
|
|
||||||
|
if (!stop) {
|
||||||
|
stop = true;
|
||||||
|
try {
|
||||||
|
await this.reFetchFeedData()
|
||||||
|
} catch (error) {}
|
||||||
|
stop = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
interval = setInterval(getAnswer, 900000);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
async getEndpoints(){
|
||||||
|
const dynamicVars = {
|
||||||
|
|
||||||
|
}
|
||||||
|
const schemas = await this.getSchemas()
|
||||||
|
const friendList = JSON.parse(localStorage.getItem('friends-my-friend-list') || "[]")
|
||||||
|
const names = friendList.map(friend => `name=${friend.name}`).join('&');
|
||||||
|
if(names.length === 0){
|
||||||
|
this.endpoints= []
|
||||||
|
this.endpointOffsets = Array(this.endpoints.length).fill(0);
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const baseurl = `${this.nodeUrl}/arbitrary/resources/search?reverse=true&exactmatchnames=true&${names}`
|
||||||
|
let formEndpoints = []
|
||||||
|
schemas.forEach((schema)=> {
|
||||||
|
const feedData = schema.feed[0]
|
||||||
|
if(feedData){
|
||||||
|
const copyFeedData = {...feedData}
|
||||||
|
const fullUrl = constructUrl(baseurl, copyFeedData.search, dynamicVars);
|
||||||
|
if(fullUrl){
|
||||||
|
formEndpoints.push({
|
||||||
|
url: fullUrl, schemaName: schema.name, schema: copyFeedData
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
})
|
||||||
|
this.endpoints= formEndpoints
|
||||||
|
this.endpointOffsets = Array(this.endpoints.length).fill(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
async firstUpdated(){
|
||||||
|
this.viewElement = this.shadowRoot.getElementById('viewElement');
|
||||||
|
this.downObserverElement =
|
||||||
|
this.shadowRoot.getElementById('downObserver');
|
||||||
|
this.elementObserver();
|
||||||
|
|
||||||
|
|
||||||
|
try {
|
||||||
|
await new Promise((res)=> {
|
||||||
|
setTimeout(() => {
|
||||||
|
res()
|
||||||
|
}, 5000);
|
||||||
|
})
|
||||||
|
if(this.mySelectedFeeds.length === 0){
|
||||||
|
await this.getEndpoints()
|
||||||
|
|
||||||
|
this.loadAndMergeData();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.getFeedOnInterval()
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getMoreFeed(){
|
||||||
|
if(!this.hasInitialFetch) return
|
||||||
|
if(this.feedToRender.length === this.feed.length ) return
|
||||||
|
this.feedToRender = this.feed.slice(0, this.feedToRender.length + 20)
|
||||||
|
this.requestUpdate()
|
||||||
|
}
|
||||||
|
|
||||||
|
async refresh(){
|
||||||
|
try {
|
||||||
|
await this.getEndpoints()
|
||||||
|
this.reFetchFeedData()
|
||||||
|
} catch (error) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
elementObserver() {
|
||||||
|
const options = {
|
||||||
|
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.feedToRender.length < 20) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.getMoreFeed();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fetchDataFromEndpoint(endpointIndex, count) {
|
||||||
|
const offset = this.endpointOffsets[endpointIndex];
|
||||||
|
const url = `${this.endpoints[endpointIndex].url}&limit=${count}&offset=${offset}`;
|
||||||
|
const res = await fetch(url)
|
||||||
|
const data = await res.json()
|
||||||
|
return data.map((i)=> {
|
||||||
|
return {
|
||||||
|
...this.endpoints[endpointIndex],
|
||||||
|
...i
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async initialLoad() {
|
||||||
|
let results = [];
|
||||||
|
let totalFetched = 0;
|
||||||
|
let i = 0;
|
||||||
|
let madeProgress = true;
|
||||||
|
let exhaustedEndpoints = new Set();
|
||||||
|
|
||||||
|
while (totalFetched < totalDesiredCount && madeProgress) {
|
||||||
|
madeProgress = false;
|
||||||
|
this.isLoading = true
|
||||||
|
for (i = 0; i < this.endpoints.length; i++) {
|
||||||
|
if (exhaustedEndpoints.has(i)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const remainingCount = totalDesiredCount - totalFetched;
|
||||||
|
|
||||||
|
// If we've already reached the desired count, break
|
||||||
|
if (remainingCount <= 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
let fetchCount = Math.min(perEndpointCount, remainingCount);
|
||||||
|
let data = await this.fetchDataFromEndpoint(i, fetchCount);
|
||||||
|
|
||||||
|
// Increment the offset for this endpoint by the number of items fetched
|
||||||
|
this.endpointOffsets[i] += data.length;
|
||||||
|
|
||||||
|
if (data.length > 0) {
|
||||||
|
madeProgress = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.length < fetchCount) {
|
||||||
|
exhaustedEndpoints.add(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
results = results.concat(data);
|
||||||
|
totalFetched += data.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (exhaustedEndpoints.size === this.endpoints.length) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.isLoading = false
|
||||||
|
this.hasFetched = true;
|
||||||
|
// Trim the results if somehow they are over the totalDesiredCount
|
||||||
|
return results.slice(0, totalDesiredCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
trimDataToLimit(data, limit) {
|
||||||
|
return data.slice(0, limit);
|
||||||
|
}
|
||||||
|
|
||||||
|
mergeData(newData, existingData) {
|
||||||
|
const existingIds = new Set(existingData.map(item => item.identifier)); // Assume each item has a unique 'id'
|
||||||
|
const uniqueNewData = newData.filter(item => !existingIds.has(item.identifier));
|
||||||
|
return uniqueNewData.concat(existingData);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async addExtraData(data){
|
||||||
|
let newData = []
|
||||||
|
for (let item of data) {
|
||||||
|
let newItem = {
|
||||||
|
...item,
|
||||||
|
schema: {
|
||||||
|
...item.schema,
|
||||||
|
customParams: {...item.schema.customParams}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let newResource = {
|
||||||
|
identifier: newItem.identifier,
|
||||||
|
service: newItem.service,
|
||||||
|
name: newItem.name
|
||||||
|
}
|
||||||
|
if(newItem.schema){
|
||||||
|
const resource = newItem
|
||||||
|
|
||||||
|
let clickValue1 = newItem.schema.click;
|
||||||
|
|
||||||
|
const resolvedClickValue1 = replacePlaceholders(clickValue1, resource, newItem.schema.customParams);
|
||||||
|
newItem.link = resolvedClickValue1
|
||||||
|
newData.push(newItem)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return newData
|
||||||
|
|
||||||
|
}
|
||||||
|
async reFetchFeedData() {
|
||||||
|
// Resetting offsets to start fresh.
|
||||||
|
this.endpointOffsets = Array(this.endpoints.length).fill(0);
|
||||||
|
await this.getEndpoints()
|
||||||
|
const oldIdentifiers = new Set(this.feed.map(item => item.identifier));
|
||||||
|
const newData = await this.initialLoad();
|
||||||
|
|
||||||
|
// Filter out items that are already in the feed
|
||||||
|
const trulyNewData = newData.filter(item => !oldIdentifiers.has(item.identifier));
|
||||||
|
|
||||||
|
if (trulyNewData.length > 0) {
|
||||||
|
// Adding extra data and merging with old data
|
||||||
|
const enhancedNewData = await this.addExtraData(trulyNewData);
|
||||||
|
|
||||||
|
// Merge new data with old data immutably
|
||||||
|
this.feed = [...enhancedNewData, ...this.feed];
|
||||||
|
|
||||||
|
this.feed.sort((a, b) => new Date(b.created) - new Date(a.created)); // Sort by timestamp, most recent first
|
||||||
|
this.feed = this.trimDataToLimit(this.feed, maxResultsInMemory); // Trim to the maximum allowed in memory
|
||||||
|
this.feedToRender = this.feed.slice(0, 20);
|
||||||
|
this.hasInitialFetch = true;
|
||||||
|
|
||||||
|
const created = trulyNewData[0].created;
|
||||||
|
let value = localStorage.getItem('lastSeenFeed');
|
||||||
|
if (((+value || 0) < created)) {
|
||||||
|
this.setHasNewFeed(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
async loadAndMergeData() {
|
||||||
|
let allData = this.feed
|
||||||
|
const newData = await this.initialLoad();
|
||||||
|
allData = await this.addExtraData(newData)
|
||||||
|
allData = this.mergeData(newData, allData);
|
||||||
|
allData.sort((a, b) => new Date(b.created) - new Date(a.created)); // Sort by timestamp, most recent first
|
||||||
|
allData = this.trimDataToLimit(allData, maxResultsInMemory); // Trim to the maximum allowed in memory
|
||||||
|
this.feed = [...allData]
|
||||||
|
this.feedToRender = this.feed.slice(0,20)
|
||||||
|
this.hasInitialFetch = true
|
||||||
|
if(allData.length > 0){
|
||||||
|
const created = allData[0].created
|
||||||
|
let value = localStorage.getItem('lastSeenFeed')
|
||||||
|
if (((+value || 0) < created)) {
|
||||||
|
this.setHasNewFeed(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return html`
|
||||||
|
<div class="container">
|
||||||
|
<div id="viewElement" class="container-body" style=${"position: relative"}>
|
||||||
|
${this.isLoading ? html`
|
||||||
|
<div style="width:100%;display: flex; justify-content:center">
|
||||||
|
<paper-spinner-lite active></paper-spinner-lite>
|
||||||
|
</div>
|
||||||
|
` : ''}
|
||||||
|
${this.hasFetched && !this.isLoading && this.feed.length === 0 ? html`
|
||||||
|
<div style="width:100%;display: flex; justify-content:center">
|
||||||
|
<p>${translate('friends.friends18')}</p>
|
||||||
|
</div>
|
||||||
|
` : ''}
|
||||||
|
${this.feedToRender.map((item) => {
|
||||||
|
return html`<feed-item
|
||||||
|
.resource=${item}
|
||||||
|
appName=${'Q-Blog'}
|
||||||
|
link=${item.link}
|
||||||
|
></feed-item>`;
|
||||||
|
})}
|
||||||
|
<div id="downObserver"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
customElements.define('friends-feed', FriendsFeed);
|
||||||
|
|
||||||
|
export function substituteDynamicVar(value, dynamicVars) {
|
||||||
|
if (typeof value !== 'string') return value;
|
||||||
|
|
||||||
|
const pattern = /\$\$\{([a-zA-Z0-9_]+)\}\$\$/g; // Adjusted pattern to capture $${name}$$ with curly braces
|
||||||
|
|
||||||
|
return value.replace(pattern, (match, p1) => {
|
||||||
|
return dynamicVars[p1] !== undefined ? dynamicVars[p1] : match;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function constructUrl(base, search, dynamicVars) {
|
||||||
|
let queryStrings = [];
|
||||||
|
|
||||||
|
for (const [key, value] of Object.entries(search)) {
|
||||||
|
const substitutedValue = substituteDynamicVar(value, dynamicVars);
|
||||||
|
queryStrings.push(`${key}=${encodeURIComponent(substitutedValue)}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return queryStrings.length > 0 ? `${base}&${queryStrings.join('&')}` : base;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export function replacePlaceholders(template, resource, customParams) {
|
||||||
|
const dataSource = { resource, customParams };
|
||||||
|
|
||||||
|
return template.replace(/\$\$\{(.*?)\}\$\$/g, (match, p1) => {
|
||||||
|
const keys = p1.split('.');
|
||||||
|
let value = dataSource;
|
||||||
|
|
||||||
|
for (let key of keys) {
|
||||||
|
if (value[key] !== undefined) {
|
||||||
|
value = value[key];
|
||||||
|
} else {
|
||||||
|
return match; // Return placeholder unchanged
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,81 @@
|
|||||||
|
import { LitElement, html, css } from 'lit';
|
||||||
|
import '@material/mwc-icon';
|
||||||
|
import './friends-side-panel.js';
|
||||||
|
import '@vaadin/tooltip';
|
||||||
|
import { get } from 'lit-translate';
|
||||||
|
|
||||||
|
|
||||||
|
class FriendsSidePanelParent extends LitElement {
|
||||||
|
static get properties() {
|
||||||
|
return {
|
||||||
|
isOpen: {type: Boolean},
|
||||||
|
hasNewFeed: {type: Boolean}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.isOpen = false
|
||||||
|
this.hasNewFeed = 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 */
|
||||||
|
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
setHasNewFeed(val){
|
||||||
|
this.hasNewFeed = val
|
||||||
|
}
|
||||||
|
render() {
|
||||||
|
return html`
|
||||||
|
<mwc-icon id="friends-icon" @click=${()=> {
|
||||||
|
this.isOpen = !this.isOpen
|
||||||
|
if(this.isOpen && this.hasNewFeed){
|
||||||
|
localStorage.setItem('lastSeenFeed', Date.now());
|
||||||
|
this.hasNewFeed = false
|
||||||
|
this.shadowRoot.querySelector("friends-side-panel").selected = 'feed'
|
||||||
|
}
|
||||||
|
}} style="color: ${this.hasNewFeed ? 'green' : 'var(--black)'}; cursor:pointer;user-select:none"
|
||||||
|
>group</mwc-icon
|
||||||
|
>
|
||||||
|
<vaadin-tooltip
|
||||||
|
for="friends-icon"
|
||||||
|
position="bottom"
|
||||||
|
hover-delay=${400}
|
||||||
|
hide-delay=${1}
|
||||||
|
text=${get('friends.friends17')}>
|
||||||
|
</vaadin-tooltip>
|
||||||
|
<friends-side-panel .setHasNewFeed=${(val)=> this.setHasNewFeed(val)} ?isOpen=${this.isOpen} .setIsOpen=${(val)=> this.isOpen = val}></friends-side-panel>
|
||||||
|
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
customElements.define('friends-side-panel-parent', FriendsSidePanelParent);
|
151
core/src/components/friends-view/friends-side-panel.js
Normal file
151
core/src/components/friends-view/friends-side-panel.js
Normal file
@ -0,0 +1,151 @@
|
|||||||
|
import { LitElement, html, css } from 'lit';
|
||||||
|
import '@material/mwc-icon';
|
||||||
|
import './friends-view'
|
||||||
|
import './friends-feed'
|
||||||
|
import { translate } from 'lit-translate';
|
||||||
|
class FriendsSidePanel extends LitElement {
|
||||||
|
static get properties() {
|
||||||
|
return {
|
||||||
|
setIsOpen: { attribute: false},
|
||||||
|
isOpen: {type: Boolean},
|
||||||
|
selected: {type: String},
|
||||||
|
setHasNewFeed: {attribute: false},
|
||||||
|
closeSidePanel: {attribute: false, type: Object}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(){
|
||||||
|
super()
|
||||||
|
this.selected = 'friends'
|
||||||
|
this.closeSidePanel = this.closeSidePanel.bind(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
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;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex-grow: 1;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
.content::-webkit-scrollbar-track {
|
||||||
|
background-color: whitesmoke;
|
||||||
|
border-radius: 7px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content::-webkit-scrollbar {
|
||||||
|
width: 12px;
|
||||||
|
border-radius: 7px;
|
||||||
|
background-color: whitesmoke;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content::-webkit-scrollbar-thumb {
|
||||||
|
background-color: rgb(180, 176, 176);
|
||||||
|
border-radius: 7px;
|
||||||
|
transition: all 0.3s ease-in-out;
|
||||||
|
}
|
||||||
|
.parent {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.active {
|
||||||
|
font-size: 16px;
|
||||||
|
background: var(--black);
|
||||||
|
color: var(--white);
|
||||||
|
padding: 5px;
|
||||||
|
border-radius: 2px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.default {
|
||||||
|
font-size: 16px;
|
||||||
|
color: var(--black);
|
||||||
|
padding: 5px;
|
||||||
|
border-radius: 2px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.default-content {
|
||||||
|
visibility: hidden;
|
||||||
|
position: absolute;
|
||||||
|
z-index: -50;
|
||||||
|
}
|
||||||
|
|
||||||
|
`;
|
||||||
|
|
||||||
|
refreshFeed(){
|
||||||
|
|
||||||
|
this.shadowRoot.querySelector('friends-feed').refresh()
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
closeSidePanel(){
|
||||||
|
this.setIsOpen(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return html`
|
||||||
|
<div class="parent">
|
||||||
|
<div class="header">
|
||||||
|
<div style="display:flex;align-items:center;gap:10px">
|
||||||
|
<span @click=${()=> this.selected = 'friends'} class="${this.selected === 'friends' ? 'active' : 'default'}">${translate('friends.friend12')}</span>
|
||||||
|
<span @click=${()=> this.selected = 'feed'} class="${this.selected === 'feed' ? 'active' : 'default'}">${translate('friends.friend13')}</span>
|
||||||
|
</div>
|
||||||
|
<div style="display:flex;gap:15px;align-items:center">
|
||||||
|
<mwc-icon @click=${()=> {
|
||||||
|
this.refreshFeed()
|
||||||
|
}} style="color: var(--black); cursor:pointer;">refresh</mwc-icon>
|
||||||
|
<mwc-icon style="cursor:pointer" @click=${()=> {
|
||||||
|
this.setIsOpen(false)
|
||||||
|
}}>close</mwc-icon>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="content">
|
||||||
|
<div class="${this.selected === 'friends' ? 'active-content' : 'default-content'}">
|
||||||
|
<friends-view .closeSidePanel=${this.closeSidePanel} .refreshFeed=${()=>this.refreshFeed()}></friends-view>
|
||||||
|
</div>
|
||||||
|
<div class="${this.selected === 'feed' ? 'active-content' : 'default-content'}">
|
||||||
|
<friends-feed .setHasNewFeed=${(val)=> this.setHasNewFeed(val)}></friends-feed>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
customElements.define('friends-side-panel', FriendsSidePanel);
|
182
core/src/components/friends-view/friends-view-css.js
Normal file
182
core/src/components/friends-view/friends-view-css.js
Normal file
@ -0,0 +1,182 @@
|
|||||||
|
import { css } from 'lit'
|
||||||
|
|
||||||
|
export const friendsViewStyles = css`
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
.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;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.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: hover 0.3s ease-in-out;
|
||||||
|
background: none;
|
||||||
|
border-radius: 50%;
|
||||||
|
padding: 6px 3px;
|
||||||
|
font-size: 21px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-icon:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
background: #d7d7d75c;
|
||||||
|
}
|
||||||
|
`
|
398
core/src/components/friends-view/friends-view.js
Normal file
398
core/src/components/friends-view/friends-view.js
Normal file
@ -0,0 +1,398 @@
|
|||||||
|
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},
|
||||||
|
mySelectedFeeds: {type: Array},
|
||||||
|
refreshFeed: {attribute: false},
|
||||||
|
closeSidePanel: {attribute: false, 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 = [];
|
||||||
|
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)
|
||||||
|
this._updateFriends = this._updateFriends.bind(this)
|
||||||
|
this._updateFeed = this._updateFeed.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();
|
||||||
|
this.mySelectedFeeds = JSON.parse(localStorage.getItem('friends-my-selected-feeds') || "[]")
|
||||||
|
this.friendList = JSON.parse(localStorage.getItem('friends-my-friend-list') || "[]")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
_updateFriends(event) {
|
||||||
|
const detail = event.detail
|
||||||
|
this.friendList = detail
|
||||||
|
}
|
||||||
|
_updateFeed(event) {
|
||||||
|
console.log({event})
|
||||||
|
const detail = event.detail
|
||||||
|
console.log({detail})
|
||||||
|
this.mySelectedFeeds = detail
|
||||||
|
this.requestUpdate()
|
||||||
|
}
|
||||||
|
|
||||||
|
connectedCallback() {
|
||||||
|
super.connectedCallback()
|
||||||
|
console.log('callback')
|
||||||
|
window.addEventListener('friends-my-friend-list-event', this._updateFriends)
|
||||||
|
window.addEventListener('friends-my-selected-feeds-event', this._updateFeed)
|
||||||
|
}
|
||||||
|
|
||||||
|
disconnectedCallback() {
|
||||||
|
window.removeEventListener('friends-my-friend-list-event', this._updateFriends)
|
||||||
|
window.addEventListener('friends-my-selected-feeds-event', this._updateFeed)
|
||||||
|
super.disconnectedCallback()
|
||||||
|
}
|
||||||
|
|
||||||
|
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 = [
|
||||||
|
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}`
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
async unFollowName(name) {
|
||||||
|
let items = [
|
||||||
|
name
|
||||||
|
]
|
||||||
|
let namesJsonString = JSON.stringify({ "items": items })
|
||||||
|
|
||||||
|
let ret = await parentEpml.request('apiCall', {
|
||||||
|
url: `/lists/followedNames?apiKey=${this.getApiKey()}`,
|
||||||
|
method: 'DELETE',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: `${namesJsonString}`
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
async addToFriendList(val, isRemove){
|
||||||
|
const copyVal = {...val}
|
||||||
|
delete copyVal.mySelectedFeeds
|
||||||
|
if(isRemove){
|
||||||
|
this.friendList = this.friendList.filter((item)=> item.name !== copyVal.name)
|
||||||
|
}else if(this.editContent){
|
||||||
|
const findFriend = this.friendList.findIndex(item=> item.name === copyVal.name)
|
||||||
|
if(findFriend !== -1){
|
||||||
|
const copyList = [...this.friendList]
|
||||||
|
copyList[findFriend] = copyVal
|
||||||
|
this.friendList = copyList
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
this.friendList = [...this.friendList, copyVal]
|
||||||
|
}
|
||||||
|
if(!copyVal.willFollow || isRemove) {
|
||||||
|
this.unFollowName(copyVal.name)
|
||||||
|
} else if(copyVal.willFollow){
|
||||||
|
this.myFollowName(copyVal.name)
|
||||||
|
}
|
||||||
|
this.setMySelectedFeeds(val.mySelectedFeeds)
|
||||||
|
await new Promise((res)=> {
|
||||||
|
setTimeout(()=> {
|
||||||
|
res()
|
||||||
|
},50)
|
||||||
|
})
|
||||||
|
this.userSelected = {};
|
||||||
|
this.shadowRoot.getElementById('sendTo').value = ''
|
||||||
|
this.isLoading = false;
|
||||||
|
this.isOpenAddFriendsModal = false
|
||||||
|
this.editContent = null
|
||||||
|
this.setMyFriends(this.friendList)
|
||||||
|
if(!isRemove && this.friendList.length === 1){
|
||||||
|
this.refreshFeed()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setMyFriends(friendList){
|
||||||
|
localStorage.setItem('friends-my-friend-list', JSON.stringify(friendList));
|
||||||
|
const tempSettingsData= JSON.parse(localStorage.getItem('temp-settings-data') || "{}")
|
||||||
|
const newTemp = {
|
||||||
|
...tempSettingsData,
|
||||||
|
userLists: {
|
||||||
|
data: [friendList],
|
||||||
|
timestamp: Date.now()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
localStorage.setItem('temp-settings-data', JSON.stringify(newTemp));
|
||||||
|
this.dispatchEvent(
|
||||||
|
new CustomEvent('temp-settings-data-event', {
|
||||||
|
bubbles: true,
|
||||||
|
composed: true
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
|
setMySelectedFeeds(mySelectedFeeds){
|
||||||
|
this.mySelectedFeeds = mySelectedFeeds
|
||||||
|
const tempSettingsData= JSON.parse(localStorage.getItem('temp-settings-data') || "{}")
|
||||||
|
const newTemp = {
|
||||||
|
...tempSettingsData,
|
||||||
|
friendsFeed: {
|
||||||
|
data: mySelectedFeeds,
|
||||||
|
timestamp: Date.now()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
localStorage.setItem('temp-settings-data', JSON.stringify(newTemp));
|
||||||
|
localStorage.setItem('friends-my-selected-feeds', JSON.stringify(mySelectedFeeds));
|
||||||
|
}
|
||||||
|
openEditFriend(val){
|
||||||
|
this.isOpenAddFriendsModal = true
|
||||||
|
this.userSelected = val
|
||||||
|
this.editContent = {...val, mySelectedFeeds: this.mySelectedFeeds}
|
||||||
|
}
|
||||||
|
|
||||||
|
onClose(){
|
||||||
|
this.isLoading = false;
|
||||||
|
this.isOpenAddFriendsModal = false
|
||||||
|
this.editContent = null
|
||||||
|
this.userSelected = {}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
console.log('rendered1')
|
||||||
|
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:search"
|
||||||
|
class="search-icon">
|
||||||
|
</vaadin-icon>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="search-results-div">
|
||||||
|
<chat-search-results
|
||||||
|
.onClickFunc=${(result) => {
|
||||||
|
this.userSelected = result;
|
||||||
|
this.isOpenAddFriendsModal = true
|
||||||
|
|
||||||
|
this.userFound = [];
|
||||||
|
this.userFoundModalOpen = false;
|
||||||
|
}}
|
||||||
|
.closeFunc=${() => {
|
||||||
|
this.userFoundModalOpen = false;
|
||||||
|
this.userFound = [];
|
||||||
|
}}
|
||||||
|
.searchResults=${this.userFound}
|
||||||
|
?isOpen=${this.userFoundModalOpen}
|
||||||
|
?loading=${this.isLoading}>
|
||||||
|
</chat-search-results>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
${this.friendList.map((item) => {
|
||||||
|
return html`<chat-side-nav-heads
|
||||||
|
activeChatHeadUrl=""
|
||||||
|
.setActiveChatHeadUrl=${(val) => {
|
||||||
|
|
||||||
|
}}
|
||||||
|
.chatInfo=${item}
|
||||||
|
.openEditFriend=${(val)=> this.openEditFriend(val)}
|
||||||
|
.closeSidePanel=${this.closeSidePanel}
|
||||||
|
></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, isRemove)=> this.addToFriendList(val, isRemove)}
|
||||||
|
.editContent=${this.editContent}
|
||||||
|
.onClose=${()=> this.onClose()}
|
||||||
|
.mySelectedFeeds=${this.mySelectedFeeds}
|
||||||
|
>
|
||||||
|
</add-friends-modal>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
customElements.define('friends-view', FriendsView);
|
593
core/src/components/friends-view/save-settings-qdn.js
Normal file
593
core/src/components/friends-view/save-settings-qdn.js
Normal file
@ -0,0 +1,593 @@
|
|||||||
|
import { LitElement, html, css } from 'lit';
|
||||||
|
import '@material/mwc-icon';
|
||||||
|
import './friends-side-panel.js';
|
||||||
|
import { connect } from 'pwa-helpers';
|
||||||
|
import { store } from '../../store.js';
|
||||||
|
import WebWorker from 'web-worker:./computePowWorkerFile.src.js';
|
||||||
|
import '@polymer/paper-spinner/paper-spinner-lite.js';
|
||||||
|
import '@vaadin/tooltip';
|
||||||
|
import { get, translate } from 'lit-translate';
|
||||||
|
import {
|
||||||
|
decryptGroupData,
|
||||||
|
encryptDataGroup,
|
||||||
|
objectToBase64,
|
||||||
|
uint8ArrayToBase64,
|
||||||
|
uint8ArrayToObject,
|
||||||
|
} from '../../../../plugins/plugins/core/components/qdn-action-encryption.js';
|
||||||
|
import { publishData } from '../../../../plugins/plugins/utils/publish-image.js';
|
||||||
|
import { parentEpml } from '../show-plugin.js';
|
||||||
|
import '../notification-view/popover.js';
|
||||||
|
|
||||||
|
class SaveSettingsQdn extends connect(store)(LitElement) {
|
||||||
|
static get properties() {
|
||||||
|
return {
|
||||||
|
isOpen: { type: Boolean },
|
||||||
|
syncPercentage: { type: Number },
|
||||||
|
settingsRawData: { type: Object },
|
||||||
|
valuesToBeSavedOnQdn: { type: Object },
|
||||||
|
resourceExists: { type: Boolean },
|
||||||
|
isSaving: { type: Boolean },
|
||||||
|
fee: { type: Object },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.isOpen = false;
|
||||||
|
this.getGeneralSettingsQdn = this.getGeneralSettingsQdn.bind(this);
|
||||||
|
this._updateTempSettingsData = this._updateTempSettingsData.bind(this);
|
||||||
|
this.setValues = this.setValues.bind(this);
|
||||||
|
this.saveToQdn = this.saveToQdn.bind(this);
|
||||||
|
this.syncPercentage = 0;
|
||||||
|
this.hasRetrievedResource = false;
|
||||||
|
this.hasAttemptedToFetchResource = false;
|
||||||
|
this.resourceExists = undefined;
|
||||||
|
this.settingsRawData = null;
|
||||||
|
this.nodeUrl = this.getNodeUrl();
|
||||||
|
this.myNode = this.getMyNode();
|
||||||
|
this.valuesToBeSavedOnQdn = {};
|
||||||
|
this.isSaving = false;
|
||||||
|
this.fee = null;
|
||||||
|
}
|
||||||
|
static styles = css`
|
||||||
|
:host {
|
||||||
|
margin-right: 20px;
|
||||||
|
}
|
||||||
|
.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 */
|
||||||
|
}
|
||||||
|
.notActive {
|
||||||
|
opacity: 0.5;
|
||||||
|
cursor: default;
|
||||||
|
color: var(--black);
|
||||||
|
}
|
||||||
|
.active {
|
||||||
|
opacity: 1;
|
||||||
|
cursor: pointer;
|
||||||
|
color: green;
|
||||||
|
}
|
||||||
|
.accept-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;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.accept-button:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
background-color: #03a8f485;
|
||||||
|
}
|
||||||
|
|
||||||
|
.undo-button {
|
||||||
|
font-family: Roboto, sans-serif;
|
||||||
|
letter-spacing: 0.3px;
|
||||||
|
font-weight: 300;
|
||||||
|
padding: 8px 5px;
|
||||||
|
border-radius: 3px;
|
||||||
|
text-align: center;
|
||||||
|
color: #f44336;
|
||||||
|
transition: all 0.3s ease-in-out;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.undo-button:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
background-color: #f4433663;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
getNodeUrl() {
|
||||||
|
const myNode =
|
||||||
|
window.parent.reduxStore.getState().app.nodeConfig.knownNodes[
|
||||||
|
window.parent.reduxStore.getState().app.nodeConfig.node
|
||||||
|
];
|
||||||
|
|
||||||
|
const nodeUrl =
|
||||||
|
myNode.protocol + '://' + myNode.domain + ':' + myNode.port;
|
||||||
|
return nodeUrl;
|
||||||
|
}
|
||||||
|
getMyNode() {
|
||||||
|
const myNode =
|
||||||
|
window.parent.reduxStore.getState().app.nodeConfig.knownNodes[
|
||||||
|
window.parent.reduxStore.getState().app.nodeConfig.node
|
||||||
|
];
|
||||||
|
|
||||||
|
return myNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getRawData(dataItem) {
|
||||||
|
const url = `${this.nodeUrl}/arbitrary/${dataItem.service}/${dataItem.name}/${dataItem.identifier}?encoding=base64`;
|
||||||
|
const res = await fetch(url);
|
||||||
|
const data = await res.text();
|
||||||
|
if (data.error) throw new Error('Cannot retrieve your data from qdn');
|
||||||
|
const decryptedData = decryptGroupData(data);
|
||||||
|
const decryptedDataToBase64 = uint8ArrayToObject(decryptedData);
|
||||||
|
return decryptedDataToBase64;
|
||||||
|
}
|
||||||
|
|
||||||
|
async setValues(response, resource) {
|
||||||
|
this.settingsRawData = response;
|
||||||
|
const rawDataTimestamp = resource.updated;
|
||||||
|
|
||||||
|
const tempSettingsData = JSON.parse(
|
||||||
|
localStorage.getItem('temp-settings-data') || '{}'
|
||||||
|
);
|
||||||
|
|
||||||
|
const userLists = response.userLists || [];
|
||||||
|
const friendsFeed = response.friendsFeed;
|
||||||
|
const myMenuPlugs = response.myMenuPlugs;
|
||||||
|
|
||||||
|
this.valuesToBeSavedOnQdn = {};
|
||||||
|
if (
|
||||||
|
userLists.length > 0 &&
|
||||||
|
(!tempSettingsData.userLists ||
|
||||||
|
(tempSettingsData.userLists &&
|
||||||
|
tempSettingsData.userLists.timestamp < rawDataTimestamp))
|
||||||
|
) {
|
||||||
|
const friendList = userLists[0];
|
||||||
|
const copyPayload = [...friendList];
|
||||||
|
|
||||||
|
localStorage.setItem(
|
||||||
|
'friends-my-friend-list',
|
||||||
|
JSON.stringify(friendList)
|
||||||
|
);
|
||||||
|
this.dispatchEvent(
|
||||||
|
new CustomEvent('friends-my-friend-list-event', {
|
||||||
|
bubbles: true,
|
||||||
|
composed: true,
|
||||||
|
detail: copyPayload,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
} else if (
|
||||||
|
tempSettingsData.userLists &&
|
||||||
|
tempSettingsData.userLists.timestamp > rawDataTimestamp
|
||||||
|
) {
|
||||||
|
this.valuesToBeSavedOnQdn = {
|
||||||
|
...this.valuesToBeSavedOnQdn,
|
||||||
|
userLists: {
|
||||||
|
data: tempSettingsData.userLists.data,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
friendsFeed &&
|
||||||
|
(!tempSettingsData.friendsFeed ||
|
||||||
|
(tempSettingsData.friendsFeed &&
|
||||||
|
tempSettingsData.friendsFeed.timestamp < rawDataTimestamp))
|
||||||
|
) {
|
||||||
|
const copyPayload = [...friendsFeed];
|
||||||
|
|
||||||
|
localStorage.setItem(
|
||||||
|
'friends-my-selected-feeds',
|
||||||
|
JSON.stringify(friendsFeed)
|
||||||
|
);
|
||||||
|
this.dispatchEvent(
|
||||||
|
new CustomEvent('friends-my-selected-feeds-event', {
|
||||||
|
bubbles: true,
|
||||||
|
composed: true,
|
||||||
|
detail: copyPayload,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
} else if (
|
||||||
|
tempSettingsData.friendsFeed &&
|
||||||
|
tempSettingsData.friendsFeed.timestamp > rawDataTimestamp
|
||||||
|
) {
|
||||||
|
this.valuesToBeSavedOnQdn = {
|
||||||
|
...this.valuesToBeSavedOnQdn,
|
||||||
|
friendsFeed: {
|
||||||
|
data: tempSettingsData.friendsFeed.data,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
myMenuPlugs &&
|
||||||
|
(!tempSettingsData.myMenuPlugs ||
|
||||||
|
(tempSettingsData.myMenuPlugs &&
|
||||||
|
tempSettingsData.myMenuPlugs.timestamp < rawDataTimestamp))
|
||||||
|
) {
|
||||||
|
if (Array.isArray(myMenuPlugs)) {
|
||||||
|
const copyPayload = [...myMenuPlugs];
|
||||||
|
|
||||||
|
localStorage.setItem(
|
||||||
|
'myMenuPlugs',
|
||||||
|
JSON.stringify(myMenuPlugs)
|
||||||
|
);
|
||||||
|
|
||||||
|
this.dispatchEvent(
|
||||||
|
new CustomEvent('myMenuPlugs-event', {
|
||||||
|
bubbles: true,
|
||||||
|
composed: true,
|
||||||
|
detail: copyPayload,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else if (
|
||||||
|
tempSettingsData.myMenuPlugs &&
|
||||||
|
tempSettingsData.myMenuPlugs.timestamp > rawDataTimestamp
|
||||||
|
) {
|
||||||
|
this.valuesToBeSavedOnQdn = {
|
||||||
|
...this.valuesToBeSavedOnQdn,
|
||||||
|
myMenuPlugs: {
|
||||||
|
data: tempSettingsData.myMenuPlugs.data,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getGeneralSettingsQdn() {
|
||||||
|
try {
|
||||||
|
const arbFee = await this.getArbitraryFee();
|
||||||
|
this.fee = arbFee;
|
||||||
|
this.hasAttemptedToFetchResource = true;
|
||||||
|
let resource;
|
||||||
|
const nameObject = store.getState().app.accountInfo.names[0];
|
||||||
|
if (!nameObject) throw new Error('no name');
|
||||||
|
const name = nameObject.name;
|
||||||
|
this.error = '';
|
||||||
|
const url = `${this.nodeUrl}/arbitrary/resources/search?service=DOCUMENT_PRIVATE&identifier=qortal_general_settings&name=${name}&prefix=true&exactmatchnames=true&excludeblocked=true&limit=20`;
|
||||||
|
const res = await fetch(url);
|
||||||
|
let data = '';
|
||||||
|
try {
|
||||||
|
data = await res.json();
|
||||||
|
if (Array.isArray(data)) {
|
||||||
|
data = data.filter(
|
||||||
|
(item) => item.identifier === 'qortal_general_settings'
|
||||||
|
);
|
||||||
|
|
||||||
|
if (data.length > 0) {
|
||||||
|
this.resourceExists = true;
|
||||||
|
const dataItem = data[0];
|
||||||
|
try {
|
||||||
|
const response = await this.getRawData(dataItem);
|
||||||
|
if (response.version) {
|
||||||
|
this.setValues(response, dataItem);
|
||||||
|
} else {
|
||||||
|
this.error = 'Cannot get saved user settings';
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log({ error });
|
||||||
|
this.error = 'Cannot get saved user settings';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.resourceExists = false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.error = 'Unable to perform query';
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
data = {
|
||||||
|
error: 'No resource found',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (resource) {
|
||||||
|
this.hasRetrievedResource = true;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log({ error });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stateChanged(state) {
|
||||||
|
if (
|
||||||
|
state.app.accountInfo &&
|
||||||
|
state.app.accountInfo.names.length &&
|
||||||
|
state.app.nodeStatus &&
|
||||||
|
state.app.nodeStatus.syncPercent !== this.syncPercentage
|
||||||
|
) {
|
||||||
|
this.syncPercentage = state.app.nodeStatus.syncPercent;
|
||||||
|
|
||||||
|
if (
|
||||||
|
!this.hasAttemptedToFetchResource &&
|
||||||
|
state.app.nodeStatus.syncPercent === 100
|
||||||
|
) {
|
||||||
|
this.getGeneralSettingsQdn();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getArbitraryFee() {
|
||||||
|
const timestamp = Date.now();
|
||||||
|
const url = `${this.nodeUrl}/transactions/unitfee?txType=ARBITRARY×tamp=${timestamp}`;
|
||||||
|
const response = await fetch(url);
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Error when fetching arbitrary fee');
|
||||||
|
}
|
||||||
|
const data = await response.json();
|
||||||
|
const arbitraryFee = (Number(data) / 1e8).toFixed(8);
|
||||||
|
return {
|
||||||
|
timestamp,
|
||||||
|
fee: Number(data),
|
||||||
|
feeToShow: arbitraryFee,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async saveToQdn() {
|
||||||
|
try {
|
||||||
|
this.isSaving = true;
|
||||||
|
if (this.resourceExists === true && this.error)
|
||||||
|
throw new Error('Unable to save');
|
||||||
|
|
||||||
|
const nameObject = store.getState().app.accountInfo.names[0];
|
||||||
|
if (!nameObject) throw new Error('no name');
|
||||||
|
const name = nameObject.name;
|
||||||
|
const identifer = 'qortal_general_settings';
|
||||||
|
const filename = 'qortal_general_settings.json';
|
||||||
|
const selectedAddress = store.getState().app.selectedAddress;
|
||||||
|
const getArbitraryFee = await this.getArbitraryFee();
|
||||||
|
const feeAmount = getArbitraryFee.fee;
|
||||||
|
const friendsList = JSON.parse(
|
||||||
|
localStorage.getItem('friends-my-friend-list') || '[]'
|
||||||
|
);
|
||||||
|
const friendsFeed = JSON.parse(
|
||||||
|
localStorage.getItem('friends-my-selected-feeds') || '[]'
|
||||||
|
);
|
||||||
|
const myMenuPlugs = JSON.parse(
|
||||||
|
localStorage.getItem('myMenuPlugs') || '[]'
|
||||||
|
);
|
||||||
|
|
||||||
|
let newObject;
|
||||||
|
|
||||||
|
if (this.resourceExists === false) {
|
||||||
|
newObject = {
|
||||||
|
version: 1,
|
||||||
|
userLists: [friendsList],
|
||||||
|
friendsFeed,
|
||||||
|
myMenuPlugs,
|
||||||
|
};
|
||||||
|
} else if (this.settingsRawData) {
|
||||||
|
const tempSettingsData = JSON.parse(
|
||||||
|
localStorage.getItem('temp-settings-data') || '{}'
|
||||||
|
);
|
||||||
|
newObject = {
|
||||||
|
...this.settingsRawData,
|
||||||
|
};
|
||||||
|
for (const key in tempSettingsData) {
|
||||||
|
if (tempSettingsData[key].hasOwnProperty('data')) {
|
||||||
|
if (
|
||||||
|
key === 'userLists' &&
|
||||||
|
!Array.isArray(tempSettingsData[key].data)
|
||||||
|
)
|
||||||
|
continue;
|
||||||
|
if (
|
||||||
|
key === 'friendsFeed' &&
|
||||||
|
!Array.isArray(tempSettingsData[key].data)
|
||||||
|
)
|
||||||
|
continue;
|
||||||
|
if (
|
||||||
|
key === 'myMenuPlugs' &&
|
||||||
|
!Array.isArray(tempSettingsData[key].data)
|
||||||
|
)
|
||||||
|
continue;
|
||||||
|
newObject[key] = tempSettingsData[key].data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const newObjectToBase64 = await objectToBase64(newObject);
|
||||||
|
const encryptedData = encryptDataGroup({
|
||||||
|
data64: newObjectToBase64,
|
||||||
|
publicKeys: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
const worker = new WebWorker();
|
||||||
|
try {
|
||||||
|
const resPublish = await publishData({
|
||||||
|
registeredName: encodeURIComponent(name),
|
||||||
|
file: encryptedData,
|
||||||
|
service: 'DOCUMENT_PRIVATE',
|
||||||
|
identifier: encodeURIComponent(identifer),
|
||||||
|
parentEpml: parentEpml,
|
||||||
|
uploadType: 'file',
|
||||||
|
selectedAddress: selectedAddress,
|
||||||
|
worker: worker,
|
||||||
|
isBase64: true,
|
||||||
|
filename: filename,
|
||||||
|
apiVersion: 2,
|
||||||
|
withFee: true,
|
||||||
|
feeAmount: feeAmount,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.resourceExists = true;
|
||||||
|
this.setValues(newObject, {
|
||||||
|
updated: Date.now(),
|
||||||
|
});
|
||||||
|
localStorage.setItem('temp-settings-data', JSON.stringify({}));
|
||||||
|
this.valuesToBeSavedOnQdn = {};
|
||||||
|
worker.terminate();
|
||||||
|
} catch (error) {
|
||||||
|
worker.terminate();
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log({ error });
|
||||||
|
} finally {
|
||||||
|
this.isSaving = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_updateTempSettingsData() {
|
||||||
|
this.valuesToBeSavedOnQdn = JSON.parse(
|
||||||
|
localStorage.getItem('temp-settings-data') || '{}'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
connectedCallback() {
|
||||||
|
super.connectedCallback();
|
||||||
|
window.addEventListener(
|
||||||
|
'temp-settings-data-event',
|
||||||
|
this._updateTempSettingsData
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
disconnectedCallback() {
|
||||||
|
window.removeEventListener(
|
||||||
|
'temp-settings-data-event',
|
||||||
|
this._updateTempSettingsData
|
||||||
|
);
|
||||||
|
super.disconnectedCallback();
|
||||||
|
}
|
||||||
|
render() {
|
||||||
|
return html`
|
||||||
|
${this.isSaving ||
|
||||||
|
(!this.error && this.resourceExists === undefined)
|
||||||
|
? html`
|
||||||
|
<paper-spinner-lite
|
||||||
|
active
|
||||||
|
style="display: block; margin: 0 auto;"
|
||||||
|
></paper-spinner-lite>
|
||||||
|
`
|
||||||
|
: html`
|
||||||
|
<mwc-icon
|
||||||
|
id="save-icon"
|
||||||
|
class=${Object.values(this.valuesToBeSavedOnQdn)
|
||||||
|
.length > 0 || this.resourceExists === false
|
||||||
|
? 'active'
|
||||||
|
: 'notActive'}
|
||||||
|
@click=${() => {
|
||||||
|
if (
|
||||||
|
Object.values(this.valuesToBeSavedOnQdn)
|
||||||
|
.length > 0 ||
|
||||||
|
this.resourceExists === false
|
||||||
|
) {
|
||||||
|
if (!this.fee) return;
|
||||||
|
// this.saveToQdn()
|
||||||
|
const target =
|
||||||
|
this.shadowRoot.getElementById(
|
||||||
|
'popover-notification'
|
||||||
|
);
|
||||||
|
const popover =
|
||||||
|
this.shadowRoot.querySelector(
|
||||||
|
'popover-component'
|
||||||
|
);
|
||||||
|
if (popover) {
|
||||||
|
popover.openPopover(target);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// this.isOpen = !this.isOpen
|
||||||
|
}}
|
||||||
|
style="user-select:none"
|
||||||
|
>save</mwc-icon
|
||||||
|
>
|
||||||
|
<vaadin-tooltip
|
||||||
|
for="save-icon"
|
||||||
|
position="bottom"
|
||||||
|
hover-delay=${300}
|
||||||
|
hide-delay=${1}
|
||||||
|
text=${this.error
|
||||||
|
? get('save.saving1')
|
||||||
|
: Object.values(this.valuesToBeSavedOnQdn)
|
||||||
|
.length > 0 ||
|
||||||
|
this.resourceExists === false
|
||||||
|
? get('save.saving3')
|
||||||
|
: get('save.saving2')}
|
||||||
|
>
|
||||||
|
</vaadin-tooltip>
|
||||||
|
<popover-component for="save-icon" message="">
|
||||||
|
<div style="margin-bottom:20px">
|
||||||
|
<p style="margin:10px 0px; font-size:16px">
|
||||||
|
${`${get('walletpage.wchange12')}: ${
|
||||||
|
this.fee ? this.fee.feeToShow : ''
|
||||||
|
}`}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
style="display:flex;justify-content:space-between;gap:10px"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="undo-button"
|
||||||
|
@click="${() => {
|
||||||
|
localStorage.setItem(
|
||||||
|
'temp-settings-data',
|
||||||
|
JSON.stringify({})
|
||||||
|
);
|
||||||
|
this.valuesToBeSavedOnQdn = {};
|
||||||
|
const popover =
|
||||||
|
this.shadowRoot.querySelector(
|
||||||
|
'popover-component'
|
||||||
|
);
|
||||||
|
if (popover) {
|
||||||
|
popover.closePopover();
|
||||||
|
}
|
||||||
|
this.getGeneralSettingsQdn();
|
||||||
|
}}"
|
||||||
|
>
|
||||||
|
${translate('save.saving4')}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="accept-button"
|
||||||
|
@click="${() => {
|
||||||
|
this.saveToQdn();
|
||||||
|
const popover =
|
||||||
|
this.shadowRoot.querySelector(
|
||||||
|
'popover-component'
|
||||||
|
);
|
||||||
|
if (popover) {
|
||||||
|
popover.closePopover();
|
||||||
|
}
|
||||||
|
}}"
|
||||||
|
>
|
||||||
|
${translate('browserpage.bchange28')}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</popover-component>
|
||||||
|
`}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
customElements.define('save-settings-qdn', SaveSettingsQdn);
|
@ -49,7 +49,7 @@ class WelcomePage extends LitElement {
|
|||||||
this.theme = localStorage.getItem('qortalTheme') ? localStorage.getItem('qortalTheme') : 'light'
|
this.theme = localStorage.getItem('qortalTheme') ? localStorage.getItem('qortalTheme') : 'light'
|
||||||
}
|
}
|
||||||
|
|
||||||
firstUpdate() {
|
firstUpdated() {
|
||||||
// ...
|
// ...
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -111,15 +111,30 @@ class NotificationBellGeneral extends connect(store)(LitElement) {
|
|||||||
>
|
>
|
||||||
${hasOngoing
|
${hasOngoing
|
||||||
? html`
|
? html`
|
||||||
<mwc-icon style="color: green;cursor:pointer"
|
<mwc-icon id="notification-general-icon" style="color: green;cursor:pointer;user-select:none"
|
||||||
>notifications</mwc-icon
|
>notifications</mwc-icon
|
||||||
>
|
>
|
||||||
|
<vaadin-tooltip
|
||||||
|
for="notification-general-icon"
|
||||||
|
position="bottom"
|
||||||
|
hover-delay=${400}
|
||||||
|
hide-delay=${1}
|
||||||
|
text=${get('notifications.notify4')}>
|
||||||
|
</vaadin-tooltip>
|
||||||
`
|
`
|
||||||
: html`
|
: html`
|
||||||
<mwc-icon
|
<mwc-icon
|
||||||
style="color: var(--black); cursor:pointer"
|
id="notification-general-icon"
|
||||||
|
style="color: var(--black); cursor:pointer;user-select:none"
|
||||||
>notifications</mwc-icon
|
>notifications</mwc-icon
|
||||||
>
|
>
|
||||||
|
<vaadin-tooltip
|
||||||
|
for="notification-general-icon"
|
||||||
|
position="bottom"
|
||||||
|
hover-delay=${400}
|
||||||
|
hide-delay=${1}
|
||||||
|
text=${get('notifications.notify4')}>
|
||||||
|
</vaadin-tooltip>
|
||||||
`}
|
`}
|
||||||
</div>
|
</div>
|
||||||
${hasOngoing
|
${hasOngoing
|
||||||
@ -147,6 +162,9 @@ class NotificationBellGeneral extends connect(store)(LitElement) {
|
|||||||
@blur=${this.handleBlur}
|
@blur=${this.handleBlur}
|
||||||
>
|
>
|
||||||
<div class="notifications-list">
|
<div class="notifications-list">
|
||||||
|
${this.notifications.length === 0 ? html`
|
||||||
|
<p style="font-size: 16px; width: 100%; text-align:center;margin-top:20px;">${translate('notifications.notify3')}</p>
|
||||||
|
` : ''}
|
||||||
${repeat(
|
${repeat(
|
||||||
this.notifications,
|
this.notifications,
|
||||||
(notification) => notification.reference.signature, // key function
|
(notification) => notification.reference.signature, // key function
|
||||||
@ -169,7 +187,6 @@ class NotificationBellGeneral extends connect(store)(LitElement) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_toggleNotifications() {
|
_toggleNotifications() {
|
||||||
if (this.notifications.length === 0) return;
|
|
||||||
this.showNotifications = !this.showNotifications;
|
this.showNotifications = !this.showNotifications;
|
||||||
if (this.showNotifications) {
|
if (this.showNotifications) {
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
@ -184,7 +201,6 @@ class NotificationBellGeneral extends connect(store)(LitElement) {
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
position: relative;
|
position: relative;
|
||||||
margin-right: 20px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.count {
|
.count {
|
||||||
|
@ -8,6 +8,8 @@ import '@polymer/iron-icons/iron-icons.js'
|
|||||||
import { store } from '../../store.js'
|
import { store } from '../../store.js'
|
||||||
import { setNewTab } from '../../redux/app/app-actions.js'
|
import { setNewTab } from '../../redux/app/app-actions.js'
|
||||||
import { routes } from '../../plugins/routes.js'
|
import { routes } from '../../plugins/routes.js'
|
||||||
|
import '@material/mwc-icon';
|
||||||
|
|
||||||
import config from '../../notifications/config.js'
|
import config from '../../notifications/config.js'
|
||||||
import '../../../../plugins/plugins/core/components/TimeAgo.js'
|
import '../../../../plugins/plugins/core/components/TimeAgo.js'
|
||||||
|
|
||||||
@ -138,11 +140,29 @@ class NotificationBell extends connect(store)(LitElement) {
|
|||||||
return html`
|
return html`
|
||||||
<div class="layout">
|
<div class="layout">
|
||||||
${this.notificationCount ? html`
|
${this.notificationCount ? html`
|
||||||
<paper-icon-button style="color: green;" icon="icons:mail" @click=${() => this._toggleNotifications()} title="Q-Mail"></paper-icon-button>
|
<mwc-icon @click=${() => this._toggleNotifications()} id="notification-mail-icon" style="color: green;cursor:pointer;user-select:none"
|
||||||
|
>mail</mwc-icon
|
||||||
|
>
|
||||||
|
<vaadin-tooltip
|
||||||
|
for="notification-mail-icon"
|
||||||
|
position="bottom"
|
||||||
|
hover-delay=${400}
|
||||||
|
hide-delay=${1}
|
||||||
|
text="Q-Mail">
|
||||||
|
</vaadin-tooltip>
|
||||||
|
|
||||||
` : html`
|
` : html`
|
||||||
<paper-icon-button icon="icons:mail" @click=${() => {
|
<mwc-icon @click=${() => this._openTabQmail()} id="notification-mail-icon" style="color: var(--black); cursor:pointer;user-select:none"
|
||||||
this._openTabQmail()
|
>mail</mwc-icon
|
||||||
}} title="Q-Mail"></paper-icon-button>
|
>
|
||||||
|
<vaadin-tooltip
|
||||||
|
for="notification-mail-icon"
|
||||||
|
position="bottom"
|
||||||
|
hover-delay=${400}
|
||||||
|
hide-delay=${1}
|
||||||
|
text="Q-Mail">
|
||||||
|
</vaadin-tooltip>
|
||||||
|
|
||||||
`}
|
`}
|
||||||
|
|
||||||
${this.notificationCount ? html`
|
${this.notificationCount ? html`
|
||||||
@ -218,8 +238,8 @@ class NotificationBell extends connect(store)(LitElement) {
|
|||||||
|
|
||||||
.count {
|
.count {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 2px;
|
top: -5px;
|
||||||
right: 0px;
|
right: -5px;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
background-color: red;
|
background-color: red;
|
||||||
color: white;
|
color: white;
|
||||||
@ -229,6 +249,7 @@ class NotificationBell extends connect(store)(LitElement) {
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.nocount {
|
.nocount {
|
||||||
|
@ -23,6 +23,8 @@ export class PopoverComponent extends LitElement {
|
|||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
color: var(--black)
|
color: var(--black)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
static properties = {
|
static properties = {
|
||||||
@ -40,7 +42,6 @@ export class PopoverComponent extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
attachToTarget(target) {
|
attachToTarget(target) {
|
||||||
console.log({target})
|
|
||||||
if (!this.popperInstance && target) {
|
if (!this.popperInstance && target) {
|
||||||
this.popperInstance = createPopper(target, this, {
|
this.popperInstance = createPopper(target, this, {
|
||||||
placement: 'bottom',
|
placement: 'bottom',
|
||||||
@ -66,7 +67,8 @@ export class PopoverComponent extends LitElement {
|
|||||||
render() {
|
render() {
|
||||||
return html`
|
return html`
|
||||||
<span class="close-icon" @click="${this.closePopover}"><mwc-icon style="color: var(--black)">close</mwc-icon></span>
|
<span class="close-icon" @click="${this.closePopover}"><mwc-icon style="color: var(--black)">close</mwc-icon></span>
|
||||||
<div><mwc-icon style="color: var(--black)">info</mwc-icon> ${this.message}</div>
|
<div><mwc-icon style="color: var(--black)">info</mwc-icon> ${this.message} <slot></slot>
|
||||||
|
</div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -114,7 +114,6 @@ class QortThemeToggle extends LitElement {
|
|||||||
} else {
|
} else {
|
||||||
this.theme = 'light';
|
this.theme = 'light';
|
||||||
}
|
}
|
||||||
|
|
||||||
this.dispatchEvent(
|
this.dispatchEvent(
|
||||||
new CustomEvent('qort-theme-change', {
|
new CustomEvent('qort-theme-change', {
|
||||||
bubbles: true,
|
bubbles: true,
|
||||||
|
@ -26,11 +26,9 @@ import '@vaadin/grid'
|
|||||||
import '@vaadin/text-field'
|
import '@vaadin/text-field'
|
||||||
import '../custom-elements/frag-file-input.js'
|
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) {
|
class ShowPlugin extends connect(store)(LitElement) {
|
||||||
static get properties() {
|
static get properties() {
|
||||||
@ -435,9 +433,21 @@ class ShowPlugin extends connect(store)(LitElement) {
|
|||||||
@click="${() => {
|
@click="${() => {
|
||||||
this.currentTab = index
|
this.currentTab = index
|
||||||
}}"
|
}}"
|
||||||
|
@mousedown="${(event) => {
|
||||||
|
if (event.button === 1) {
|
||||||
|
event.preventDefault();
|
||||||
|
this.removeTab(index, tab.id);
|
||||||
|
}
|
||||||
|
}}"
|
||||||
>
|
>
|
||||||
<div id="icon-${tab.id}" class="${this.currentTab === index ? "iconActive" : "iconInactive"}">
|
<div id="icon-${tab.id}" class="${this.currentTab === index ? "iconActive" : "iconInactive"}">
|
||||||
<mwc-icon>${icon}</mwc-icon>
|
${tab.myPlugObj && tab.myPlugObj.url === "myapp" ? html`
|
||||||
|
<tab-avatar appname=${title} appicon=${icon}></tab-avatar>
|
||||||
|
` : html`
|
||||||
|
<mwc-icon>${icon}</mwc-icon>
|
||||||
|
`}
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="tabCard">
|
<div class="tabCard">
|
||||||
${count ? html`
|
${count ? html`
|
||||||
@ -996,6 +1006,7 @@ class NavBar extends connect(store)(LitElement) {
|
|||||||
border-top-right-radius: 20px;
|
border-top-right-radius: 20px;
|
||||||
border-bottom-left-radius: 20px;
|
border-bottom-left-radius: 20px;
|
||||||
border-bottom-right-radius: 10px;
|
border-bottom-right-radius: 10px;
|
||||||
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.app-list .app-icon:hover .removeIcon {
|
.app-list .app-icon:hover .removeIcon {
|
||||||
@ -1009,14 +1020,14 @@ class NavBar extends connect(store)(LitElement) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.menuIconPos {
|
.menuIconPos {
|
||||||
position: relative;
|
|
||||||
right: -2px;
|
right: -2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.removeIconPos {
|
.removeIconPos {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: -36px;
|
top: -10px;
|
||||||
left: 0;
|
right: -10px;
|
||||||
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.menuIconPos:hover .removeIcon {
|
.menuIconPos:hover .removeIcon {
|
||||||
@ -1028,9 +1039,7 @@ class NavBar extends connect(store)(LitElement) {
|
|||||||
color: var(--black);
|
color: var(--black);
|
||||||
--mdc-icon-size: 28px;
|
--mdc-icon-size: 28px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
position: absolute;
|
position: relative;
|
||||||
top: 30px;
|
|
||||||
left: 123px;
|
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1192,6 +1201,7 @@ class NavBar extends connect(store)(LitElement) {
|
|||||||
this.myFollowedNamesList = []
|
this.myFollowedNamesList = []
|
||||||
this.searchContentString = ''
|
this.searchContentString = ''
|
||||||
this.searchNameResources = []
|
this.searchNameResources = []
|
||||||
|
this._updateMyMenuPlugins = this._updateMyMenuPlugins.bind(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
@ -1458,6 +1468,49 @@ class NavBar extends connect(store)(LitElement) {
|
|||||||
await this.getMyFollowedNamesList()
|
await this.getMyFollowedNamesList()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async _updateMyMenuPlugins(event) {
|
||||||
|
await new Promise((res)=> {
|
||||||
|
setTimeout(() => {
|
||||||
|
res()
|
||||||
|
}, 1000);
|
||||||
|
})
|
||||||
|
const detail = event.detail
|
||||||
|
this.myMenuPlugins = detail
|
||||||
|
const addressInfo = this.addressInfo
|
||||||
|
const isMinter = addressInfo?.error !== 124 && +addressInfo?.level > 0
|
||||||
|
const isSponsor = +addressInfo?.level >= 5
|
||||||
|
|
||||||
|
|
||||||
|
if (!isMinter) {
|
||||||
|
this.newMenuList = this.myMenuPlugins.filter((minter) => {
|
||||||
|
return minter.url !== 'minting'
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
this.newMenuList = this.myMenuPlugins.filter((minter) => {
|
||||||
|
return minter.url !== 'become-minter'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isSponsor) {
|
||||||
|
this.myMenuList = this.newMenuList.filter((sponsor) => {
|
||||||
|
return sponsor.url !== 'sponsorship-list'
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
this.myMenuList = this.newMenuList
|
||||||
|
}
|
||||||
|
|
||||||
|
this.requestUpdate()
|
||||||
|
}
|
||||||
|
|
||||||
|
connectedCallback() {
|
||||||
|
super.connectedCallback()
|
||||||
|
window.addEventListener('myMenuPlugs-event', this._updateMyMenuPlugins) }
|
||||||
|
|
||||||
|
disconnectedCallback() {
|
||||||
|
window.removeEventListener('myMenuPlugs-event', this._updateMyMenuPlugins)
|
||||||
|
super.disconnectedCallback()
|
||||||
|
}
|
||||||
|
|
||||||
openImportDialog() {
|
openImportDialog() {
|
||||||
this.shadowRoot.getElementById('importTabMenutDialog').show()
|
this.shadowRoot.getElementById('importTabMenutDialog').show()
|
||||||
}
|
}
|
||||||
@ -1468,7 +1521,9 @@ class NavBar extends connect(store)(LitElement) {
|
|||||||
localStorage.removeItem("myMenuPlugs")
|
localStorage.removeItem("myMenuPlugs")
|
||||||
myFile = file
|
myFile = file
|
||||||
const newTabMenu = JSON.parse((myFile) || "[]")
|
const newTabMenu = JSON.parse((myFile) || "[]")
|
||||||
|
const copyPayload = [...newTabMenu]
|
||||||
localStorage.setItem("myMenuPlugs", JSON.stringify(newTabMenu))
|
localStorage.setItem("myMenuPlugs", JSON.stringify(newTabMenu))
|
||||||
|
this.saveSettingToTemp(copyPayload)
|
||||||
this.shadowRoot.getElementById('importTabMenutDialog').close()
|
this.shadowRoot.getElementById('importTabMenutDialog').close()
|
||||||
this.myMenuPlugins = JSON.parse(localStorage.getItem("myMenuPlugs") || "[]")
|
this.myMenuPlugins = JSON.parse(localStorage.getItem("myMenuPlugs") || "[]")
|
||||||
this.firstUpdated()
|
this.firstUpdated()
|
||||||
@ -1951,8 +2006,10 @@ class NavBar extends connect(store)(LitElement) {
|
|||||||
|
|
||||||
if (myNameRes !== false) {
|
if (myNameRes !== false) {
|
||||||
oldMenuPlugs.push(newMenuPlugsItem)
|
oldMenuPlugs.push(newMenuPlugsItem)
|
||||||
|
const copyPayload = [...oldMenuPlugs]
|
||||||
|
|
||||||
localStorage.setItem("myMenuPlugs", JSON.stringify(oldMenuPlugs))
|
localStorage.setItem("myMenuPlugs", JSON.stringify(oldMenuPlugs))
|
||||||
|
this.saveSettingToTemp(copyPayload)
|
||||||
|
|
||||||
let myplugstring2 = get("walletpage.wchange52")
|
let myplugstring2 = get("walletpage.wchange52")
|
||||||
parentEpml.request('showSnackBar', `${myplugstring2}`)
|
parentEpml.request('showSnackBar', `${myplugstring2}`)
|
||||||
@ -2014,9 +2071,10 @@ class NavBar extends connect(store)(LitElement) {
|
|||||||
|
|
||||||
if (myNameRes !== false) {
|
if (myNameRes !== false) {
|
||||||
oldMenuPlugs.push(newMenuPlugsItem)
|
oldMenuPlugs.push(newMenuPlugsItem)
|
||||||
|
const copyPayload = [...oldMenuPlugs]
|
||||||
|
|
||||||
localStorage.setItem("myMenuPlugs", JSON.stringify(oldMenuPlugs))
|
localStorage.setItem("myMenuPlugs", JSON.stringify(oldMenuPlugs))
|
||||||
|
this.saveSettingToTemp(copyPayload)
|
||||||
let myplugstring2 = get("walletpage.wchange52")
|
let myplugstring2 = get("walletpage.wchange52")
|
||||||
parentEpml.request('showSnackBar', `${myplugstring2}`)
|
parentEpml.request('showSnackBar', `${myplugstring2}`)
|
||||||
|
|
||||||
@ -2084,9 +2142,10 @@ class NavBar extends connect(store)(LitElement) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
oldMenuPlugs.push(newMenuPlugsItem)
|
oldMenuPlugs.push(newMenuPlugsItem)
|
||||||
|
const copyPayload = [...oldMenuPlugs]
|
||||||
|
|
||||||
localStorage.setItem("myMenuPlugs", JSON.stringify(oldMenuPlugs))
|
localStorage.setItem("myMenuPlugs", JSON.stringify(oldMenuPlugs))
|
||||||
|
this.saveSettingToTemp(copyPayload)
|
||||||
let myplugstring2 = get("walletpage.wchange52")
|
let myplugstring2 = get("walletpage.wchange52")
|
||||||
parentEpml.request('showSnackBar', `${myplugstring2}`)
|
parentEpml.request('showSnackBar', `${myplugstring2}`)
|
||||||
|
|
||||||
@ -2149,11 +2208,20 @@ class NavBar extends connect(store)(LitElement) {
|
|||||||
|
|
||||||
renderRemoveIcon(appurl, appicon, appname, appid, appplugin) {
|
renderRemoveIcon(appurl, appicon, appname, appid, appplugin) {
|
||||||
return html`
|
return html`
|
||||||
<div class="removeIconPos" title="${translate('tabmenu.tm22')}" @click="${() => this.openRemoveApp(appname, appid, appurl)}">
|
|
||||||
|
<div class="menuIconPos" @click="${() => this.changePage(appplugin)}">
|
||||||
|
<div class="removeIconPos" title="${translate('tabmenu.tm22')}" @click="${(event) => {
|
||||||
|
event.stopPropagation();
|
||||||
|
this.openRemoveApp(appname, appid, appurl)
|
||||||
|
} }">
|
||||||
<mwc-icon class="removeIcon">backspace</mwc-icon>
|
<mwc-icon class="removeIcon">backspace</mwc-icon>
|
||||||
</div>
|
</div>
|
||||||
<div class="menuIconPos" @click="${() => this.changePage(appplugin)}">
|
${appurl === 'myapp' ? html`
|
||||||
|
<app-avatar appicon=${appicon} appname=${appname}></app-avatar>
|
||||||
|
` : html`
|
||||||
<mwc-icon class="menuIcon">${appicon}</mwc-icon>
|
<mwc-icon class="menuIcon">${appicon}</mwc-icon>
|
||||||
|
|
||||||
|
`}
|
||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
}
|
}
|
||||||
@ -2210,14 +2278,36 @@ class NavBar extends connect(store)(LitElement) {
|
|||||||
const pluginToRemove = this.pluginNumberToDelete
|
const pluginToRemove = this.pluginNumberToDelete
|
||||||
this.newMenuFilter = []
|
this.newMenuFilter = []
|
||||||
this.newMenuFilter = this.myMenuList.filter((item) => item.pluginNumber !== pluginToRemove)
|
this.newMenuFilter = this.myMenuList.filter((item) => item.pluginNumber !== pluginToRemove)
|
||||||
|
const copyPayload = [...this.newMenuFilter]
|
||||||
|
|
||||||
const myNewObj = JSON.stringify(this.newMenuFilter)
|
const myNewObj = JSON.stringify(this.newMenuFilter)
|
||||||
localStorage.removeItem("myMenuPlugs")
|
localStorage.removeItem("myMenuPlugs")
|
||||||
localStorage.setItem("myMenuPlugs", myNewObj)
|
localStorage.setItem("myMenuPlugs", myNewObj)
|
||||||
|
this.saveSettingToTemp(copyPayload)
|
||||||
this.myMenuPlugins = JSON.parse(localStorage.getItem("myMenuPlugs") || "[]")
|
this.myMenuPlugins = JSON.parse(localStorage.getItem("myMenuPlugs") || "[]")
|
||||||
this.firstUpdated()
|
this.firstUpdated()
|
||||||
this.closeRemoveApp()
|
this.closeRemoveApp()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
saveSettingToTemp(data){
|
||||||
|
const tempSettingsData= JSON.parse(localStorage.getItem('temp-settings-data') || "{}")
|
||||||
|
const newTemp = {
|
||||||
|
...tempSettingsData,
|
||||||
|
myMenuPlugs: {
|
||||||
|
data: data,
|
||||||
|
timestamp: Date.now()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
localStorage.setItem('temp-settings-data', JSON.stringify(newTemp));
|
||||||
|
this.dispatchEvent(
|
||||||
|
new CustomEvent('temp-settings-data-event', {
|
||||||
|
bubbles: true,
|
||||||
|
composed: true
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
closeRemoveApp() {
|
closeRemoveApp() {
|
||||||
this.shadowRoot.querySelector('#removePlugin').close()
|
this.shadowRoot.querySelector('#removePlugin').close()
|
||||||
this.pluginNameToDelete = ''
|
this.pluginNameToDelete = ''
|
||||||
@ -2348,3 +2438,148 @@ class NavBar extends connect(store)(LitElement) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
customElements.define('nav-bar', NavBar)
|
customElements.define('nav-bar', NavBar)
|
||||||
|
|
||||||
|
|
||||||
|
class AppAvatar extends LitElement {
|
||||||
|
static get properties() {
|
||||||
|
return {
|
||||||
|
hasAvatar: { type: Boolean },
|
||||||
|
isImageLoaded: {type: Boolean},
|
||||||
|
appicon: {type: String},
|
||||||
|
appname: {type: String}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super()
|
||||||
|
|
||||||
|
this.hasAvatar = false
|
||||||
|
this.isImageLoaded = false
|
||||||
|
this.imageFetches = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
static get styles() {
|
||||||
|
|
||||||
|
return css`
|
||||||
|
:host {
|
||||||
|
position: absolute;
|
||||||
|
left: 50%;
|
||||||
|
top: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.menuIcon {
|
||||||
|
color: var(--app-icon);
|
||||||
|
--mdc-icon-size: 64px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
`
|
||||||
|
}
|
||||||
|
|
||||||
|
createImage(imageUrl) {
|
||||||
|
const imageHTMLRes = new Image();
|
||||||
|
imageHTMLRes.src = imageUrl;
|
||||||
|
imageHTMLRes.style= "border-radius:10px; font-size:14px; object-fit: fill;height:60px;width:60px";
|
||||||
|
|
||||||
|
imageHTMLRes.onload = () => {
|
||||||
|
this.isImageLoaded = true;
|
||||||
|
}
|
||||||
|
imageHTMLRes.onerror = () => {
|
||||||
|
if (this.imageFetches < 1) {
|
||||||
|
setTimeout(() => {
|
||||||
|
this.imageFetches = this.imageFetches + 1;
|
||||||
|
imageHTMLRes.src = imageUrl;
|
||||||
|
}, 5000);
|
||||||
|
} else {
|
||||||
|
this.isImageLoaded = false
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return imageHTMLRes;
|
||||||
|
}
|
||||||
|
|
||||||
|
render(){
|
||||||
|
let avatarImg = ""
|
||||||
|
if (this.appname) {
|
||||||
|
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.appname}/qortal_avatar?async=true&apiKey=${myNode.apiKey}`;
|
||||||
|
avatarImg = this.createImage(avatarUrl)
|
||||||
|
}
|
||||||
|
|
||||||
|
return html`
|
||||||
|
${this.isImageLoaded ? html`
|
||||||
|
${avatarImg}
|
||||||
|
` : html`
|
||||||
|
<mwc-icon class="menuIcon">${this.appicon}</mwc-icon>
|
||||||
|
`}
|
||||||
|
`
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
customElements.define('app-avatar', AppAvatar)
|
||||||
|
|
||||||
|
class TabAvatar extends LitElement {
|
||||||
|
static get properties() {
|
||||||
|
return {
|
||||||
|
hasAvatar: { type: Boolean },
|
||||||
|
isImageLoaded: {type: Boolean},
|
||||||
|
appicon: {type: String},
|
||||||
|
appname: {type: String}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super()
|
||||||
|
|
||||||
|
this.hasAvatar = false
|
||||||
|
this.isImageLoaded = false
|
||||||
|
this.imageFetches = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
createImage(imageUrl) {
|
||||||
|
const imageHTMLRes = new Image();
|
||||||
|
imageHTMLRes.src = imageUrl;
|
||||||
|
imageHTMLRes.style= "border-radius:4px; font-size:14px; object-fit: fill;height:24px;width:24px";
|
||||||
|
|
||||||
|
imageHTMLRes.onload = () => {
|
||||||
|
this.isImageLoaded = true;
|
||||||
|
}
|
||||||
|
imageHTMLRes.onerror = () => {
|
||||||
|
if (this.imageFetches < 1) {
|
||||||
|
setTimeout(() => {
|
||||||
|
this.imageFetches = this.imageFetches + 1;
|
||||||
|
imageHTMLRes.src = imageUrl;
|
||||||
|
}, 5000);
|
||||||
|
} else {
|
||||||
|
this.isImageLoaded = false
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return imageHTMLRes;
|
||||||
|
}
|
||||||
|
|
||||||
|
render(){
|
||||||
|
let avatarImg = ""
|
||||||
|
if (this.appname) {
|
||||||
|
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.appname}/qortal_avatar?async=true&apiKey=${myNode.apiKey}`;
|
||||||
|
avatarImg = this.createImage(avatarUrl)
|
||||||
|
}
|
||||||
|
|
||||||
|
return html`
|
||||||
|
${this.isImageLoaded ? html`
|
||||||
|
${avatarImg}
|
||||||
|
` : html`
|
||||||
|
<mwc-icon>${this.appicon}</mwc-icon>
|
||||||
|
`}
|
||||||
|
`
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
customElements.define('tab-avatar', TabAvatar)
|
||||||
|
@ -68,7 +68,9 @@ export default (state = INITIAL_STATE, action) => {
|
|||||||
loggedIn: false,
|
loggedIn: false,
|
||||||
loggingIn: false,
|
loggingIn: false,
|
||||||
wallet: INITIAL_STATE.wallet,
|
wallet: INITIAL_STATE.wallet,
|
||||||
selectedAddress: INITIAL_STATE.selectedAddress
|
selectedAddress: INITIAL_STATE.selectedAddress,
|
||||||
|
accountInfo: INITIAL_STATE.accountInfo
|
||||||
|
|
||||||
}
|
}
|
||||||
case ADD_PLUGIN:
|
case ADD_PLUGIN:
|
||||||
return {
|
return {
|
||||||
|
@ -218,7 +218,6 @@ class ChatGroupsManagement extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
nameRenderer(person){
|
nameRenderer(person){
|
||||||
console.log({person})
|
|
||||||
return html`
|
return html`
|
||||||
<vaadin-horizontal-layout style="align-items: center;display:flex" theme="spacing">
|
<vaadin-horizontal-layout style="align-items: center;display:flex" theme="spacing">
|
||||||
<vaadin-avatar style="margin-right:5px" img="${person.pictureUrl}" .name="${person.displayName}"></vaadin-avatar>
|
<vaadin-avatar style="margin-right:5px" img="${person.pictureUrl}" .name="${person.displayName}"></vaadin-avatar>
|
||||||
@ -229,9 +228,7 @@ class ChatGroupsManagement extends LitElement {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
return html`
|
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 -->
|
<!-- Leave Group Dialog -->
|
||||||
<wrapper-modal
|
<wrapper-modal
|
||||||
.removeImage=${() => {
|
.removeImage=${() => {
|
||||||
|
@ -1199,6 +1199,11 @@ class ChatPage extends LitElement {
|
|||||||
}, 2000)
|
}, 2000)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const chatScrollerElement = this.shadowRoot.querySelector('chat-scroller');
|
||||||
|
if (chatScrollerElement && chatScrollerElement.disableFetching) {
|
||||||
|
chatScrollerElement.disableFetching = false
|
||||||
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
this.isLoadingGoToRepliedMessage = {
|
this.isLoadingGoToRepliedMessage = {
|
||||||
@ -1668,12 +1673,20 @@ class ChatPage extends LitElement {
|
|||||||
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
const lastMsg = list.at(-1)
|
||||||
this.messagesRendered = {
|
if(lastMsg){
|
||||||
messages: list,
|
const count = await parentEpml.request('apiCall', {
|
||||||
type: 'inBetween',
|
type: 'api',
|
||||||
message: messageToGoTo
|
url: `/chat/messages/count?after=${lastMsg.timestamp}&involving=${window.parent.reduxStore.getState().app.selectedAddress.address}&involving=${this._chatId}&limit=20&reverse=false`
|
||||||
}
|
})
|
||||||
|
this.messagesRendered = {
|
||||||
|
messages: list,
|
||||||
|
type: 'inBetween',
|
||||||
|
message: messageToGoTo,
|
||||||
|
count
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
this.isLoadingOldMessages = false
|
this.isLoadingOldMessages = false
|
||||||
|
|
||||||
@ -1727,11 +1740,20 @@ class ChatPage extends LitElement {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
this.messagesRendered = {
|
const lastMsg = list.at(-1)
|
||||||
messages: list,
|
if(lastMsg){
|
||||||
type: 'inBetween',
|
const count = await parentEpml.request('apiCall', {
|
||||||
signature: messageToGoTo.signature
|
type: 'api',
|
||||||
}
|
url: `/chat/messages/count?after=${lastMsg.timestamp}&txGroupId=${Number(this._chatId)}&limit=20&reverse=false`
|
||||||
|
})
|
||||||
|
this.messagesRendered = {
|
||||||
|
messages: list,
|
||||||
|
type: 'inBetween',
|
||||||
|
signature: messageToGoTo.signature,
|
||||||
|
count
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
this.isLoadingOldMessages = false
|
this.isLoadingOldMessages = false
|
||||||
|
@ -345,7 +345,7 @@ class ChatScroller extends LitElement {
|
|||||||
this.requestUpdate();
|
this.requestUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
async newListMessages(newMessages) {
|
async newListMessages(newMessages, count) {
|
||||||
let data = [];
|
let data = [];
|
||||||
const copy = [...newMessages];
|
const copy = [...newMessages];
|
||||||
copy.forEach((newMessage) => {
|
copy.forEach((newMessage) => {
|
||||||
@ -368,6 +368,9 @@ class ChatScroller extends LitElement {
|
|||||||
// url: `/chat/messages?involving=${window.parent.reduxStore.getState().app.selectedAddress.address}&involving=${this._chatId}&limit=${chatLimit}&reverse=true&before=${scrollElement.messageObj.timestamp}&haschatreference=false&encoding=BASE64`
|
// url: `/chat/messages?involving=${window.parent.reduxStore.getState().app.selectedAddress.address}&involving=${this._chatId}&limit=${chatLimit}&reverse=true&before=${scrollElement.messageObj.timestamp}&haschatreference=false&encoding=BASE64`
|
||||||
// })
|
// })
|
||||||
this.messagesToRender = data;
|
this.messagesToRender = data;
|
||||||
|
if (count > 0) {
|
||||||
|
this.disableAddingNewMessages = true;
|
||||||
|
}
|
||||||
this.clearLoaders();
|
this.clearLoaders();
|
||||||
this.requestUpdate();
|
this.requestUpdate();
|
||||||
await this.updateComplete;
|
await this.updateComplete;
|
||||||
@ -645,7 +648,7 @@ class ChatScroller extends LitElement {
|
|||||||
else if (this.messages.type === 'inBetween')
|
else if (this.messages.type === 'inBetween')
|
||||||
this.newListMessages(
|
this.newListMessages(
|
||||||
this.messages.messages,
|
this.messages.messages,
|
||||||
this.messages.signature
|
this.messages.count
|
||||||
);
|
);
|
||||||
else if (this.messages.type === 'update')
|
else if (this.messages.type === 'update')
|
||||||
this.replaceMessagesWithUpdateByArray(this.messages.messages);
|
this.replaceMessagesWithUpdateByArray(this.messages.messages);
|
||||||
|
@ -1,12 +1,10 @@
|
|||||||
import { LitElement, html } from 'lit'
|
import { LitElement, html } from 'lit'
|
||||||
import { render } from 'lit/html.js'
|
import { render } from 'lit/html.js'
|
||||||
import { Epml } from '../../../epml.js'
|
|
||||||
import { chatSearchResultsStyles } from './ChatSearchResults-css.js'
|
import { chatSearchResultsStyles } from './ChatSearchResults-css.js'
|
||||||
import { use, get, translate, translateUnsafeHTML, registerTranslateConfig } from 'lit-translate'
|
import { use, get, translate, translateUnsafeHTML, registerTranslateConfig } from 'lit-translate'
|
||||||
import '@vaadin/icon'
|
import '@vaadin/icon'
|
||||||
import '@vaadin/icons'
|
import '@vaadin/icons'
|
||||||
|
|
||||||
const parentEpml = new Epml({ type: 'WINDOW', source: window.parent })
|
|
||||||
|
|
||||||
export class ChatSearchResults extends LitElement {
|
export class ChatSearchResults extends LitElement {
|
||||||
static get properties() {
|
static get properties() {
|
||||||
@ -19,7 +17,10 @@ export class ChatSearchResults extends LitElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static styles = [chatSearchResultsStyles]
|
static get styles() {
|
||||||
|
return [chatSearchResultsStyles];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return html`
|
return html`
|
||||||
|
@ -8,7 +8,6 @@ font-size: 28px;
|
|||||||
color: var(--chat-bubble-msg-color);
|
color: var(--chat-bubble-msg-color);
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
padding: 10px 0;
|
padding: 10px 0;
|
||||||
user-select: none;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.avatar-container {
|
.avatar-container {
|
||||||
|
@ -1,14 +1,11 @@
|
|||||||
import { LitElement, html } from 'lit'
|
import { LitElement, html } from 'lit'
|
||||||
import { render } from 'lit/html.js'
|
import { translate } from 'lit-translate'
|
||||||
import { use, get, translate, translateUnsafeHTML, registerTranslateConfig } from 'lit-translate'
|
|
||||||
import { userInfoStyles } from './UserInfo-css.js'
|
import { userInfoStyles } from './UserInfo-css.js'
|
||||||
import { Epml } from '../../../../epml'
|
|
||||||
import { cropAddress } from '../../../utils/cropAddress.js'
|
import { cropAddress } from '../../../utils/cropAddress.js'
|
||||||
|
|
||||||
import '@polymer/paper-progress/paper-progress.js'
|
import '@polymer/paper-progress/paper-progress.js'
|
||||||
import '@vaadin/button'
|
import '@vaadin/button'
|
||||||
|
|
||||||
const parentEpml = new Epml({ type: 'WINDOW', source: window.parent })
|
|
||||||
|
|
||||||
export class UserInfo extends LitElement {
|
export class UserInfo extends LitElement {
|
||||||
static get properties() {
|
static get properties() {
|
||||||
@ -29,7 +26,10 @@ export class UserInfo extends LitElement {
|
|||||||
this.imageFetches = 0
|
this.imageFetches = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
static styles = [userInfoStyles]
|
static get styles() {
|
||||||
|
return [userInfoStyles];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
createImage(imageUrl) {
|
createImage(imageUrl) {
|
||||||
const imageHTMLRes = new Image()
|
const imageHTMLRes = new Image()
|
||||||
|
@ -89,6 +89,47 @@ export function base64ToUint8Array(base64) {
|
|||||||
return bytes
|
return bytes
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function uint8ArrayToObject(uint8Array) {
|
||||||
|
// Decode the byte array using TextDecoder
|
||||||
|
const decoder = new TextDecoder()
|
||||||
|
const jsonString = decoder.decode(uint8Array)
|
||||||
|
|
||||||
|
// Convert the JSON string back into an object
|
||||||
|
const obj = JSON.parse(jsonString)
|
||||||
|
|
||||||
|
return obj
|
||||||
|
}
|
||||||
|
|
||||||
|
export function objectToBase64(obj) {
|
||||||
|
// Step 1: Convert the object to a JSON string
|
||||||
|
const jsonString = JSON.stringify(obj);
|
||||||
|
|
||||||
|
// Step 2: Create a Blob from the JSON string
|
||||||
|
const blob = new Blob([jsonString], { type: 'application/json' });
|
||||||
|
|
||||||
|
// Step 3: Create a FileReader to read the Blob as a base64-encoded string
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onloadend = () => {
|
||||||
|
if (typeof reader.result === 'string') {
|
||||||
|
// Remove 'data:application/json;base64,' prefix
|
||||||
|
const base64 = reader.result.replace(
|
||||||
|
'data:application/json;base64,',
|
||||||
|
''
|
||||||
|
);
|
||||||
|
resolve(base64);
|
||||||
|
} else {
|
||||||
|
reject(new Error('Failed to read the Blob as a base64-encoded string'));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
reader.onerror = () => {
|
||||||
|
reject(reader.error);
|
||||||
|
};
|
||||||
|
reader.readAsDataURL(blob);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export const encryptData = ({ data64, recipientPublicKey }) => {
|
export const encryptData = ({ data64, recipientPublicKey }) => {
|
||||||
|
|
||||||
|
@ -516,6 +516,27 @@ class Chat extends LitElement {
|
|||||||
chatHeads = JSON.parse(chatHeads)
|
chatHeads = JSON.parse(chatHeads)
|
||||||
this.getChatHeadFromState(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
|
||||||
|
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
|
||||||
|
})
|
||||||
|
window.parent.reduxStore.dispatch(
|
||||||
|
window.parent.reduxAction.setSideEffectAction(null))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
})
|
||||||
parentEpml.request('apiCall', {
|
parentEpml.request('apiCall', {
|
||||||
url: `/addresses/balance/${window.parent.reduxStore.getState().app.selectedAddress.address}`
|
url: `/addresses/balance/${window.parent.reduxStore.getState().app.selectedAddress.address}`
|
||||||
}).then(res => {
|
}).then(res => {
|
||||||
|
@ -281,10 +281,7 @@ class WebBrowser extends LitElement {
|
|||||||
else {
|
else {
|
||||||
identifier = null;
|
identifier = null;
|
||||||
}
|
}
|
||||||
}
|
}extractComponents
|
||||||
|
|
||||||
const path = parts.join("/");
|
|
||||||
|
|
||||||
const components = {};
|
const components = {};
|
||||||
components["service"] = service;
|
components["service"] = service;
|
||||||
components["name"] = name;
|
components["name"] = name;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user