From 435f6b190547f5e734c2a489e908c335d99ce439 Mon Sep 17 00:00:00 2001 From: PhilReact Date: Wed, 11 Oct 2023 21:59:34 -0500 Subject: [PATCH] extra fixes --- blog-test.json | 31 ++ core/language/us.json | 5 +- .../friends-view/add-friends-modal.js | 501 ++++++++++++------ core/src/components/friends-view/feed-item.js | 47 +- .../friends-view/friend-item-actions.js | 1 - .../components/friends-view/friends-feed.js | 133 ++++- .../friends-view/friends-side-panel.js | 10 +- .../components/friends-view/friends-view.js | 75 ++- .../components/notification-view/popover.js | 1 - 9 files changed, 584 insertions(+), 220 deletions(-) create mode 100644 blog-test.json diff --git a/blog-test.json b/blog-test.json new file mode 100644 index 00000000..35323b11 --- /dev/null +++ b/blog-test.json @@ -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;" + } + } + ] +} diff --git a/core/language/us.json b/core/language/us.json index 370137da..e43bec41 100644 --- a/core/language/us.json +++ b/core/language/us.json @@ -1190,6 +1190,9 @@ "friend10": "Edit friend", "friend11": "Following", "friend12": "Friends", - "friend13": "Feed" + "friend13": "Feed", + "friend14": "Remove friend", + "friend15": "Feed settings", + "friend16": "Select the Q-Apps you want updates from, especially those related to your friends." } } \ No newline at end of file diff --git a/core/src/components/friends-view/add-friends-modal.js b/core/src/components/friends-view/add-friends-modal.js index 8d353d67..20973871 100644 --- a/core/src/components/friends-view/add-friends-modal.js +++ b/core/src/components/friends-view/add-friends-modal.js @@ -10,8 +10,10 @@ import { import '@material/mwc-button'; import '@material/mwc-dialog'; import '@material/mwc-checkbox'; +import { connect } from 'pwa-helpers'; +import { store } from '../../store'; -class AddFriendsModal extends LitElement { +class AddFriendsModal extends connect(store)(LitElement) { static get properties() { return { isOpen: { type: Boolean }, @@ -21,9 +23,11 @@ class AddFriendsModal extends LitElement { alias: { type: String }, willFollow: { type: Boolean }, notes: { type: String }, - onSubmit: {attribute: false}, - editContent: {type: Object}, - onClose: {attribute: false} + onSubmit: { attribute: false }, + editContent: { type: Object }, + onClose: { attribute: false }, + mySelectedFeeds: { type: Array }, + availableFeeedSchemas: {type: Array} }; } @@ -34,18 +38,22 @@ class AddFriendsModal extends LitElement { this.alias = ''; this.willFollow = true; this.notes = ''; + this.nodeUrl = this.getNodeUrl(); + this.myNode = this.getMyNode(); + this.mySelectedFeeds = []; + this.availableFeeedSchemas = [] } 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; - } + * { + --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; @@ -69,9 +77,36 @@ class AddFriendsModal extends LitElement { color: var(--black); } - .close-button { - display: block; - --mdc-theme-primary: red; + .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; @@ -83,167 +118,331 @@ class AddFriendsModal extends LitElement { 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 { + display: block; 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; - } + 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; - } + 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; + } `; } firstUpdated() {} - clearFields(){ + 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(){ + addFriend() { + this.onSubmit({ + name: this.userSelected.name, + alias: this.alias, + notes: this.notes, + willFollow: this.willFollow, + mySelectedFeeds: this.mySelectedFeeds + + }); + this.clearFields(); + this.onClose(); + } - this.onSubmit({ - name: this.userSelected.name, - alias: this.alias, - notes: this.notes, - willFollow: this.willFollow - }) - 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) { + 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.notes = this.editContent.notes ?? ''; + this.willFollow = this.editContent.willFollow ?? true; + this.alias = this.editContent.alias ?? ''; + } + if ( + changedProperties && + changedProperties.has('isOpen') && this.isOpen + ) { + this.getAvailableFeedSchemas() } - + } + + async getAvailableFeedSchemas() { + try { + 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) {} } render() { return html` - `; } } diff --git a/core/src/components/friends-view/feed-item.js b/core/src/components/friends-view/feed-item.js index 12e72518..9c88affa 100644 --- a/core/src/components/friends-view/feed-item.js +++ b/core/src/components/friends-view/feed-item.js @@ -13,7 +13,10 @@ import { store } from '../../store'; import { setNewTab } from '../../redux/app/app-actions'; import ShortUniqueId from 'short-unique-id'; -const requestQueue = new RequestQueueWithPromise(5); +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() { @@ -140,7 +143,6 @@ export class FeedItem extends connect(store)(LitElement) { this.status = { status: '' } - this.url = "" this.isReady = false this.nodeUrl = this.getNodeUrl() this.myNode = this.getMyNode() @@ -195,21 +197,23 @@ getMyNode(){ 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' - } + 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 + // const responseData2 = await response2.json() + // return responseData2 } updateDisplayWithPlaceholders(display, resource, rawdata) { @@ -244,13 +248,16 @@ getMyNode(){ 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}`) + 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() + console.log({rawData}) const object = { - title: "$${rawdata.title}$$", + ...this.resource.schema.display } - this.updateDisplayWithPlaceholders(object, {},rawData) + this.updateDisplayWithPlaceholders(object, {},rawData.data) this.feedItem = object this.status = response.data @@ -301,8 +308,12 @@ getMyNode(){ // check if progress is 100% and clear interval if true if (res.status === 'READY') { - - this.feedItem = await this.getRawData() + 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 @@ -312,11 +323,7 @@ getMyNode(){ async _fetchImage() { try { - this.fetchVideoUrl({ - name: this.resource.name, - service: this.resource.service, - identifier: this.resource.identifier - }) + this.fetchVideoUrl() this.fetchStatus() } catch (error) { /* empty */ } } diff --git a/core/src/components/friends-view/friend-item-actions.js b/core/src/components/friends-view/friend-item-actions.js index 19ade862..e7013e72 100644 --- a/core/src/components/friends-view/friend-item-actions.js +++ b/core/src/components/friends-view/friend-item-actions.js @@ -90,7 +90,6 @@ export class FriendItemActions extends connect(store)(LitElement) { } attachToTarget(target) { - console.log({ target }); if (!this.popperInstance && target) { this.popperInstance = createPopper(target, this, { placement: 'bottom', diff --git a/core/src/components/friends-view/friends-feed.js b/core/src/components/friends-view/friends-feed.js index 691b07cd..d494cc4b 100644 --- a/core/src/components/friends-view/friends-feed.js +++ b/core/src/components/friends-view/friends-feed.js @@ -5,6 +5,8 @@ import { friendsViewStyles } from './friends-view-css'; import { connect } from 'pwa-helpers'; import { store } from '../../store'; import './feed-item' +import '@polymer/paper-spinner/paper-spinner-lite.js' + const perEndpointCount = 20; const totalDesiredCount = 100; @@ -13,7 +15,8 @@ class FriendsFeed extends connect(store)(LitElement) { static get properties() { return { feed: {type: Array}, - setHasNewFeed: {attribute:false} + setHasNewFeed: {attribute:false}, + isLoading: {type: Boolean} }; } constructor(){ @@ -57,30 +60,84 @@ class FriendsFeed extends connect(store)(LitElement) { return myNode; } + async getSchemas(schemas){ + + + + 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, 600000); + + } + async firstUpdated(){ this.viewElement = this.shadowRoot.getElementById('viewElement'); this.downObserverElement = this.shadowRoot.getElementById('downObserver'); this.elementObserver(); - const feedData = schema.feed[0] let schemaObj = {...schema} const dynamicVars = { } const getEndpoints = async () => { - - const baseurl = `${this.nodeUrl}/arbitrary/resources/search?reverse=true` - const fullUrl = constructUrl(baseurl, feedData.search, dynamicVars); - this.endpoints= [{url: fullUrl, schemaName: schema.name, schema: feedData }] - this.endpointOffsets = Array(this.endpoints.length).fill(0); + const schemas = await this.getSchemas(schemaList) + const friendList = JSON.parse(localStorage.getItem('friends-my-friend-list') || "[]") + console.log({friendList}) + const names = friendList.map(friend => `name=${friend.name}`).join('&'); + console.log({names}) + 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); } try { - getEndpoints() + await getEndpoints() this.loadAndMergeData(); - +this.getFeedOnInterval() } catch (error) { console.log(error) @@ -147,7 +204,7 @@ this.loadAndMergeData(); while (totalFetched < totalDesiredCount && madeProgress) { madeProgress = false; - + this.isLoading = true for (i = 0; i < this.endpoints.length; i++) { if (exhaustedEndpoints.has(i)) { continue; @@ -182,7 +239,7 @@ this.loadAndMergeData(); break; } } - + this.isLoading = false // Trim the results if somehow they are over the totalDesiredCount return results.slice(0, totalDesiredCount); } @@ -233,6 +290,37 @@ this.loadAndMergeData(); return newData } + async reFetchFeedData() { + // Resetting offsets to start fresh. + this.endpointOffsets = Array(this.endpoints.length).fill(0); + + 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 @@ -261,7 +349,11 @@ this.loadAndMergeData(); return html`
- + ${this.isLoading ? html` +
+ +
+ ` : ''} ${this.feedToRender.map((item) => { return html` 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.shadowRoot.querySelector('friends-feed').reFetchFeedData() + }} style="color: var(--black); cursor:pointer;">refresh { this.setIsOpen(false) }}>close +
+
@@ -118,9 +124,7 @@ class FriendsSidePanel extends LitElement {
this.setHasNewFeed(val)}>
- +
diff --git a/core/src/components/friends-view/friends-view.js b/core/src/components/friends-view/friends-view.js index cfbb248b..a024b124 100644 --- a/core/src/components/friends-view/friends-view.js +++ b/core/src/components/friends-view/friends-view.js @@ -40,7 +40,8 @@ class FriendsView extends connect(store)(LitElement) { userFoundModalOpen: {type: Boolean}, userFound: { type: Array}, isOpenAddFriendsModal: {type: Boolean}, - editContent: {type: Object} + editContent: {type: Object}, + mySelectedFeeds: {type: Array} }; } static get styles() { @@ -57,11 +58,7 @@ class FriendsView extends connect(store)(LitElement) { window.parent.reduxStore.getState().app.selectedAddress.address; this.errorMessage = ''; this.successMessage = ''; - this.friendList = [{ - - name: "Phil" - - }]; + this.friendList = []; this.userSelected = {}; this.isLoading = false; this.userFoundModalOpen = false @@ -99,6 +96,8 @@ class FriendsView extends connect(store)(LitElement) { 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') || "[]") } elementObserver() { @@ -177,41 +176,68 @@ class FriendsView extends connect(store)(LitElement) { 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) + + 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 + } + 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] = val + copyList[findFriend] = copyVal this.friendList = copyList } } else { - this.friendList = [...this.friendList, val] + this.friendList = [...this.friendList, copyVal] } - if(val.willFollow){ - this.myFollowName(val.name) - } - + if(!copyVal.willFollow || isRemove) { + this.unFollowName(copyVal.name) + } else if(copyVal.willFollow){ + this.myFollowName(copyVal.name) + } + this.setMySelectedFeeds(val.mySelectedFeeds) this.userSelected = {}; this.isLoading = false; this.isOpenAddFriendsModal = false this.editContent = null + this.setMyFriends(this.friendList) + } + setMyFriends(friendList){ + localStorage.setItem('friends-my-friend-list', JSON.stringify(friendList)); + } + setMySelectedFeeds(mySelectedFeeds){ + this.mySelectedFeeds = mySelectedFeeds + localStorage.setItem('friends-my-selected-feeds', JSON.stringify(mySelectedFeeds)); } openEditFriend(val){ this.isOpenAddFriendsModal = true this.userSelected = val - this.editContent = val + this.editContent = {...val, mySelectedFeeds: this.mySelectedFeeds} } onClose(){ @@ -288,9 +314,10 @@ class FriendsView extends connect(store)(LitElement) { this.isOpenAddFriendsModal = val }} .userSelected=${this.userSelected} - .onSubmit=${(val)=> this.addToFriendList(val)} + .onSubmit=${(val, isRemove)=> this.addToFriendList(val, isRemove)} .editContent=${this.editContent} .onClose=${()=> this.onClose()} + .mySelectedFeeds=${this.mySelectedFeeds} > `; diff --git a/core/src/components/notification-view/popover.js b/core/src/components/notification-view/popover.js index 43e85a94..6b835d91 100644 --- a/core/src/components/notification-view/popover.js +++ b/core/src/components/notification-view/popover.js @@ -40,7 +40,6 @@ export class PopoverComponent extends LitElement { } attachToTarget(target) { - console.log({target}) if (!this.popperInstance && target) { this.popperInstance = createPopper(target, this, { placement: 'bottom',