Replace leaflet with maplibre GL
This commit is contained in:
@@ -74,8 +74,7 @@ module.exports = {
|
|||||||
plugins: [
|
plugins: [
|
||||||
new webpack.ProvidePlugin({
|
new webpack.ProvidePlugin({
|
||||||
$: require.resolve('jquery'),
|
$: require.resolve('jquery'),
|
||||||
jQuery: require.resolve('jquery'),
|
jQuery: require.resolve('jquery')
|
||||||
//L: require.resolve('leaflet')
|
|
||||||
}),
|
}),
|
||||||
new CopyWebpackPlugin({
|
new CopyWebpackPlugin({
|
||||||
patterns: [{
|
patterns: [{
|
||||||
@@ -93,7 +92,8 @@ module.exports = {
|
|||||||
new CleanWebpackPlugin(),
|
new CleanWebpackPlugin(),
|
||||||
new webpack.DefinePlugin({
|
new webpack.DefinePlugin({
|
||||||
__VUE_OPTIONS_API__: 'true',
|
__VUE_OPTIONS_API__: 'true',
|
||||||
__VUE_PROD_DEVTOOLS__: 'false'
|
__VUE_PROD_DEVTOOLS__: 'false',
|
||||||
|
__VUE_PROD_HYDRATION_MISMATCH_DETAILS__: 'false'
|
||||||
}),
|
}),
|
||||||
new VueLoaderPlugin()
|
new VueLoaderPlugin()
|
||||||
],
|
],
|
||||||
|
|||||||
1124
package-lock.json
generated
1124
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -2,7 +2,6 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.23.2",
|
"@babel/core": "^7.23.2",
|
||||||
"@babel/preset-env": "^7.23.2",
|
"@babel/preset-env": "^7.23.2",
|
||||||
"@vue-leaflet/vue-leaflet": "^0.10.1",
|
|
||||||
"babel-loader": "^9.1.3",
|
"babel-loader": "^9.1.3",
|
||||||
"resolve-url-loader": "^5.0.0",
|
"resolve-url-loader": "^5.0.0",
|
||||||
"symlink-webpack-plugin": "^1.1.0",
|
"symlink-webpack-plugin": "^1.1.0",
|
||||||
@@ -34,10 +33,8 @@
|
|||||||
"jquery": "^3.7.1",
|
"jquery": "^3.7.1",
|
||||||
"jquery-mousewheel": "^3.1.13",
|
"jquery-mousewheel": "^3.1.13",
|
||||||
"jquery.waitforimages": "^2.4.0",
|
"jquery.waitforimages": "^2.4.0",
|
||||||
"leaflet": "^1.9.4",
|
|
||||||
"leaflet-geometryutil": "^0.10.2",
|
|
||||||
"leaflet.heightgraph": "^1.4.0",
|
|
||||||
"lightbox2": "^2.11.4",
|
"lightbox2": "^2.11.4",
|
||||||
|
"maplibre-gl": "^3.6.2",
|
||||||
"resize-observer-polyfill": "^1.5.1",
|
"resize-observer-polyfill": "^1.5.1",
|
||||||
"sass": "^1.69.4",
|
"sass": "^1.69.4",
|
||||||
"sass-loader": "^13.3.2",
|
"sass-loader": "^13.3.2",
|
||||||
|
|||||||
@@ -61,7 +61,8 @@ export default {
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div id="#main">
|
<div id="main">
|
||||||
<component :is="currentView" />
|
<component :is="currentView" />
|
||||||
</div>
|
</div>
|
||||||
|
<div id="mobile" class="mobile"></div>
|
||||||
</template>
|
</template>
|
||||||
@@ -1,44 +1,299 @@
|
|||||||
<script>
|
<script>
|
||||||
//Leaflet
|
import 'maplibre-gl/dist/maplibre-gl.css';
|
||||||
import 'leaflet';
|
import { Map, NavigationControl, Marker } from 'maplibre-gl';
|
||||||
import 'leaflet-geometryutil';
|
|
||||||
import 'leaflet.heightgraph';
|
import lightbox from '../scripts/lightbox.js';
|
||||||
import '../scripts/leaflet.helpers';
|
|
||||||
import { LMap, LTileLayer } from "@vue-leaflet/vue-leaflet";
|
import SimpleBar from 'simplebar';
|
||||||
|
|
||||||
|
import SpotIcon from './spotIcon.vue';
|
||||||
|
import SpotButton from './spotButton.vue';
|
||||||
|
import ProjectPost from './projectPost.vue';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
LMap,
|
SpotIcon,
|
||||||
LTileLayer
|
SpotButton,
|
||||||
|
ProjectPost
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
server: this.spot.consts.server,
|
server: this.spot.consts.server,
|
||||||
zoom: 13
|
feedPanelOpen: this.spot.isMobile(),
|
||||||
|
settingsPanelOpen: true,
|
||||||
|
markerSize: {width: 32, height: 32},
|
||||||
|
simpleBar: null,
|
||||||
|
project: {},
|
||||||
|
modeHisto: false,
|
||||||
|
posts: [],
|
||||||
|
nlFeedbacks: [],
|
||||||
|
nlLoading: false,
|
||||||
|
feed: {loading:true, updatable:true, outOfData:false, refIdFirst:0, refIdLast:0},
|
||||||
|
user: {name:'', email:''}
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
computed: {
|
||||||
|
projectClasses() {
|
||||||
|
return [
|
||||||
|
this.feedPanelOpen?'with-feed':'',
|
||||||
|
this.settingsPanelOpen?'with-settings':''
|
||||||
|
].filter(n => n).join(' ');
|
||||||
|
},
|
||||||
|
nlClasses() {
|
||||||
|
return [
|
||||||
|
this.nlAction,
|
||||||
|
this.nlLoading?'loading':''
|
||||||
|
].filter(n => n).join(' ');
|
||||||
|
},
|
||||||
|
subscribed() {
|
||||||
|
return this.user.id_user > 0;
|
||||||
|
},
|
||||||
|
nlAction() {
|
||||||
|
return this.subscribed?'unsubscribe':'subscribe';
|
||||||
|
}
|
||||||
|
},
|
||||||
inject: ['spot'],
|
inject: ['spot'],
|
||||||
mounted() {
|
mounted() {
|
||||||
|
//Set default project
|
||||||
if(this.$parent.hash.items.length==0) this.$parent.setHash(this.$parent.hash.page, [this.spot.vars('default_project_codename')]);
|
if(this.$parent.hash.items.length==0) this.$parent.setHash(this.$parent.hash.page, [this.spot.vars('default_project_codename')]);
|
||||||
|
else {
|
||||||
|
this.initEvents();
|
||||||
|
this.initProject();
|
||||||
|
this.initLightbox();
|
||||||
|
this.initFeed();
|
||||||
|
this.initSettings();
|
||||||
|
this.initMap();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
initEvents() {
|
||||||
|
this.spot.addPage('project', {
|
||||||
|
//TODO
|
||||||
|
});
|
||||||
|
},
|
||||||
|
initProject() {
|
||||||
|
this.project = this.spot.vars(['projects', this.$parent.hash.items[0]]);
|
||||||
|
this.modeHisto = (this.project.mode == this.spot.consts.modes.histo);
|
||||||
|
|
||||||
|
if(!this.spot.isMobile()) this.toggleFeedPanel(true, 'none');
|
||||||
|
},
|
||||||
|
initLightbox() {
|
||||||
|
lightbox.option({
|
||||||
|
alwaysShowNavOnTouchDevices: true,
|
||||||
|
albumLabel: '<i class="fa fa-fw fa-lg fa-media push"></i> %1 / %2',
|
||||||
|
fadeDuration: 300,
|
||||||
|
imageFadeDuration: 400,
|
||||||
|
positionFromTop: 0,
|
||||||
|
resizeDuration: 400,
|
||||||
|
hasVideo: true,
|
||||||
|
onMediaChange: (oMedia) => {
|
||||||
|
this.spot.updateHash('media', oMedia.id);
|
||||||
|
if(oMedia.set == 'post-medias') this.goToPost({type: 'media', id: oMedia.id});
|
||||||
|
},
|
||||||
|
onClosing: () => {this.spot.flushHash();}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
async initFeed() {
|
||||||
|
//Simplebar
|
||||||
|
this.simpleBar = new SimpleBar(this.$refs.feedPanel);
|
||||||
|
this.simpleBar.getScrollElement().addEventListener('scroll', (oEvent) => {this.onFeedScroll(oEvent);});
|
||||||
|
|
||||||
|
//Mobile Touchscreen Events
|
||||||
|
//TODO
|
||||||
|
|
||||||
|
//Add post Event handling
|
||||||
|
//TODO
|
||||||
|
|
||||||
|
await this.updateFeed(true, false);
|
||||||
|
|
||||||
|
//Scroll to post
|
||||||
|
if(this.$parent.hash.items.length == 3) this.goToPost({type: this.$parent.hash.items[1], id: this.$parent.hash.items[2]});
|
||||||
|
},
|
||||||
|
initSettings() {
|
||||||
|
//Scrollbar
|
||||||
|
new SimpleBar(this.$refs.settingsPanel);
|
||||||
|
|
||||||
|
this.user = this.spot.vars('user');
|
||||||
|
},
|
||||||
|
async updateFeed(bFirstChunk, bShowLoading) {
|
||||||
|
bFirstChunk = bFirstChunk || false;
|
||||||
|
bShowLoading = bShowLoading || true;
|
||||||
|
|
||||||
|
if(!this.feed.outOfData || bFirstChunk) {
|
||||||
|
//Get next chunk
|
||||||
|
if(bShowLoading) this.feed.loading = true;
|
||||||
|
let aoData = await this.spot.get2('next_feed', {id_project: this.project.id, id: this.feed.refIdLast});
|
||||||
|
let iPostCount = Object.keys(aoData.feed).length;
|
||||||
|
this.feed.loading = false;
|
||||||
|
|
||||||
|
//Update pointers
|
||||||
|
this.feed.outOfData = (iPostCount < this.spot.vars('chunk_size'));
|
||||||
|
if(iPostCount > 0) {
|
||||||
|
this.feed.refIdLast = aoData.ref_id_last;
|
||||||
|
if(bFirstChunk) this.feed.refIdFirst = aoData.ref_id_first;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Add posts
|
||||||
|
this.posts.push(...aoData.feed);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async manageSubs() {
|
||||||
|
var regexEmail = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
|
||||||
|
if(!regexEmail.test(this.user.email)) this.nlFeedbacks.push({type:'error', 'msg':this.spot.lang('nl_invalid_email')});
|
||||||
|
else {
|
||||||
|
this.spot.get2(this.nlAction, {'email': this.user.email, 'name': this.user.name}, this.nlLoading)
|
||||||
|
.then((asUser, sDesc) => {
|
||||||
|
this.nlFeedbacks.push('success', sDesc);
|
||||||
|
this.user = asUser;
|
||||||
|
})
|
||||||
|
.catch((sDesc) => {this.nlFeedbacks.push('error', sDesc);});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onFeedScroll(oEvent) {
|
||||||
|
/* TODO
|
||||||
|
var $Box = $(oEvent.currentTarget);
|
||||||
|
var $BoxContent = $Box.find('.simplebar-content');
|
||||||
|
if(($Box.scrollTop() + $(window).height()) / $BoxContent.height() >= 0.8) this.updateFeed();
|
||||||
|
*/
|
||||||
|
},
|
||||||
|
initMap() {
|
||||||
|
const map = new Map({
|
||||||
|
container: 'map',
|
||||||
|
style: {
|
||||||
|
version: 8,
|
||||||
|
sources: {
|
||||||
|
MIERUNEMAP: {
|
||||||
|
type: 'raster',
|
||||||
|
tiles: ['https://tile.mierune.co.jp/mierune_mono/{z}/{x}/{y}.png'],
|
||||||
|
tileSize: 256,
|
||||||
|
attribution:
|
||||||
|
"Maptiles by <a href='http://mierune.co.jp/' target='_blank'>MIERUNE</a>, under CC BY. Data by <a href='http://osm.org/copyright' target='_blank'>OpenStreetMap</a> contributors, under ODbL."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
layers: [
|
||||||
|
{
|
||||||
|
id: 'MIERUNEMAP',
|
||||||
|
type: 'raster',
|
||||||
|
source: 'MIERUNEMAP',
|
||||||
|
minzoom: 0,
|
||||||
|
maxzoom: 18
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
center: [139.767, 35.681],
|
||||||
|
zoom: 11
|
||||||
|
});
|
||||||
|
},
|
||||||
|
toggleFeedPanel(bShow, sMapAction) {
|
||||||
|
let bOldValue = this.feedPanelOpen;
|
||||||
|
this.feedPanelOpen = (typeof bShow === 'undefined')?(!this.feedPanelOpen):bShow;
|
||||||
|
|
||||||
|
if(bOldValue != this.feedPanelOpen) {
|
||||||
|
this.spot.onResize();
|
||||||
|
sMapAction = sMapAction || 'panTo';
|
||||||
|
/* TODO
|
||||||
|
switch(sMapAction) {
|
||||||
|
case 'none':
|
||||||
|
break;
|
||||||
|
case 'panTo':
|
||||||
|
this.spot.tmp('map').panBy(
|
||||||
|
[(this.isFeedPanelOpen()?1:-1) * this.spot.tmp('$Feed').outerWidth(true) / 2, 0],
|
||||||
|
{duration: 0.5}
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case 'panToInstant':
|
||||||
|
this.spot.tmp('map').panBy([(this.isFeedPanelOpen()?1:-1) * this.spot.tmp('$Feed').outerWidth(true) / 2, 0]);
|
||||||
|
break;
|
||||||
|
case 'fitBounds':
|
||||||
|
this.spot.tmp('map').fitBounds(
|
||||||
|
this.spot.tmp('track').getBounds(),
|
||||||
|
{
|
||||||
|
paddingTopLeft: L.point(5, this.spot.tmp('marker_size').height + 5),
|
||||||
|
paddingBottomRight: L.point(this.spot.tmp('$Feed').outerWidth(true) + 5, 5)
|
||||||
|
}
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
},
|
||||||
|
goToPost(oPost) {
|
||||||
|
/*
|
||||||
|
var sElemId = '#'+oPost.type+'-'+oPost.id;
|
||||||
|
var $Post = this.spot.tmp('$PostList').find(sElemId);
|
||||||
|
if($Post.length > 0) {
|
||||||
|
this.spot.tmp('simple-bar').getScrollElement().scrollTop += Math.round(
|
||||||
|
$Post.offset().top
|
||||||
|
- parseInt($('#feed-panel').css('padding-top'))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else console.log('Missing element ID '+sElemId);
|
||||||
|
*/
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div id="projects">
|
<div id="projects" :class="projectClasses">
|
||||||
<div id="background"></div>
|
<div id="background"></div>
|
||||||
<div id="submap">
|
<div id="submap">
|
||||||
<div class="loader fa fa-fw fa-map flicker" id="map_loading"></div>
|
<div class="loader fa fa-fw fa-map flicker"></div>
|
||||||
|
</div>
|
||||||
|
<div id="map"></div>
|
||||||
|
<div id="settings">
|
||||||
|
<div id="settings-panel">
|
||||||
|
<div class="settings-header">
|
||||||
|
<div class="logo"><img width="289" height="72" src="images/logo_black.png" alt="Spotty" /></div>
|
||||||
|
<div id="last_update"><p><span><img src="images/spot-logo-only.svg" alt="" /></span><abbr></abbr></p></div>
|
||||||
|
</div>
|
||||||
|
<div class="settings-sections">
|
||||||
|
<div ref="settingsPanel" id="settings-sections-scrollbox">
|
||||||
|
<div class="settings-section">
|
||||||
|
<h1><SpotIcon :icon="'project fa-fw'" :text="spot.lang('hikes')" /></h1>
|
||||||
|
<div id="settings-projects"></div>
|
||||||
|
</div>
|
||||||
|
<div class="settings-section">
|
||||||
|
<h1><SpotIcon :icon="'map fa-fw'" :text="spot.lang('maps')" /></h1>
|
||||||
|
<div id="layers"></div>
|
||||||
|
</div>
|
||||||
|
<div class="settings-section newsletter">
|
||||||
|
<h1><SpotIcon :icon="'newsletter fa-fw'" :text="spot.lang('newsletter')" /></h1>
|
||||||
|
<input type="email" name="email" id="email" :placeholder="spot.lang('nl_email_placeholder')" v-model="user.email" :disabled="nlLoading || subscribed" />
|
||||||
|
<SpotButton id="nl_btn" :classes="nlClasses" :title="spot.lang('nl_'+nlAction)" @click="manageSubs" />
|
||||||
|
<div id="settings-feedback" class="feedback">
|
||||||
|
<p v-for="feedback in nlFeedbacks" :class="feedback.type">
|
||||||
|
<SpotIcon :icon="feedback.type" :text="feedback.msg" />
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
{{ spot.lang(subscribed?'nl_subscribed_desc':'nl_unsubscribed_desc') }}
|
||||||
|
</div>
|
||||||
|
<div class="settings-section admin" v-if="spot.checkClearance(spot.consts.clearances.admin)">
|
||||||
|
<h1><SpotIcon :icon="'admin fa-fw'" :text="spot.lang('admin')" /></h1>
|
||||||
|
<SpotButton :text="spot.lang('admin_config')" :icon="'config'" href="#admin" />
|
||||||
|
<SpotButton :text="spot.lang('admin_upload')" :icon="'upload'" href="#upload" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="settings-footer">
|
||||||
|
<a href="https://git.lutran.fr/franzz/spot" :title="spot.lang('credits_git')" target="_blank" rel="noopener">
|
||||||
|
<SpotIcon :icon="'credits'" :text="spot.lang('credits_project')" />
|
||||||
|
</a> {{ spot.lang('credits_license') }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="feed">
|
||||||
|
<div id="feed-panel" ref="feedPanel">
|
||||||
|
<div id="feed-header">
|
||||||
|
<ProjectPost v-if="modeHisto" :options="{type: 'archived', headerless: true}" />
|
||||||
|
<ProjectPost v-else :options="{type: 'poster', relative_time: spot.lang('post_new_message')}" />
|
||||||
|
</div>
|
||||||
|
<div id="feed-posts">
|
||||||
|
<ProjectPost v-for="post in posts" :options="post" />
|
||||||
|
</div>
|
||||||
|
<div id="feed-footer" v-if="feed.loading">
|
||||||
|
<ProjectPost :options="{type: 'loading', headerless: true}" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="map">
|
|
||||||
<l-map ref="map" v-model:zoom="zoom" :center="[47.41322, -1.219482]">
|
|
||||||
<l-tile-layer
|
|
||||||
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
|
|
||||||
layer-type="base"
|
|
||||||
name="OpenStreetMap"
|
|
||||||
></l-tile-layer>
|
|
||||||
</l-map>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="mobile" class="mobile"></div>
|
|
||||||
</template>
|
</template>
|
||||||
17
src/components/projectMapLink.vue
Normal file
17
src/components/projectMapLink.vue
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
options: Object
|
||||||
|
},
|
||||||
|
inject: ['spot']
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<a
|
||||||
|
:href="'https://www.google.com/maps/place/'+options.lat_dms+'+'+options.lon_dms+'/@'+options.latitude+','+options.longitude+',10z'"
|
||||||
|
:title="spot.lang('see_on_google')"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer noopener"
|
||||||
|
>{{ options.lat_dms+' '+options.lon_dms }}</a>
|
||||||
|
</template>
|
||||||
60
src/components/projectMediaLink.vue
Normal file
60
src/components/projectMediaLink.vue
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
<script>
|
||||||
|
import spotIcon from './spotIcon.vue';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
spotIcon
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
options: Object,
|
||||||
|
type: String
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
title:''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
inject: ['spot'],
|
||||||
|
mounted() {
|
||||||
|
this.title = '<div>'+
|
||||||
|
(this.$refs.comment?this.$refs.comment.innerHTML:'')+
|
||||||
|
this.$refs[this.type=='marker'?'takenon':'postedon'].innerHTML+
|
||||||
|
this.$refs[this.type=='marker'?'postedon':'takenon'].innerHTML+
|
||||||
|
'</div>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<a
|
||||||
|
class="media-link drill"
|
||||||
|
:href="options.media_path"
|
||||||
|
:data-lightbox="type+'-medias'"
|
||||||
|
:data-type="options.subtype"
|
||||||
|
:data-id="options.id_media"
|
||||||
|
:data-title="title"
|
||||||
|
:data-orientation="options.rotate"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
:src="options.thumb_path"
|
||||||
|
:width="options.width"
|
||||||
|
:height="options.height"
|
||||||
|
:title="spot.lang((options.subtype == 'video')?'click_watch':'click_zoom')"
|
||||||
|
class="clickable"
|
||||||
|
/>
|
||||||
|
<span class="drill-icon"><spotIcon :icon="'drill-'+options.subtype" /></span>
|
||||||
|
<span v-if="options.comment" class="comment">{{ options.comment }}</span>
|
||||||
|
</a>
|
||||||
|
<div style="display:none">
|
||||||
|
<span ref="comment" class="lb-caption-line comment desktop" :title="options.comment">
|
||||||
|
<spotIcon :icon="'post'" :classes="'fa-lg fa-fw'" />
|
||||||
|
<span class="comment-text">{{ options.comment }}</span>
|
||||||
|
</span>
|
||||||
|
<span ref="postedon" class="lb-caption-line" :title="$parent.timeDiff?spot.lang('local_time', options.posted_on_formatted_local):''">
|
||||||
|
<spotIcon :icon="'upload'" :classes="'fa-lg fa-fw'" :text="options.posted_on_formatted" />
|
||||||
|
</span>
|
||||||
|
<span ref="takenon" class="lb-caption-line" :title="$parent.timeDiff?spot.lang('local_time', options.taken_on_formatted_local):''">
|
||||||
|
<spotIcon :icon="options.subtype+'-shot'" :classes="'fa-lg fa-fw'" :text="options.taken_on_formatted" />
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
166
src/components/projectPost.vue
Normal file
166
src/components/projectPost.vue
Normal file
@@ -0,0 +1,166 @@
|
|||||||
|
<script>
|
||||||
|
import spotIcon from './spotIcon.vue';
|
||||||
|
import spotButton from './spotButton.vue';
|
||||||
|
import projectMediaLink from './projectMediaLink.vue';
|
||||||
|
import projectMapLink from './projectMapLink.vue';
|
||||||
|
import projectRelTime from './projectRelTime.vue';
|
||||||
|
|
||||||
|
import autosize from 'autosize';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
spotIcon,
|
||||||
|
spotButton,
|
||||||
|
projectMediaLink,
|
||||||
|
projectMapLink,
|
||||||
|
projectRelTime
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
options: Object
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
time: '',
|
||||||
|
absTime: this.options.formatted_time,
|
||||||
|
absTimeLocal: this.options.formatted_time_local,
|
||||||
|
anchorVisible: ['message', 'media', 'post'].includes(this.options.type),
|
||||||
|
anchorTitle: this.spot.lang('copy_to_clipboard'),
|
||||||
|
anchorIcon: 'link'
|
||||||
|
};
|
||||||
|
},
|
||||||
|
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.spot.lang('counter', this.options.displayed_id)):'';
|
||||||
|
},
|
||||||
|
hash() {
|
||||||
|
let asHash = this.spot.getHash();
|
||||||
|
return '#'+[asHash.page, asHash.items[0], this.options.type, this.options.id].join(this.spot.consts.hash_sep);
|
||||||
|
},
|
||||||
|
timeDiff() {
|
||||||
|
return (this.options.formatted_time && this.options.formatted_time_local != this.options.formatted_time);
|
||||||
|
},
|
||||||
|
modeHisto() {
|
||||||
|
return (this.$parent.project.mode==this.spot.consts.modes.histo);
|
||||||
|
},
|
||||||
|
relTime() {
|
||||||
|
return this.modeHisto?this.options.formatted_time.substr(0, 10):this.options.relative_time;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
inject: ['spot'],
|
||||||
|
methods: {
|
||||||
|
copyAnchor() {
|
||||||
|
copyTextToClipboard(this.spot.consts.server+this.spot.hash());
|
||||||
|
this.anchorTitle = this.spot.lang('link_copied');
|
||||||
|
this.anchorIcon = 'copied';
|
||||||
|
setTimeout(()=>{ //TODO animation
|
||||||
|
this.anchorTitle = this.spot.lang('copy_to_clipboard');
|
||||||
|
this.anchorIcon = 'link';
|
||||||
|
}, 5000);
|
||||||
|
},
|
||||||
|
panMapToMessage() {
|
||||||
|
//TODO
|
||||||
|
/*
|
||||||
|
var $Parent = $(oEvent.currentTarget).parent();
|
||||||
|
var oMarker = this.spot.tmp(['markers', $Parent.data('id')]);
|
||||||
|
if(this.isMobile()) {
|
||||||
|
this.toggleFeedPanel(false, 'panToInstant');
|
||||||
|
this.spot.tmp('map').setView(oMarker.getLatLng(), 15);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
var iOffset = (this.isFeedPanelOpen()?1:-1)*this.spot.tmp('$Feed').outerWidth(true)/2 - (this.isSettingsPanelOpen()?1:-1)*this.spot.tmp('$Settings').outerWidth(true)/2;
|
||||||
|
var iRatio = -1 * iOffset / $('body').outerWidth(true);
|
||||||
|
this.spot.tmp('map').setOffsetView(iRatio, oMarker.getLatLng(), 15);
|
||||||
|
}
|
||||||
|
|
||||||
|
$Parent.data('clicked', true);
|
||||||
|
if(!oMarker.isPopupOpen()) oMarker.openPopup();
|
||||||
|
*/
|
||||||
|
},
|
||||||
|
openMarkerPopup() {
|
||||||
|
//TODO
|
||||||
|
/*
|
||||||
|
let oMarker = this.spot.tmp(['markers', $(oEvent.currentTarget).data('id')]);
|
||||||
|
if(this.spot.tmp('map') && this.spot.tmp('map').getBounds().contains(oMarker.getLatLng()) && !oMarker.isPopupOpen()) oMarker.openPopup();
|
||||||
|
*/
|
||||||
|
},
|
||||||
|
closeMarkerPopup() {
|
||||||
|
//TODO
|
||||||
|
/*
|
||||||
|
let $This = $(oEvent.currentTarget);
|
||||||
|
let oMarker = this.spot.tmp(['markers', $This.data('id')]);
|
||||||
|
if(oMarker && oMarker.isPopupOpen() && !$This.data('clicked')) oMarker.closePopup();
|
||||||
|
$This.data('clicked', false);
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
},
|
||||||
|
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">
|
||||||
|
<span class="index">
|
||||||
|
<a v-if="anchorVisible" class="link desktop" @click="copyAnchor" ref="anchor" :href="hash" :title="anchorTitle"><spotIcon :icon="anchorIcon" /></a>
|
||||||
|
<spotIcon :icon="subType" :text="displayedId" />
|
||||||
|
</span>
|
||||||
|
<span class="time" @mouseover="time = relTime" @mouseleave="time = timeDiff?spot.lang('your_time', absTime):absTime">{{ time }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="body">
|
||||||
|
<div v-if="options.type == 'message'" class="body-box" @mouseenter="openMarkerPopup" @mouseleave="closeMarkerPopup">
|
||||||
|
<p><spotIcon :icon="'coords'" /><projectMapLink :options="options" /></p>
|
||||||
|
<p><spotIcon :icon="'time'" :text="absTime" /></p>
|
||||||
|
<p v-if="timeDiff"><spotIcon :icon="'timezone'" /><projectRelTime :localTime="absTimeLocal" :offset="options.day_offset" /></p>
|
||||||
|
<a class="drill" @click.prevent="panMapToMessage">
|
||||||
|
<span v-if="options.weather_icon && options.weather_icon!='unknown'" class="weather clickable" :title="spot.lang(options.weather_cond)">
|
||||||
|
<spotIcon :icon="options.weather_icon" />
|
||||||
|
<span>{{ options.weather_temp+'°C' }}</span>
|
||||||
|
</span>
|
||||||
|
<img class="staticmap clickable" :title="spot.lang('click_zoom')" :src="options.static_img_url" />
|
||||||
|
<span class="drill-icon fa-stack clickable">
|
||||||
|
<spotIcon :icon="'message'" :classes="'fa-stack-2x clickable'" />
|
||||||
|
<spotIcon :icon="'message-in'" :classes="'fa-stack-1x fa-rotate-270'" />
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div v-else-if="options.type == 'media'" class="body-box">
|
||||||
|
<projectMediaLink :options="options" :type="'post'" />
|
||||||
|
</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>
|
||||||
|
<p v-else-if="options.type == 'poster'" class="message">
|
||||||
|
<textarea ref="post" name="post" :placeholder="spot.lang('post_message')" class="autoExpand" rows="1" v-model="$parent.post"></textarea>
|
||||||
|
<input type="text" name="name" :placeholder="spot.lang('post_name')" v-model="$parent.user.name" />
|
||||||
|
<spotButton name="submit" :aria-label="spot.lang('send')" :title="spot.lang('send')" :icon="'send'" />
|
||||||
|
</p>
|
||||||
|
<div v-else-if="options.type == 'archived'">
|
||||||
|
<p><spotIcon :icon="'success'" /></p>
|
||||||
|
<p>{{ spot.lang('mode_histo') }}</p>
|
||||||
|
</div>
|
||||||
|
<div v-else-if="options.type == 'loading'">
|
||||||
|
<p class="flicker"><spotIcon :icon="'post'" /></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
20
src/components/projectRelTime.vue
Normal file
20
src/components/projectRelTime.vue
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
localTime: String,
|
||||||
|
offset: String
|
||||||
|
},
|
||||||
|
inject: ['spot']
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<span>
|
||||||
|
{{ localTime.substr(-5) }}
|
||||||
|
<sup
|
||||||
|
v-if="iOffset != '0'"
|
||||||
|
:title="offset+' '+spot.lang('unit_day')+' ('+localTime.substr(0, 5)+')'"
|
||||||
|
|
||||||
|
>{{ ' '+offset }}</sup>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
@@ -23,9 +23,7 @@
|
|||||||
<title>Spotty</title>
|
<title>Spotty</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="container">
|
<div id="container"></div>
|
||||||
<div id="main"></div>
|
|
||||||
</div>
|
|
||||||
<script type="module" src="[#]filepath_js[#]"></script>
|
<script type="module" src="[#]filepath_js[#]"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
@@ -38,4 +38,4 @@ import SpotVue from '../Spot.vue';
|
|||||||
|
|
||||||
const oSpotVue = createApp(SpotVue);
|
const oSpotVue = createApp(SpotVue);
|
||||||
oSpotVue.provide('spot', window.oSpot);
|
oSpotVue.provide('spot', window.oSpot);
|
||||||
oSpotVue.mount('#main');
|
oSpotVue.mount('#container');
|
||||||
@@ -1101,7 +1101,11 @@ export default class Project {
|
|||||||
.addIcon('fa-'+asData.subtype+'-shot fa-lg fa-fw', true)
|
.addIcon('fa-'+asData.subtype+'-shot fa-lg fa-fw', true)
|
||||||
.append(asData.taken_on_formatted);
|
.append(asData.taken_on_formatted);
|
||||||
|
|
||||||
var $Title = $('<div>').append($Comment).append(sType=='marker'?$TakenOn:$PostedOn).append(sType=='marker'?$PostedOn:$TakenOn);
|
var $Title = $('<div>')
|
||||||
|
.append($Comment)
|
||||||
|
.append(sType=='marker'?$TakenOn:$PostedOn)
|
||||||
|
.append(sType=='marker'?$PostedOn:$TakenOn);
|
||||||
|
|
||||||
var $Link =
|
var $Link =
|
||||||
$('<a>', {
|
$('<a>', {
|
||||||
'class': 'media-link drill',
|
'class': 'media-link drill',
|
||||||
|
|||||||
@@ -83,10 +83,11 @@ export default class Spot {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async get2(sAction, oVars) {
|
async get2(sAction, oVars, bLoading) {
|
||||||
oVars = oVars || {};
|
oVars = oVars || {};
|
||||||
oVars['a'] = sAction;
|
oVars['a'] = sAction;
|
||||||
oVars['t'] = this.consts.timezone;
|
oVars['t'] = this.consts.timezone;
|
||||||
|
bLoading = true;
|
||||||
|
|
||||||
let oUrl = new URL(this.consts.server+this.consts.process_page);
|
let oUrl = new URL(this.consts.server+this.consts.process_page);
|
||||||
oUrl.search = new URLSearchParams(oVars).toString();
|
oUrl.search = new URLSearchParams(oVars).toString();
|
||||||
@@ -95,9 +96,13 @@ export default class Spot {
|
|||||||
let oUrl = new URL(this.consts.server+this.consts.process_page);
|
let oUrl = new URL(this.consts.server+this.consts.process_page);
|
||||||
oUrl.search = new URLSearchParams(oVars).toString();
|
oUrl.search = new URLSearchParams(oVars).toString();
|
||||||
const oRequest = await fetch(oUrl, {method: 'GET', /*body: JSON.stringify(oVars),*/ headers: {"Content-Type": "application/json"}});
|
const oRequest = await fetch(oUrl, {method: 'GET', /*body: JSON.stringify(oVars),*/ headers: {"Content-Type": "application/json"}});
|
||||||
if(!oRequest.ok) throw new Error('Error HTTP '+oRequest.status+': '+oRequest.statusText);
|
if(!oRequest.ok) {
|
||||||
|
bLoading = false;
|
||||||
|
throw new Error('Error HTTP '+oRequest.status+': '+oRequest.statusText);
|
||||||
|
}
|
||||||
else {
|
else {
|
||||||
let oResponse = await oRequest.json();
|
let oResponse = await oRequest.json();
|
||||||
|
bLoading = false;
|
||||||
if(oResponse.desc.substr(0, this.consts.lang_prefix.length)==this.consts.lang_prefix) oResponse.desc = this.lang(oData.desc.substr(this.consts.lang_prefix.length));
|
if(oResponse.desc.substr(0, this.consts.lang_prefix.length)==this.consts.lang_prefix) oResponse.desc = this.lang(oData.desc.substr(this.consts.lang_prefix.length));
|
||||||
|
|
||||||
if(oResponse.result == this.consts.error) return Promise.reject(oResponse.desc);
|
if(oResponse.result == this.consts.error) return Promise.reject(oResponse.desc);
|
||||||
@@ -105,6 +110,7 @@ export default class Spot {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch(oError) {
|
catch(oError) {
|
||||||
|
bLoading = false;
|
||||||
throw oError;
|
throw oError;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -126,6 +132,10 @@ export default class Spot {
|
|||||||
return sLang;
|
return sLang;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isMobile() {
|
||||||
|
return $('#mobile').is(':visible');
|
||||||
|
}
|
||||||
|
|
||||||
/* Page Switch - Trigger & Event catching */
|
/* Page Switch - Trigger & Event catching */
|
||||||
|
|
||||||
onHashChange() {
|
onHashChange() {
|
||||||
|
|||||||
@@ -1,114 +0,0 @@
|
|||||||
$theme : "spot-theme";
|
|
||||||
$base-color : #CCC;
|
|
||||||
$highlight-color : #FFF;
|
|
||||||
$background : rgba($base-color, 0.2);
|
|
||||||
$drag-color : rgba($highlight-color, 0.2);
|
|
||||||
$axis-color : darken($base-color,20%);
|
|
||||||
$stroke-color : darken($base-color,40%);
|
|
||||||
$stroke-width-mouse-focus : 1;
|
|
||||||
$stroke-width-height-focus: 2;
|
|
||||||
$stroke-width-axis : 2;
|
|
||||||
|
|
||||||
@import '../../node_modules/leaflet/dist/leaflet.css';
|
|
||||||
@import '../../node_modules/leaflet.heightgraph/src/L.Control.Heightgraph.css';
|
|
||||||
|
|
||||||
/* Leaflet fixes */
|
|
||||||
.leaflet-container {
|
|
||||||
background: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.leaflet-popup {
|
|
||||||
.leaflet-popup-content-wrapper {
|
|
||||||
border-radius: 5px;
|
|
||||||
padding: 0;
|
|
||||||
|
|
||||||
.leaflet-popup-content {
|
|
||||||
margin: 0;
|
|
||||||
padding: 1rem;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.leaflet-control.spot-control, .leaflet-control.heightgraph .heightgraph-toggle {
|
|
||||||
@extend .clickable;
|
|
||||||
width: 44px;
|
|
||||||
height: 44px;
|
|
||||||
text-align: center;
|
|
||||||
box-shadow: none;
|
|
||||||
|
|
||||||
.fa {
|
|
||||||
@extend .control-icon;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Leaflet Heightgraph fixes */
|
|
||||||
|
|
||||||
.legend-text, .tick, .tick text, .focusbox, .height-focus.circle, .height-focus.label, .lineSelection, .horizontalLineText {
|
|
||||||
fill: #333 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.axis path, .focusbox rect, .focusLine line, .height-focus.label rect, .height-focus.line, .horizontalLine {
|
|
||||||
stroke: #333 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.focusbox rect, .height-focus.label rect {
|
|
||||||
stroke-width: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.focusLine line, .focusbox rect, .height-focus.label rect {
|
|
||||||
-webkit-filter: drop-shadow(1px 0px 2px rgba(0, 0, 0, 0.6));
|
|
||||||
filter: drop-shadow(1px 0px 2px rgba(0, 0, 0, 0.6));
|
|
||||||
}
|
|
||||||
|
|
||||||
.height-focus.label rect, .focusbox rect {
|
|
||||||
fill: rgba(255,255,255,.6);
|
|
||||||
}
|
|
||||||
|
|
||||||
.heightgraph.leaflet-control {
|
|
||||||
svg.heightgraph-container {
|
|
||||||
background: none;
|
|
||||||
border-radius: 0;
|
|
||||||
|
|
||||||
.area {
|
|
||||||
@include drop-shadow(0.6);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.horizontalLine {
|
|
||||||
stroke-width: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.heightgraph-toggle {
|
|
||||||
background: none;
|
|
||||||
|
|
||||||
.heightgraph-toggle-icon {
|
|
||||||
@extend .control-icon;
|
|
||||||
@extend .fa-elev-chart;
|
|
||||||
height: 44px;
|
|
||||||
position: static;
|
|
||||||
background: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.heightgraph-close-icon {
|
|
||||||
@extend .control-icon;
|
|
||||||
@extend .fa-unsubscribe;
|
|
||||||
background: none;
|
|
||||||
font-size: 20px;
|
|
||||||
line-height: 26px;
|
|
||||||
width: 26px;
|
|
||||||
text-align: center;
|
|
||||||
display: none;
|
|
||||||
|
|
||||||
&:before {
|
|
||||||
width: 26px;
|
|
||||||
height: 26px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.leaflet-default-icon-path {
|
|
||||||
background-image: none;
|
|
||||||
}
|
|
||||||
@@ -422,26 +422,18 @@ $legend-color: $post-color;
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
padding-top: $block-spacing;
|
padding-top: $block-spacing;
|
||||||
|
|
||||||
#posts_list {
|
#feed-header {
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
#poster {
|
|
||||||
&.histo-mode .poster, &:not(.histo-mode) .archived {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.poster {
|
.poster {
|
||||||
textarea#post {
|
textarea[name=post] {
|
||||||
margin-bottom: 1em;
|
margin-bottom: 1em;
|
||||||
width: calc(100% - 2em);
|
width: calc(100% - 2em);
|
||||||
}
|
}
|
||||||
|
|
||||||
input#name {
|
input[name=name] {
|
||||||
width: calc(100% - 6em);
|
width: calc(100% - 6em);
|
||||||
}
|
}
|
||||||
|
|
||||||
button#submit {
|
button[name=submit] {
|
||||||
margin-left: 1em;
|
margin-left: 1em;
|
||||||
margin-bottom: 0.5em;
|
margin-bottom: 0.5em;
|
||||||
}
|
}
|
||||||
@@ -452,6 +444,10 @@ $legend-color: $post-color;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#feed-posts {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
.body-box {
|
.body-box {
|
||||||
position:relative;
|
position:relative;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
@@ -5,7 +5,6 @@
|
|||||||
/* Modules */
|
/* Modules */
|
||||||
@import 'fa';
|
@import 'fa';
|
||||||
@import 'lightbox';
|
@import 'lightbox';
|
||||||
@import 'leaflet';
|
|
||||||
@import '../../node_modules/simplebar/dist/simplebar.css';
|
@import '../../node_modules/simplebar/dist/simplebar.css';
|
||||||
|
|
||||||
/* Pages Specific CSS */
|
/* Pages Specific CSS */
|
||||||
|
|||||||
Reference in New Issue
Block a user