Harmonize marker / popup rendering
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -5,3 +5,4 @@
|
|||||||
/node_modules/
|
/node_modules/
|
||||||
/log.html
|
/log.html
|
||||||
/dist/
|
/dist/
|
||||||
|
.codex
|
||||||
@@ -82,7 +82,6 @@ module.exports = {
|
|||||||
from: path.resolve(LIB, 'index.php'),
|
from: path.resolve(LIB, 'index.php'),
|
||||||
to: 'index.php'
|
to: 'index.php'
|
||||||
},
|
},
|
||||||
{ from: 'src/images/footprint_mapbox.png', to: 'images' },
|
|
||||||
{ from: 'src/images/logo_black.png', to: 'images' },
|
{ from: 'src/images/logo_black.png', to: 'images' },
|
||||||
{ from: 'src/images/spot-logo-only.svg', to: 'images' }
|
{ from: 'src/images/spot-logo-only.svg', to: 'images' }
|
||||||
],
|
],
|
||||||
|
|||||||
16
lib/Spot.php
16
lib/Spot.php
@@ -280,6 +280,12 @@ class Spot extends Main
|
|||||||
usort($asMessages, function($a, $b){return $a['unix_time'] > $b['unix_time'];});
|
usort($asMessages, function($a, $b){return $a['unix_time'] > $b['unix_time'];});
|
||||||
$bHasMsg = !empty($asMessages);
|
$bHasMsg = !empty($asMessages);
|
||||||
|
|
||||||
|
foreach($asMessages as &$asMessage) {
|
||||||
|
$asMessage['id'] = $asMessage[Db::getId(Feed::MSG_TABLE)];
|
||||||
|
$asMessage['type'] = 'message';
|
||||||
|
$asMessage['subtype'] = 'message';
|
||||||
|
}
|
||||||
|
|
||||||
//Add medias
|
//Add medias
|
||||||
$asMedias = $this->getMedias('taken_on', $asMediaIds);
|
$asMedias = $this->getMedias('taken_on', $asMediaIds);
|
||||||
usort($asMedias, function($a, $b){return $a['unix_time'] > $b['unix_time'];});
|
usort($asMedias, function($a, $b){return $a['unix_time'] > $b['unix_time'];});
|
||||||
@@ -289,11 +295,13 @@ class Spot extends Main
|
|||||||
$iMaxIndex = count($asMessages) - 1;
|
$iMaxIndex = count($asMessages) - 1;
|
||||||
foreach($asMedias as $asMedia) {
|
foreach($asMedias as $asMedia) {
|
||||||
if($asMedia['latitude']!='' && $asMedia['longitude']!='') {
|
if($asMedia['latitude']!='' && $asMedia['longitude']!='') {
|
||||||
|
$asMedia['id'] = $asMedia[Db::getId(Media::MEDIA_TABLE)];
|
||||||
|
$asMedia['type'] = 'media';
|
||||||
$asMedia['lat_dms'] = self::decToDms($asMedia['latitude'], 'lat');
|
$asMedia['lat_dms'] = self::decToDms($asMedia['latitude'], 'lat');
|
||||||
$asMedia['lon_dms'] = self::decToDms($asMedia['longitude'], 'lon');
|
$asMedia['lon_dms'] = self::decToDms($asMedia['longitude'], 'lon');
|
||||||
$asGeoMedias[] = $asMedia;
|
$asGeoMedias[] = $asMedia;
|
||||||
}
|
}
|
||||||
elseif($bHasMsg) {
|
if($bHasMsg) {
|
||||||
while($iIndex <= $iMaxIndex && $asMedia['unix_time'] > $asMessages[$iIndex]['unix_time']) $iIndex++;
|
while($iIndex <= $iMaxIndex && $asMedia['unix_time'] > $asMessages[$iIndex]['unix_time']) $iIndex++;
|
||||||
|
|
||||||
//All medias before first message or after last message are assigned to first/last message respectively
|
//All medias before first message or after last message are assigned to first/last message respectively
|
||||||
@@ -308,13 +316,15 @@ class Spot extends Main
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$asMarkers = [...$asMessages, ...$asGeoMedias];
|
||||||
|
usort($asMarkers, function($a, $b){return $a['unix_time'] > $b['unix_time'];});
|
||||||
|
|
||||||
//Spot Last Update
|
//Spot Last Update
|
||||||
$asLastUpdate = array();
|
$asLastUpdate = array();
|
||||||
$this->addTimeStamp($asLastUpdate, $this->oProject->getLastUpdate());
|
$this->addTimeStamp($asLastUpdate, $this->oProject->getLastUpdate());
|
||||||
|
|
||||||
$asResult = array(
|
$asResult = array(
|
||||||
'messages' => $asMessages,
|
'markers' => $asMarkers,
|
||||||
'medias' => $asGeoMedias,
|
|
||||||
'maps' => $this->oMap->getProjectMaps($this->oProject->getProjectId()),
|
'maps' => $this->oMap->getProjectMaps($this->oProject->getProjectId()),
|
||||||
'last_update' => $asLastUpdate
|
'last_update' => $asLastUpdate
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -28,8 +28,14 @@ export default {
|
|||||||
lastUpdate: { unix_time: 0, relative_time: '', formatted_time: ''},
|
lastUpdate: { unix_time: 0, relative_time: '', formatted_time: ''},
|
||||||
feedPanelOpen: false,
|
feedPanelOpen: false,
|
||||||
settingsPanelOpen: false,
|
settingsPanelOpen: false,
|
||||||
markers: {messages: null, medias: null},
|
track: null,
|
||||||
|
markers: [],
|
||||||
markerSize: {width: 32, height: 32},
|
markerSize: {width: 32, height: 32},
|
||||||
|
markerProps: {
|
||||||
|
image: {mainClasses: 'media', iconMain: 'marker', iconSub: 'image'},
|
||||||
|
video: {mainClasses: 'media', iconMain: 'marker', iconSub: 'video'},
|
||||||
|
message: {mainClasses: 'message', iconMain: 'marker', iconSub: 'footprint', iconSubTransform: 'rotate-270'}
|
||||||
|
},
|
||||||
currProject: {},
|
currProject: {},
|
||||||
modeHisto: false,
|
modeHisto: false,
|
||||||
posts: [],
|
posts: [],
|
||||||
@@ -38,6 +44,7 @@ export default {
|
|||||||
baseMaps: {},
|
baseMaps: {},
|
||||||
baseMap: null,
|
baseMap: null,
|
||||||
map: null,
|
map: null,
|
||||||
|
mapReady: false,
|
||||||
hikes: {
|
hikes: {
|
||||||
colors:{'main':'#00ff78', 'off-track':'#0000ff', 'hitchhiking':'#FF7814'},
|
colors:{'main':'#00ff78', 'off-track':'#0000ff', 'hitchhiking':'#FF7814'},
|
||||||
width: 4
|
width: 4
|
||||||
@@ -49,7 +56,8 @@ export default {
|
|||||||
projectClasses() {
|
projectClasses() {
|
||||||
return [
|
return [
|
||||||
this.feedPanelOpen?'with-feed':'',
|
this.feedPanelOpen?'with-feed':'',
|
||||||
this.settingsPanelOpen?'with-settings':''
|
this.settingsPanelOpen?'with-settings':'',
|
||||||
|
this.mapReady?'map-ready':''
|
||||||
].filter(n => n).join(' ');
|
].filter(n => n).join(' ');
|
||||||
},
|
},
|
||||||
nlClasses() {
|
nlClasses() {
|
||||||
@@ -75,6 +83,7 @@ export default {
|
|||||||
'hash.items.0'(newProjectCodename, oldProjectCodename) {
|
'hash.items.0'(newProjectCodename, oldProjectCodename) {
|
||||||
if(newProjectCodename && newProjectCodename != oldProjectCodename) {
|
if(newProjectCodename && newProjectCodename != oldProjectCodename) {
|
||||||
this.hash.items = [newProjectCodename];
|
this.hash.items = [newProjectCodename];
|
||||||
|
this.toggleSettingsPanel(false, 'none');
|
||||||
this.init();
|
this.init();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -122,8 +131,10 @@ export default {
|
|||||||
this.initMap()
|
this.initMap()
|
||||||
]);
|
]);
|
||||||
|
|
||||||
//Direct link: Scroll to post
|
//Direct link or default project positioning
|
||||||
if(this.hash.items.length == 3) this.findPost({type: this.hash.items[1], id: this.hash.items[2]});
|
await this.setInitialMapPosition();
|
||||||
|
|
||||||
|
this.mapReady = true;
|
||||||
},
|
},
|
||||||
quit() {
|
quit() {
|
||||||
lightbox.end();
|
lightbox.end();
|
||||||
@@ -152,6 +163,7 @@ export default {
|
|||||||
this.modeHisto = (this.currProject.mode == this.consts.modes.histo);
|
this.modeHisto = (this.currProject.mode == this.consts.modes.histo);
|
||||||
this.feed = {loading:false, updatable:true, outOfData:false, refIdFirst:0, refIdLast:0, firstChunk:true};
|
this.feed = {loading:false, updatable:true, outOfData:false, refIdFirst:0, refIdLast:0, firstChunk:true};
|
||||||
this.posts = [];
|
this.posts = [];
|
||||||
|
this.mapReady = false;
|
||||||
//this.baseMap = null;
|
//this.baseMap = null;
|
||||||
this.baseMaps = {};
|
this.baseMaps = {};
|
||||||
},
|
},
|
||||||
@@ -166,7 +178,7 @@ export default {
|
|||||||
hasVideo: true,
|
hasVideo: true,
|
||||||
onMediaChange: (oMedia) => {
|
onMediaChange: (oMedia) => {
|
||||||
this.hash.items = [this.currProject.codename, 'media', oMedia.id];
|
this.hash.items = [this.currProject.codename, 'media', oMedia.id];
|
||||||
if(oMedia.set == 'post-medias') this.goToPost({type: 'media', id: oMedia.id});
|
if(oMedia.set == 'post-medias') this.goToPost('media', oMedia.id);
|
||||||
},
|
},
|
||||||
onClosing: () => {this.hash.items = [this.hash.items[0]];}
|
onClosing: () => {this.hash.items = [this.hash.items[0]];}
|
||||||
});
|
});
|
||||||
@@ -178,6 +190,8 @@ export default {
|
|||||||
//Mobile Touchscreen Events
|
//Mobile Touchscreen Events
|
||||||
//TODO
|
//TODO
|
||||||
|
|
||||||
|
this.toggleFeedPanel(!this.isMobile(), 'none');
|
||||||
|
|
||||||
//Add post Event handling
|
//Add post Event handling
|
||||||
//TODO
|
//TODO
|
||||||
|
|
||||||
@@ -209,8 +223,7 @@ export default {
|
|||||||
const aoMarkers = await oMarkersPromise;
|
const aoMarkers = await oMarkersPromise;
|
||||||
this.baseMaps = aoMarkers.maps;
|
this.baseMaps = aoMarkers.maps;
|
||||||
this.baseMap = this.baseMaps.find((asBM) => asBM.default_map)?.codename ?? null;
|
this.baseMap = this.baseMaps.find((asBM) => asBM.default_map)?.codename ?? null;
|
||||||
this.markers.messages = aoMarkers.messages;
|
this.markers = aoMarkers.markers;
|
||||||
this.markers.medias = aoMarkers.medias;
|
|
||||||
this.lastUpdate = aoMarkers.last_update;
|
this.lastUpdate = aoMarkers.last_update;
|
||||||
|
|
||||||
//Force wait for load event
|
//Force wait for load event
|
||||||
@@ -237,21 +250,19 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//Add track
|
//Add track
|
||||||
const oTrack = await oTrackPromise;
|
this.addTrack(await oTrackPromise);
|
||||||
this.addTrack(oTrack);
|
|
||||||
|
|
||||||
//Centering map
|
|
||||||
await this.positionMap(oTrack);
|
|
||||||
|
|
||||||
//Add Markers
|
//Add Markers
|
||||||
this.addMarkers();
|
this.markers.forEach(oMarker => this.addMarker(oMarker));
|
||||||
|
|
||||||
//Force wait for idle event
|
//Force wait for idle event
|
||||||
await new Promise((resolve) => {
|
await new Promise((resolve) => {
|
||||||
this.map.once('idle', resolve);
|
if(this.map.loaded() && this.map.areTilesLoaded()) resolve();
|
||||||
|
else this.map.once('idle', resolve);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
addTrack(oTrack) {
|
addTrack(oTrack, bCenter=false) {
|
||||||
|
this.track = oTrack;
|
||||||
this.map.addSource('track', {
|
this.map.addSource('track', {
|
||||||
'type': 'geojson',
|
'type': 'geojson',
|
||||||
'data': oTrack
|
'data': oTrack
|
||||||
@@ -280,167 +291,37 @@ export default {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
async addMarkers() {
|
addMarker(oMarker) {
|
||||||
this.map.addImage('markerIcon', (await this.map.loadImage('images/footprint_mapbox.png')).data);
|
|
||||||
this.map.addSource('markers', {
|
|
||||||
type:'geojson',
|
|
||||||
data: {
|
|
||||||
type: 'FeatureCollection',
|
|
||||||
features: this.convertMsgToFeatures(this.markers.messages)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
this.map.addLayer({
|
|
||||||
'id': 'markers',
|
|
||||||
'type': 'symbol',
|
|
||||||
'source': 'markers',
|
|
||||||
'layout': {'icon-image': 'markerIcon'}
|
|
||||||
});
|
|
||||||
this.map.on('click', 'markers', (e) => {
|
|
||||||
this.openMarkerPopup(e.features[0]);
|
|
||||||
});
|
|
||||||
|
|
||||||
/*
|
|
||||||
this.markers.messages.forEach(msg => {
|
|
||||||
const el = document.createElement('div');
|
|
||||||
|
|
||||||
const app = createApp(SpotIconStack, {iconMain: 'message', iconSub:'message-in', iconSubRotation: 270});
|
|
||||||
app.mount(el);
|
|
||||||
|
|
||||||
new Marker({element: el, anchor: 'bottom'})
|
|
||||||
.setLngLat([msg.longitude, msg.latitude])
|
|
||||||
.addTo(this.map);
|
|
||||||
});
|
|
||||||
*/
|
|
||||||
|
|
||||||
//Medias
|
|
||||||
//TODO Use same way of displaying markers (so that openMarkerPopup works on all markers)
|
|
||||||
this.markers.medias.forEach(msg => {
|
|
||||||
const $Marker = document.createElement('div');
|
const $Marker = document.createElement('div');
|
||||||
createApp(SpotIconStack, {mainClasses: 'media', iconMain: 'marker', iconSub: msg.subtype}).mount($Marker);
|
createApp(SpotIconStack, this.markerProps[oMarker.subtype]).mount($Marker);
|
||||||
|
|
||||||
const marker = new Marker({element: $Marker, anchor: 'bottom'})
|
new Marker({element: $Marker, anchor: 'bottom'})
|
||||||
.setLngLat([msg.longitude, msg.latitude])
|
.setLngLat([oMarker.longitude, oMarker.latitude])
|
||||||
.addTo(this.map)
|
.addTo(this.map)
|
||||||
.getElement().addEventListener('click', (oEvent) => {
|
.getElement()
|
||||||
|
.addEventListener('click', (oEvent) => {
|
||||||
oEvent.preventDefault();
|
oEvent.preventDefault();
|
||||||
oEvent.stopPropagation();
|
oEvent.stopPropagation();
|
||||||
this.openMediaPopup(msg);
|
this.openMarkerPopup(oMarker.id, oMarker.type);
|
||||||
});
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
async positionMap(oTrack) {
|
openMarkerPopup(iMarkerId, sMarkerType) {
|
||||||
let bOpenFeedPanel = !this.isMobile();
|
|
||||||
let oBounds = new LngLatBounds();
|
|
||||||
|
|
||||||
if( //Blog Mode: Fit to last message
|
|
||||||
this.currProject.mode == this.consts.modes.blog &&
|
|
||||||
this.markers.messages.length > 0 &&
|
|
||||||
this.hash.items[2] != 'message'
|
|
||||||
) {
|
|
||||||
|
|
||||||
let oLastMsg = this.markers.messages[this.markers.messages.length - 1];
|
|
||||||
oBounds.extend(new LngLat(oLastMsg.longitude, oLastMsg.latitude));
|
|
||||||
}
|
|
||||||
else { //Pre/Histo Mode: Fit to track
|
|
||||||
|
|
||||||
for(const iFeatureId in oTrack.features) {
|
|
||||||
oBounds = oTrack.features[iFeatureId].geometry.coordinates.reduce(
|
|
||||||
(bounds, coord) => {
|
|
||||||
return bounds.extend(coord);
|
|
||||||
},
|
|
||||||
oBounds
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const iFeedPanelPadding = bOpenFeedPanel?(getOuterWidth(this.$refs.feed)/2):0;
|
|
||||||
await this.map.fitBounds(
|
|
||||||
oBounds,
|
|
||||||
{
|
|
||||||
padding: {
|
|
||||||
top: 20,
|
|
||||||
bottom: 20,
|
|
||||||
left: (20 + iFeedPanelPadding),
|
|
||||||
right: (20 + iFeedPanelPadding)
|
|
||||||
},
|
|
||||||
animate: false,
|
|
||||||
maxZoom: 15
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
//Toggle only when map is ready, for the tilt effet
|
|
||||||
this.toggleFeedPanel(bOpenFeedPanel);
|
|
||||||
},
|
|
||||||
convertMsgToFeatures(oMsg) {
|
|
||||||
return oMsg.map(oMsg => ({
|
|
||||||
'type': 'Feature',
|
|
||||||
'geometry': {
|
|
||||||
'type': 'Point',
|
|
||||||
'coordinates': [oMsg.longitude, oMsg.latitude]
|
|
||||||
},
|
|
||||||
'properties': {
|
|
||||||
...oMsg,
|
|
||||||
...{'description': ''}
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
},
|
|
||||||
openMediaPopup(oMedia) {
|
|
||||||
this.closeMarkerPopup();
|
this.closeMarkerPopup();
|
||||||
|
|
||||||
|
let oMarker = this.markers.find((oCandidate) => oCandidate.id == iMarkerId && oCandidate.type == sMarkerType);
|
||||||
|
|
||||||
const $Popup = document.createElement('div');
|
const $Popup = document.createElement('div');
|
||||||
this.popup.element = new Popup({
|
this.popup.element = new Popup({
|
||||||
anchor: 'bottom',
|
anchor: 'bottom',
|
||||||
offset: [0, this.markerSize.height * -1],
|
offset: [0, this.markerSize.height * -1], //FIXME
|
||||||
closeButton: false
|
closeButton: false
|
||||||
})
|
})
|
||||||
.setDOMContent($Popup)
|
.setDOMContent($Popup)
|
||||||
.setLngLat([oMedia.longitude, oMedia.latitude])
|
.setLngLat([oMarker.longitude, oMarker.latitude])
|
||||||
.setMaxWidth(300)
|
|
||||||
.addTo(this.map);
|
.addTo(this.map);
|
||||||
|
|
||||||
this.popup.content = createApp(ProjectPopup, {
|
this.popup.content = createApp(ProjectPopup, {
|
||||||
type: 'media',
|
options: oMarker,
|
||||||
options: oMedia,
|
|
||||||
medias: [oMedia],
|
|
||||||
project: this.currProject
|
|
||||||
});
|
|
||||||
this.popup.content
|
|
||||||
.provide('spot', this.spot)
|
|
||||||
.provide('lang', this.lang)
|
|
||||||
.provide('consts', this.consts)
|
|
||||||
.mount($Popup);
|
|
||||||
},
|
|
||||||
openMarkerPopup(oFeature) {
|
|
||||||
this.closeMarkerPopup();
|
|
||||||
|
|
||||||
//Convert ID Message to feature
|
|
||||||
if(typeof oFeature == 'number') {
|
|
||||||
const oMatchingFeatures = this.map.querySourceFeatures('markers', {
|
|
||||||
filter: ['==', ['get', 'id_message'], oFeature]
|
|
||||||
});
|
|
||||||
|
|
||||||
if(!oMatchingFeatures.length) {
|
|
||||||
console.warn('Marker not found: ', oFeature);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
else oFeature = oMatchingFeatures[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
const $Popup = document.createElement('div');
|
|
||||||
this.popup.element = new Popup({
|
|
||||||
anchor: 'bottom',
|
|
||||||
offset: [0, this.markerSize.height * -1],
|
|
||||||
closeButton: false
|
|
||||||
})
|
|
||||||
.setDOMContent($Popup)
|
|
||||||
.setLngLat(oFeature.geometry.coordinates)
|
|
||||||
.setMaxWidth(300)
|
|
||||||
.addTo(this.map);
|
|
||||||
|
|
||||||
this.popup.content = createApp(ProjectPopup, {
|
|
||||||
type: 'message',
|
|
||||||
options: oFeature.properties,
|
|
||||||
medias: JSON.parse(oFeature.properties.medias || '[]'),
|
|
||||||
project: this.currProject
|
project: this.currProject
|
||||||
});
|
});
|
||||||
this.popup.content
|
this.popup.content
|
||||||
@@ -459,6 +340,75 @@ export default {
|
|||||||
this.popup.element = null;
|
this.popup.element = null;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
async setInitialMapPosition() {
|
||||||
|
if(this.hash.items.length == 3) {
|
||||||
|
await this.findPost(this.hash.items[1], this.hash.items[2]);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
let oBounds = new LngLatBounds();
|
||||||
|
if( //Blog Mode: Fit to last message
|
||||||
|
this.currProject.mode == this.consts.modes.blog &&
|
||||||
|
this.markers.length > 0
|
||||||
|
) {
|
||||||
|
|
||||||
|
let oLastMsg = this.markers.at(-1);
|
||||||
|
oBounds.extend(new LngLat(oLastMsg.longitude, oLastMsg.latitude));
|
||||||
|
}
|
||||||
|
else { //Pre/Histo Mode: Fit to track
|
||||||
|
|
||||||
|
for(const iFeatureId in this.track.features) {
|
||||||
|
oBounds = this.track.features[iFeatureId].geometry.coordinates.reduce(
|
||||||
|
(bounds, coord) => {
|
||||||
|
return bounds.extend(coord);
|
||||||
|
},
|
||||||
|
oBounds
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const iFeedPanelPadding = this.feedPanelOpen?(getOuterWidth(this.$refs.feed)):0;
|
||||||
|
await this.map.fitBounds(
|
||||||
|
oBounds,
|
||||||
|
{
|
||||||
|
padding: {
|
||||||
|
top: 20,
|
||||||
|
bottom: 20,
|
||||||
|
left: 20,
|
||||||
|
right: 20 + iFeedPanelPadding
|
||||||
|
},
|
||||||
|
animate: false,
|
||||||
|
maxZoom: 15
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async findPost(sPostType, iPostId) {
|
||||||
|
let oRef = this.goToPost(sPostType, iPostId);
|
||||||
|
if(oRef) {
|
||||||
|
await oRef.executeMainAction(0);
|
||||||
|
}
|
||||||
|
else if(!this.feed.outOfData) {
|
||||||
|
await this.getNextFeed();
|
||||||
|
await this.findPost(sPostType, iPostId);
|
||||||
|
}
|
||||||
|
else console.log('Missing element ID "'+iPostId+'" of type "'+sPostType+'"');
|
||||||
|
},
|
||||||
|
goToPost(sPostType, iPostId) {
|
||||||
|
let bFound = false;
|
||||||
|
let aoRefs = this.$refs.posts.filter((post) => {return post.postId == sPostType+'-'+iPostId;});
|
||||||
|
if(aoRefs.length > 0) {
|
||||||
|
let oRef = aoRefs[0];
|
||||||
|
this.$refs.feedSimpleBar.scrollElement.scrollTop += Math.round(
|
||||||
|
oRef.$el.getBoundingClientRect().top
|
||||||
|
+ window.pageYOffset
|
||||||
|
- parseFloat(getComputedStyle(this.$refs.feedSimpleBar.$el).paddingTop)
|
||||||
|
);
|
||||||
|
|
||||||
|
//this.hash.items = [this.hash.items[0]];
|
||||||
|
|
||||||
|
return oRef;
|
||||||
|
}
|
||||||
|
},
|
||||||
async getNextFeed() {
|
async getNextFeed() {
|
||||||
if(!this.feed.outOfData && !this.feed.loading) {
|
if(!this.feed.outOfData && !this.feed.loading) {
|
||||||
//Get next chunk
|
//Get next chunk
|
||||||
@@ -504,31 +454,28 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//Add new Markers
|
//Add new Markers
|
||||||
if(Object.keys(aoData.messages).length > 0) {
|
if(Object.keys(aoData.markers).length > 0) {
|
||||||
const oMarkerSource = this.map.getSource('markers');
|
this.markers.push(...aoData.markers);
|
||||||
oMarkerSource.setData({
|
aoData.messages.forEach(oMarker => this.addMarker(oMarker));
|
||||||
type: 'FeatureCollection',
|
|
||||||
features: [...oMarkerSource._data.features, ...this.convertMsgToFeatures(aoData.messages)]
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO medias
|
|
||||||
|
|
||||||
//Message Last Update
|
//Message Last Update
|
||||||
this.lastUpdate = aoData.last_update;
|
this.lastUpdate = aoData.last_update;
|
||||||
|
|
||||||
//Reschedule
|
//Reschedule
|
||||||
this.setFeedUpdateTimer(this.refreshRate);
|
this.setFeedUpdateTimer(this.refreshRate);
|
||||||
},
|
},
|
||||||
panToBetweenPanels(oLngLat, iZoom, fCallback) {
|
panToBetweenPanels(oLngLat, iZoom, iAnimDuration=500) {
|
||||||
const iXOffset = (this.settingsPanelOpen?getOuterWidth(this.$refs.settings):0) - (this.feedPanelOpen?getOuterWidth(this.$refs.feed):0);
|
const iXOffset = (this.settingsPanelOpen?getOuterWidth(this.$refs.settings):0) - (this.feedPanelOpen?getOuterWidth(this.$refs.feed):0);
|
||||||
|
|
||||||
this.map.once('moveend', fCallback);
|
return new Promise((resolve) => {
|
||||||
|
this.map.once('moveend', resolve);
|
||||||
this.map.easeTo({
|
this.map.easeTo({
|
||||||
center: oLngLat,
|
center: oLngLat,
|
||||||
zoom: iZoom,
|
zoom: iZoom,
|
||||||
offset: [iXOffset / 2, 0],
|
offset: [iXOffset / 2, 0],
|
||||||
duration: 500
|
duration: iAnimDuration
|
||||||
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
isMarkerVisible(oLngLat){
|
isMarkerVisible(oLngLat){
|
||||||
@@ -610,33 +557,6 @@ export default {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
async findPost(oPost) {
|
|
||||||
let oRef = this.goToPost(oPost);
|
|
||||||
if(oRef) {
|
|
||||||
oRef.executeMainAction();
|
|
||||||
}
|
|
||||||
else if(!this.feed.outOfData) {
|
|
||||||
await this.getNextFeed();
|
|
||||||
this.findPost(oPost);
|
|
||||||
}
|
|
||||||
else console.log('Missing element ID "'+oPost.id+'" of type "'+oPost.type+'"');
|
|
||||||
},
|
|
||||||
goToPost(oPost) {
|
|
||||||
let bFound = false;
|
|
||||||
let aoRefs = this.$refs.posts.filter((post) => {return post.postId == oPost.type+'-'+oPost.id;});
|
|
||||||
if(aoRefs.length == 1) {
|
|
||||||
let oRef = aoRefs[0];
|
|
||||||
this.$refs.feedSimpleBar.scrollElement.scrollTop += Math.round(
|
|
||||||
oRef.$el.getBoundingClientRect().top
|
|
||||||
+ window.pageYOffset
|
|
||||||
- parseFloat(getComputedStyle(this.$refs.feedSimpleBar.$el).paddingTop)
|
|
||||||
);
|
|
||||||
|
|
||||||
//this.hash.items = [this.hash.items[0]];
|
|
||||||
|
|
||||||
return oRef;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ export default {
|
|||||||
options: Object,
|
options: Object,
|
||||||
type: String
|
type: String
|
||||||
},
|
},
|
||||||
|
emits: ['opening-lightbox'],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
title:''
|
title:''
|
||||||
@@ -41,6 +42,7 @@ export default {
|
|||||||
:data-id="options.id_media"
|
:data-id="options.id_media"
|
||||||
:data-title="title"
|
:data-title="title"
|
||||||
:data-orientation="options.rotate"
|
:data-orientation="options.rotate"
|
||||||
|
@click="$emit('opening-lightbox', $event)"
|
||||||
ref="link"
|
ref="link"
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
|
|||||||
@@ -13,23 +13,24 @@ export default {
|
|||||||
projectRelTime
|
projectRelTime
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
type: String,
|
|
||||||
options: Object,
|
options: Object,
|
||||||
medias: Array,
|
|
||||||
project: Object
|
project: Object
|
||||||
},
|
},
|
||||||
inject: ['lang', 'consts'],
|
inject: ['lang', 'consts'],
|
||||||
computed: {
|
computed: {
|
||||||
timeIcon() {
|
timeIcon() {
|
||||||
return (this.type == 'media')?'image-shot':'time';
|
return (this.options.type == 'media')?'image-shot':'time';
|
||||||
|
},
|
||||||
|
medias() {
|
||||||
|
return (this.options.type == 'media')?[this.options]:this.options?.medias;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div :class="type">
|
<div :class="options.type">
|
||||||
<div class="header" v-if="type=='message'">
|
<div class="header" v-if="options.type=='message'">
|
||||||
<h1>
|
<h1>
|
||||||
<spotIcon :icon="'message'" size="lg" :text="lang.get('feed.counter', options.displayed_id)" width="auto" />
|
<spotIcon :icon="'message'" size="lg" :text="lang.get('feed.counter', options.displayed_id)" width="auto" />
|
||||||
<span class="message-type">({{ options.type }})</span>
|
<span class="message-type">({{ options.type }})</span>
|
||||||
@@ -50,8 +51,8 @@ export default {
|
|||||||
<div class="section weather" v-if="options.weather_icon && options.weather_icon!='unknown'" :title="options.weather_cond==''?'':lang.get('weather.'+options.weather_icon)">
|
<div class="section weather" v-if="options.weather_icon && options.weather_icon!='unknown'" :title="options.weather_cond==''?'':lang.get('weather.'+options.weather_icon)">
|
||||||
<spotIcon :icon="options.weather_icon" fixed-width size="lg" :text="options.weather_temp+'°C'" />
|
<spotIcon :icon="options.weather_icon" fixed-width size="lg" :text="options.weather_temp+'°C'" />
|
||||||
</div>
|
</div>
|
||||||
<div v-if="medias.length > 0" class="section medias">
|
<div v-if="medias" class="section medias">
|
||||||
<spotIcon v-if="type=='message'" icon="media" fixed-width size="lg" :text="lang.get('media.nearby')" />
|
<spotIcon v-if="options.type=='message'" icon="media" fixed-width size="lg" :text="lang.get('media.nearby')" />
|
||||||
<div class="medias-list">
|
<div class="medias-list">
|
||||||
<projectMediaLink v-for="media in medias" :options="media" :type="'marker'" />
|
<projectMediaLink v-for="media in medias" :options="media" :type="'marker'" />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -84,19 +84,19 @@
|
|||||||
this.anchorIcon = 'link';
|
this.anchorIcon = 'link';
|
||||||
}, 5000);
|
}, 5000);
|
||||||
},
|
},
|
||||||
panMapToMessage() {
|
panMapToMarker(iAnimDuration=500) {
|
||||||
this.popupRequested = true;
|
this.popupRequested = true;
|
||||||
|
|
||||||
if(this.isMobile()) this.project.toggleFeedPanel(false, 'panToInstant');
|
if(this.isMobile()) this.project.toggleFeedPanel(false, 'panToInstant');
|
||||||
this.map.panToBetweenPanels(
|
this.hash.items = [this.hash.items[0], this.options.type, this.options.id];
|
||||||
this.lngLat,
|
|
||||||
this.focusZoomLevel,
|
return this.map.panToBetweenPanels(this.lngLat, this.focusZoomLevel, iAnimDuration).then(() => {
|
||||||
() => {this.map.openMarkerPopup(this.options.id_message);}
|
this.openMarkerPopup();
|
||||||
);
|
});
|
||||||
this.hash.items = [this.hash.items[0], this.options.type, this.options.id_message];
|
|
||||||
},
|
},
|
||||||
openMarkerPopup() {
|
openMarkerPopup() {
|
||||||
this.mouseOverDrill = true;
|
this.mouseOverDrill = true;
|
||||||
if(this.map.isMarkerVisible(this.lngLat)) this.map.openMarkerPopup(this.options.id_message);
|
if(this.map.isMarkerVisible(this.lngLat)) this.map.openMarkerPopup(this.options.id, this.options.type);
|
||||||
},
|
},
|
||||||
closeMarkerPopup() {
|
closeMarkerPopup() {
|
||||||
this.mouseOverDrill = false;
|
this.mouseOverDrill = false;
|
||||||
@@ -127,12 +127,12 @@
|
|||||||
executeMainAction() {
|
executeMainAction() {
|
||||||
switch(this.options.type) {
|
switch(this.options.type) {
|
||||||
case 'message':
|
case 'message':
|
||||||
this.panMapToMessage();
|
return this.panMapToMarker(0);
|
||||||
break;
|
|
||||||
case 'media':
|
case 'media':
|
||||||
this.$refs.medialink.openMedia();
|
this.$refs.medialink.openMedia();
|
||||||
if(this.lngLat) this.map.panToBetweenPanels(this.lngLat, this.focusZoomLevel);
|
if(this.lngLat) return this.panMapToMarker(0);
|
||||||
break;
|
default:
|
||||||
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -161,12 +161,12 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="body">
|
<div class="body">
|
||||||
<div v-if="options.type == 'message'" class="body-box">
|
<div v-if="options.type == 'message'" class="body-box">
|
||||||
<div class="drill" @click.prevent="executeMainAction" @mouseenter="openMarkerPopup" @mouseleave="closeMarkerPopup">
|
<div class="drill" @click.prevent="() => {this.panMapToMarker();}" @mouseenter="openMarkerPopup" @mouseleave="closeMarkerPopup">
|
||||||
<span v-if="options.weather_icon && options.weather_icon!='unknown'" class="weather clickable" :title="lang.get('weather.'+options.weather_icon)">
|
<span v-if="options.weather_icon && options.weather_icon!='unknown'" class="weather clickable" :title="lang.get('weather.'+options.weather_icon)">
|
||||||
<spotIcon :icon="options.weather_icon" :text="Math.round(options.weather_temp)+'°C'" text-classes="temperature" />
|
<spotIcon :icon="options.weather_icon" :text="Math.round(options.weather_temp)+'°C'" text-classes="temperature" />
|
||||||
</span>
|
</span>
|
||||||
<img class="staticmap clickable" :title="lang.get('media.click_zoom')" :src="options.static_img_url" />
|
<img class="staticmap clickable" :title="lang.get('media.click_zoom')" :src="options.static_img_url" />
|
||||||
<spotIconStack :mainClasses="'message drill-icon'" :iconMain="drillMainIcon" :iconSub="'footprint'" :icon-sub-transform="'rotate-270'" />
|
<spotIconStack :mainClasses="'message drill-icon'" :iconMain="drillMainIcon" iconSub="footprint" :icon-sub-transform="'rotate-270'" />
|
||||||
<div class="comment">
|
<div class="comment">
|
||||||
<p>
|
<p>
|
||||||
<spotIcon :icon="'coords'" margin="right" size="lg" />
|
<spotIcon :icon="'coords'" margin="right" size="lg" />
|
||||||
@@ -179,7 +179,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="options.type == 'media'" class="body-box">
|
<div v-else-if="options.type == 'media'" class="body-box">
|
||||||
<projectMediaLink :options="options" :type="'post'" ref="medialink" />
|
<projectMediaLink :options="options" :type="'post'" ref="medialink" @opening-lightbox="() => {if(this.lngLat) return this.panMapToMarker();}" />
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="options.type == 'post'">
|
<div v-else-if="options.type == 'post'">
|
||||||
<p class="message">{{ options.content }}</p>
|
<p class="message">{{ options.content }}</p>
|
||||||
|
|||||||
@@ -88,7 +88,6 @@ const ICONS = {
|
|||||||
map: faMapLocationDot,
|
map: faMapLocationDot,
|
||||||
marker: faLocationPin,
|
marker: faLocationPin,
|
||||||
footprint: faShoePrints,
|
footprint: faShoePrints,
|
||||||
'message-in': faShoePrints,
|
|
||||||
'track-off-track': faPersonHiking,
|
'track-off-track': faPersonHiking,
|
||||||
'track-main': faPersonHiking,
|
'track-main': faPersonHiking,
|
||||||
'track-hitchhiking': faCarSide,
|
'track-hitchhiking': faCarSide,
|
||||||
|
|||||||
@@ -10,6 +10,11 @@ $thumbnail-max-size: 60px;
|
|||||||
top: 0;
|
top: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
visibility: hidden;
|
||||||
|
|
||||||
|
.map-ready & {
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
|
||||||
.maplibregl-popup {
|
.maplibregl-popup {
|
||||||
max-width: 300px;
|
max-width: 300px;
|
||||||
|
|||||||
Reference in New Issue
Block a user