From a2f388a7b826710438159817bf776010dd406e15 Mon Sep 17 00:00:00 2001 From: PhilReact Date: Fri, 6 Oct 2023 01:36:01 -0500 Subject: [PATCH 01/21] added list for friends --- core/language/us.json | 13 + core/src/components/app-view.js | 5 + .../friends-view/ChatSideNavHeads.js | 222 +++++++++++++ .../friends-view/add-friends-modal.js | 253 +++++++++++++++ .../friends-view/friend-item-actions.js | 224 +++++++++++++ .../friends-view/friends-side-panel-parent.js | 61 ++++ .../friends-view/friends-side-panel.js | 61 ++++ .../friends-view/friends-view-css.js | 178 ++++++++++ .../components/friends-view/friends-view.js | 303 ++++++++++++++++++ .../notification-bell-general.js | 4 +- core/src/components/show-plugin.js | 6 +- package-lock.json | 43 --- .../core/components/ChatSearchResults.js | 7 +- .../core/messaging/q-chat/q-chat.src.js | 22 ++ 14 files changed, 1350 insertions(+), 52 deletions(-) create mode 100644 core/src/components/friends-view/ChatSideNavHeads.js create mode 100644 core/src/components/friends-view/add-friends-modal.js create mode 100644 core/src/components/friends-view/friend-item-actions.js create mode 100644 core/src/components/friends-view/friends-side-panel-parent.js create mode 100644 core/src/components/friends-view/friends-side-panel.js create mode 100644 core/src/components/friends-view/friends-view-css.js create mode 100644 core/src/components/friends-view/friends-view.js diff --git a/core/language/us.json b/core/language/us.json index 76458f9f..257c770b 100644 --- a/core/language/us.json +++ b/core/language/us.json @@ -1176,5 +1176,18 @@ "notify1": "Confirming transaction", "notify2": "Transaction confirmed", "explanation": "Your transaction is getting confirmed. To track its progress, click on the bell icon." + }, + "friends": { + "friend1": "Add name", + "friend2": "Add friend", + "friend3": "Adding a friend allows you to connect easily with that person. Be sure to also follow that user to support the hosting of their published resources.", + "friend4": "Notes", + "friend5": "Follow name", + "friend6": "Alias", + "friend7": "Add an alias to better remember your friend (Optional)", + "friend8": "Send a Q-Chat message", + "friend9": "Send a Q-Mail", + "friend10": "Edit friend", + "friend11": "Following" } } \ No newline at end of file diff --git a/core/src/components/app-view.js b/core/src/components/app-view.js index ca5a6732..814a18b9 100644 --- a/core/src/components/app-view.js +++ b/core/src/components/app-view.js @@ -43,6 +43,7 @@ import '../functional-components/side-menu-item.js' import './start-minting.js' import './notification-view/notification-bell.js' import './notification-view/notification-bell-general.js' +import './friends-view/friends-side-panel-parent.js' const parentEpml = new Epml({ type: 'WINDOW', source: window.parent }) @@ -559,8 +560,10 @@ class AppView extends connect(store)(LitElement) {
+ +
@@ -647,6 +650,8 @@ class AppView extends connect(store)(LitElement) {
+
+ ` } diff --git a/core/src/components/friends-view/ChatSideNavHeads.js b/core/src/components/friends-view/ChatSideNavHeads.js new file mode 100644 index 00000000..f74536d0 --- /dev/null +++ b/core/src/components/friends-view/ChatSideNavHeads.js @@ -0,0 +1,222 @@ +import { LitElement, html, css } from 'lit' +import { render } from 'lit/html.js' +import { use, get, translate, translateUnsafeHTML, registerTranslateConfig } from 'lit-translate' +import '@material/mwc-icon' +import '@vaadin/tooltip'; + +import './friend-item-actions' + +class ChatSideNavHeads extends LitElement { + static get properties() { + return { + selectedAddress: { type: Object }, + config: { type: Object }, + chatInfo: { type: Object }, + iconName: { type: String }, + activeChatHeadUrl: { type: String }, + isImageLoaded: { type: Boolean }, + setActiveChatHeadUrl: {attribute: false}, + openEditFriend: {attribute: false} + } + } + + static get styles() { + return css` + :host { + width: 100%; + } + ul { + list-style-type: none; + } + li { + padding: 10px 2px 10px 5px; + cursor: pointer; + width: 100%; + display: flex; + box-sizing: border-box; + font-size: 14px; + transition: 0.2s background-color; + } + + li:hover { + background-color: var(--lightChatHeadHover); + } + + .active { + background: var(--menuactive); + border-left: 4px solid #3498db; + } + + .img-icon { + font-size:40px; + color: var(--chat-group); + } + + .status { + color: #92959e; + } + + .clearfix { + display: flex; + align-items: center; + } + + .clearfix:after { + visibility: hidden; + display: block; + font-size: 0; + content: " "; + clear: both; + height: 0; + } + ` + } + + constructor() { + super() + this.selectedAddress = {} + this.config = { + user: { + node: { + + } + } + } + this.chatInfo = {} + this.iconName = '' + this.activeChatHeadUrl = '' + this.isImageLoaded = false + this.imageFetches = 0 + } + + createImage(imageUrl) { + const imageHTMLRes = new Image(); + imageHTMLRes.src = imageUrl; + imageHTMLRes.style= "width:30px; height:30px; float: left; border-radius:50%; font-size:14px"; + imageHTMLRes.onclick= () => { + this.openDialogImage = true; + } + imageHTMLRes.onload = () => { + this.isImageLoaded = true; + } + imageHTMLRes.onerror = () => { + if (this.imageFetches < 4) { + setTimeout(() => { + this.imageFetches = this.imageFetches + 1; + imageHTMLRes.src = imageUrl; + }, 500); + } else { + this.isImageLoaded = false + } + }; + return imageHTMLRes; + } + + render() { + let avatarImg = "" + if (this.chatInfo.name) { + const myNode = window.parent.reduxStore.getState().app.nodeConfig.knownNodes[window.parent.reduxStore.getState().app.nodeConfig.node]; + const nodeUrl = myNode.protocol + '://' + myNode.domain + ':' + myNode.port; + const avatarUrl = `${nodeUrl}/arbitrary/THUMBNAIL/${this.chatInfo.name}/qortal_avatar?async=true&apiKey=${myNode.apiKey}`; + avatarImg = this.createImage(avatarUrl) + } + + return html` +
  • { + 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}`}> +
    + ${this.isImageLoaded ? html`${avatarImg}` : html``} + ${!this.isImageLoaded && !this.chatInfo.name && !this.chatInfo.groupName + ? html`account_circle` + : html``} + ${!this.isImageLoaded && this.chatInfo.name + ? html`
    + ${this.chatInfo.name.charAt(0)} +
    ` + : ""} + ${!this.isImageLoaded && this.chatInfo.groupName + ? html`
    + ${this.chatInfo.groupName.charAt(0)} +
    ` + : ""} +
    +
    + + ${this.chatInfo.groupName + ? this.chatInfo.groupName + : this.chatInfo.name !== undefined + ? this.chatInfo.name + : this.chatInfo.address.substr(0, 15)} + +
    +
    + +
    +
    + ${this.chatInfo.willFollow ? html` + connect_without_contact + + + ` : ''} +
    +
  • + { + this.openEditFriend(this.chatInfo) + }} + name=${this.chatInfo.name} + > + ` + } + + + + 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) diff --git a/core/src/components/friends-view/add-friends-modal.js b/core/src/components/friends-view/add-friends-modal.js new file mode 100644 index 00000000..eabe0adb --- /dev/null +++ b/core/src/components/friends-view/add-friends-modal.js @@ -0,0 +1,253 @@ +import { LitElement, html, css } from 'lit'; +import { render } from 'lit/html.js'; +import { + use, + get, + translate, + translateUnsafeHTML, + registerTranslateConfig, +} from 'lit-translate'; +import '@material/mwc-button'; +import '@material/mwc-dialog'; +import '@material/mwc-checkbox'; + +class AddFriendsModal extends LitElement { + static get properties() { + return { + isOpen: { type: Boolean }, + setIsOpen: { attribute: false }, + isLoading: { type: Boolean }, + userSelected: { type: Object }, + alias: { type: String }, + willFollow: { type: Boolean }, + notes: { type: String }, + onSubmit: {attribute: false}, + editContent: {type: Object}, + onClose: {attribute: false} + }; + } + + constructor() { + super(); + this.isOpen = false; + this.isLoading = false; + this.alias = ''; + this.willFollow = true; + this.notes = ''; + } + + static get styles() { + return css` + * { + --mdc-theme-primary: rgb(3, 169, 244); + --mdc-theme-secondary: var(--mdc-theme-primary); + --mdc-theme-surface: var(--white); + --mdc-dialog-content-ink-color: var(--black); + --mdc-dialog-min-width: 400px; + --mdc-dialog-max-width: 1024px; + } + .input { + width: 90%; + outline: 0; + border-width: 0 0 2px; + border-color: var(--mdc-theme-primary); + background-color: transparent; + padding: 10px; + font-family: Roboto, sans-serif; + font-size: 15px; + color: var(--chat-bubble-msg-color); + box-sizing: border-box; + } + + .input::selection { + background-color: var(--mdc-theme-primary); + color: white; + } + + .input::placeholder { + opacity: 0.6; + color: var(--black); + } + + .close-button { + display: block; + --mdc-theme-primary: red; + } + .checkbox-row { + position: relative; + display: flex; + align-items: center; + align-content: center; + font-family: Montserrat, sans-serif; + font-weight: 600; + color: var(--black); + } + .modal-overlay { + display: block; + position: fixed; + top: 0; + left: 0; + width: 100vw; + height: 100vh; + background-color: rgba(0, 0, 0, 0.5); /* Semi-transparent backdrop */ + z-index: 1000; + } + + .modal-content { + position: fixed; + top: 50vh; + left: 50vw; + transform: translate(-50%, -50%); + background-color: var(--mdc-theme-surface); + width: 80vw; + max-width: 600px; + padding: 20px; + box-shadow: rgba(0, 0, 0, 0.1) 0px 4px 6px; + z-index: 1001; + border-radius: 5px; + max-height: 80vh; + overflow: auto; + } + .modal-overlay.hidden { + display: none; + } + `; + } + + firstUpdated() {} + + clearFields(){ + this.alias = ''; + this.willFollow = true; + this.notes = ''; + } + + addFriend(){ + + this.onSubmit({ + name: this.userSelected.name, + alias: this.alias, + notes: this.notes, + willFollow: this.willFollow + }) + this.clearFields() + this.onClose() + } + + + async updated(changedProperties) { + if (changedProperties && changedProperties.has('editContent') && this.editContent) { + console.log('this.editedContent', this.editContent) + this.userSelected = { + name: this.editContent.name ?? '', + } + this.notes = this.editContent.notes ?? '' + this.willFollow = this.editContent.willFollow ?? true + this.alias = this.editContent.alias ?? '' + } + + + } + + render() { + console.log('hello2', this.editContent) + return html` + + `; + } +} + +customElements.define('add-friends-modal', AddFriendsModal); diff --git a/core/src/components/friends-view/friend-item-actions.js b/core/src/components/friends-view/friend-item-actions.js new file mode 100644 index 00000000..6b0f55af --- /dev/null +++ b/core/src/components/friends-view/friend-item-actions.js @@ -0,0 +1,224 @@ +// popover-component.js +import { LitElement, html, css } from 'lit'; +import { createPopper } from '@popperjs/core'; +import '@material/mwc-icon'; +import { use, get, translate } from 'lit-translate'; +import { store } from '../../store'; +import { connect } from 'pwa-helpers'; +import { setNewTab, setSideEffectAction } from '../../redux/app/app-actions'; +import ShortUniqueId from 'short-unique-id'; + +export class FriendItemActions extends connect(store)(LitElement) { + static styles = css` + :host { + display: none; + position: absolute; + background-color: var(--white); + border: 1px solid #ddd; + padding: 8px; + z-index: 10; + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); + color: var(--black); + max-width: 250px; + } + + .close-icon { + cursor: pointer; + float: right; + margin-left: 10px; + color: var(--black); + } + + .send-message-button { + font-family: Roboto, sans-serif; + letter-spacing: 0.3px; + font-weight: 300; + padding: 8px 5px; + border-radius: 3px; + text-align: center; + color: var(--mdc-theme-primary); + transition: all 0.3s ease-in-out; + } + + .send-message-button:hover { + cursor: pointer; + background-color: #03a8f485; + } + .action-parent { + display: flex; + flex-direction: column; + width: 100%; + } + + div[tabindex='0']:focus { + outline: none; + } + `; + + static get properties() { + return { + for: { type: String, reflect: true }, + message: { type: String }, + openEditFriend: { attribute: false }, + name: { type: String }, + }; + } + + constructor() { + super(); + this.message = ''; + this.nodeUrl = this.getNodeUrl(); + this.uid = new ShortUniqueId(); + this.getUserAddress = this.getUserAddress.bind(this) + } + getNodeUrl() { + const myNode = + store.getState().app.nodeConfig.knownNodes[ + window.parent.reduxStore.getState().app.nodeConfig.node + ]; + + const nodeUrl = + myNode.protocol + '://' + myNode.domain + ':' + myNode.port; + return nodeUrl; + } + + firstUpdated() { + // We'll defer the popper attachment to the openPopover() method to ensure target availability + } + + attachToTarget(target) { + console.log({ target }); + if (!this.popperInstance && target) { + this.popperInstance = createPopper(target, this, { + placement: 'bottom', + strategy: 'fixed', + }); + } + } + + openPopover(target) { + this.attachToTarget(target); + this.style.display = 'block'; + setTimeout(() => { + this.shadowRoot.getElementById('parent-div').focus(); + }, 50); + } + + closePopover() { + this.style.display = 'none'; + if (this.popperInstance) { + this.popperInstance.destroy(); + this.popperInstance = null; + } + this.requestUpdate(); + } + handleBlur() { + setTimeout(() => { + this.closePopover(); + }, 0); + } + + async getUserAddress() { + try { + const url = `${this.nodeUrl}/names/${this.name}`; + const res = await fetch(url); + const result = await res.json(); + if (result.error === 401) { + return ''; + } else { + return result.owner; + } + } catch (error) { + return ''; + } + } + + render() { + return html` +
    + close +
    +
    + ${translate('friends.friend10')} +
    +
    + ${translate('friends.friend8')} +
    +
    + ${translate('friends.friend9')} +
    +
    +
    + `; + } +} + +customElements.define('friend-item-actions', FriendItemActions); diff --git a/core/src/components/friends-view/friends-side-panel-parent.js b/core/src/components/friends-view/friends-side-panel-parent.js new file mode 100644 index 00000000..c8564523 --- /dev/null +++ b/core/src/components/friends-view/friends-side-panel-parent.js @@ -0,0 +1,61 @@ +import { LitElement, html, css } from 'lit'; +import '@material/mwc-icon'; +import './friends-side-panel.js'; +class FriendsSidePanelParent extends LitElement { + static get properties() { + return { + isOpen: {type: Boolean} + }; + } + + + constructor() { + super(); + this.isOpen = false + } + static styles = css` + .header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 16px; + border-bottom: 1px solid #e0e0e0; + } + + .content { + padding: 16px; + } + .close { + visibility: hidden; + position: fixed; + z-index: -100; + right: -1000px; + } + + .parent-side-panel { + transform: translateX(100%); /* start from outside the right edge */ + transition: transform 0.3s ease-in-out; + } + .parent-side-panel.open { + transform: translateX(0); /* slide in to its original position */ + + } + `; + + render() { + return html` + { + this.isOpen = !this.isOpen + }} style="color: var(--black); cursor:pointer" + >group + this.isOpen = val}> + + + `; + } + + +} + +customElements.define('friends-side-panel-parent', FriendsSidePanelParent); diff --git a/core/src/components/friends-view/friends-side-panel.js b/core/src/components/friends-view/friends-side-panel.js new file mode 100644 index 00000000..009e7d16 --- /dev/null +++ b/core/src/components/friends-view/friends-side-panel.js @@ -0,0 +1,61 @@ +import { LitElement, html, css } from 'lit'; +import '@material/mwc-icon'; +import './friends-view' +class FriendsSidePanel extends LitElement { + static get properties() { + return { + setIsOpen: { attribute: false}, + isOpen: {type: Boolean} + }; + } + + static styles = css` + :host { + display: block; + position: fixed; + top: 55px; + right: 0px; + width: 420px; + max-width: 95%; + height: calc(100vh - 55px); + background-color: var(--white); + border-left: 1px solid rgb(224, 224, 224); + overflow-y: auto; + z-index: 1; + transform: translateX(100%); /* start from outside the right edge */ + transition: transform 0.3s ease-in-out; + } + :host([isOpen]) { + transform: unset; /* slide in to its original position */ + } + + .header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 16px; + border-bottom: 1px solid #e0e0e0; + } + + .content { + padding: 16px; + } + `; + + render() { + return html` +
    + Panel Title + { + this.setIsOpen(false) + }}>close +
    +
    +
    + + `; + } + +} + +customElements.define('friends-side-panel', FriendsSidePanel); diff --git a/core/src/components/friends-view/friends-view-css.js b/core/src/components/friends-view/friends-view-css.js new file mode 100644 index 00000000..c1d2be4b --- /dev/null +++ b/core/src/components/friends-view/friends-view-css.js @@ -0,0 +1,178 @@ +import { css } from 'lit' + +export const friendsViewStyles = css` + .top-bar-icon { + cursor: pointer; + height: 18px; + width: 18px; + transition: 0.2s all; + } + + .top-bar-icon:hover { + color: var(--black); + } + + .modal-button { + font-family: Roboto, sans-serif; + font-size: 16px; + color: var(--mdc-theme-primary); + background-color: transparent; + padding: 8px 10px; + border-radius: 5px; + border: none; + transition: all 0.3s ease-in-out; + } + + .close-row { + width: 100%; + display: flex; + justify-content: flex-end; + height: 50px; + flex:0 + + } + + .container-body { + width: 100%; + display: flex; + flex-direction: column; + flex-grow: 1; + margin-top: 5px; + padding: 0px 6px; + box-sizing: border-box; + align-items: center; + } + + .container-body::-webkit-scrollbar-track { + background-color: whitesmoke; + border-radius: 7px; + } + + .container-body::-webkit-scrollbar { + width: 6px; + border-radius: 7px; + background-color: whitesmoke; + } + + .container-body::-webkit-scrollbar-thumb { + background-color: rgb(180, 176, 176); + border-radius: 7px; + transition: all 0.3s ease-in-out; + } + + .container-body::-webkit-scrollbar-thumb:hover { + background-color: rgb(148, 146, 146); + cursor: pointer; + } + + p { + color: var(--black); + margin: 0px; + padding: 0px; + word-break: break-all; + } + + .container { + display: flex; + width: 100%; + flex-direction: column; + height: 100%; + } + + .chat-right-panel-label { + font-family: Montserrat, sans-serif; + color: var(--group-header); + padding: 5px; + font-size: 13px; + user-select: none; + } + + .group-info { + display: flex; + flex-direction: column; + justify-content: flex-start; + gap: 10px; + } + + .group-name { + font-family: Raleway, sans-serif; + font-size: 20px; + color: var(--chat-bubble-msg-color); + text-align: center; + user-select: none; + } + + .group-description { + font-family: Roboto, sans-serif; + color: var(--chat-bubble-msg-color); + letter-spacing: 0.3px; + font-weight: 300; + font-size: 14px; + margin-top: 15px; + word-break: break-word; + user-select: none; + } + + .group-subheader { + font-family: Montserrat, sans-serif; + font-size: 14px; + color: var(--chat-bubble-msg-color); + } + + .group-data { + font-family: Roboto, sans-serif; + letter-spacing: 0.3px; + font-weight: 300; + font-size: 14px; + color: var(--chat-bubble-msg-color); + } + .search-results-div { + position: absolute; + top: 25px; + right: 25px; + } + + .name-input { + width: 100%; + outline: 0; + border-width: 0 0 2px; + border-color: var(--mdc-theme-primary); + background-color: transparent; + padding: 10px; + font-family: Roboto, sans-serif; + font-size: 15px; + color: var(--chat-bubble-msg-color); + box-sizing: border-box; + } + + .name-input::selection { + background-color: var(--mdc-theme-primary); + color: white; + } + + .name-input::placeholder { + opacity: 0.9; + color: var(--black); + } + + .search-field { + width: 100%; + position: relative; + } + + .search-icon { + position: absolute; + right: 3px; + color: var(--chat-bubble-msg-color); + transition: all 0.3s ease-in-out; + background: none; + border-radius: 50%; + padding: 6px 3px; + font-size: 21px; + } + + .search-icon:hover { + cursor: pointer; + background: #d7d7d75c; + } +` diff --git a/core/src/components/friends-view/friends-view.js b/core/src/components/friends-view/friends-view.js new file mode 100644 index 00000000..c4f3c198 --- /dev/null +++ b/core/src/components/friends-view/friends-view.js @@ -0,0 +1,303 @@ +import { LitElement, html, css } from 'lit'; +import { render } from 'lit/html.js'; +import { connect } from 'pwa-helpers'; + +import '@material/mwc-button'; +import '@material/mwc-dialog'; +import '@polymer/paper-spinner/paper-spinner-lite.js'; +import '@polymer/paper-progress/paper-progress.js'; +import '@material/mwc-icon'; +import '@vaadin/icon' +import '@vaadin/icons' +import '@vaadin/button'; +import './ChatSideNavHeads'; +import '../../../../plugins/plugins/core/components/ChatSearchResults' +import './add-friends-modal' + +import { + use, + get, + translate, + translateUnsafeHTML, + registerTranslateConfig, +} from 'lit-translate'; +import { store } from '../../store'; +import { friendsViewStyles } from './friends-view-css'; +import { parentEpml } from '../show-plugin'; + +class FriendsView extends connect(store)(LitElement) { + static get properties() { + return { + error: { type: Boolean }, + toggle: { attribute: false }, + userName: { type: String }, + errorMessage: { type: String }, + successMessage: { type: String }, + setUserName: { attribute: false }, + friendList: { type: Array }, + userSelected: { type: Object }, + isLoading: {type: Boolean}, + userFoundModalOpen: {type: Boolean}, + userFound: { type: Array}, + isOpenAddFriendsModal: {type: Boolean}, + editContent: {type: Object} + }; + } + static get styles() { + return [friendsViewStyles]; + } + + constructor() { + super(); + this.error = false; + this.observerHandler = this.observerHandler.bind(this); + this.viewElement = ''; + this.downObserverElement = ''; + this.myAddress = + window.parent.reduxStore.getState().app.selectedAddress.address; + this.errorMessage = ''; + this.successMessage = ''; + this.friendList = [{ + + name: "Phil" + + }]; + this.userSelected = {}; + this.isLoading = false; + this.userFoundModalOpen = false + this.userFound = []; + this.nodeUrl = this.getNodeUrl(); + this.myNode = this.getMyNode(); + this.isOpenAddFriendsModal = false + this.editContent = null + this.addToFriendList = this.addToFriendList.bind(this) + } + + getNodeUrl() { + const myNode = + store.getState().app.nodeConfig.knownNodes[ + window.parent.reduxStore.getState().app.nodeConfig.node + ]; + + const nodeUrl = + myNode.protocol + '://' + myNode.domain + ':' + myNode.port; + return nodeUrl; + } + getMyNode() { + const myNode = + store.getState().app.nodeConfig.knownNodes[ + window.parent.reduxStore.getState().app.nodeConfig.node + ]; + + return myNode; + } + + getMoreFriends() {} + + firstUpdated() { + this.viewElement = this.shadowRoot.getElementById('viewElement'); + this.downObserverElement = + this.shadowRoot.getElementById('downObserver'); + this.elementObserver(); + } + + elementObserver() { + const options = { + root: this.viewElement, + rootMargin: '0px', + threshold: 1, + }; + // identify an element to observe + const elementToObserve = this.downObserverElement; + // passing it a callback function + const observer = new IntersectionObserver( + this.observerHandler, + options + ); + // call `observe()` on that MutationObserver instance, + // passing it the element to observe, and the options object + observer.observe(elementToObserve); + } + + observerHandler(entries) { + if (!entries[0].isIntersecting) { + return; + } else { + if (this.friendList.length < 20) { + return; + } + this.getMoreFriends(); + } + } + + async userSearch() { + const nameValue = this.shadowRoot.getElementById('sendTo').value + if(!nameValue) { + this.userFound = [] + this.userFoundModalOpen = true + return; + } + try { + const url = `${this.nodeUrl}/names/${nameValue}` + const res = await fetch(url) + const result = await res.json() + if (result.error === 401) { + this.userFound = [] + } else { + this.userFound = [ + ...this.userFound, + result, + ]; + } + this.userFoundModalOpen = true; + } catch (error) { + // let err4string = get("chatpage.cchange35"); + // parentEpml.request('showSnackBar', `${err4string}`) + } + } + + getApiKey() { + const apiNode = window.parent.reduxStore.getState().app.nodeConfig.knownNodes[window.parent.reduxStore.getState().app.nodeConfig.node] + let apiKey = apiNode.apiKey + return apiKey + } + + async myFollowName(name) { + let items = [ + name + ] + let namesJsonString = JSON.stringify({ "items": items }) + + let ret = await parentEpml.request('apiCall', { + url: `/lists/followedNames?apiKey=${this.getApiKey()}`, + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: `${namesJsonString}` + }) + + if (ret === true) { + this.myFollowedNames = this.myFollowedNames.filter(item => item != name) + this.myFollowedNames.push(name) + } else { + let err3string = get("appspage.schange22") + parentEpml.request('showSnackBar', `${err3string}`) + } + return ret + } + addToFriendList(val){ + if(this.editContent){ + const findFriend = this.friendList.findIndex(item=> item.name === val.name) + if(findFriend !== -1){ + const copyList = [...this.friendList] + copyList[findFriend] = val + this.friendList = copyList + + } + + } else { + this.friendList = [...this.friendList, val] + } + if(val.willFollow){ + this.myFollowName(val.name) + } + + this.userSelected = {}; + this.isLoading = false; + this.isOpenAddFriendsModal = false + this.editContent = null + } + openEditFriend(val){ + this.isOpenAddFriendsModal = true + this.userSelected = val + this.editContent = val + } + + onClose(){ + console.log('hello100') + this.isLoading = false; + this.isOpenAddFriendsModal = false + this.editContent = null + this.userSelected = {} + } + + render() { + console.log('friends', this.userSelected); + return html` +
    +
    +

    My Friends

    +
    + { + if(e.key === 'Enter'){ + this.userSearch() + } + }} + /> + + + + +
    +
    + { + this.userSelected = result; + this.isOpenAddFriendsModal = true + + console.log({result}); + this.userFound = []; + this.userFoundModalOpen = false; + }} + .closeFunc=${() => { + this.userFoundModalOpen = false; + this.userFound = []; + }} + .searchResults=${this.userFound} + ?isOpen=${this.userFoundModalOpen} + ?loading=${this.isLoading}> + +
    +
    + + ${this.friendList.map((item) => { + return html` { + + }} + .chatInfo=${item} + .openEditFriend=${(val)=> this.openEditFriend(val)} + >`; + })} +
    +
    +
    + { + this.isOpenAddFriendsModal = val + }} + .userSelected=${this.userSelected} + .onSubmit=${(val)=> this.addToFriendList(val)} + .editContent=${this.editContent} + .onClose=${()=> this.onClose()} + > + + `; + } +} + +customElements.define('friends-view', FriendsView); diff --git a/core/src/components/notification-view/notification-bell-general.js b/core/src/components/notification-view/notification-bell-general.js index 125c8095..1a0a72fb 100644 --- a/core/src/components/notification-view/notification-bell-general.js +++ b/core/src/components/notification-view/notification-bell-general.js @@ -111,13 +111,13 @@ class NotificationBellGeneral extends connect(store)(LitElement) { > ${hasOngoing ? html` - notifications ` : html` notifications `} diff --git a/core/src/components/show-plugin.js b/core/src/components/show-plugin.js index c6ff4d34..abf21b9c 100644 --- a/core/src/components/show-plugin.js +++ b/core/src/components/show-plugin.js @@ -26,11 +26,9 @@ import '@vaadin/grid' import '@vaadin/text-field' import '../custom-elements/frag-file-input.js' -const chatLastSeen = localForage.createInstance({ - name: "chat-last-seen", -}) -const parentEpml = new Epml({ type: 'WINDOW', source: window.parent }) + +export const parentEpml = new Epml({ type: 'WINDOW', source: window.parent }) class ShowPlugin extends connect(store)(LitElement) { static get properties() { diff --git a/package-lock.json b/package-lock.json index 71aadf01..1c1fab36 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7407,15 +7407,6 @@ "node": ">=0.10.0" } }, - "node_modules/is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/is-plain-object": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", @@ -7462,15 +7453,6 @@ "is-potential-custom-element-name": "^1.0.0" } }, - "node_modules/is-valid-element-name": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-valid-element-name/-/is-valid-element-name-1.0.0.tgz", - "integrity": "sha512-GZITEJY2LkSjQfaIPBha7eyZv+ge0PhBR7KITeCCWvy7VBQrCUdFkvpI+HrAPQjVtVjy1LvlEkqQTHckoszruw==", - "dev": true, - "dependencies": { - "is-potential-custom-element-name": "^1.0.0" - } - }, "node_modules/is-wsl": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", @@ -7758,19 +7740,6 @@ "node": ">= 0.8.0" } }, - "node_modules/levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, "node_modules/lie": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/lie/-/lie-3.1.1.tgz", @@ -9746,18 +9715,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/strip-outer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/strip-outer/-/strip-outer-1.0.1.tgz", diff --git a/plugins/plugins/core/components/ChatSearchResults.js b/plugins/plugins/core/components/ChatSearchResults.js index b8b19cb5..bf2d7075 100644 --- a/plugins/plugins/core/components/ChatSearchResults.js +++ b/plugins/plugins/core/components/ChatSearchResults.js @@ -1,12 +1,10 @@ import { LitElement, html } from 'lit' import { render } from 'lit/html.js' -import { Epml } from '../../../epml.js' import { chatSearchResultsStyles } from './ChatSearchResults-css.js' import { use, get, translate, translateUnsafeHTML, registerTranslateConfig } from 'lit-translate' import '@vaadin/icon' import '@vaadin/icons' -const parentEpml = new Epml({ type: 'WINDOW', source: window.parent }) export class ChatSearchResults extends LitElement { static get properties() { @@ -19,7 +17,10 @@ export class ChatSearchResults extends LitElement { } } - static styles = [chatSearchResultsStyles] + static get styles() { + return [chatSearchResultsStyles]; + } + render() { return html` diff --git a/plugins/plugins/core/messaging/q-chat/q-chat.src.js b/plugins/plugins/core/messaging/q-chat/q-chat.src.js index f73558c4..b15388ca 100644 --- a/plugins/plugins/core/messaging/q-chat/q-chat.src.js +++ b/plugins/plugins/core/messaging/q-chat/q-chat.src.js @@ -221,6 +221,8 @@ class Chat extends LitElement { } render() { + console.log('chatHeads', this.chatHeads) + console.log('chatHeadsObj', this.chatHeadsObj) return html`
    @@ -516,6 +518,26 @@ class Chat extends LitElement { chatHeads = JSON.parse(chatHeads) this.getChatHeadFromState(chatHeads) }) + parentEpml.subscribe('side_effect_action', async sideEffectActionParam => { + const sideEffectAction = JSON.parse(sideEffectActionParam) + + if(sideEffectAction && sideEffectAction.type === 'openPrivateChat'){ + const name = sideEffectAction.data.name + const address = sideEffectAction.data.address + console.log({address}, this.chatHeadsObj) + if(this.chatHeadsObj.direct && this.chatHeadsObj.direct.find(item=> item.address === address)){ + this.setActiveChatHeadUrl(`direct/${address}`) + window.parent.reduxStore.dispatch( + window.parent.reduxAction.setSideEffectAction(null)) + } else { + this.setOpenPrivateMessage({ + open: true, + name: name + }) + } + + } + }) parentEpml.request('apiCall', { url: `/addresses/balance/${window.parent.reduxStore.getState().app.selectedAddress.address}` }).then(res => { From a20cd240ef34e764172687a49736685e7e48f4e1 Mon Sep 17 00:00:00 2001 From: PhilReact Date: Sat, 7 Oct 2023 01:07:08 -0500 Subject: [PATCH 02/21] ability to parse schema --- .../components/friends-view/friends-feed.js | 244 ++++++++++++++++++ .../friends-view/friends-side-panel-parent.js | 3 +- .../friends-view/friends-side-panel.js | 6 +- .../src/components/login-view/welcome-page.js | 2 +- 4 files changed, 251 insertions(+), 4 deletions(-) create mode 100644 core/src/components/friends-view/friends-feed.js diff --git a/core/src/components/friends-view/friends-feed.js b/core/src/components/friends-view/friends-feed.js new file mode 100644 index 00000000..a20d1733 --- /dev/null +++ b/core/src/components/friends-view/friends-feed.js @@ -0,0 +1,244 @@ +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'; +class FriendsFeed extends connect(store)(LitElement) { + static get properties() { + return { + feed: {type: Array} + }; + } + constructor(){ + super() + this.feed = [] + this.nodeUrl = this.getNodeUrl(); + this.myNode = this.getMyNode(); + } + + 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; + } + + async firstUpdated(){ + console.log('sup') + const feedData = schema.feed[0] + let schemaObj = {...schema} + const dynamicVars = { + name: 'Phil' + } + const getMail = async () => { + + const baseurl = `${this.nodeUrl}/arbitrary/resources/search?reverse=true` + const fullUrl = constructUrl(baseurl, feedData.search, dynamicVars); + console.log({fullUrl}) + const response = await fetch(fullUrl, { + method: 'GET', + headers: { + 'Content-Type': 'application/json' + } + }) + + const data = await response.json() + return data; + } + try { + getMail() + const resource = { + name: 'Phil', + identifier: 'q-blog-Mugician-post-Love-Explosion-Festival--yJ8kuo' + } + // First, evaluate methods to get values for customParams +await updateCustomParamsWithMethods(schemaObj, resource); +console.log({schemaObj}) +// Now, generate your final URLs +let clickValue1 = schemaObj.feed[0].click; + +const resolvedClickValue1 = replacePlaceholders(clickValue1, resource, schemaObj.feed[0].customParams); + +console.log(resolvedClickValue1); + + } catch (error) { + console.log(error) + } + } + + render() { + console.log('ron') + return html` +
    +
    + hi + + + ${this.feed.map((item) => { + return html`

    hello

    `; + })} +
    +
    +
    + `; + } + +} + +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; +} + +function executeMethodInWorker(methodString, externalArgs) { + return new Promise((resolve, reject) => { + const workerFunction = ` + self.onmessage = function(event) { + const method = ${methodString}; + const result = method(event.data.externalArgs); + self.postMessage(result); + } + `; + + const blob = new Blob([workerFunction], { type: 'application/javascript' }); + const blobURL = URL.createObjectURL(blob); + const worker = new Worker(blobURL); + + worker.onmessage = function(event) { + resolve(event.data); + worker.terminate(); + URL.revokeObjectURL(blobURL); + }; + + worker.onerror = function(error) { + reject(error); + worker.terminate(); + URL.revokeObjectURL(blobURL); + }; + + worker.postMessage({ externalArgs }); + }); +} + + + +export async function updateCustomParamsWithMethods(schema,resource) { + for (const key in schema.feed[0].customParams) { + const value = schema.feed[0].customParams[key]; + + if (value.startsWith("**methods.") && value.endsWith("**")) { + const methodInvocation = value.slice(10, -2).split('('); + const methodName = methodInvocation[0]; + + if (schema.feed[0].methods[methodName]) { + const methodResult = await executeMethodInWorker(schema.feed[0].methods[methodName].toString(), resource); + console.log({methodResult}) + schema.feed[0].customParams[key] = methodResult; + } + } + } +} + +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; + }); +} + + + + + +export const schema = { + name: "Q-Blog", + feed: [ + { + id:"post-creation", + version: 1, + updated: 1696646223261, + title: "Q-Blog Post creations", + description: "blablabla", + search: { + name:"$${name}$$", + query: "-post-", + identifier: "q-blog-", + service: "BLOG_POST", + exactmatchnames: true + }, + click: "qortal://APP/Q-Blog/$${resource.name}$$/$${customParams.blogId}$$/$${customParams.shortIdentifier}$$", + display: "", + customParams: { + blogId: "**methods.getBlogId(resource)**", + shortIdentifier: "**methods.getShortId(resource)**" + }, + methods: { + getShortId: function(resource) { + const str = resource.identifier + const arr = str.split('-post-') + const shortIdentifier = arr[1] + + return shortIdentifier + }, + getBlogId: function(resource) { + const str = resource.identifier + const arr = str.split('-post-') + const id = arr[0] + let blogId = "" + if (id.startsWith('q-blog-')) { + blogId = id.substring(7); + } else { + blogId= id; + } + return blogId + } + } + } + ] +} \ No newline at end of file diff --git a/core/src/components/friends-view/friends-side-panel-parent.js b/core/src/components/friends-view/friends-side-panel-parent.js index c8564523..a31ac737 100644 --- a/core/src/components/friends-view/friends-side-panel-parent.js +++ b/core/src/components/friends-view/friends-side-panel-parent.js @@ -46,12 +46,11 @@ class FriendsSidePanelParent extends LitElement { return html` { this.isOpen = !this.isOpen - }} style="color: var(--black); cursor:pointer" + }} style="color: var(--black); cursor:pointer;user-select:none" >group this.isOpen = val}> - `; } diff --git a/core/src/components/friends-view/friends-side-panel.js b/core/src/components/friends-view/friends-side-panel.js index 009e7d16..96c2be4f 100644 --- a/core/src/components/friends-view/friends-side-panel.js +++ b/core/src/components/friends-view/friends-side-panel.js @@ -1,6 +1,7 @@ import { LitElement, html, css } from 'lit'; import '@material/mwc-icon'; import './friends-view' +import './friends-feed' class FriendsSidePanel extends LitElement { static get properties() { return { @@ -51,7 +52,10 @@ class FriendsSidePanel extends LitElement { }}>close
    -
    + + + +
    `; } diff --git a/core/src/components/login-view/welcome-page.js b/core/src/components/login-view/welcome-page.js index 905c5777..0929476d 100644 --- a/core/src/components/login-view/welcome-page.js +++ b/core/src/components/login-view/welcome-page.js @@ -49,7 +49,7 @@ class WelcomePage extends LitElement { this.theme = localStorage.getItem('qortalTheme') ? localStorage.getItem('qortalTheme') : 'light' } - firstUpdate() { + firstUpdated() { // ... } From 8eacfca4d153ca22d8bde2869d056947c621533b Mon Sep 17 00:00:00 2001 From: PhilReact Date: Sun, 8 Oct 2023 23:17:21 -0500 Subject: [PATCH 03/21] started friends feed --- core/language/us.json | 4 +- .../friends-view/ChatSideNavHeads.js | 2 +- core/src/components/friends-view/feed-item.js | 341 ++++++++++++++++++ .../friends-view/friend-item-actions.js | 12 + .../components/friends-view/friends-feed.js | 115 +++++- .../components/friends-view/friends-view.js | 2 +- 6 files changed, 466 insertions(+), 10 deletions(-) create mode 100644 core/src/components/friends-view/feed-item.js diff --git a/core/language/us.json b/core/language/us.json index 257c770b..b99cb5df 100644 --- a/core/language/us.json +++ b/core/language/us.json @@ -1185,8 +1185,8 @@ "friend5": "Follow name", "friend6": "Alias", "friend7": "Add an alias to better remember your friend (Optional)", - "friend8": "Send a Q-Chat message", - "friend9": "Send a Q-Mail", + "friend8": "Send Q-Chat", + "friend9": "Send Q-Mail", "friend10": "Edit friend", "friend11": "Following" } diff --git a/core/src/components/friends-view/ChatSideNavHeads.js b/core/src/components/friends-view/ChatSideNavHeads.js index f74536d0..534756ac 100644 --- a/core/src/components/friends-view/ChatSideNavHeads.js +++ b/core/src/components/friends-view/ChatSideNavHeads.js @@ -164,7 +164,7 @@ class ChatSideNavHeads extends LitElement { ${this.chatInfo.groupName ? this.chatInfo.groupName : this.chatInfo.name !== undefined - ? this.chatInfo.name + ? (this.chatInfo.alias || this.chatInfo.name) : this.chatInfo.address.substr(0, 15)} diff --git a/core/src/components/friends-view/feed-item.js b/core/src/components/friends-view/feed-item.js new file mode 100644 index 00000000..d5f40fe2 --- /dev/null +++ b/core/src/components/friends-view/feed-item.js @@ -0,0 +1,341 @@ +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'; +const requestQueue = new RequestQueueWithPromise(5); + +export class FeedItem extends LitElement { + static get properties() { + return { + resource: { type: Object }, + isReady: { type: Boolean}, + status: {type: Object}, + feedItem: {type: Object} + }; + } + + static get styles() { + return css` + * { + --mdc-theme-text-primary-on-background: var(--black); + } + img { + max-width:45vh; + max-height:40vh; + 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: 45vh; + height: 40vh; + } + + 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.url = "" + this.isReady = false + this.nodeUrl = this.getNodeUrl() + this.myNode = this.getMyNode() + this.hasCalledWhenDownloaded = false + this.isFetching = false + + 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() + this.url = `${this.nodeUrl}/arbitrary/${this.resource.service}/${this.resource.name}/${this.resource.identifier}?async=true&apiKey=${this.myNode.apiKey}` + + } + + async getRawData(){ + const url = `${this.nodeUrl}/arbitrary/${this.resource.service}/${this.resource.name}/${this.resource.identifier}?apiKey=${this.myNode.apiKey}` + 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 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 = { + title: "$${rawdata.title}$$", + } + this.updateDisplayWithPlaceholders(object, {},rawData) + 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') { + + this.feedItem = await this.getRawData() + clearInterval(intervalId) + this.status = res + this.isReady = true + } + }, 5000) // 1 second interval + } + + async _fetchImage() { + try { + this.fetchVideoUrl({ + name: this.resource.name, + service: this.resource.service, + identifier: this.resource.identifier + }) + this.fetchStatus() + } catch (error) { /* empty */ } + } + + firstUpdated(){ + this.observer.observe(this); + + } + + + + + + + + + + + + + + + + render() { + console.log('this.feedItem', this.feedItem) + return html` +
    + ${ + this.status.status !== 'READY' + ? html` +
    +
    +

    ${`${Math.round(this.status.percentLoaded || 0 + ).toFixed(0)}% `}${translate('chatpage.cchange94')}

    +
    + ` + : '' + } + ${this.status.status === 'READY' && this.feedItem ? html` +
    + ready +
    + ` : ''} + +
    + + ` + + + } +} + +customElements.define('feed-item', FeedItem); diff --git a/core/src/components/friends-view/friend-item-actions.js b/core/src/components/friends-view/friend-item-actions.js index 6b0f55af..19ade862 100644 --- a/core/src/components/friends-view/friend-item-actions.js +++ b/core/src/components/friends-view/friend-item-actions.js @@ -38,6 +38,9 @@ export class FriendItemActions extends connect(store)(LitElement) { 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 { @@ -148,6 +151,9 @@ export class FriendItemActions extends connect(store)(LitElement) { this.openEditFriend(); this.closePopover(); }}" + > + edit ${translate('friends.friend10')} @@ -186,6 +192,9 @@ export class FriendItemActions extends connect(store)(LitElement) { ); this.closePopover(); }}" + > + send ${translate('friends.friend8')} @@ -212,6 +221,9 @@ export class FriendItemActions extends connect(store)(LitElement) { ); this.closePopover(); }}" + > + mail ${translate('friends.friend9')} diff --git a/core/src/components/friends-view/friends-feed.js b/core/src/components/friends-view/friends-feed.js index a20d1733..2be72bf0 100644 --- a/core/src/components/friends-view/friends-feed.js +++ b/core/src/components/friends-view/friends-feed.js @@ -4,6 +4,10 @@ import './friends-view' import { friendsViewStyles } from './friends-view-css'; import { connect } from 'pwa-helpers'; import { store } from '../../store'; +import './feed-item' +const perEndpointCount = 20; +const totalDesiredCount = 100; +const maxResultsInMemory = 300; class FriendsFeed extends connect(store)(LitElement) { static get properties() { return { @@ -15,6 +19,10 @@ class FriendsFeed extends connect(store)(LitElement) { this.feed = [] 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) } static get styles() { @@ -51,7 +59,10 @@ class FriendsFeed extends connect(store)(LitElement) { const baseurl = `${this.nodeUrl}/arbitrary/resources/search?reverse=true` const fullUrl = constructUrl(baseurl, feedData.search, dynamicVars); - console.log({fullUrl}) + this.endpoints= ['http://127.0.0.1:12391/arbitrary/resources/search?reverse=true&query=-post-&identifier=q-blog-&service=BLOG_POST&exactmatchnames=true&limit=20'] + this.endpointOffsets = Array(this.endpoints.length).fill(0); // Initialize offsets for each endpoint to 0 + + console.log('this.endpoints', this.endpoints) const response = await fetch(fullUrl, { method: 'GET', headers: { @@ -77,22 +88,109 @@ let clickValue1 = schemaObj.feed[0].click; const resolvedClickValue1 = replacePlaceholders(clickValue1, resource, schemaObj.feed[0].customParams); console.log(resolvedClickValue1); +this.loadAndMergeData(); + } catch (error) { console.log(error) } } + async fetchDataFromEndpoint(endpointIndex, count) { + const offset = this.endpointOffsets[endpointIndex]; + const url = `${this.endpoints[endpointIndex]}&limit=${count}&offset=${offset}`; + return fetch(url).then(res => res.json()); + } + + + async initialLoad() { + let results = []; + let totalFetched = 0; + let i = 0; + let madeProgress = true; + let exhaustedEndpoints = new Set(); + + while (totalFetched < totalDesiredCount && madeProgress) { + madeProgress = false; + + 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; + } + } + + // 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 loadAndMergeData() { + let allData = this.feed + const newData = await this.initialLoad(); + 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] + } + + render() { - console.log('ron') + console.log('ron', this.feed) return html`
    - hi - ${this.feed.map((item) => { - return html`

    hello

    `; + return html``; })}
    @@ -198,6 +296,7 @@ export function replacePlaceholders(template, resource, customParams) { export const schema = { name: "Q-Blog", + defaultFeedIndex: 0, feed: [ { id:"post-creation", @@ -213,7 +312,11 @@ export const schema = { exactmatchnames: true }, click: "qortal://APP/Q-Blog/$${resource.name}$$/$${customParams.blogId}$$/$${customParams.shortIdentifier}$$", - display: "", + display: { + title: "$${rawdata.title}$$", + description: "$${rawdata.description}$$", + coverImage: "$${rawdata.image}$$" + }, customParams: { blogId: "**methods.getBlogId(resource)**", shortIdentifier: "**methods.getShortId(resource)**" diff --git a/core/src/components/friends-view/friends-view.js b/core/src/components/friends-view/friends-view.js index c4f3c198..80b006ee 100644 --- a/core/src/components/friends-view/friends-view.js +++ b/core/src/components/friends-view/friends-view.js @@ -246,7 +246,7 @@ class FriendsView extends connect(store)(LitElement) { From fac7ae9004332faf1ce2071e3347a2e987ca0b54 Mon Sep 17 00:00:00 2001 From: PhilReact Date: Tue, 10 Oct 2023 18:49:18 -0500 Subject: [PATCH 04/21] feed in separate palce --- core/language/us.json | 4 +- core/src/components/friends-view/feed-item.js | 180 +++++++++++- .../components/friends-view/friends-feed.js | 276 +++++++++++++----- .../friends-view/friends-side-panel.js | 78 ++++- .../friends-view/friends-view-css.js | 6 +- .../components/friends-view/friends-view.js | 2 +- .../friends-view/webworkerParseFeedData.js | 0 .../plugins/core/qdn/browser/browser.src.js | 5 +- 8 files changed, 453 insertions(+), 98 deletions(-) create mode 100644 core/src/components/friends-view/webworkerParseFeedData.js diff --git a/core/language/us.json b/core/language/us.json index b99cb5df..370137da 100644 --- a/core/language/us.json +++ b/core/language/us.json @@ -1188,6 +1188,8 @@ "friend8": "Send Q-Chat", "friend9": "Send Q-Mail", "friend10": "Edit friend", - "friend11": "Following" + "friend11": "Following", + "friend12": "Friends", + "friend13": "Feed" } } \ No newline at end of file diff --git a/core/src/components/friends-view/feed-item.js b/core/src/components/friends-view/feed-item.js index d5f40fe2..ab67d17e 100644 --- a/core/src/components/friends-view/feed-item.js +++ b/core/src/components/friends-view/feed-item.js @@ -7,15 +7,23 @@ 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(5); -export class FeedItem extends LitElement { +export class FeedItem extends connect(store)(LitElement) { static get properties() { return { resource: { type: Object }, isReady: { type: Boolean}, status: {type: Object}, - feedItem: {type: Object} + feedItem: {type: Object}, + appName: {type: String}, + link: {type: String} }; } @@ -23,10 +31,15 @@ export class FeedItem extends LitElement { return css` * { --mdc-theme-text-primary-on-background: var(--black); + box-sizing: border-box; + } + :host { + width: 100%; + box-sizing: border-box; } img { - max-width:45vh; - max-height:40vh; + width:100%; + max-height:30vh; border-radius: 5px; cursor: pointer; position: relative; @@ -51,9 +64,40 @@ export class FeedItem extends LitElement { } .defaultSize { - width: 45vh; - height: 40vh; + 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; + } + .avatar { + width: 42px; + height: 42px; + } + .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; @@ -102,6 +146,7 @@ export class FeedItem extends LitElement { this.myNode = this.getMyNode() this.hasCalledWhenDownloaded = false this.isFetching = false + this.uid = new ShortUniqueId() this.observer = new IntersectionObserver(entries => { for (const entry of entries) { @@ -283,11 +328,93 @@ getMyNode(){ - + async goToFeedLink(){ + try { + console.log('this.link', this.link) + 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 +} + @@ -297,6 +424,20 @@ getMyNode(){ render() { console.log('this.feedItem', this.feedItem) + let avatarImg + const avatarUrl = `${this.nodeUrl}/arbitrary/THUMBNAIL/${this.resource.name}/qortal_avatar?async=true&apiKey=${this.myNode.apiKey}`; + avatarImg = html``; + let avatarImgApp + const avatarUrl2 = `${this.nodeUrl}/arbitrary/THUMBNAIL/${this.appName}/qortal_avatar?async=true&apiKey=${this.myNode.apiKey}`; + avatarImgApp = html``; return html`
    ${ this.status.status !== 'READY' ? html`
    - ready +
    +
    +
    + ${avatarImg}
    ${this.resource.name} +
    +
    +

    ${this.feedItem.title}

    +
    +
    +
    + ${avatarImgApp} +
    + +
    ` : ''} diff --git a/core/src/components/friends-view/friends-feed.js b/core/src/components/friends-view/friends-feed.js index 2be72bf0..9a9b33f5 100644 --- a/core/src/components/friends-view/friends-feed.js +++ b/core/src/components/friends-view/friends-feed.js @@ -5,6 +5,7 @@ import { friendsViewStyles } from './friends-view-css'; import { connect } from 'pwa-helpers'; import { store } from '../../store'; import './feed-item' + const perEndpointCount = 20; const totalDesiredCount = 100; const maxResultsInMemory = 300; @@ -17,18 +18,25 @@ class FriendsFeed extends connect(store)(LitElement) { 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) + } static get styles() { return [friendsViewStyles]; } + + getNodeUrl() { const myNode = store.getState().app.nodeConfig.knownNodes[ @@ -49,45 +57,27 @@ class FriendsFeed extends connect(store)(LitElement) { } async firstUpdated(){ - console.log('sup') + this.viewElement = this.shadowRoot.getElementById('viewElement'); + this.downObserverElement = + this.shadowRoot.getElementById('downObserver'); + this.elementObserver(); const feedData = schema.feed[0] let schemaObj = {...schema} const dynamicVars = { - name: 'Phil' + } - const getMail = async () => { + const getEndpoints = async () => { const baseurl = `${this.nodeUrl}/arbitrary/resources/search?reverse=true` const fullUrl = constructUrl(baseurl, feedData.search, dynamicVars); - this.endpoints= ['http://127.0.0.1:12391/arbitrary/resources/search?reverse=true&query=-post-&identifier=q-blog-&service=BLOG_POST&exactmatchnames=true&limit=20'] - this.endpointOffsets = Array(this.endpoints.length).fill(0); // Initialize offsets for each endpoint to 0 + this.endpoints= [{url: fullUrl, schemaName: schema.name, schema: feedData }] + this.endpointOffsets = Array(this.endpoints.length).fill(0); - console.log('this.endpoints', this.endpoints) - const response = await fetch(fullUrl, { - method: 'GET', - headers: { - 'Content-Type': 'application/json' - } - }) - - const data = await response.json() - return data; + } try { - getMail() - const resource = { - name: 'Phil', - identifier: 'q-blog-Mugician-post-Love-Explosion-Festival--yJ8kuo' - } - // First, evaluate methods to get values for customParams -await updateCustomParamsWithMethods(schemaObj, resource); -console.log({schemaObj}) -// Now, generate your final URLs -let clickValue1 = schemaObj.feed[0].click; + getEndpoints() -const resolvedClickValue1 = replacePlaceholders(clickValue1, resource, schemaObj.feed[0].customParams); - -console.log(resolvedClickValue1); this.loadAndMergeData(); @@ -96,10 +86,59 @@ this.loadAndMergeData(); } } + getMoreFeed(){ + if(!this.hasInitialFetch) return + console.log('getting more feed') + if(this.feedToRender.length === this.feed.length ) return + this.feedToRender = this.feed.slice(0, this.feedToRender.length + 20) + this.requestUpdate() + } + + + elementObserver() { + const options = { + rootMargin: '0px', + threshold: 1, + }; + // identify an element to observe + console.log('this', this.viewElement, this.downObserverElement) + 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) { + console.log({entries}) + if (!entries[0].isIntersecting) { + return; + } else { + console.log('this.feedToRender', this.feedToRender) + if (this.feedToRender.length < 20) { + return; + } + this.getMoreFeed(); + } + } + async fetchDataFromEndpoint(endpointIndex, count) { const offset = this.endpointOffsets[endpointIndex]; - const url = `${this.endpoints[endpointIndex]}&limit=${count}&offset=${offset}`; - return fetch(url).then(res => res.json()); + const url = `${this.endpoints[endpointIndex].url}&limit=${count}&offset=${offset}`; + const res = await fetch(url) + const data = await res.json() + console.log({data}) + return data.map((i)=> { + return { + ...this.endpoints[endpointIndex], + ...i + } + }) + } @@ -165,17 +204,55 @@ this.loadAndMergeData(); 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 + // First, evaluate methods to get values for customParams + await updateCustomParamsWithMethods(newItem.schema, newResource); + // Now, generate your final URLs + let clickValue1 = newItem.schema.click; + + const resolvedClickValue1 = replacePlaceholders(clickValue1, resource, newItem.schema.customParams); + newItem.link = resolvedClickValue1 + newData.push(newItem) + } + } + return newData + + } 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 } - + + + + render() { console.log('ron', this.feed) @@ -183,13 +260,11 @@ this.loadAndMergeData();
    - ${this.feed.map((item) => { + ${this.feedToRender.map((item) => { return html``; })}
    @@ -223,13 +298,43 @@ export function constructUrl(base, search, dynamicVars) { return queryStrings.length > 0 ? `${base}&${queryStrings.join('&')}` : base; } +function validateMethodString(methodString) { + // Check for IIFE + const iifePattern = /^\(.*\)\s*\(\)/; + if (iifePattern.test(methodString)) { + throw new Error("IIFE detected!"); + } + + // Check for disallowed keywords + const disallowed = ["eval", "Function", "fetch", "XMLHttpRequest"]; + for (const keyword of disallowed) { + if (methodString.includes(keyword)) { + throw new Error(`Disallowed keyword detected: ${keyword}`); + } + } + + // ... Add more validation steps here ... + + return true; +} + function executeMethodInWorker(methodString, externalArgs) { return new Promise((resolve, reject) => { + if (!validateMethodString(methodString)) { + reject(new Error("Invalid method string provided.")); + return; + } + const workerFunction = ` self.onmessage = function(event) { - const method = ${methodString}; - const result = method(event.data.externalArgs); - self.postMessage(result); + const methodFunction = new Function("resource", "${methodString}"); + const result = methodFunction(event.data.externalArgs); + + if (typeof result === 'string' || typeof result === 'number') { + self.postMessage(result); + } else { + self.postMessage(''); + } } `; @@ -238,9 +343,16 @@ function executeMethodInWorker(methodString, externalArgs) { const worker = new Worker(blobURL); worker.onmessage = function(event) { - resolve(event.data); - worker.terminate(); - URL.revokeObjectURL(blobURL); + if (typeof event.data === 'string' || typeof event.data === 'number') { + resolve(event.data); + worker.terminate(); + URL.revokeObjectURL(blobURL); + } else { + resolve(""); + worker.terminate(); + URL.revokeObjectURL(blobURL); + } + }; worker.onerror = function(error) { @@ -256,17 +368,24 @@ function executeMethodInWorker(methodString, externalArgs) { export async function updateCustomParamsWithMethods(schema,resource) { - for (const key in schema.feed[0].customParams) { - const value = schema.feed[0].customParams[key]; - + console.log({schema, resource}) + for (const key in schema.customParams) { + const value = schema.customParams[key]; + console.log({value}) if (value.startsWith("**methods.") && value.endsWith("**")) { const methodInvocation = value.slice(10, -2).split('('); const methodName = methodInvocation[0]; - if (schema.feed[0].methods[methodName]) { - const methodResult = await executeMethodInWorker(schema.feed[0].methods[methodName].toString(), resource); + if (schema.methods[methodName]) { + const newResource = { + identifier: resource.identifier, + name: resource.name, + service: resource.service + } + console.log({newResource}) + const methodResult = await executeMethodInWorker(schema.methods[methodName], newResource); console.log({methodResult}) - schema.feed[0].customParams[key] = methodResult; + schema.customParams[key] = methodResult; } } } @@ -292,9 +411,9 @@ export function replacePlaceholders(template, resource, customParams) { +// export const schemaList = [schema] - -export const schema = { + const schema = { name: "Q-Blog", defaultFeedIndex: 0, feed: [ @@ -305,7 +424,6 @@ export const schema = { title: "Q-Blog Post creations", description: "blablabla", search: { - name:"$${name}$$", query: "-post-", identifier: "q-blog-", service: "BLOG_POST", @@ -321,27 +439,35 @@ export const schema = { blogId: "**methods.getBlogId(resource)**", shortIdentifier: "**methods.getShortId(resource)**" }, - methods: { - getShortId: function(resource) { - const str = resource.identifier - const arr = str.split('-post-') - const shortIdentifier = arr[1] - - return shortIdentifier - }, - getBlogId: function(resource) { - const str = resource.identifier - const arr = str.split('-post-') - const id = arr[0] - let blogId = "" - if (id.startsWith('q-blog-')) { - blogId = id.substring(7); - } else { - blogId= id; - } - return blogId - } + "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;" } + // methods: { + // getShortId: function(resource) { + // console.log({resource}) + // const str = resource.identifier + // const arr = str.split('-post-') + // const shortIdentifier = arr[1] + + // return shortIdentifier + // }, + // getBlogId: function(resource) { + // console.log({resource}) + // const str = resource.identifier + // const arr = str.split('-post-') + // const id = arr[0] + // let blogId = "" + // if (id.startsWith('q-blog-')) { + // blogId = id.substring(7); + // } else { + // blogId= id; + // } + // return blogId + // } + // } } ] -} \ No newline at end of file +} + +// export const schema = JSON.stringify(schema2, null, 2); // 2 spaces indentation diff --git a/core/src/components/friends-view/friends-side-panel.js b/core/src/components/friends-view/friends-side-panel.js index 96c2be4f..76740d5c 100644 --- a/core/src/components/friends-view/friends-side-panel.js +++ b/core/src/components/friends-view/friends-side-panel.js @@ -2,13 +2,20 @@ 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} + isOpen: {type: Boolean}, + selected: {type: String} }; } + + constructor(){ + super() + this.selected = 'friends' + } static styles = css` :host { @@ -21,7 +28,6 @@ class FriendsSidePanel extends LitElement { height: calc(100vh - 55px); background-color: var(--white); border-left: 1px solid rgb(224, 224, 224); - overflow-y: auto; z-index: 1; transform: translateX(100%); /* start from outside the right edge */ transition: transform 0.3s ease-in-out; @@ -40,23 +46,85 @@ class FriendsSidePanel extends LitElement { .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; + } + `; render() { return html` +
    - Panel Title +
    + this.selected = 'friends'} class="${this.selected === 'friends' ? 'active' : 'default'}">${translate('friends.friend12')} + this.selected = 'feed'} class="${this.selected === 'feed' ? 'active' : 'default'}">${translate('friends.friend13')} +
    { this.setIsOpen(false) }}>close
    - - +
    + +
    +
    + +
    + +
    +
    `; } diff --git a/core/src/components/friends-view/friends-view-css.js b/core/src/components/friends-view/friends-view-css.js index c1d2be4b..303d510d 100644 --- a/core/src/components/friends-view/friends-view-css.js +++ b/core/src/components/friends-view/friends-view-css.js @@ -1,6 +1,9 @@ import { css } from 'lit' export const friendsViewStyles = css` +* { + box-sizing: border-box; +} .top-bar-icon { cursor: pointer; height: 18px; @@ -41,6 +44,7 @@ export const friendsViewStyles = css` padding: 0px 6px; box-sizing: border-box; align-items: center; + gap: 10px; } .container-body::-webkit-scrollbar-track { @@ -164,7 +168,7 @@ export const friendsViewStyles = css` position: absolute; right: 3px; color: var(--chat-bubble-msg-color); - transition: all 0.3s ease-in-out; + transition: hover 0.3s ease-in-out; background: none; border-radius: 50%; padding: 6px 3px; diff --git a/core/src/components/friends-view/friends-view.js b/core/src/components/friends-view/friends-view.js index 80b006ee..f591348f 100644 --- a/core/src/components/friends-view/friends-view.js +++ b/core/src/components/friends-view/friends-view.js @@ -270,7 +270,7 @@ class FriendsView extends connect(store)(LitElement) { ?loading=${this.isLoading}>
    -
    + ${this.friendList.map((item) => { return html` Date: Tue, 10 Oct 2023 22:06:44 -0500 Subject: [PATCH 05/21] added color for new feed --- core/src/components/app-view.js | 3 +- .../friends-view/add-friends-modal.js | 2 - core/src/components/friends-view/feed-item.js | 2 - .../components/friends-view/friends-feed.js | 45 ++++---------- .../friends-view/friends-side-panel-parent.js | 16 ++++- .../friends-view/friends-side-panel.js | 5 +- .../components/friends-view/friends-view.js | 3 - .../friends-view/save-settings-qdn.js | 60 +++++++++++++++++++ .../core/messaging/q-chat/q-chat.src.js | 2 - 9 files changed, 88 insertions(+), 50 deletions(-) create mode 100644 core/src/components/friends-view/save-settings-qdn.js diff --git a/core/src/components/app-view.js b/core/src/components/app-view.js index 814a18b9..cb12b505 100644 --- a/core/src/components/app-view.js +++ b/core/src/components/app-view.js @@ -44,7 +44,7 @@ import './start-minting.js' import './notification-view/notification-bell.js' import './notification-view/notification-bell-general.js' import './friends-view/friends-side-panel-parent.js' - +import './friends-view/save-settings-qdn.js' const parentEpml = new Epml({ type: 'WINDOW', source: window.parent }) @@ -560,6 +560,7 @@ class AppView extends connect(store)(LitElement) {
    + diff --git a/core/src/components/friends-view/add-friends-modal.js b/core/src/components/friends-view/add-friends-modal.js index eabe0adb..8d353d67 100644 --- a/core/src/components/friends-view/add-friends-modal.js +++ b/core/src/components/friends-view/add-friends-modal.js @@ -137,7 +137,6 @@ class AddFriendsModal extends LitElement { async updated(changedProperties) { if (changedProperties && changedProperties.has('editContent') && this.editContent) { - console.log('this.editedContent', this.editContent) this.userSelected = { name: this.editContent.name ?? '', } @@ -150,7 +149,6 @@ class AddFriendsModal extends LitElement { } render() { - console.log('hello2', this.editContent) return html`