217 lines
5.9 KiB
Vue
217 lines
5.9 KiB
Vue
<script>
|
|
import Simplebar from 'simplebar-vue';
|
|
|
|
import SpotIcon from '@components/spotIcon';
|
|
import ProjectPost from '@components/projectPost';
|
|
|
|
export default {
|
|
components: {
|
|
SpotIcon,
|
|
ProjectPost,
|
|
Simplebar
|
|
},
|
|
props: {
|
|
project: Object,
|
|
modeHisto: Boolean
|
|
},
|
|
data() {
|
|
return {
|
|
loading: false,
|
|
updatable: true,
|
|
outOfData: false,
|
|
refIdFirst: 0,
|
|
refIdLast: 0,
|
|
firstChunk: true,
|
|
isOpen: false,
|
|
posts: [],
|
|
refreshRate: 60,
|
|
swipe: {x: null, y: null}
|
|
};
|
|
},
|
|
emits: ['request-last-update', 'new-markers', 'toggle'],
|
|
inject: ['api', 'lang', 'consts', 'isMobile'],
|
|
provide() {
|
|
return {
|
|
feed: {
|
|
checkNewFeed: this.checkNewFeed
|
|
}
|
|
};
|
|
},
|
|
watch: {
|
|
project() {
|
|
this.syncUpdateTimer();
|
|
},
|
|
modeHisto() {
|
|
this.syncUpdateTimer();
|
|
},
|
|
firstChunk() {
|
|
this.syncUpdateTimer();
|
|
}
|
|
},
|
|
mounted() {
|
|
this.getScrollElement()?.addEventListener('scroll', this.onFeedScroll);
|
|
this.syncUpdateTimer();
|
|
},
|
|
beforeUnmount() {
|
|
this.getScrollElement()?.removeEventListener('scroll', this.onFeedScroll);
|
|
this.setUpdateTimer(-1);
|
|
},
|
|
methods: {
|
|
async init() {
|
|
this.setUpdateTimer(-1);
|
|
this.loading = false;
|
|
this.updatable = true;
|
|
this.outOfData = false;
|
|
this.refIdFirst = 0;
|
|
this.refIdLast = 0;
|
|
this.firstChunk = true;
|
|
this.posts = [];
|
|
this.swipe = {x: null, y: null};
|
|
|
|
await this.$nextTick();
|
|
this.toggle(!this.isMobile(), 0);
|
|
|
|
await this.getNextFeed();
|
|
this.getScrollElement().scrollTop = 0;
|
|
this.syncUpdateTimer();
|
|
},
|
|
getScrollElement() {
|
|
return this.$refs.feedSimpleBar?.scrollElement;
|
|
},
|
|
async findPost(sPostType, iPostId) {
|
|
let vPost = await this.goToPost(sPostType, iPostId);
|
|
if(vPost) {
|
|
await vPost.executeMainAction(0);
|
|
return vPost;
|
|
}
|
|
else if(!this.outOfData) {
|
|
await this.getNextFeed();
|
|
await this.$nextTick();
|
|
return this.findPost(sPostType, iPostId);
|
|
}
|
|
else console.log('Missing element ID "'+iPostId+'" of type "'+sPostType+'"');
|
|
return null;
|
|
},
|
|
async goToPost(sPostType, iPostId) {
|
|
let avPosts = this.$refs.posts.filter((post) => {return post.postId == sPostType+'-'+iPostId;});
|
|
if(avPosts.length == 0) return null;
|
|
|
|
//Force next update to have enough subsequent elements to position the post on top of the page
|
|
await this.getNextFeed();
|
|
|
|
let vPost = avPosts[0];
|
|
this.getScrollElement().scrollTop += Math.round(
|
|
vPost.$el.getBoundingClientRect().top
|
|
+ window.pageYOffset
|
|
- parseFloat(getComputedStyle(this.$refs.feedSimpleBar.$el).paddingTop)
|
|
);
|
|
|
|
return vPost;
|
|
},
|
|
async getNextFeed() {
|
|
if(!this.project || this.outOfData || this.loading) return true;
|
|
|
|
//Get next chunk
|
|
this.loading = true;
|
|
let aoData = await this.api.get('next_feed', {id_project: this.project.id, id: this.refIdLast});
|
|
let iPostCount = Object.keys(aoData.feed).length;
|
|
|
|
//Update pointers
|
|
this.outOfData = (iPostCount < this.consts.chunk_size);
|
|
if(iPostCount > 0) {
|
|
this.refIdLast = aoData.ref_id_last;
|
|
if(this.firstChunk) this.refIdFirst = aoData.ref_id_first;
|
|
}
|
|
|
|
//Add posts
|
|
this.posts.push(...aoData.feed);
|
|
|
|
this.loading = false;
|
|
this.firstChunk = false;
|
|
|
|
return true;
|
|
},
|
|
onFeedScroll(oEvent) {
|
|
const box = oEvent.currentTarget;
|
|
const content = box.querySelector('.simplebar-content');
|
|
|
|
if((box.scrollTop + box.clientHeight) / (content?.offsetHeight || 1) >= 0.8) this.getNextFeed();
|
|
},
|
|
onTouchStart(oEvent) {
|
|
if(!this.isMobile() || !this.isOpen || oEvent.touches.length != 1) return;
|
|
|
|
const oTouch = oEvent.touches[0];
|
|
this.swipe = {x: oTouch.clientX, y: oTouch.clientY};
|
|
},
|
|
onTouchEnd(oEvent) {
|
|
const oTouch = oEvent.changedTouches[0];
|
|
if(!oTouch || this.swipe.x === null) return;
|
|
|
|
const iDeltaX = oTouch.clientX - this.swipe.x;
|
|
const iDeltaY = oTouch.clientY - this.swipe.y;
|
|
|
|
if(iDeltaX > 80 && Math.abs(iDeltaX) > Math.abs(iDeltaY) * 1.5) this.toggle();
|
|
|
|
this.swipe = {x: null, y: null};
|
|
},
|
|
setUpdateTimer(iSeconds) {
|
|
if(typeof this.feedTimer != 'undefined') clearTimeout(this.feedTimer);
|
|
if(iSeconds >= 0) this.feedTimer = setTimeout(this.onUpdateTimer, iSeconds * 1000);
|
|
},
|
|
syncUpdateTimer() {
|
|
this.setUpdateTimer((!!this.project && !this.modeHisto && !this.firstChunk)?this.refreshRate:-1);
|
|
},
|
|
async onUpdateTimer() {
|
|
await this.checkNewFeed();
|
|
this.syncUpdateTimer();
|
|
},
|
|
async checkNewFeed() {
|
|
if(!this.project) return;
|
|
|
|
let aoData = await this.api.get('new_feed', {id_project: this.project.id, id: this.refIdFirst});
|
|
const aoFeed = aoData.feed || [];
|
|
const aoMarkers = aoData.markers || [];
|
|
|
|
if(aoFeed.length > 0) {
|
|
//Update pointer
|
|
this.refIdFirst = aoData.ref_id_first;
|
|
|
|
//Add new posts
|
|
this.posts.unshift(...aoFeed);
|
|
}
|
|
|
|
if(aoMarkers.length > 0) this.$emit('new-markers', aoMarkers);
|
|
this.$emit('request-last-update');
|
|
},
|
|
toggle(bShow, iAnimDuration=500) {
|
|
this.isOpen = (typeof bShow == 'boolean')?bShow:(!this.isOpen);
|
|
this.$emit('toggle', this.isOpen, iAnimDuration);
|
|
return this.isOpen;
|
|
},
|
|
getWidth() {
|
|
return this.$el.getBoundingClientRect().width;
|
|
}
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<div id="feed" class="panel panel-right" @touchstart.passive="onTouchStart" @touchend.passive="onTouchEnd">
|
|
<Simplebar id="feed-panel" class="panel-content" ref="feedSimpleBar">
|
|
<div id="feed-header">
|
|
<ProjectPost v-if="modeHisto" :options="{type: 'archived', headerless: true}" />
|
|
<ProjectPost v-else :options="{type: 'poster', relative_time: lang.get('post.new_message')}" />
|
|
</div>
|
|
<div v-if="project" id="feed-posts">
|
|
<ProjectPost v-for="post in posts" :options="post" ref="posts" />
|
|
</div>
|
|
<div id="feed-footer" v-if="loading">
|
|
<ProjectPost :options="{type: 'loading', headerless: true}" />
|
|
</div>
|
|
</Simplebar>
|
|
<div v-if="project" :class="'panel-control panel-control-icon panel-control-'+(isMobile()?'bottom':'top')" @click="toggle">
|
|
<SpotIcon :icon="isOpen?'next':'post'" />
|
|
</div>
|
|
</div>
|
|
</template>
|