mirror of
https://github.com/Qortal/qortal-ui.git
synced 2025-02-11 17:55:51 +00:00
started friends feed
This commit is contained in:
parent
a20cd240ef
commit
8eacfca4d1
@ -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"
|
||||
}
|
||||
|
@ -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)}
|
||||
</span>
|
||||
</div>
|
||||
|
341
core/src/components/friends-view/feed-item.js
Normal file
341
core/src/components/friends-view/feed-item.js
Normal file
@ -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`
|
||||
<div
|
||||
class=${[
|
||||
`image-container`,
|
||||
this.status.status !== 'READY'
|
||||
? 'defaultSize'
|
||||
: '',
|
||||
this.status.status !== 'READY'
|
||||
? 'hideImg'
|
||||
: '',
|
||||
].join(' ')}
|
||||
>
|
||||
${
|
||||
this.status.status !== 'READY'
|
||||
? html`
|
||||
<div
|
||||
style="display:flex;flex-direction:column;width:100%;height:100%;justify-content:center;align-items:center;"
|
||||
>
|
||||
<div
|
||||
class=${`smallLoading`}
|
||||
></div>
|
||||
<p style="color: var(--black)">${`${Math.round(this.status.percentLoaded || 0
|
||||
).toFixed(0)}% `}${translate('chatpage.cchange94')}</p>
|
||||
</div>
|
||||
`
|
||||
: ''
|
||||
}
|
||||
${this.status.status === 'READY' && this.feedItem ? html`
|
||||
<div style="position:relative">
|
||||
ready
|
||||
</div>
|
||||
` : ''}
|
||||
|
||||
</div>
|
||||
|
||||
`
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('feed-item', FeedItem);
|
@ -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();
|
||||
}}"
|
||||
>
|
||||
<mwc-icon style="color: var(--black)"
|
||||
>edit</mwc-icon
|
||||
>
|
||||
${translate('friends.friend10')}
|
||||
</div>
|
||||
@ -186,6 +192,9 @@ export class FriendItemActions extends connect(store)(LitElement) {
|
||||
);
|
||||
this.closePopover();
|
||||
}}"
|
||||
>
|
||||
<mwc-icon style="color: var(--black)"
|
||||
>send</mwc-icon
|
||||
>
|
||||
${translate('friends.friend8')}
|
||||
</div>
|
||||
@ -212,6 +221,9 @@ export class FriendItemActions extends connect(store)(LitElement) {
|
||||
);
|
||||
this.closePopover();
|
||||
}}"
|
||||
>
|
||||
<mwc-icon style="color: var(--black)"
|
||||
>mail</mwc-icon
|
||||
>
|
||||
${translate('friends.friend9')}
|
||||
</div>
|
||||
|
@ -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`
|
||||
<div class="container">
|
||||
<div id="viewElement" class="container-body" style=${"position: relative"}>
|
||||
hi
|
||||
|
||||
|
||||
${this.feed.map((item) => {
|
||||
return html`<p>hello</p>`;
|
||||
return html`<feed-item
|
||||
.resource=${{
|
||||
name: item.name,
|
||||
service: item.service,
|
||||
identifier: item.identifier,
|
||||
}}
|
||||
></feed-item>`;
|
||||
})}
|
||||
<div id="downObserver"></div>
|
||||
</div>
|
||||
@ -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)**"
|
||||
|
@ -246,7 +246,7 @@ class FriendsView extends connect(store)(LitElement) {
|
||||
<vaadin-icon
|
||||
@click=${this.userSearch}
|
||||
slot="icon"
|
||||
icon="vaadin:open-book"
|
||||
icon="vaadin:search"
|
||||
class="search-icon">
|
||||
</vaadin-icon>
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user