Replace leaflet with maplibre GL

This commit is contained in:
2024-01-11 21:01:21 +01:00
parent 7853c6e285
commit c2956ac373
16 changed files with 1333 additions and 526 deletions

View File

@@ -1,44 +1,299 @@
<script>
//Leaflet
import 'leaflet';
import 'leaflet-geometryutil';
import 'leaflet.heightgraph';
import '../scripts/leaflet.helpers';
import { LMap, LTileLayer } from "@vue-leaflet/vue-leaflet";
import 'maplibre-gl/dist/maplibre-gl.css';
import { Map, NavigationControl, Marker } from 'maplibre-gl';
import lightbox from '../scripts/lightbox.js';
import SimpleBar from 'simplebar';
import SpotIcon from './spotIcon.vue';
import SpotButton from './spotButton.vue';
import ProjectPost from './projectPost.vue';
export default {
components: {
LMap,
LTileLayer
SpotIcon,
SpotButton,
ProjectPost
},
data() {
return {
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'],
mounted() {
//Set default project
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>
<template>
<div id="projects">
<div id="projects" :class="projectClasses">
<div id="background"></div>
<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">
<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 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>
<div id="mobile" class="mobile"></div>
</template>