This commit is contained in:
@@ -0,0 +1,216 @@
|
||||
<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="map-container map-container-right" @touchstart.passive="onTouchStart" @touchend.passive="onTouchEnd">
|
||||
<Simplebar id="feed-panel" class="map-panel" 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="'map-control map-control-icon feed-control map-control-'+(isMobile()?'bottom':'top')" @click="toggle">
|
||||
<SpotIcon :icon="isOpen?'next':'post'" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
Reference in New Issue
Block a user