Manage map initialization sequence better + Fix map/project radio buttons

This commit is contained in:
2026-04-24 16:44:11 +02:00
parent 635b3781e3
commit eb0ded0d26
6 changed files with 115 additions and 92 deletions

View File

@@ -72,9 +72,9 @@ export default {
}, },
watch: { watch: {
baseMap(sNewBaseMap, sOldBaseMap) { baseMap(sNewBaseMap, sOldBaseMap) {
if(this.map.isStyleLoaded()) { if(this.map?.isStyleLoaded()) {
if(sOldBaseMap) this.map.setLayoutProperty(sOldBaseMap, 'visibility', 'none'); if(sOldBaseMap && this.map.getLayer(sOldBaseMap)) this.map.setLayoutProperty(sOldBaseMap, 'visibility', 'none');
if(sNewBaseMap) this.map.setLayoutProperty(sNewBaseMap, 'visibility', 'visible'); if(sNewBaseMap && this.map.getLayer(sNewBaseMap)) this.map.setLayoutProperty(sNewBaseMap, 'visibility', 'visible');
} }
}, },
}, },
@@ -190,45 +190,25 @@ export default {
const oMarkersPromise = this.spot.get2('markers', {id_project: this.currProject.id}); const oMarkersPromise = this.spot.get2('markers', {id_project: this.currProject.id});
const oTrackPromise = this.spot.get2('geojson', {id_project: this.currProject.id}); const oTrackPromise = this.spot.get2('geojson', {id_project: this.currProject.id});
//Get Map Info //Build Map
const aoMarkers = await oMarkersPromise;
this.baseMap = null;
this.baseMaps = aoMarkers.maps;
this.markers.messages = aoMarkers.messages;
this.markers.medias = aoMarkers.medias;
this.lastUpdate = aoMarkers.last_update;
//Base maps (raster tiles)
let asSources = {};
let asLayers = [];
for(const asBaseMap of this.baseMaps) {
asSources[asBaseMap.codename] = {
type: 'raster',
tiles: [asBaseMap.pattern],
tileSize: asBaseMap.tile_size
};
asLayers.push({
id: asBaseMap.codename,
type: 'raster',
source: asBaseMap.codename,
'layout': {'visibility': 'none'},
minZoom: asBaseMap.min_zoom,
maxZoom: asBaseMap.max_zoom
});
}
//Map
if(this.map) this.map.remove(); if(this.map) this.map.remove();
this.map = new Map({ this.map = new Map({
container: 'map', container: 'map',
style: { style: {
version: 8, version: 8,
sources: asSources, sources: {},
layers: asLayers layers: []
}, },
attributionControl: false attributionControl: false
}); });
const oMarkerImagePromise = this.map.loadImage('images/footprint_mapbox.png');
//Parse Map Info
const aoMarkers = await oMarkersPromise;
this.baseMaps = aoMarkers.maps;
this.baseMap = this.baseMaps.find((asBM) => asBM.default_map)?.codename ?? null;
this.markers.messages = aoMarkers.messages;
this.markers.medias = aoMarkers.medias;
this.lastUpdate = aoMarkers.last_update;
//Force wait for load event //Force wait for load event
await new Promise((resolve) => { await new Promise((resolve) => {
@@ -236,11 +216,39 @@ export default {
else this.map.once('load', resolve); else this.map.once('load', resolve);
}); });
//Default Basemap //Base maps (raster tiles)
this.baseMap = this.baseMaps.filter((asBM) => asBM.default_map)[0].codename; for(const asBaseMap of this.baseMaps) {
this.map.addSource(asBaseMap.codename, {
type: 'raster',
tiles: [asBaseMap.pattern],
tileSize: asBaseMap.tile_size
});
this.map.addLayer({
id: asBaseMap.codename,
type: 'raster',
source: asBaseMap.codename,
'layout': {'visibility': asBaseMap.codename == this.baseMap ? 'visible' : 'none'},
minZoom: asBaseMap.min_zoom,
maxZoom: asBaseMap.max_zoom
});
}
//Get track //Add track
const oTrack = await oTrackPromise; const oTrack = await oTrackPromise;
this.addTrack(oTrack);
//Centering map
await this.positionMap(oTrack);
//Add Markers
this.addMarkers();
//Force wait for idle event
await new Promise((resolve) => {
this.map.once('idle', resolve);
});
},
addTrack(oTrack) {
this.map.addSource('track', { this.map.addSource('track', {
'type': 'geojson', 'type': 'geojson',
'data': oTrack 'data': oTrack
@@ -248,9 +256,9 @@ export default {
//Color mapping //Color mapping
let asColorMapping = ['match', ['get', 'type']]; let asColorMapping = ['match', ['get', 'type']];
for(const sHikeType in this.hikes.colors) { for(const [sHikeType, sColor] of Object.entries(this.hikes.colors)) {
asColorMapping.push(sHikeType); asColorMapping.push(sHikeType);
asColorMapping.push(this.hikes.colors[sHikeType]); asColorMapping.push(sColor);
} }
asColorMapping.push('black'); //fallback value asColorMapping.push('black'); //fallback value
@@ -268,9 +276,9 @@ export default {
'line-width': this.hikes.width 'line-width': this.hikes.width
} }
}); });
},
//Markers async addMarkers() {
this.map.addImage('markerIcon', (await oMarkerImagePromise).data); this.map.addImage('markerIcon', (await this.map.loadImage('images/footprint_mapbox.png')).data);
this.map.addSource('markers', { this.map.addSource('markers', {
type:'geojson', type:'geojson',
data: { data: {
@@ -305,7 +313,7 @@ export default {
//TODO Use same way of displaying markers (so that openMarkerPopup works on all markers) //TODO Use same way of displaying markers (so that openMarkerPopup works on all markers)
this.markers.medias.forEach(msg => { this.markers.medias.forEach(msg => {
const $Marker = document.createElement('div'); const $Marker = document.createElement('div');
const app = createApp(SpotIconStack, {iconMain: 'message', iconSub:'image'}); const app = createApp(SpotIconStack, {iconMain: 'media', iconSub: msg.subtype+'-in'});
app.mount($Marker); app.mount($Marker);
const $Popup = document.createElement('div'); const $Popup = document.createElement('div');
@@ -332,21 +340,22 @@ export default {
.setPopup(popupElement) .setPopup(popupElement)
.addTo(this.map); .addTo(this.map);
}); });
},
//Centering map async positionMap(oTrack) {
let bOpenFeedPanel = !this.mobile; let bOpenFeedPanel = !this.mobile;
let oBounds = new LngLatBounds(); let oBounds = new LngLatBounds();
if(
if( //Blog Mode: Fit to last message
this.currProject.mode == this.spot.consts.modes.blog && this.currProject.mode == this.spot.consts.modes.blog &&
this.markers.messages.length > 0 && this.markers.messages.length > 0 &&
this.$parent.hash.items[2] != 'message' this.$parent.hash.items[2] != 'message'
) { ) {
//Fit to last message
let oLastMsg = this.markers.messages[this.markers.messages.length - 1]; let oLastMsg = this.markers.messages[this.markers.messages.length - 1];
oBounds.extend(new LngLat(oLastMsg.longitude, oLastMsg.latitude)); oBounds.extend(new LngLat(oLastMsg.longitude, oLastMsg.latitude));
} }
else { else { //Pre/Histo Mode: Fit to track
//Fit to track
for(const iFeatureId in oTrack.features) { for(const iFeatureId in oTrack.features) {
oBounds = oTrack.features[iFeatureId].geometry.coordinates.reduce( oBounds = oTrack.features[iFeatureId].geometry.coordinates.reduce(
(bounds, coord) => { (bounds, coord) => {
@@ -356,6 +365,7 @@ export default {
); );
} }
} }
const iFeedPanelPadding = bOpenFeedPanel?(getOuterWidth(this.$refs.feed)/2):0; const iFeedPanelPadding = bOpenFeedPanel?(getOuterWidth(this.$refs.feed)/2):0;
await this.map.fitBounds( await this.map.fitBounds(
oBounds, oBounds,
@@ -373,11 +383,6 @@ export default {
//Toggle only when map is ready, for the tilt effet //Toggle only when map is ready, for the tilt effet
this.toggleFeedPanel(bOpenFeedPanel); this.toggleFeedPanel(bOpenFeedPanel);
//Force wait for idle event
await new Promise((resolve) => {
this.map.once('idle', resolve);
});
}, },
convertMsgToFeatures(oMsg) { convertMsgToFeatures(oMsg) {
return oMsg.map(oMsg => ({ return oMsg.map(oMsg => ({
@@ -640,9 +645,9 @@ export default {
<div class="settings-section"> <div class="settings-section">
<h1><SpotIcon :icon="'project'" :classes="'fa-fw'" :text="spot.lang('hikes')" /></h1> <h1><SpotIcon :icon="'project'" :classes="'fa-fw'" :text="spot.lang('hikes')" /></h1>
<div class="settings-section-body"> <div class="settings-section-body">
<div class="radio" v-for="project in projects"> <div class="radio" v-for="project in projects" :key="'project-'+project.id">
<input type="radio" :id="project.id" :value="project.codename" v-model="$parent.hash.items[0]" /> <input type="radio" :id="'project-'+project.id" :value="project.codename" v-model="$parent.hash.items[0]" />
<label :for="project.id"> <label :for="'project-'+project.id">
<span>{{ project.name }}</span> <span>{{ project.name }}</span>
<a class="download" :href="project.gpxfilepath" :title="spot.lang('track_download')" @click.stop="()=>{}"> <a class="download" :href="project.gpxfilepath" :title="spot.lang('track_download')" @click.stop="()=>{}">
<SpotIcon :icon="'download'" :classes="'push-left'" /> <SpotIcon :icon="'download'" :classes="'push-left'" />
@@ -654,9 +659,9 @@ export default {
<div class="settings-section"> <div class="settings-section">
<h1><SpotIcon :icon="'map'" :classes="'fa-fw'" :text="spot.lang('maps')" /></h1> <h1><SpotIcon :icon="'map'" :classes="'fa-fw'" :text="spot.lang('maps')" /></h1>
<div class="settings-section-body"> <div class="settings-section-body">
<div class="radio" v-for="bm in baseMaps"> <div class="radio" v-for="bm in baseMaps" :key="'map-'+bm.id_map">
<input type="radio" :id="bm.id_map" :value="bm.codename" v-model="baseMap" /> <input type="radio" :id="'map-'+bm.id_map" :value="bm.codename" v-model="baseMap" />
<label :for="bm.id_map">{{ this.spot.lang('map_'+bm.codename) }}</label> <label :for="'map-'+bm.id_map">{{ this.spot.lang('map_'+bm.codename) }}</label>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -40,6 +40,9 @@ export default {
<spotIcon :icon="'coords'" :classes="'fa-fw fa-lg'" :margin="true" /> <spotIcon :icon="'coords'" :classes="'fa-fw fa-lg'" :margin="true" />
<projectMapLink :options="options" /> <projectMapLink :options="options" />
</p> </p>
<p class="altitude" v-if="options.altitude">
<spotIcon :icon="'altitude'" :classes="'fa-fw fa-lg'" :text="options.altitude+'m'" />
</p>
<p class="time"> <p class="time">
<projectRelTime :icon="timeIcon" :localTime="options.formatted_time_local" :siteTime="options.formatted_time" :offset="options.day_offset" /> <projectRelTime :icon="timeIcon" :localTime="options.formatted_time_local" :siteTime="options.formatted_time" :offset="options.day_offset" />
<span v-if="project.mode==spot.consts.modes.blog"> ({{ options.relative_time }})</span> <span v-if="project.mode==spot.consts.modes.blog"> ({{ options.relative_time }})</span>

View File

@@ -18,10 +18,12 @@ $post-hover: $default-hover;
$post-bg: $default-bg; $post-bg: $default-bg;
$message: hsl(109, 45%, 27%); $message: hsl(109, 45%, 27%);
$message-flashy: hsl(148, 100%, 50%);
$message-hover: color.adjust($message, $lightness: -10%, $space: hsl); $message-hover: color.adjust($message, $lightness: -10%, $space: hsl);
$message-bg: color.adjust($message, $lightness: 60%, $space: hsl); $message-bg: color.adjust($message, $lightness: 60%, $space: hsl);
$media: hsl(214, 45%, 27%); $media: hsl(214, 45%, 27%);
$media-flashy: hsl(193, 100%, 50%);
$media-hover: color.adjust($media, $lightness: -10%, $space: hsl); $media-hover: color.adjust($media, $lightness: -10%, $space: hsl);
$media-bg: color.adjust($media, $lightness: 60%, $space: hsl); $media-bg: color.adjust($media, $lightness: 60%, $space: hsl);
$media-bg-light: color.adjust($media, $lightness: 60%, $alpha: -0.4, $space: hsl); $media-bg-light: color.adjust($media, $lightness: 60%, $alpha: -0.4, $space: hsl);
@@ -38,7 +40,7 @@ $download-hover: #0078A8;
//Legend colors //Legend colors
$legend: $default; $legend: $default;
$main-track: #00ff78; $main-track: $message-flashy;
$off-track: #0000ff; $off-track: #0000ff;
$hitchhiking: #ff7814; $hitchhiking: #ff7814;

View File

@@ -71,7 +71,9 @@
.#{variables.$fa-css-prefix}-post:before { content: functions.fa-content(variables.$fa-var-comment); } .#{variables.$fa-css-prefix}-post:before { content: functions.fa-content(variables.$fa-var-comment); }
.#{variables.$fa-css-prefix}-media:before { content: functions.fa-content(variables.$fa-var-photo-video); } .#{variables.$fa-css-prefix}-media:before { content: functions.fa-content(variables.$fa-var-photo-video); }
.#{variables.$fa-css-prefix}-video:before { content: functions.fa-content(variables.$fa-var-film); } .#{variables.$fa-css-prefix}-video:before { content: functions.fa-content(variables.$fa-var-film); }
.#{variables.$fa-css-prefix}-video-in:before { content: functions.fa-content(variables.$fa-var-film); }
.#{variables.$fa-css-prefix}-image:before { content: functions.fa-content(variables.$fa-var-image); } .#{variables.$fa-css-prefix}-image:before { content: functions.fa-content(variables.$fa-var-image); }
.#{variables.$fa-css-prefix}-image-in:before { content: functions.fa-content(variables.$fa-var-image); }
.#{variables.$fa-css-prefix}-message:before { content: functions.fa-content(variables.$fa-var-map-marker); } .#{variables.$fa-css-prefix}-message:before { content: functions.fa-content(variables.$fa-var-map-marker); }
.#{variables.$fa-css-prefix}-message-in:before { content: functions.fa-content(variables.$fa-var-shoe-prints); } .#{variables.$fa-css-prefix}-message-in:before { content: functions.fa-content(variables.$fa-var-shoe-prints); }
.#{variables.$fa-css-prefix}-time:before { content: functions.fa-content(variables.$fa-var-clock); } .#{variables.$fa-css-prefix}-time:before { content: functions.fa-content(variables.$fa-var-clock); }

View File

@@ -51,20 +51,6 @@
} }
} }
.message {
margin: 0;
}
.signature {
margin: var.$elem-spacing 0 0 0;
text-align: right;
font-style: italic;
img {
vertical-align: baseline;
margin: 0 0.2em calc((1em - 24px)/2) 0;
position: relative;
}
}
.header { .header {
padding: 0 var.$block-spacing; padding: 0 var.$block-spacing;
position: relative; position: relative;
@@ -91,6 +77,7 @@
} }
} }
} }
.body { .body {
clear: both; clear: both;
padding: 0em var.$block-spacing var.$block-spacing; padding: 0em var.$block-spacing var.$block-spacing;
@@ -191,6 +178,22 @@
&.post { &.post {
.body { .body {
padding: 0em 1em 0.5em; padding: 0em 1em 0.5em;
.message {
margin: 0;
}
.signature {
margin: var.$elem-spacing 0 0 0;
text-align: right;
font-style: italic;
img {
vertical-align: baseline;
margin: 0 0.2em calc((1em - 24px)/2) 0;
position: relative;
}
}
} }
} }
@@ -218,6 +221,17 @@
background: color.$media-bg-light; background: color.$media-bg-light;
} }
.drill-icon {
font-size: 3em;
.fa-drill-image {
color: transparent;
}
.fa-drill-video {
color: color.$over-img-bg;
}
}
&:hover { &:hover {
.drill-icon { .drill-icon {
.fa-drill-image, .fa-drill-video { .fa-drill-image, .fa-drill-video {
@@ -229,17 +243,6 @@
opacity: 0; opacity: 0;
} }
} }
.drill-icon {
font-size: 3em;
.fa-drill-image {
color: transparent;
}
.fa-drill-video {
color: color.$over-img-bg;
}
}
} }
img { img {

View File

@@ -1,3 +1,4 @@
@use "fa/variables";
@use "var"; @use "var";
@use "color"; @use "color";
@@ -35,23 +36,30 @@
} }
} }
.fa-stack { .#{variables.$fa-css-prefix}-stack {
.fa-message { .#{variables.$fa-css-prefix}-message {
font-size: 32px; font-size: 32px;
text-shadow: color.$over-img-shadow 3px 3px 3px; text-shadow: color.$over-img-shadow 3px 3px 3px;
color: color.$main-track; color: color.$message-flashy;
} }
.fa-message-in { .#{variables.$fa-css-prefix}-media {
@extend .#{variables.$fa-css-prefix}-message;
color: color.$media-flashy;
}
.#{variables.$fa-css-prefix}-message-in {
font-size: 13px; font-size: 13px;
color: color.$message; color: color.$message;
top: 1px; top: 1px;
} }
.fa-track-start, .fa-track-end { .#{variables.$fa-css-prefix}-media-in {
color: color.$media;
}
.#{variables.$fa-css-prefix}-track-start, .#{variables.$fa-css-prefix}-track-end {
color: color.$track-start; color: color.$track-start;
font-size: 14px; font-size: 14px;
top: 1px; top: 1px;
} }
.fa-track-end { .#{variables.$fa-css-prefix}-track-end {
color: color.$track-end; color: color.$track-end;
} }
} }