Files
spot/masks/project.html

1123 lines
39 KiB
HTML

<div id="projects">
<div id="background"></div>
<div id="submap">
<div class="loader fa fa-fw fa-map flicker" id="map_loading"></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 id="settings-sections-scrollbox">
<div class="settings-section">
<h1><i class="fa fa-fw push fa-project"></i>[#]lang:hikes[#]</h1>
<div id="settings-projects"></div>
</div>
<div class="settings-section">
<h1><i class="fa fa-fw push fa-map"></i>[#]lang:maps[#]</h1>
<div id="layers"></div>
</div>
<div class="settings-section newsletter">
<h1><i class="fa fa-fw push fa-newsletter"></i>[#]lang:newsletter[#]</h1>
<input type="email" name="email" id="email" placeholder="[#]lang:nl_email_placeholder[#]" /><button id="nl_btn"><span><i class="fa"></i></span></button>
<div id="settings-feedback" class="feedback"></div>
<div id="nl_desc"></div>
</div>
<div class="settings-section admin" id="admin_link">
<h1><i class="fa fa-fw push fa-admin"></i>[#]lang:admin[#]</h1>
<a class="button" id="admin_config" name="admin_config" href="#admin"><i class="fa fa-config push"></i>[#]lang:admin_config[#]</a>
<a class="button" id="admin_upload" name="admin_upload" href="#upload"><i class="fa fa-upload push"></i>[#]lang:admin_upload[#]</a>
</div>
</div>
</div>
<div class="settings-footer"><a href="https://git.lutran.fr/franzz/spot" title="[#]lang:credits_git[#]" target="_blank" rel="noopener"><i class="fa fa-credits push"></i>[#]lang:credits_project[#]</a> [#]lang:credits_license[#]</div>
</div>
</div>
<div id="feed">
<div id="feed-panel">
<div id="poster"></div>
<div id="posts_list"></div>
<div id="loading"></div>
</div>
</div>
<div id="elems">
<div id="settings-button" class="spot-control"><i class="fa fa-menu"></i></div>
<div id="feed-button" class="spot-control"><i class="fa"></i></div>
<div id="legend" class="leaflet-control-layers leaflet-control leaflet-control-layers-expanded">
<div class="track"><span class="line main"></span><span class="desc">[#]lang:track_main[#]</span></div>
<div class="track"><span class="line off-track"></span><span class="desc">[#]lang:track_off-track[#]</span></div>
<div class="track"><span class="line hitchhiking"></span><span class="desc">[#]lang:track_hitchhiking[#]</span></div>
</div>
<div id="title" class="leaflet-control-layers leaflet-control leaflet-control-layers-expanded leaflet-control-inline"><span id="project_name" class=""></span></div>
</div>
</div>
<div id="mobile" class="mobile"></div>
<script type="text/javascript">
oSpot.onSamePageMove = function(asHash) {
if(self.tmp('first_exec')) initPage(asHash);
else if(asHash.items[0] != self.vars(['project', 'codename'])) {
self.tmp('map').remove();
self.tmp('$Map').empty();
self.tmp('map', null);
self.tmp('$PostList').empty();
setFeedUpdateTimer(-1);
initProject(asHash.items[0]);
}
return false;
};
oSpot.pageInit = function(asHash) {
self.tmp('first_exec', true);
initPage(asHash);
};
oSpot.onResize = function() {
self.tmp('map_offset', -1 * (isFeedPanelOpen()?self.tmp('$Feed').outerWidth(true):0) / $('body').outerWidth(true));
if(typeof self.tmp('elev') != 'undefined' && self.tmp('elev')._showState) {
self.tmp('elev').resize({width:getElevWidth()});
}
};
oSpot.onQuitPage = function() {
setFeedUpdateTimer(-1);
return true;
}
oSpot.onKeydown = function(oEvent) {
switch(oEvent.which) {
case 27:
if(!isLightboxOpen()) {
$bFeedPanelOpen = isFeedPanelOpen();
$bSettingsPanelOpen = isSettingsPanelOpen();
$bAnimation = ($bFeedPanelOpen && $bSettingsPanelOpen)?'none':null;
if($bFeedPanelOpen) toggleFeedPanel(false, $bAnimation);
if($bSettingsPanelOpen) toggleSettingsPanel(false, $bAnimation);
}
break;
}
}
function toggleFeedPanel(bShow, sMapAction) {
bOldValue = isFeedPanelOpen();
self.tmp('$Projects').toggleClass('with-feed', (typeof bShow === 'undefined')?null:bShow);
bNewValue = isFeedPanelOpen();
if(bOldValue != bNewValue) {
oSpot.onResize();
sMapAction = sMapAction || 'panTo';
switch(sMapAction) {
case 'none': break;
case 'panTo': oSpot.tmp('map').panBy([(isFeedPanelOpen()?1:-1)*self.tmp('$Feed').outerWidth(true)/2, 0], {duration: 0.5}); break;
case 'panToInstant': oSpot.tmp('map').panBy([(isFeedPanelOpen()?1:-1)*self.tmp('$Feed').outerWidth(true)/2, 0]); break;
case 'fitBounds': oSpot.tmp('map').fitBounds(self.tmp('track').getBounds(), {paddingTopLeft: L.point(5, self.tmp('marker_size').height + 5), paddingBottomRight: L.point(self.tmp('$Feed').outerWidth(true) + 5, 5)}); break;
}
}
}
function isFeedPanelOpen() {
return self.tmp('$Projects').hasClass('with-feed');
}
function toggleSettingsPanel(bShow, sMapAction) {
bOldValue = isSettingsPanelOpen();
self.tmp('$Projects').toggleClass('with-settings', (typeof bShow === 'undefined')?null:bShow);
bNewValue = isSettingsPanelOpen();
if(bOldValue != bNewValue) {
oSpot.onResize();
sMapAction = sMapAction || 'panTo';
switch(sMapAction) {
case 'none': break;
case 'panTo': oSpot.tmp('map').panBy([(isSettingsPanelOpen()?-1:1)*self.tmp('$Settings').outerWidth(true)/2, 0], {duration: 0.5}); break;
case 'panToInstant': oSpot.tmp('map').panBy([(isSettingsPanelOpen()?-1:1)*self.tmp('$Settings').outerWidth(true)/2, 0]); break;
}
}
}
function isSettingsPanelOpen() {
return self.tmp('$Projects').hasClass('with-settings');
}
function updateSettingsPanel(asLastUpdate) {
var $LastUpdate = self.tmp('$Settings').find('#last_update').toggle(self.vars(['project', 'mode']) == self.consts.modes.blog && asLastUpdate.unix_time > 0);
$LastUpdate.find('abbr')
.attr('title', asLastUpdate.formatted_time)
.text(oSpot.lang('last_update')+' '+asLastUpdate.relative_time);
}
function isLightboxOpen() {
return $('#lightbox').is(':visible');
}
function isMobile() {
return $('#mobile').is(':visible');
}
function initPage(asHash) {
if(asHash.items.length==0) self.setHash(asHash.page, [self.vars('default_project_codename')]);
else {
self.tmp('$Projects', $('#projects'));
self.tmp('$Map', $('#map'));
self.tmp('$Poster', $('#poster'));
self.tmp('$PostList', $('#posts_list'));
self.tmp('$Feed', $('#feed'));
self.tmp('$Settings', $('#settings'));
self.tmp('$Title', $('#title'));
self.tmp('updatable', true);
self.tmp('out-of-data', false);
self.tmp('markers', 'object');
self.tmp('trail-markers', 'object');
self.tmp('marker_size', {width: 32, height: 32});
if(!isMobile()) toggleFeedPanel(true, 'none');
//Lightbox options
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) => {
oSpot.updateHash('media', oMedia.id);
if(oMedia.set == 'post-medias') goToPost({type: 'media', id: oMedia.id});
}
});
//Assign Track Type Colors
self.tmp('track-type-styles', 'object');
$('#legend').find('.line').each(function(iKey, oLegend){
var $Legend = $(oLegend);
var sTrackType = $Legend.attr('class').replace('line', '').trim();
self.tmp(['track-type-styles', sTrackType], {weight: parseInt($Legend.css('height')), color: $Legend.css('background-color'), opacity: 1});
});
//Post Panel one-off init (see initPosts for project related init)
//Scrollbar
self.tmp('simple-bar', new SimpleBar($('#feed-panel')[0]));
self.tmp('simple-bar').getScrollElement().addEventListener('scroll', onFeedScroll);
//Add "Loading" Post
getPost({type: 'loading', headerless: true, formatted_time: '', relative_time: ''}).appendTo($('#loading'));
//Mobile Touchscreen Events
self.tmp('$Feed').onSwipe(
(oPos) => {
this.$Panels = $('.leaflet-right').add(self.tmp('$Feed')).each(function() {
$(this).data('initial-offset', parseInt($(this).css('right')));
});
},
(oPos) => {
this.$Panels.each(function(){
$(this).css({'right': ($(this).data('initial-offset') + Math.min(oPos.xStart - oPos.xMove, 0))+'px', 'transition': 'none'});
});
},
(oPos) => {
if(oPos.xEnd - oPos.xStart > 100 && Math.abs(oPos.yEnd - oPos.yStart) < 100) toggleFeedPanel(false);
this.$Panels.each(function(){
$(this).css({'right': '', 'transition': ''});
});
}
);
self.tmp('$Settings').onSwipe(
(oPos) => {
this.$Panels = $('.leaflet-left').add(self.tmp('$Settings')).each(function() {
$(this).data('initial-offset', parseInt($(this).css('left')));
});
},
(oPos) => {
this.$Panels.each(function(){
$(this).css({'left': ($(this).data('initial-offset') + Math.min(oPos.xMove - oPos.xStart, 0))+'px', 'transition': 'none'});
});
},
(oPos) => {
if(oPos.xEnd - oPos.xStart < -100 && Math.abs(oPos.yEnd - oPos.yStart) < 100) toggleSettingsPanel(false);
this.$Panels.each(function(){
$(this).css({'left': '', 'transition': ''});
});
}
);
//Feed Panel
initPosts();
//Settings Panel
initSettings();
//project Bootstrap
var oFocusPost = asHash.items[1]?{type: asHash.items[1], id: asHash.items[2]}:null;
initProject(asHash.items[0], oFocusPost);
}
}
function initProject(sProjectCodeName, oFocusPost){
self.tmp('first_exec', false);
self.vars('project', self.vars(['projects', sProjectCodeName]));
//Page Title
self.setPageTitle(oSpot.vars(['project', 'name']));
self.tmp('$Title').find('#project_name').text(oSpot.vars(['project', 'name']));
//Load Track & Markers
$.when(
//Markers: Spot Messages & Medias
self.get(
'markers',
function(){},
{id_project: self.vars(['project', 'id'])},
function(e){console.log(e);}
),
//Project Geojson: Hike track
$.ajax({
dataType: 'json',
url: self.vars(['project', 'geofilepath']),
mimeType: 'application/json'
})
).done(function(aoMessages, aoTracks) {
var asData = aoMessages[0]['data'];
setMapLayers(asData['maps']);
initSpotMessages(asData['messages'], aoTracks[0]);
updateSettingsPanel(asData['last_update']);
});
//Show/Hide Poster Panel
var bHistoMode = (self.vars(['project', 'mode']) == self.consts.modes.histo);
self.tmp('$Poster').toggleClass('histo-mode', bHistoMode);
//Feed auto-update
updateFeed(true, false, function(){focusOnPost(oFocusPost);});
self.tmp('simple-bar').getScrollElement().scrollTop = 0;
if(!bHistoMode) onAutoUpdate(true);
}
function initPosts() {
//Add post form
self.tmp('$Poster').append(getPost({type: 'poster', formatted_time: '', relative_time: oSpot.lang('post_new_message')}));
//Add archived Project Notice
self.tmp('$Poster').append(getPost({type: 'archived', headerless: true}));
//Auto-adjust text area height
autosize($('#post'));
//Add post Event handling
$('#submit').click(function(){
if(self.tmp('$Poster').checkForm())
{
self.get(
'add_post',
function()
{
$('#post').val('');
onAutoUpdate();
},
{
id_project: self.vars(['project', 'id']),
name: $('#name').val(),
content: $('#post').val()
}
);
}
});
}
function initSettings(){
//Scrollbar
new SimpleBar($('#settings-sections-scrollbox')[0]);
//Feedback display function
var settingsFeedback = function(sType, sMsg){
$('<p>', {'class': sType})
.append($('<i>', {'class':'fa push fa-'+sType}))
.append(sMsg)
.appendTo($('#settings-feedback'))
.slideDown('fast')
.delay(5000)
.slideUp('fast', function(){$(this).remove();});
};
//Newsletter Subscription
$('#nl_btn').click(function(){
var sAction = $(this).prop('name');
var sEmail = $('#email').val();
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(sEmail)) settingsFeedback('error', oSpot.lang('nl_invalid_email'));
else {
oSpot.get(
sAction,
function(asData, sDesc) {
settingsFeedback('success', sDesc);
oSpot.vars('user', asData);
setUserInterface();
},
{'email': sEmail, 'name': $('#name').val()},
function(sDesc) {settingsFeedback('error', sDesc);},
function(sState) {
var bLoading = (sState=='start');
$('#nl_btn')
.prop('disabled', bLoading)
.toggleClass('loading', bLoading);
}
);
}
});
//Admin Access
$('#admin_link').toggle(oSpot.checkClearance(self.consts.clearances.admin));
//Twink interface with user data
setUserInterface();
}
function setUserInterface() {
var asUserInfo = oSpot.vars('user');
if(asUserInfo.id_user) { //Subscribed User
$('#email').val(asUserInfo.email).prop('disabled', true);
$('#nl_btn').attr({name: 'unsubscribe', title: oSpot.lang('nl_unsubscribe'), 'class':'unsubscribe'});
$('#nl_desc').text(oSpot.lang('nl_subscribed_desc'));
//Populate nickname
if(asUserInfo.name) $('#name').val(asUserInfo.name);
}
else { //Unsubscribed User
$('#email').val('').prop('disabled', false);
$('#nl_btn').attr({name: 'subscribe', title: oSpot.lang('nl_subscribe'), 'class':'subscribe'});
$('#nl_desc').text(oSpot.lang('nl_unsubscribed_desc'));
}
}
function getElevWidth() {
var
iPageWidth = self.tmp('$Projects').width(),
iFeedPanelWidth = isFeedPanelOpen()?self.tmp('$Feed').outerWidth(true):0,
iSettingsPanelWidth = isSettingsPanelOpen()?self.tmp('$Settings').outerWidth(true):0,
iLegendWidth = $('.leaflet-bottom.leaflet-left > .leaflet-control-layers').outerWidth(true),
oElevRightMarging = parseInt($('.leaflet-bottom.leaflet-right > .leaflet-control-scale').css('margin-right').slice(0, -2));
return iPageWidth - iFeedPanelWidth - iSettingsPanelWidth - iLegendWidth - oElevRightMarging;
}
function setMapLayers(asLayers) {
oSpot.tmp('layers', {});
$.each(asLayers, function(iKey, asLayer) {
var sMapName = oSpot.lang('map_'+asLayer.codename);
var asMapOptions = {
tileSize: parseInt(asLayer.tile_size),
zoomOffset: (parseInt(asLayer.tile_size) / -256 + 1),
minZoom: parseInt(asLayer.min_zoom),
maxZoom: parseInt(asLayer.max_zoom),
attribution: asLayer.attribution
}
if(asLayer.token != '') asMapOptions.token = asLayer.token;
oSpot.tmp('layers')[sMapName] = L.tileLayer(asLayer.pattern, asMapOptions);
});
}
function initSpotMessages(aoMessages, aoTracks) {
var bIsMobile = isMobile();
//Map
var oMap = L.map(self.tmp('$Map')[0], {
layers: [oSpot.tmp('layers')[oSpot.lang('map_satellite')]],
attributionControl: false,
zoomControl: false
});
self.tmp('map', oMap);
//Controls: Settings Panel
var oSettingsPanel = L.control({position: bIsMobile?'bottomleft':'topleft'});
var $SettingsButton = $('#settings-button').clone();
$SettingsButton.click(toggleSettingsPanel);
oSettingsPanel.onAdd = function(oMap) {return $SettingsButton[0];};
//Project Title
var oTitle = L.control({position: bIsMobile?'bottomleft':'topleft'});
var $Title = $('#title').clone();
oTitle.onAdd = function(oMap) {return $Title[0];};
if(bIsMobile) {
oTitle.addTo(oMap);
oSettingsPanel.addTo(oMap);
}
else {
oSettingsPanel.addTo(oMap);
oTitle.addTo(oMap);
}
//Controls: Feed Panel
var oFeedPanel = L.control({position: bIsMobile?'bottomright':'topright'});
var $FeedButton = $('#feed-button').clone();
$FeedButton.click(toggleFeedPanel);
oFeedPanel.onAdd = function(oMap) {return $FeedButton[0];};
oFeedPanel.addTo(oMap);
//Controls: Legend
if(!bIsMobile) {
var oLegend = L.control({position: 'bottomleft'});
oLegend.onAdd = function(oMap) {return $('#legend').clone()[0];};
oLegend.addTo(oMap);
}
//Controls: Projects
var $Labels = $('<div>', {'class': 'leaflet-control-layers-base'});
$.each(self.vars('projects'), function(sCodeName, asProject){
var asRadioAttrs = {'type': 'radio', 'class': 'leaflet-control-layers-selector', 'name':'project', 'value': sCodeName};
if(asProject.id == self.vars(['project', 'id'])) asRadioAttrs.checked = 'checked';
var $Radio = $('<input>', asRadioAttrs).change(function(){
toggleSettingsPanel(false);
self.setHash(self.vars('page'), [$(this).val()]);
});
var $Label = $('<label>').append($('<div>')
.append($Radio)
.append($('<span>').text(' '+asProject.name))
.append($('<a>', {'class':'fa fa-download push-left', href:asProject.gpxfilepath, title:oSpot.lang('track_download')}).click(function(e){e.stopPropagation();}))
);
$Labels.append($Label);
});
$('#settings-projects').empty().append($Labels);
//Controls: Scale
oScale = L.control.scale({imperial: false, position: bIsMobile?'topright':'bottomright'}).addTo(oMap);
//Controls: Elevation
if(!bIsMobile) {
var aoElevTracks = {type: 'FeatureCollection', features:[], properties: {summary: 'trackType'}};
var aoLegend = {trackType: {}};
for(var i in aoTracks.features) {
if(aoTracks.features[i].properties.type != 'hitchhiking') {
//Feature
var oTrack = jQuery.extend(true, {}, aoTracks.features[i]);
var sType = aoTracks.features[i].properties.type;
oTrack.properties.attributeType = sType;
aoElevTracks.features.push(oTrack);
//Legend
aoLegend.trackType[sType] = {text: oSpot.lang('track_'+sType), color: self.tmp(['track-type-styles', sType, 'color'])};
}
}
var oElev = L.control.heightgraph({
position: 'bottomright',
expand: false,
width: getElevWidth(),
height: 200,
margins: {
top: 10,
right: 30,
bottom: 55,
left: 50
},
translation: {
distance: oSpot.lang('distance'),
elevation: oSpot.lang('elevation'),
segment_length: oSpot.lang('segment_length'),
type: oSpot.lang('type'),
legend: oSpot.lang('legend')
},
xTicks: 3,
yTicks: 2,
mappings: aoLegend,
highlightStyle: {
weight: self.tmp(['track-type-styles', 'main', 'weight']),
opacity: 1,
color: '#326526'
},
expandCallback: function(bExpanded) {
$('.leaflet-control-scale').toggle(!bExpanded);
let iElevWidth = getElevWidth();
if(iElevWidth != self.tmp('elev').options.width) self.tmp('elev').resize({width:iElevWidth});
}
});
oElev.addTo(oMap);
oElev.addData([aoElevTracks]);
self.tmp('elev', oElev);
self.tmp('elev-data', [aoElevTracks]);
}
//Controls: Tiles (layers): Add & Move to Settings Panel
L.control.layers(oSpot.tmp('layers'), null, {position: 'topleft'}).addTo(oMap);
$('#layers').empty().append($('.leaflet-control-layers-list .leaflet-control-layers-base'));
//Actual Tracks: Track with corresponding colors
var oActualTracks = L.geoJson(aoTracks, {
style: function(oTrack) {
return self.tmp(['track-type-styles', oTrack.properties.type]);
}
}).addTo(oMap);
//"Hover" Tracks: Wider track (thus hover area) to avoid flickering popups
self.tmp('track', L.geoJson(aoTracks, {
style: {weight: 20, opacity: 0},
onEachFeature: function(feature, oLayer) {
var asProperties = feature.properties;
var $Tooltip = $('<div>', {'class':'track_tooltip'});
//Title
$('<h1>', {'title':asProperties.name})
.addIcon('fa-track-'+asProperties.type+' fa-fw', true)
.append(asProperties.name)
.appendTo($Tooltip);
//Description
var $TooltipBody = $('<div>', {'class':'body'}).appendTo($Tooltip);
if(asProperties.Name != asProperties.description && asProperties.description != '') {
$('<p>', {'class':'description', 'title':asProperties.description}).text(asProperties.description).appendTo($TooltipBody);
}
//Calculate Details
var aiCoords = feature.geometry.coordinates;
if(aiCoords && asProperties.type != 'hitchhiking') {
var iDistance = 0, iElevDrop = 0, iElevGain = 0, iTime = 0;
//Track duration (in hours)
for (var i = 1; i < aiCoords.length; i++) {
var oCurrPoint = new L.LatLng(aiCoords[i][1], aiCoords[i][0]);
var oPrevPoint = new L.LatLng(aiCoords[i - 1][1], aiCoords[i - 1][0]);
var iElevDelta = aiCoords[i][2] - aiCoords[i - 1][2];
var iSegDistance = oCurrPoint.distanceTo(oPrevPoint);
iDistance += iSegDistance;
iElevDrop += Math.min(iElevDelta, 0);
iElevGain += Math.max(iElevDelta, 0);
var iSpeedCorrecRatio = 0;
var iAngle = iElevDelta / iSegDistance;
if(iAngle < -1) iSpeedCorrecRatio = 0.5;
else if(iAngle < -0.2) iSpeedCorrecRatio = 1.25;
else if(iAngle < 0.1) iSpeedCorrecRatio = 1;
else if(iAngle < 0.25) iSpeedCorrecRatio = 0.85;
else if(iAngle < 0.5) iSpeedCorrecRatio = 0.6;
else if(iAngle < 1) iSpeedCorrecRatio = 0.5;
else iSpeedCorrecRatio = 0.25;
iTime += iSegDistance / 1000 * iSpeedCorrecRatio / 3.5 ; //default speed: 3.5km/h
}
//Conversion of hours into natural language
var sDuration = oSpot.getNaturalDuration(iTime);
//Tooltip details info
$TooltipBody
.append($('<div>', {'class':'separator'}))
.append($('<div>', {'class':'details'})
.append($('<p>', {'class':'detail'}).addIcon('fa-distance fa-fw', true).append(Math.round(iDistance/1000)+'km'))
.append($('<p>', {'class':'detail'}).addIcon('fa-time fa-fw', true).append(sDuration))
.append($('<p>', {'class':'detail'}).addIcon('fa-elev-gain fa-fw', true).append(iElevGain+'m'))
.append($('<p>', {'class':'detail'}).addIcon('fa-elev-drop fa-fw', true).append(iElevDrop+'m'))
);
//Trail Start/End
self.tmp(['trail-markers', asProperties.name], {
'start' : L.marker(new L.latLng(aiCoords[0][1], aiCoords[0][0]), {icon: getDivIcon('track-start')}),
'end' : L.marker(new L.latLng(aiCoords[aiCoords.length - 1][1], aiCoords[aiCoords.length - 1][0]), {icon: getDivIcon('track-end')})
});
oLayer
.bindPopup($Tooltip[0])
.on('click', function(e) {
toggleSoftPopup(e, 'open');
})
.on('popupclose', function(e) {
toggleSoftPopup(e, 'close');
});
}
}
}).addTo(oMap));
//Last message
var oLastMsg = (aoMessages.length > 0)?aoMessages[aoMessages.length-1]:{};
//Centering map
var iFeedPanelWidth = bIsMobile?0:parseInt(self.tmp('$Feed').outerWidth(true));
var iTitleHeight = bIsMobile?self.tmp('$Title').outerHeight(true):0;
if(
self.vars(['project', 'mode']) == self.consts.modes.blog &&
!$.isEmptyObject(oLastMsg) &&
self.getHash()[2] != 'message'
) {
//Zoom on last message
oMap.setView(L.latLng(oLastMsg.latitude, oLastMsg.longitude), 15);
oMap.panBy([iFeedPanelWidth/2, /*bIsMobile?(self.tmp('$Title').outerHeight(true) * -1/2):*/0]);
}
else {
oMap.fitBounds(
self.tmp('track').getBounds(),
{
paddingTopLeft: L.point(5, 5 + self.tmp('marker_size').height), //So that the first marker isn't off screen
paddingBottomRight: L.point(5 + iFeedPanelWidth, 5 + iTitleHeight)
}
);
}
//Add Spot messages
addSpotMessages(aoMessages);
//Open tooltip on latest message in mobile mode
if(
!$.isEmptyObject(oLastMsg) &&
self.vars(['project', 'mode']) == self.consts.modes.blog &&
(!oLastMsg.medias || oLastMsg.medias.length < 3) &&
isMobile()
) oSpot.tmp(['markers', oLastMsg.id_message]).openPopup();
/*
oSpot.tmp('tracks', aoTracks);
next(24, 0, 0, 5);
*/
}
function addSpotMessages(aoMessages) {
//Spot Messages
var iWorkSpaceMinWidth = isMobile()?self.tmp('$Projects').width():(self.tmp('$Projects').width() - self.tmp('$Feed').outerWidth(true) - self.tmp('$Settings').outerWidth(true));
$.each(aoMessages, function(iKey, oMsg){
//Marker
var oMarker = L.marker(L.latLng(oMsg.latitude, oMsg.longitude), {
id: oMsg.id_message,
riseOnHover: true,
icon: getDivIcon('message-in fa-rotate-270')
}).addTo(self.tmp('map'));
//Tooltip
$Tooltip = $('<div>', {'class':'info-window'})
.append($('<h1>')
.addIcon('fa-message fa-lg', true)
.append($('<span>').text('Message '+oSpot.lang('counter', oMsg.displayed_id)))
.append($('<span>', {'class':'message-type'}).text('('+oMsg.type+')'))
)
.append($('<div>', {'class':'separator'}))
.append($('<p>', {'class':'coordinates'})
.addIcon('fa-coords fa-fw fa-lg', true)
.append(getGoogleMapsLink(oMsg)))
.append($('<p>', {'class':'time'})
.addIcon('fa-time fa-fw fa-lg', true)
.append(oMsg.formatted_time+(self.vars(['project', 'mode'])==self.consts.modes.blog?' ('+oMsg.relative_time+')':'')));
//Tooltip: Time Zone
if(oMsg.formatted_time_local != oMsg.formatted_time) {
$Tooltip.append($('<p>', {'class':'timezone'})
.addIcon('fa-timezone fa-fw fa-lg', true)
.append(oSpot.lang('local_time', getRelativeTime(oMsg.formatted_time_local, oMsg.day_offset))));
}
//Weather
if(oMsg.weather_icon && oMsg.weather_icon!='unknown') {
$Tooltip.append($('<p>', {'class':'weather', 'title':(oMsg.weather_cond==''?'':oSpot.lang(oMsg.weather_cond))})
.addIcon('fa-'+oMsg.weather_icon+' fa-fw fa-lg', true)
.append(oMsg.weather_temp+'°C')
);
}
//Tooltip: Medias
if(oMsg.medias) {
var $Medias = $('<div>', {'class':'medias'});
$.each(oMsg.medias, function(iKey, asMedia) {
$Medias.append(getMediaLink(asMedia, 'marker'));
});
$Tooltip.append($Medias);
}
oMarker.bindPopup($Tooltip[0], {
maxWidth: iWorkSpaceMinWidth,
autoPan: false,
closeOnClick: true,
offset: new L.Point(0, -30)
});
oSpot.tmp(['markers', oMsg.id_message], oMarker);
});
}
function toggleSoftPopup(e, sMode) {
switch(sMode) {
case 'open':
var oLatLng = L.GeometryUtil.closest(self.tmp('map'), e.sourceTarget, e.latlng);
var oPopup = e.target.getPopup();
if(!oPopup.isOpen()) oPopup.setLatLng(oLatLng).openOn(self.tmp('map'));
var asTrailMarkers = self.tmp(['trail-markers', e.target.feature.properties.name]);
var oPointStart = self.tmp('map').latLngToLayerPoint(asTrailMarkers.start.getLatLng());
var oPointEnd = self.tmp('map').latLngToLayerPoint(asTrailMarkers.end.getLatLng());
if(oPointStart.distanceTo(oPointEnd) > 200) {
asTrailMarkers.start.addTo(self.tmp('map'));
asTrailMarkers.end.addTo(self.tmp('map'));
}
break;
case 'close':
//e.target.closePopup();
var asTrailMarkers = self.tmp(['trail-markers', e.target.feature.properties.name]);
asTrailMarkers.start.remove();
asTrailMarkers.end.remove();
break;
}
}
function getDivIcon(sIcon) {
return L.divIcon({
className: '',
html: '<span class="fa-stack"><i class="fa fa-message fa-stack-2x"></i><i class="fa fa-'+sIcon+' fa-stack-1x"></i></span>',
iconSize: [self.tmp('marker_size').width, self.tmp('marker_size').height],
iconAnchor: [self.tmp('marker_size').width / 2, self.tmp('marker_size').height] //position from icon's top left corner (iconAnchor = marker's position)
});
}
function onFeedScroll() {
var $Box = $(this);
var $BoxContent = $Box.find('.simplebar-content');
if(($Box.scrollTop() + $(window).height()) / $BoxContent.height() >= 0.8) updateFeed();
}
function onAutoUpdate(bStart) {
bStart = bStart || false;
if(!bStart) checkNewFeed();
setFeedUpdateTimer(60, onAutoUpdate);
}
function checkNewFeed() {
self.get(
'new_feed',
function(asData) {
var iItemCount = Object.keys(asData.feed).length;
if(iItemCount > 0) self.tmp('ref_id_first', asData.ref_id_first);
//Feed
var $Posts = $('<div>');
$.each(asData.feed, function(iKey, asPost) {
$Posts.append(getPost(asPost));
});
self.tmp('$PostList').prepend($Posts.children());
//Markers
addSpotMessages(asData.messages);
//Message Last Update
updateSettingsPanel(asData.last_update);
}, {
id_project: self.vars(['project', 'id']),
id: self.tmp('ref_id_first')
}
);
}
function setFeedUpdateTimer(iSeconds, fCallback) {
if(typeof self.tmp('update_timer') != 'undefined') clearTimeout(self.tmp('update_timer'));
if(iSeconds >= 0) self.tmp('update_timer', setTimeout(fCallback, iSeconds * 1000));
}
function updateFeed(bFirstChunk, bDiscrete, fCallback) {
bFirstChunk = bFirstChunk || false;
bDiscrete = bDiscrete || false;
fCallback = fCallback || function(){};
//First/Last Item displayed on the feed
if(bFirstChunk) self.tmp('ref_id_last', 0);
if(self.tmp('updatable')) {
if(!self.tmp('out-of-data') || bFirstChunk) {
self.tmp('updatable', false);
if(!bDiscrete) $('#loading').show();
var $Posts = $('<div>');
var a$Medias = [];
self.get(
'next_feed',
function(asData) {
$('#loading').hide();
var iItemCount = Object.keys(asData.feed).length;
//Update pointers
self.tmp('out-of-data', iItemCount < self.vars('chunk_size'));
if(iItemCount > 0) {
self.tmp('ref_id_last', asData.ref_id_last);
if(bFirstChunk === true) self.tmp('ref_id_first', asData.ref_id_first);
}
//Add posts
$.each(asData.feed, function(iKey, asPost){
var $Post = getPost(asPost);
$Posts.append($Post);
if(asPost.type == 'media' && isLightboxOpen()) a$Medias.push($Post.find('.media-link'));
});
if(bFirstChunk === true) self.tmp('$PostList').empty();
self.tmp('$PostList').append($Posts.children());
self.tmp('$PostList').find('img').waitForImages(true).done(fCallback);
//Add media to lightbox
$.each(a$Medias, function(iKey, $Media) {
lightbox.addToAlbum($Media);
});
if(a$Medias.length > 0) lightbox.updateDetails();
self.tmp('updatable', true);
}, {
id_project: self.vars(['project', 'id']),
id: self.tmp('ref_id_last')
}
);
}
}
else if(bFirstChunk) setFeedUpdateTimer(0.2, function(){updateFeed(bFirstChunk, bDiscrete, fCallback);});
}
function focusOnPost(oFocusPost) {
if(oFocusPost) {
if(oFocusPost.type=='message' && (!oSpot.tmp(['markers', oFocusPost.id]) || !oSpot.tmp('map'))) setTimeout(function(){focusOnPost(oFocusPost);}, 100);
else {
var sElemId = '#'+oFocusPost.type+'-'+oFocusPost.id;
var $Post = oSpot.tmp('$PostList').find(sElemId);
var bGetToTop = (oSpot.tmp('$PostList').height() - (($Post.length > 0)?$Post.position().top:0) >= $(window).height());
if($Post.length > 0 && (bGetToTop || self.tmp('out-of-data'))) {
goToPost(oFocusPost);
if(oFocusPost.type=='media' || oFocusPost.type=='message') $Post.find('a.drill').click();
}
else if(!self.tmp('out-of-data')) updateFeed(false, false, function() {focusOnPost(oFocusPost);});
else console.log('Missing element ID '+sElemId);
//Reset Hash
oSpot.flushHash(['post', 'message']);
}
}
}
function goToPost(oPost) {
var sElemId = '#'+oPost.type+'-'+oPost.id;
var $Post = oSpot.tmp('$PostList').find(sElemId);
if($Post.length > 0) {
self.tmp('simple-bar').getScrollElement().scrollTop += Math.round(
$Post.offset().top
- parseInt($('#feed-panel').css('padding-top'))
/*
$Post.position().top
+ parseInt($Post.css('margin-top'))
+ self.tmp('$Poster').outerHeight(true)
+ parseInt($('#feed-panel').css('padding-top'))
*/
);
}
else console.log('Missing element ID '+sElemId);
}
function getRelativeTime(sLocalTime, iOffset) {
let $Time = $('<span>').text(sLocalTime.substr(-5));
if(iOffset != '0') $Time.append($('<sup>', {'title': iOffset+' '+oSpot.lang('unit_day')+' ('+sLocalTime.substr(0, 5)+')'}).text(' '+iOffset));
return $Time.html();
}
function getPost(asPost) {
asPost.headerless = asPost.headerless || false;
var bLink = false;
var asHash = oSpot.getHash();
var sHash = '#'+[asHash.page, asHash.items[0], asPost.type, asPost.id].join(self.consts.hash_sep);
var $Post = $('<div>', {'class':'post-item '+asPost.type+(asPost.headerless?' headerless':'')});
if(asPost.id) $Post.prop('id', asPost.type+'-'+asPost.id);
var sRelTime = (asPost.relative_time!='')?((self.vars('project') && self.vars(['project', 'mode'])==self.consts.modes.histo)?asPost.formatted_time.substr(0, 10):asPost.relative_time):'';
var sAbsTime = asPost.formatted_time;
var sAbsTimeLocal = asPost.formatted_time_local;
var bTimeDiff = (sAbsTime && sAbsTimeLocal != sAbsTime);
var sType = asPost.subtype || asPost.type;
var $Body = {};
switch(asPost.type) {
case 'message':
bLink = true;
$Body = $('<div>', {'class':'body-box'})
.data('id', asPost.id_message)
.data('clicked', false)
.append($('<p>').addIcon('fa-coords', true).append(getGoogleMapsLink(asPost)))
.append($('<p>').addIcon('fa-time', true).append(sAbsTime))
.append(bTimeDiff?$('<p>').addIcon('fa-timezone', true).append(oSpot.lang('local_time', getRelativeTime(sAbsTimeLocal, asPost.day_offset))):'')
.append($('<a>', {'class':'drill'})
.append((!asPost.weather_icon || asPost.weather_icon=='unknown')?'':$('<span>', {'class':'weather clickable', 'title':oSpot.lang(asPost.weather_cond)}).addIcon('fa-'+asPost.weather_icon).append($('<span>').text(asPost.weather_temp+'°C')))
.append($('<img>', {'class':'staticmap clickable', title: oSpot.lang('click_zoom'), src: asPost.static_img_url}))
.append($('<span>', {'class': 'drill-icon fa-stack clickable'})
.addIcon('fa-message fa-stack-2x clickable')
.addIcon('fa-message-in fa-stack-1x fa-rotate-270')
)
.click(function(){
var $Parent = $(this).parent();
var oMarker = oSpot.tmp(['markers', $Parent.data('id')]);
if(isMobile()) {
toggleFeedPanel(false, 'panToInstant');
oSpot.tmp('map').setView(oMarker.getLatLng(), 15);
}
else {
var iOffset = (isFeedPanelOpen()?1:-1)*self.tmp('$Feed').outerWidth(true)/2 - (isSettingsPanelOpen()?1:-1)*self.tmp('$Settings').outerWidth(true)/2;
var iRatio = -1 * iOffset / $('body').outerWidth(true);
oSpot.tmp('map').setOffsetView(iRatio, oMarker.getLatLng(), 15);
}
$Parent.data('clicked', true);
if(!oMarker.isPopupOpen()) oMarker.openPopup();
})
)
.hover(
function(){
var oMarker = oSpot.tmp(['markers', $(this).data('id')]);
if(oSpot.tmp('map') && oSpot.tmp('map').getBounds().contains(oMarker.getLatLng()) && !oMarker.isPopupOpen()) oMarker.openPopup();
},
function(){
var oMarker = oSpot.tmp(['markers', $(this).data('id')]);
if(oMarker && oMarker.isPopupOpen() && !$(this).data('clicked')) oMarker.closePopup();
$(this).data('clicked', false);
}
);
break;
case 'media':
bLink = true;
$Body = $('<div>', {'class':'body-box'}).append(getMediaLink(asPost, 'post'));
if(asPost.comment) $Body.find('a.drill').append($('<span>', {'class':'comment'}).text(asPost.comment));
break;
case 'post':
bLink = true;
var $SigImg = asPost.gravatar?$('<img>').attr({'src':'data:image/png;base64, '+asPost.gravatar, 'width':'24', 'height':'24', 'alt':'--'}):($('<span>').text('-- '));
$Body = $('<div>')
.append($('<p>', {'class':'message'}).text(asPost.content))
.append($('<p>', {'class':'signature'})
.append($SigImg)
.append($('<span>').text(asPost.formatted_name))
);
break;
case 'poster':
$Body = $('<p>', {'class':'message'})
.append($('<textarea>', {id:'post', name:'post', placeholder:oSpot.lang('post_message'), 'class':'autoExpand', rows:'1'}))
.append($('<input>', {type:'text', id:'name', name:'name', placeholder:oSpot.lang('post_name')}))
.append($('<button>', {type:'button', id:'submit', name:'submit', 'aria-label':oSpot.lang('send')}).addIcon('fa-send'));
break;
case 'archived':
$Body = $('<div>')
.append($('<p>').addIcon('fa-success'))
.append($('<p>').text(oSpot.lang('mode_histo')));
break;
case 'loading':
$Body = $('<p>', {'class':'flicker'}).addIcon('fa-post');
break;
}
$Post
.append($('<div>', {'class':'header'})
.append($('<span>', {'class':'index'})
.addIcon('fa-'+sType)
.append(asPost.displayed_id?' '+oSpot.lang('counter', asPost.displayed_id):'')
)
.append($('<span>', {'class':'time', title:bTimeDiff?oSpot.lang('local_time', sAbsTimeLocal):''}).hoverSwap(sRelTime, bTimeDiff?oSpot.lang('your_time', sAbsTime):sAbsTime)))
.append($('<div>', {'class':'body'}).append($Body));
if(bLink) {
$Post.find('.index').append($('<a>', {'class':'link desktop', 'href':sHash, 'title':oSpot.lang('copy_to_clipboard')})
.data('url', self.consts.server+sHash)
.addIcon('fa-link')
.click(function(){
let $Link = $(this);
copyTextToClipboard($Link.data('url'));
$Link.prop('title', oSpot.lang('link_copied')).find('i.fa').addClass('copied');
$Link.delay(5000).fadeOut(300, function(){
$Link.prop('title', oSpot.lang('copy_to_clipboard')).find('i.fa').removeClass('copied');
$Link.fadeIn(300);
});
})
);
}
return $Post;
}
function getMediaLink(asData, sType) {
var bTimeDiff = (asData.posted_on_formatted && asData.posted_on_formatted_local != asData.posted_on_formatted);
var $Comment = (!asData.comment || asData.comment == '')?'':
$('<span>', {'class': 'lb-caption-line comment desktop', 'title': asData.comment})
.addIcon('fa-post fa-lg fa-fw', true)
.append($('<span>', {'class':'comment-text'}).text(asData.comment));
var $PostedOn =
$('<span>', {'class': 'lb-caption-line', title: bTimeDiff?oSpot.lang('local_time', asData.posted_on_formatted_local):''})
.addIcon('fa-upload fa-lg fa-fw', true)
.append(asData.posted_on_formatted);
var $TakenOn = (asData.taken_on == asData.posted_on)?'':
$('<span>', {'class': 'lb-caption-line', title: bTimeDiff?oSpot.lang('local_time', asData.taken_on_formatted_local):''})
.addIcon('fa-'+asData.subtype+'-shot fa-lg fa-fw', true)
.append(asData.taken_on_formatted);
var $Title = $('<div>').append($Comment).append(sType=='marker'?$TakenOn:$PostedOn).append(sType=='marker'?$PostedOn:$TakenOn);
var $Link =
$('<a>', {
'class': 'media-link drill',
'href': asData.media_path,
'data-lightbox': sType+'-medias',
'data-type': asData.subtype,
'data-id': asData.id_media,
'data-title': $Title.html(),
'data-orientation': asData.rotate
})
.append($('<img>')
.attr({
'src': asData.thumb_path,
'width': asData.width, //set image ratio so that the required space can be reserved
'height': asData.height,
'title': oSpot.lang((asData.subtype == 'video')?'click_watch':'click_zoom'),
'class':'clickable'
})
)
.append($('<span>', {'class': 'drill-icon'}).addIcon('fa-drill-'+asData.subtype));
return $Link;
}
function getGoogleMapsLink(asInfo) {
return $('<a>', {
href:'https://www.google.com/maps/place/'+asInfo.lat_dms+'+'+asInfo.lon_dms+'/@'+asInfo.latitude+','+asInfo.longitude+',10z',
title: oSpot.lang('see_on_google'),
target: '_blank',
rel: 'noreferrer noopener'
}).text(asInfo.lat_dms+' '+asInfo.lon_dms);
}
/*
function next(iCurrTrack, iCurrIndex, iCurrOffset, iCurrZoom) {
var aoTracks = oSpot.tmp('tracks');
var aoOffset = {0:[0,0], 1:[-1,0], 2:[-1,1], 3:[0,1], 4:[1,1], 5:[1,0], 6:[1,-1], 7:[0,-1], 8:[-1,-1]};
console.log('Getting Track '+iCurrTrack+'/'+(aoTracks.features.length - 1)+', '+
'Point '+iCurrIndex+'/'+(aoTracks.features[iCurrTrack].geometry.coordinates.length - 1)+', '+
'Zoom '+iCurrZoom+'/14, '+
'Offset ['+aoOffset[iCurrOffset][0]+','+aoOffset[iCurrOffset][1]+']');
//Position map
var iLat = aoTracks.features[iCurrTrack].geometry.coordinates[iCurrIndex][1] + aoOffset[iCurrOffset][1] * 0.0347910214271;
var iLng = aoTracks.features[iCurrTrack].geometry.coordinates[iCurrIndex][0] + aoOffset[iCurrOffset][0] * 0.1016235351560;
oSpot.tmp('map').setView(L.latLng(iLat, iLng), iCurrZoom);
//Go to next
iCurrOffset++;
if(iCurrZoom < 13 || iCurrOffset > 8) {
iCurrOffset = 0;
iCurrZoom++;
if(iCurrZoom > 14) {
iCurrZoom = 5;
iCurrIndex += 100;
if(!(iCurrIndex in aoTracks.features[iCurrTrack].geometry.coordinates)) {
iCurrIndex = 0;
iCurrTrack++;
if(!(iCurrTrack in aoTracks.features)) return true;
}
}
}
timer = setTimeout(function(){next(iCurrTrack, iCurrIndex, iCurrOffset, iCurrZoom);}, 1000);
}
function stop() {
clearTimeout(timer);
}
*/
</script>