This commit is contained in:
@@ -0,0 +1,236 @@
|
||||
<script>
|
||||
import appIcon from '@components/AppIcon';
|
||||
import appIconStack from '@components/AppIconStack';
|
||||
import appButton from '@components/AppButton';
|
||||
import projectMediaLink from '@components/ProjectMediaLink';
|
||||
import projectMapLink from '@components/ProjectMapLink';
|
||||
import projectRelTime from '@components/ProjectRelTime';
|
||||
import { LngLat } from 'maplibre-gl';
|
||||
import { copyTextToClipboard } from '@scripts/common';
|
||||
|
||||
import autosize from 'autosize';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
appIcon,
|
||||
appIconStack,
|
||||
appButton,
|
||||
projectMediaLink,
|
||||
projectMapLink,
|
||||
projectRelTime
|
||||
},
|
||||
props: {
|
||||
options: Object
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
mouseOverHeader: false,
|
||||
absTime: this.options.formatted_time,
|
||||
absTimeLocal: this.options.formatted_time_local,
|
||||
timeDiff: (this.options.formatted_time && this.options.formatted_time_local != this.options.formatted_time),
|
||||
anchorVisible: ['message', 'media', 'post'].includes(this.options.type),
|
||||
anchorTitle: this.lang.get('post.copy_to_clipboard'),
|
||||
anchorIcon: 'link',
|
||||
popupRequested: false,
|
||||
mouseOverDrill: false,
|
||||
postMessage: '',
|
||||
sending: false,
|
||||
focusZoomLevel: 15
|
||||
};
|
||||
},
|
||||
inject: ['api', 'lang', 'project', 'feed', 'user', 'map', 'hash', 'consts', 'isMobile', 'getAnchor'],
|
||||
computed: {
|
||||
postClass() {
|
||||
let sHeaderLess = this.options.headerless?' headerless':'';
|
||||
return 'post-item '+this.options.type+sHeaderLess;
|
||||
},
|
||||
postId() {
|
||||
return this.options.id?(this.options.type+'-'+this.options.id):'';
|
||||
},
|
||||
subType() {
|
||||
return this.options.subtype || this.options.type;
|
||||
},
|
||||
displayedId() {
|
||||
return this.options.displayed_id?(this.lang.get('feed.counter', this.options.displayed_id)):'';
|
||||
},
|
||||
drillMainIcon() {
|
||||
return this.mouseOverDrill?'drill-message':'marker';
|
||||
},
|
||||
drillSubIcon() {
|
||||
return this.mouseOverDrill?null:'footprint';
|
||||
},
|
||||
anchorLink() {
|
||||
return this.getAnchor([this.hash.page, this.project.project.codename, this.options.type, this.options.id]);
|
||||
},
|
||||
modeHisto() {
|
||||
return (this.project?.project?.mode == this.consts.modes.histo);
|
||||
},
|
||||
relTime() {
|
||||
return this.modeHisto?(this.options.formatted_time || '').substr(0, 10):this.options.relative_time;
|
||||
},
|
||||
relatedMarker() {
|
||||
//Find corresponding marker
|
||||
if(!this.options.longitude && !this.options.latitude && this.options.type == 'media') {
|
||||
return this.project.markers.find((marker) => {
|
||||
return (marker.medias || []).some((media) => {
|
||||
return media.id_media == this.options.id_media;
|
||||
});
|
||||
}) || null;
|
||||
}
|
||||
else if(
|
||||
['message', 'media'].includes(this.options.type)
|
||||
&& this.options.longitude
|
||||
&& this.options.latitude
|
||||
) {
|
||||
return this.options;
|
||||
}
|
||||
else return null;
|
||||
},
|
||||
relatedMarkerLatLng() {
|
||||
let oRelatedMarker = this.relatedMarker;
|
||||
return new LngLat(oRelatedMarker.longitude, oRelatedMarker.latitude);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
copyAnchor() {
|
||||
copyTextToClipboard(this.consts.server+this.anchorLink);
|
||||
this.anchorTitle = this.lang.get('post.link_copied');
|
||||
this.anchorIcon = 'copied';
|
||||
setTimeout(()=>{ //TODO animation
|
||||
this.anchorTitle = this.lang.get('post.copy_to_clipboard');
|
||||
this.anchorIcon = 'link';
|
||||
}, 5000);
|
||||
},
|
||||
panMapToMarker(iAnimDuration=500) {
|
||||
if(typeof iAnimDuration !== 'number') iAnimDuration = 500; //panMapToMarker will provide event on direct call in vue template
|
||||
|
||||
this.popupRequested = true;
|
||||
|
||||
if(this.isMobile()) this.feed.toggle(false);
|
||||
this.hash.items = [this.project.project.codename, this.options.type, this.options.id];
|
||||
|
||||
return this.map.panToBetweenPanels(this.relatedMarkerLatLng, this.focusZoomLevel, iAnimDuration).then(() => {
|
||||
this.openMarkerPopup();
|
||||
});
|
||||
},
|
||||
onMouseEnter() {
|
||||
this.mouseOverDrill = true;
|
||||
this.openMarkerPopup();
|
||||
},
|
||||
onMouseLeave() {
|
||||
this.mouseOverDrill = false;
|
||||
this.closeMarkerPopup();
|
||||
},
|
||||
openMarkerPopup() {
|
||||
const oRelatedMarker = this.relatedMarker;
|
||||
if(oRelatedMarker && this.map.isMarkerVisible(this.relatedMarkerLatLng)) {
|
||||
this.map.openMarkerPopup(oRelatedMarker.id, oRelatedMarker.type);
|
||||
}
|
||||
},
|
||||
closeMarkerPopup() {
|
||||
if(!this.popupRequested) this.map.closePopup();
|
||||
this.popupRequested = false;
|
||||
},
|
||||
send() {
|
||||
if(this.postMessage != '' && this.user.name != '') {
|
||||
this.sending = true;
|
||||
this.api.post(
|
||||
'add_post',
|
||||
{
|
||||
id_project: this.project.project.id,
|
||||
name: this.user.name,
|
||||
content: this.postMessage
|
||||
}
|
||||
)
|
||||
.then(() => {
|
||||
this.postMessage = '';
|
||||
this.feed.checkNewFeed();
|
||||
this.sending = false;
|
||||
})
|
||||
.catch((sDesc) => {
|
||||
this.sending = false;
|
||||
});
|
||||
}
|
||||
},
|
||||
executeMainAction() {
|
||||
switch(this.options.type) {
|
||||
case 'message':
|
||||
return this.openMarkerPopup();
|
||||
case 'media':
|
||||
this.$refs.medialink.openMedia();
|
||||
if(this.relatedMarker) return this.openMarkerPopup();
|
||||
default:
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
//Auto-adjust text area height
|
||||
if(this.options.type == 'poster') autosize(this.$refs.post);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :class="postClass" :id="postId">
|
||||
<div class="header" v-if="!options.headerless">
|
||||
<div class="index">
|
||||
<appIcon :icon="subType" :text="displayedId" width="auto" />
|
||||
<a v-if="anchorVisible" class="link desktop" @click="copyAnchor" ref="anchor" :href="anchorLink" :title="anchorTitle">
|
||||
<appIcon :icon="anchorIcon" />
|
||||
</a>
|
||||
</div>
|
||||
<div class="time" @mouseleave="mouseOverHeader = false" @mouseover="mouseOverHeader = true" :title="timeDiff?lang.get('time.local', absTimeLocal):''">
|
||||
<Transition name="fade" mode="out-in">
|
||||
<span v-if="mouseOverHeader">{{ timeDiff?lang.get('time.user', absTime):absTime }}</span>
|
||||
<span v-else>{{ relTime }}</span>
|
||||
</Transition>
|
||||
</div>
|
||||
</div>
|
||||
<div class="body">
|
||||
<div v-if="options.type == 'message'" class="body-box">
|
||||
<div class="drill" @click.prevent="panMapToMarker" @pointerenter="onMouseEnter" @pointerleave="onMouseLeave">
|
||||
<span v-if="options.weather_icon && options.weather_icon!='unknown'" class="weather clickable" :title="lang.get('weather.'+options.weather_icon)">
|
||||
<appIcon :icon="options.weather_icon" :text="Math.round(options.weather_temp)+'°C'" text-classes="temperature" />
|
||||
</span>
|
||||
<img class="staticmap clickable" :title="lang.get('media.click_zoom')" :src="options.static_img_url" />
|
||||
<appIconStack mainClasses="message drill-icon" :iconMain="drillMainIcon" :iconSub="drillSubIcon" icon-sub-transform="rotate-270" />
|
||||
<div class="comment" @click.stop>
|
||||
<p v-if="!isMobile()">
|
||||
<appIcon :icon="'coords'" margin="right" size="lg" />
|
||||
<projectMapLink :options="options" />
|
||||
</p>
|
||||
<p>
|
||||
<projectRelTime :icon="'time'" :localTime="absTimeLocal" :siteTime="options.formatted_time" :offset="options.day_offset" />
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else-if="options.type == 'media'" class="body-box">
|
||||
<projectMediaLink :options="options" :type="'post'" ref="medialink" @opening-lightbox="panMapToMarker" />
|
||||
</div>
|
||||
<div v-else-if="options.type == 'post'">
|
||||
<p class="message">{{ options.content }}</p>
|
||||
<p class="signature">
|
||||
<img v-if="options.gravatar" :src="'data:image/png;base64, '+options.gravatar" width="24" height="24" alt="--" />
|
||||
<span v-else>-- </span>
|
||||
<span>{{ options.formatted_name }}</span>
|
||||
</p>
|
||||
</div>
|
||||
<div v-else-if="options.type == 'poster'" class="poster-form">
|
||||
<textarea ref="post" name="post" :placeholder="lang.get('post.message')" class="autoExpand" rows="1" v-model="postMessage"></textarea>
|
||||
<div class="poster-actions">
|
||||
<input type="text" name="name" :placeholder="lang.get('post.name')" v-model="user.name" />
|
||||
<appButton name="submit" :aria-label="lang.get('action.send')" :title="lang.get('action.send')" :icon="'send'" @click="send()" :iconClasses="sending?'flicker':''" />
|
||||
</div>
|
||||
</div>
|
||||
<div v-else-if="options.type == 'archived'">
|
||||
<p><appIcon :icon="'success'" /></p>
|
||||
<p>{{ lang.get('project.modes.histo') }}</p>
|
||||
</div>
|
||||
<div v-else-if="options.type == 'loading'">
|
||||
<p class="flicker"><appIcon :icon="'post'" /></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
Reference in New Issue
Block a user