diff --git a/inc/Media.php b/inc/Media.php index a1da486..c15d029 100644 --- a/inc/Media.php +++ b/inc/Media.php @@ -69,16 +69,19 @@ class Media extends PhpObject { return Spot::getResult(($sError==''), $sError, $asData); } - public function getMediasInfo($iMediaId=0) { - $bOwnMedia = ($iMediaId > 0); - if($bOwnMedia && empty($this->asMedia) || !$bOwnMedia && empty($this->asMedias)) { + public function getMediasInfo($oMediaIds=null) { + $bOwnMedia = is_numeric($oMediaIds); //1 value + $bConstraintArray = is_array($oMediaIds); //Custom Constraints + + if($bOwnMedia && empty($this->asMedia) || !$bOwnMedia && empty($this->asMedias) || $bConstraintArray) { if($this->oProject->getProjectId()) { $asParams = array( 'select' => array(Db::getId(self::MEDIA_TABLE), 'filename', 'taken_on', 'posted_on', 'timezone', 'rotate', 'type AS subtype', 'comment'), 'from' => self::MEDIA_TABLE, 'constraint'=> array(Db::getId(Project::PROJ_TABLE) => $this->oProject->getProjectId()) ); - if($bOwnMedia) $asParams['constraint'][Db::getId(self::MEDIA_TABLE)] = $iMediaId; + if($bOwnMedia) $asParams['constraint'][Db::getId(self::MEDIA_TABLE)] = $oMediaIds; + if($bConstraintArray) $asParams = array_merge($asParams, $oMediaIds); $asMedias = $this->oDb->selectRows($asParams); foreach($asMedias as &$asMedia) { @@ -86,13 +89,13 @@ class Media extends PhpObject { $asMedia['thumb_path'] = $this->getMediaThumbnail($asMedia['filename']); } - if(!empty($asMedias)) { + if(!empty($asMedias) && !$bConstraintArray) { if($bOwnMedia) $this->asMedia = array_shift($asMedias); else $this->asMedias = $asMedias; } } } - return $bOwnMedia?$this->asMedia:$this->asMedias; + return $bOwnMedia?$this->asMedia:$asMedias; } public function getInfo() { diff --git a/inc/Project.php b/inc/Project.php index 45d4132..98b2d6e 100644 --- a/inc/Project.php +++ b/inc/Project.php @@ -164,6 +164,17 @@ class Project extends PhpObject { return $iLastUpdate; } + public function getLastMessageId($asConstraints=array()): int { + $iLastMsg = 0; + + $asFeedIds = $this->getFeedIds(); + foreach($asFeedIds as $iFeedId) { + $iLastMsg = max($iLastMsg, (new Feed($this->oDb, $iFeedId))->getLastMessageId($asConstraints)); + } + + return $iLastMsg; + } + public function getMaps() { $sQuery = "SELECT codename, geo_name, min_zoom, max_zoom, attribution ". diff --git a/inc/Spot.php b/inc/Spot.php index bbc1121..b43659d 100755 --- a/inc/Spot.php +++ b/inc/Spot.php @@ -88,7 +88,7 @@ class Spot extends Main ( 'tables' => array ( - Feed::MSG_TABLE => array('ref_msg_id', Db::getId(Feed::FEED_TABLE), 'type', 'latitude', 'longitude', 'iso_time', 'site_time', 'timezone', 'unix_time', 'content', 'battery_state'), + Feed::MSG_TABLE => array('ref_msg_id', Db::getId(Feed::FEED_TABLE), 'type', 'latitude', 'longitude', 'iso_time', 'site_time', 'timezone', 'unix_time', 'content', 'battery_state', 'posted_on'), Feed::FEED_TABLE => array('ref_feed_id', Db::getId(Feed::SPOT_TABLE), Db::getId(Project::PROJ_TABLE), 'name', 'description', 'status', 'last_update'), Feed::SPOT_TABLE => array('ref_spot_id', 'name', 'model'), Project::PROJ_TABLE => array('name', 'codename', 'active_from', 'active_to'), @@ -146,7 +146,10 @@ class Spot extends Main ), 'cascading_delete' => array ( - Feed::SPOT_TABLE => array(Feed::MSG_TABLE) + Feed::SPOT_TABLE => array(Feed::FEED_TABLE), + Feed::FEED_TABLE => array(Feed::MSG_TABLE), + Project::PROJ_TABLE => array(Feed::FEED_TABLE, Media::MEDIA_TABLE, self::POST_TABLE), + self::MAP_TABLE => array(self::MAPPING_TABLE) ) ); } @@ -217,14 +220,13 @@ class Spot extends Main $oEmail->setDestInfo($this->oUser->getActiveUsersInfo()); //Add Position - $asMessages = $this->getSpotMessages(); - $asLastMessage = end($asMessages); + $asLastMessage = array_shift($this->getSpotMessages(array($this->oProject->getLastMessageId($this->getFeedConstraints(Feed::MSG_TABLE))))); $asLastMessage['token'] = Settings::GEO_SERVER_TOKEN; $oEmail->oTemplate->setTags($asLastMessage); $oEmail->oTemplate->setTag('date_time', 'time:'.$asLastMessage['unix_time'], 'd/m/Y, H:i'); //Add latest news feed - $asNews = $this->getNewsFeed(0, 0, true); + $asNews = $this->getNextFeed(0, true); $iPostCount = 0; foreach($asNews as $asPost) { if($asPost['type'] != 'message') { @@ -262,13 +264,15 @@ class Spot extends Main return self::getJsonResult($bSuccess, ''); } - public function getMarkers() + public function getMarkers($asMessageIds=array(), $asMediaIds=array(), $bInternal=false) { - $asMessages = $this->getSpotMessages(); + $asMessages = $this->getSpotMessages($asMessageIds); + usort($asMessages, function($a, $b){return $a['unix_time'] > $b['unix_time'];}); //Add medias if(!empty($asMessages)) { - $asMedias = $this->getMedias('taken_on'); + $asMedias = $this->getMedias('taken_on', $asMediaIds); + usort($asMedias, function($a, $b){return $a['unix_time'] > $b['unix_time'];}); //Assign medias to closest message $iIndex = 0; @@ -277,7 +281,9 @@ class Spot extends Main while($iIndex <= $iMaxIndex && $asMedia['unix_time'] > $asMessages[$iIndex]['unix_time']) { $iIndex++; } - if($iIndex == 0) $iMsgIndex = $iIndex; + + //All medias before first message or after last message are assigned to first/last message respectively + if($iIndex == 0) $iMsgIndex = $iIndex; elseif($iIndex > $iMaxIndex) $iMsgIndex = $iMaxIndex; else { $iHalfWayPoint = ($asMessages[$iIndex - 1]['unix_time'] + $asMessages[$iIndex]['unix_time'])/2; @@ -288,15 +294,17 @@ class Spot extends Main } } - //Add Project Last Update + //Spot Last Update $asLastUpdate = array(); $this->addTimeStamp($asLastUpdate, $this->oProject->getLastUpdate()); - return self::getJsonResult(true, '', array( + $asResult = array( 'messages' => $asMessages, 'maps' => $this->oProject->getMaps(), 'last_update' => $asLastUpdate - )); + ); + + return $bInternal?$asResult:self::getJsonResult(true, '', $asResult); } public function subscribe($sEmail) { @@ -327,34 +335,35 @@ class Spot extends Main return $this->oLang->getTranslation($sDesc); } - private function getSpotMessages() + private function getSpotMessages($asMsgIds=array()) { - $asMessages = array(); + $asConstraints = $this->getFeedConstraints(Feed::MSG_TABLE); + if(!empty($asMsgIds)) { + $asConstraints['constraint'][Db::getId(Feed::MSG_TABLE)] = $asMsgIds; + $asConstraints['constOpe'][Db::getId(Feed::MSG_TABLE)] = 'IN'; + } + + $asCombinedMessages = array(); //Get messages from all feeds belonging to the project $asFeeds = $this->oProject->getFeedIds(); foreach($asFeeds as $iFeedId) { $oFeed = new Feed($this->oDb, $iFeedId); - $asMessages = $oFeed->getMessages($this->oProject->getActivePeriod()); - foreach($asMessages as &$asMessage) + $asMessages = $oFeed->getMessages($asConstraints); + foreach($asMessages as $asMessage) { $asMessage['latitude'] = floatval($asMessage['latitude']); $asMessage['longitude'] = floatval($asMessage['longitude']); $asMessage['lat_dms'] = self::decToDms($asMessage['latitude'], 'lat'); $asMessage['lon_dms'] = self::decToDms($asMessage['longitude'], 'lon'); + $asMessage['displayed_id'] = $asMessage[Db::getId(Feed::MSG_TABLE)]; $this->addTimeStamp($asMessage, $asMessage['unix_time'], $asMessage['timezone']); + $asCombinedMessages[] = $asMessage; } } - //Sort chronologically - usort($asMessages, function($a, $b){return $a['unix_time'] > $b['unix_time'];}); - - //Add Display ID - $asSortedMessages = array_values($asMessages); - foreach($asSortedMessages as $iIndex=>&$asSortedMessage) $asSortedMessage['displayed_id'] = $iIndex + 1; - - return $asSortedMessages; + return $asCombinedMessages; } /** @@ -364,48 +373,44 @@ class Spot extends Main * @param String $sTimeRefField Field to calculate relative times: 'taken_on' or 'posted_on' * @return Array Medias info */ - private function getMedias($sTimeRefField) + private function getMedias($sTimeRefField, $asMediaIds=array()) { - $asMedias = $this->oMedia->getMediasInfo(); - $asValidMedias = array(); - foreach($asMedias as $asMedia) { - $sTimeRef = $asMedia[$sTimeRefField]; - if($sTimeRef >= $this->oProject->getActivePeriod('from') && $sTimeRef <= $this->oProject->getActivePeriod('to')) { - $iTimeStampTakenOn = strtotime($asMedia['taken_on']); - $iTimeStampPostedOn = strtotime($asMedia['posted_on']); - $asMedia['taken_on_formatted'] = $this->getTimeFormat($iTimeStampTakenOn); - $asMedia['taken_on_formatted_local'] = $this->getTimeFormat($iTimeStampTakenOn, $asMedia['timezone']); - $asMedia['posted_on_formatted'] = $this->getTimeFormat($iTimeStampPostedOn); - $asMedia['posted_on_formatted_local'] = $this->getTimeFormat($iTimeStampPostedOn, $asMedia['timezone']); - - $this->addTimeStamp($asMedia, strtotime($sTimeRef), $asMedia['timezone']); - $asValidMedias[] = $asMedia; - } + //Constraints + $asConstraints = $this->getFeedConstraints(Media::MEDIA_TABLE, $sTimeRefField); + if(!empty($asMediaIds)) { + $asConstraints['constraint'][Db::getId(Media::MEDIA_TABLE)] = $asMediaIds; + $asConstraints['constOpe'][Db::getId(Media::MEDIA_TABLE)] = 'IN'; } - usort($asValidMedias, function($a, $b){return $a['unix_time'] > $b['unix_time'];}); + $asMedias = $this->oMedia->getMediasInfo($asConstraints); + foreach($asMedias as &$asMedia) { + $iTimeStampTakenOn = strtotime($asMedia['taken_on']); + $iTimeStampPostedOn = strtotime($asMedia['posted_on']); + $asMedia['taken_on_formatted'] = $this->getTimeFormat($iTimeStampTakenOn); + $asMedia['taken_on_formatted_local'] = $this->getTimeFormat($iTimeStampTakenOn, $asMedia['timezone']); + $asMedia['posted_on_formatted'] = $this->getTimeFormat($iTimeStampPostedOn); + $asMedia['posted_on_formatted_local'] = $this->getTimeFormat($iTimeStampPostedOn, $asMedia['timezone']); + $asMedia['displayed_id'] = $asMedia[Db::getId(Media::MEDIA_TABLE)]; - $asSortedMedias = array_values($asValidMedias); - foreach($asSortedMedias as $iIndex=>&$asSortedMedia) $asSortedMedia['displayed_id'] = $iIndex + 1; + $this->addTimeStamp($asMedia, strtotime($asMedia[$sTimeRefField]), $asMedia['timezone']); + } - return $asSortedMedias; + return $asMedias; } - private function getPosts() + private function getPosts($asPostIds=array()) { $asInfo = array( 'select' => array(Db::getFullColumnName(self::POST_TABLE, '*'), 'gravatar'), 'from' => self::POST_TABLE, - 'join' => array(User::USER_TABLE => Db::getId(User::USER_TABLE)), - 'constraint'=> array( - Db::getId(Project::PROJ_TABLE) => $this->oProject->getProjectId(), - 'site_time' => $this->oProject->getActivePeriod('to') - ), - 'constOpe' => array( - Db::getId(Project::PROJ_TABLE) => "=", - 'site_time' => "<=" - ) + 'join' => array(User::USER_TABLE => Db::getId(User::USER_TABLE)) ); + $asInfo = array_merge($asInfo, $this->getFeedConstraints(self::POST_TABLE)); + + if(!empty($asPostIds)) { + $asInfo['constraint'][Db::getId(self::POST_TABLE)] = $asPostIds; + $asInfo['constOpe'][Db::getId(self::POST_TABLE)] = 'IN'; + } $asPosts = $this->oDb->selectRows($asInfo); foreach($asPosts as &$asPost) { @@ -415,7 +420,6 @@ class Spot extends Main $this->addTimeStamp($asPost, $iUnixTimeStamp, $asPost['timezone']); } - usort($asPosts, function($a, $b){return $a['unix_time'] > $b['unix_time'];}); return $asPosts; } @@ -427,54 +431,145 @@ class Spot extends Main if($sTimeZone != '') $asData['formatted_time_local'] = $this->getTimeFormat($iTime, $sTimeZone); } - public function getNewsFeed($iChunk=0, $iRefTimePoint=0, $bInternal=false) - { - $bHistoMode = ($this->oProject->getMode() == Project::MODE_HISTO); - $asFeeds = array(); - $asFeedTypes = array( - 'message' => array( - 'table' => Feed::MSG_TABLE, - 'feed' => $this->getSpotMessages(), - 'priority' => 0 - ), - 'media' => array( - 'table' => Media::MEDIA_TABLE, - 'feed' => $this->getMedias('posted_on'), - 'priority' => 1 - ), - 'post' => array( - 'table' => self::POST_TABLE, - 'feed' => $this->getPosts(), - 'priority' => 2 - ) + private function getFeedConstraints($sType, $sTimeField='site_time', $sReturnFormat='array') { + $asConsArray = array(); + $sConsSql = ""; + $asActPeriod = $this->oProject->getActivePeriod(); + + //Filter on Project ID + $sConsSql = "WHERE ".Db::getId(Project::PROJ_TABLE)." = ".$this->oProject->getProjectId(); + $asConsArray = array( + 'constraint'=> array(Db::getId(Project::PROJ_TABLE) => $this->oProject->getProjectId()), + 'constOpe' => array(Db::getId(Project::PROJ_TABLE) => "=") ); - foreach($asFeedTypes as $sFeedType=>$asFeedTypeInfo) { - foreach($asFeedTypeInfo['feed'] as $asFeed) { - $iTableId = $asFeed[Db::getId($asFeedTypeInfo['table'])]; - //Build unique sorting ID - $iSort = $asFeed['unix_time'].'.'.$asFeedTypeInfo['priority'].$iTableId; - - //Build feed (not including new feeds added in between chunk by concurrent access) - if($iChunk == 0 || $iSort <= $iRefTimePoint) { - $asFeeds[$iSort] = $asFeed; - $asFeeds[$iSort]['type'] = $sFeedType; - $asFeeds[$iSort]['id'] = $iTableId; - } - - //Build Reference Time Point (latest item extracted by first chunk) - if($iChunk == 0 && $iSort > $iRefTimePoint) $iRefTimePoint = $iSort; - } + //Time Filter + switch($sType) { + case Feed::MSG_TABLE: + $asConsArray['constraint'][$sTimeField] = $asActPeriod; + $asConsArray['constOpe'][$sTimeField] = "BETWEEN"; + $sConsSql .= " AND ".$sTimeField." BETWEEN '".$asActPeriod['from']."' AND '".$asActPeriod['to']."'"; + break; + case Media::MEDIA_TABLE: + $asConsArray['constraint'][$sTimeField] = $asActPeriod['to']; + $asConsArray['constOpe'][$sTimeField] = "<="; + $sConsSql .= " AND ".$sTimeField." <= '".$asActPeriod['to']."'"; + break; + case self::POST_TABLE: + $asConsArray['constraint'][$sTimeField] = $asActPeriod['to']; + $asConsArray['constOpe'][$sTimeField] = "<="; + $sConsSql .= " AND ".$sTimeField." <= '".$asActPeriod['to']."'"; + break; } - //Sort by key - if($bHistoMode) ksort($asFeeds); //Old first - else krsort($asFeeds); //New first + return ($sReturnFormat=='array')?$asConsArray:$sConsSql; + } - //Split chunks - $asFeeds = array_slice($asFeeds, $iChunk * self::FEED_CHUNK_SIZE, self::FEED_CHUNK_SIZE); + public function getNewFeed($iRefIdFirst) { + $asResult = array(); - return $bInternal?$asFeeds:self::getJsonResult(true, '', array('ref_id'=>$iRefTimePoint, 'feed' => $asFeeds)); + if($this->oProject->getMode() != Project::MODE_HISTO) { + $asMessageIds = $asMediaIds = array(); + + //New Feed Items + $asResult = $this->getFeed($iRefIdFirst, ">", "DESC"); + foreach($asResult['feed'] as $asItem) { + switch($asItem['type']) { + case 'message': + $asMessageIds[] = $asItem['id']; + break; + case 'media': + $asMediaIds[] = $asItem['id']; + break; + } + } + + //New Markers + $asMarkers = $this->getMarkers( + empty($asMessageIds)?array(0):$asMessageIds, + empty($asMediaIds)?array(0):$asMediaIds, + true + ); + + $asResult = array_merge($asResult, $asMarkers); + } + + return self::getJsonResult(true, '', $asResult); + } + + public function getNextFeed($iRefIdLast=0, $bInternal=false) { + if($this->oProject->getMode() == Project::MODE_HISTO) { + $sDirection = ">"; + $sSort = "ASC"; + } + else { + $sDirection = "<"; + $sSort = "DESC"; + } + $asResult = $this->getFeed($iRefIdLast, $sDirection, $sSort); + return $bInternal?$asResult['feed']:self::getJsonResult(true, '', $asResult); + } + + public function getFeed($iRefId=0, $sDirection, $sSort) { + $this->oDb->cleanSql($iRefId); + $this->oDb->cleanSql($sDirection); + $this->oDb->cleanSql($sSort); + + $sMediaRefField = 'posted_on'; + $sQuery = implode(" ", array( + "SELECT type, id, ref", + "FROM (", + "SELECT id_project, id_message AS id, 'message' AS type, CONCAT(UNIX_TIMESTAMP(site_time), '.0', id_message) AS ref", + "FROM ".Feed::MSG_TABLE, + "INNER JOIN feeds USING(id_feed)", + $this->getFeedConstraints(Feed::MSG_TABLE, 'site_time', 'sql'), + "UNION", + "SELECT id_project, id_media AS id, 'media' AS type, CONCAT(UNIX_TIMESTAMP(".$sMediaRefField."), '.1', id_media) AS ref", + "FROM ".Media::MEDIA_TABLE, + $this->getFeedConstraints(Media::MEDIA_TABLE, $sMediaRefField, 'sql'), + "UNION", + "SELECT id_project, id_post AS id, 'post' AS type, CONCAT(UNIX_TIMESTAMP(site_time), '.2', id_post) AS ref", + "FROM ".self::POST_TABLE, + $this->getFeedConstraints(self::POST_TABLE, 'site_time', 'sql'), + ") AS items", + ($iRefId > 0)?("WHERE ref ".$sDirection." ".$iRefId):"", + "ORDER BY ref ".$sSort, + "LIMIT ".self::FEED_CHUNK_SIZE + )); + + //Get new chunk + $asItems = $this->oDb->getArrayQuery($sQuery, true); + + //Update Reference Point with latest/earliest value + $iRefIdFirst = $iRefIdLast = 0; + if(!empty($asItems)) { + $iRefIdLast = end($asItems)['ref']; + $iRefIdFirst = reset($asItems)['ref']; + } + + //Sort Table IDs by type & Get attributes + foreach($asItems as $asItem) { + $asFeedIds[$asItem['type']][$asItem['id']] = $asItem; + } + $asFeedAttrs = array( + 'message' => $this->getSpotMessages(array_keys($asFeedIds['message'])), + 'media' => $this->getMedias($sMediaRefField, array_keys($asFeedIds['media'])), + 'post' => $this->getPosts(array_keys($asFeedIds['post'])) + ); + + //Replace Array Key with Item ID + foreach($asFeedAttrs as $sType=>$asFeedAttr) { + foreach($asFeedAttr as $asFeed) { + $asFeeds[$sType][$asFeed['id_'.$sType]] = $asFeed; + } + } + + //Assign + foreach($asItems as &$asItem) { + $asItem = array_merge($asFeeds[$asItem['type']][$asItem['id']], $asItem); + } + + return array('ref_id_last'=>$iRefIdLast, 'ref_id_first'=>$iRefIdFirst, 'sort'=>$sSort, 'feed'=>$asItems); } public function syncMedias() { diff --git a/index.php b/index.php index 1f3f54f..36d979f 100755 --- a/index.php +++ b/index.php @@ -24,7 +24,6 @@ $sAction = isset($_REQUEST['a'])?$_REQUEST['a']:''; $sTimezone = $_REQUEST['t'] ?? ''; $sName = isset($_GET['name'])?$_GET['name']:''; $sContent = isset($_GET['content'])?$_GET['content']:''; -$iChunk = isset($_GET['chunk'])?$_GET['chunk']:0; $iProjectId = $_REQUEST['id_project'] ?? 0; $sField = isset($_REQUEST['field'])?$_REQUEST['field']:''; $oValue = isset($_REQUEST['value'])?$_REQUEST['value']:''; @@ -44,8 +43,11 @@ if($sAction!='') case 'markers': $sResult = $oSpot->getMarkers(); break; - case 'feed': - $sResult = $oSpot->getNewsFeed($iChunk, $iId); + case 'next_feed': + $sResult = $oSpot->getNextFeed($iId); + break; + case 'new_feed': + $sResult = $oSpot->getNewFeed($iId); break; case 'add_post': $sResult = $oSpot->addPost($sName, $sContent); diff --git a/masks/project.html b/masks/project.html index 078cf5f..d1ec455 100644 --- a/masks/project.html +++ b/masks/project.html @@ -1,4 +1,5 @@
+
@@ -31,15 +32,15 @@
-
+
-
-
+
+
[#]lang:track_main[#]
[#]lang:track_off-track[#]
@@ -56,7 +57,7 @@ oSpot.onSamePageMove = function(asHash) { self.tmp('$Map').empty(); self.tmp('map', null); self.tmp('$PostList').empty(); - setFeedUpdateTimer(false); + setFeedUpdateTimer(-1); initProject(asHash.items[0]); } return false; @@ -82,14 +83,18 @@ oSpot.onResize = function() { }; oSpot.onQuitPage = function() { - setFeedUpdateTimer(false); + setFeedUpdateTimer(-1); return true; } oSpot.onKeydown = function(oEvent) { switch(oEvent.which) { case 27: - if(!$('#lightboxOverlay').is(':visible')) toggleFeedPanel(false); + $bFeedPanelOpen = isFeedPanelOpen(); + $bSettingsPanelOpen = isSettingsPanelOpen(); + $bAnimation = ($bFeedPanelOpen && $bSettingsPanelOpen)?'none':null; + if($bFeedPanelOpen) toggleFeedPanel(false, $bAnimation); + if($bSettingsPanelOpen) toggleSettingsPanel(false, $bAnimation); break; } } @@ -112,12 +117,17 @@ function isFeedPanelOpen() { return self.tmp('$Projects').hasClass('with-feed'); } -function toggleSettingsPanel(bShow) { +function toggleSettingsPanel(bShow, sMapAction) { self.tmp('$Projects').toggleClass('with-settings', (typeof bShow === 'undefined')?null:bShow); oSpot.onResize(); if(isMobile()) $('#post-button').toggle(!isSettingsPanelOpen()); - oSpot.tmp('map').panBy([(isSettingsPanelOpen()?-1:1)*self.tmp('$Settings').outerWidth(true)/2, 0], {duration: 0.5}); + 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() { @@ -174,16 +184,18 @@ function initPage(asHash) { //Post Panel one-off init (see initPosts for project related init) //Scrollbar - self.tmp('simple-bar', new SimpleBar($('#posts')[0])); + 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 events $("#feed").onSwipe(function(aiDelta){ if(aiDelta.x > self.tmp('$Feed').outerWidth(true)/3 && aiDelta.x > Math.abs(aiDelta.y)) toggleFeedPanel(false); }); + */ //Feed Panel initPosts(); @@ -257,7 +269,7 @@ function initPosts() { function() { $('#post').val(''); - updateFeed(true); + onAutoUpdate(); }, { id_project: self.vars(['project', 'id']), @@ -331,11 +343,6 @@ function setUserInterface() { } } -function onAutoUpdate(bStart) { - if(!bStart && self.tmp('simple-bar').getScrollElement().scrollTop == 0) updateFeed(true, true); - setFeedUpdateTimer(60, onAutoUpdate); -} - function getElevWidth() { var iPageWidth = self.tmp('$Projects').width(), @@ -518,28 +525,18 @@ function initSpotMessages(aoMessages, aoTracks) { } //Conversion of hours into natural language - var iTimeMinutes = 0, iTimeHours = 0, iTimeDays = Math.floor(iTime/8); //8 hours a day - if(iTimeDays > 1) iTimeDays = Math.round(iTimeDays * 2) / 2; //Round down to the closest half day - else { - iTimeDays = 0; - iTimeHours = Math.floor(iTime); - iTime -= iTimeHours; - - iTimeMinutes = Math.floor(iTime * 4) * 15; //Round down to the closest 15 minutes - } - var sDuration = '~ ' - +(iTimeDays>0?(iTimeDays+(iTimeDays%2==0?'':'½')+' '+oSpot.lang(iTimeDays>1?'unit_days':'unit_day')):'')//Days - +((iTimeHours>0 || iTimeDays==0)?iTimeHours+oSpot.lang('unit_hour'):'') //Hours - +((iTimeDays>0 || iTimeMinutes==0)?'':iTimeMinutes) //Minutes + var sDuration = oSpot.getNaturalDuration(iTime); //Tooltip details info - $TooltipBody.append($('
', {'class':'separator'})); - var $TooltipDetails = $('
', {'class':'details'}).appendTo($TooltipBody); - $('

', {'class':'detail'}).addIcon('fa-distance fa-fw', true).append(Math.round(iDistance/1000)+'km').appendTo($TooltipDetails); - $('

', {'class':'detail'}).addIcon('fa-time fa-fw', true).append(sDuration).appendTo($TooltipDetails); - $('

', {'class':'detail'}).addIcon('fa-elev-gain fa-fw', true).append(iElevGain+'m').appendTo($TooltipDetails); - $('

', {'class':'detail'}).addIcon('fa-elev-drop fa-fw', true).append(iElevDrop+'m').appendTo($TooltipDetails); - + $TooltipBody + .append($('

', {'class':'separator'})) + .append($('
', {'class':'details'}) + .append($('

', {'class':'detail'}).addIcon('fa-distance fa-fw', true).append(Math.round(iDistance/1000)+'km')) + .append($('

', {'class':'detail'}).addIcon('fa-time fa-fw', true).append(sDuration)) + .append($('

', {'class':'detail'}).addIcon('fa-elev-gain fa-fw', true).append(iElevGain+'m')) + .append($('

', {'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')}), @@ -558,22 +555,43 @@ function initSpotMessages(aoMessages, aoTracks) { } }).addTo(oMap)); + //Last message + var oLastMsg = (aoMessages.length > 0)?aoMessages[aoMessages.length-1]:{}; + //Centering map var iPanelWidth = isMobile()?0:parseInt(self.tmp('$Feed').outerWidth(true)); if( self.vars(['project', 'mode']) == self.consts.modes.blog && - aoMessages.length > 0 && + !$.isEmptyObject(oLastMsg) && self.getHash()[2] != 'message' ) { //Zoom on last message - var oLastMsg = aoMessages[aoMessages.length-1]; oMap.setView(L.latLng(oLastMsg.latitude, oLastMsg.longitude), 15); oMap.panBy([iPanelWidth/2, 0]); } else oMap.fitBounds(self.tmp('track').getBounds(), {paddingTopLeft: L.point(5, self.tmp('marker_size').height + 5), paddingBottomRight: L.point(5 + iPanelWidth, 5)}); + //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 = self.tmp('$Projects').width() - self.tmp('$Feed').outerWidth(true) - self.tmp('$Settings').outerWidth(true); + 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 @@ -581,7 +599,7 @@ function initSpotMessages(aoMessages, aoTracks) { id: oMsg.id_message, riseOnHover: true, icon: getDivIcon('message-in fa-rotate-270') - }).addTo(oMap); + }).addTo(self.tmp('map')); //Tooltip $Tooltip = $('

', {'class':'info-window'}) @@ -621,21 +639,8 @@ function initSpotMessages(aoMessages, aoTracks) { offset: new L.Point(0, -30) }); - //Open tooltip on latest message in mobile mode - if( - iKey === (aoMessages.length - 1) && - self.vars(['project', 'mode']) == self.consts.modes.blog && - (!oMsg.medias || oMsg.medias.length < 3) && - isMobile() - ) oMarker.openPopup(); - oSpot.tmp(['markers', oMsg.id_message], oMarker); }); - - /* - oSpot.tmp('tracks', aoTracks); - next(24, 0, 0, 5); - */ } function toggleSoftPopup(e, sMode) { @@ -662,44 +667,6 @@ function toggleSoftPopup(e, sMode) { } } -/* -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); -} -*/ - function getDivIcon(sIcon) { return L.divIcon({ className: '', @@ -715,12 +682,50 @@ function onFeedScroll() { 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 = $('
'); + $.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(){}; - if(bFirstChunk) self.tmp('ref_id', 0); + //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) { @@ -729,39 +734,36 @@ function updateFeed(bFirstChunk, bDiscrete, fCallback) { var $Posts = $('
'); - if(bFirstChunk===true) { - self.tmp('news_chunk', 0); - } - self.get( - 'feed', + '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); + } - self.tmp('ref_id', asData.ref_id); - + //Add posts + if(bFirstChunk === true) self.tmp('$PostList').empty(); $.each(asData.feed, function(iKey, asPost){ $Posts.append(getPost(asPost)); }); - - self.tmp('news_chunk', self.tmp('news_chunk') + 1); - self.tmp('out-of-data', Object.keys(asData.feed).length != self.vars('chunk_size')); - - if(bFirstChunk===true) self.tmp('$PostList').empty(); self.tmp('$PostList').append($Posts.children()); - self.tmp('$PostList').find('img').waitForImages(true).done(fCallback); self.tmp('updatable', true); }, { id_project: self.vars(['project', 'id']), - chunk: self.tmp('news_chunk'), - id: self.tmp('ref_id') + id: self.tmp('ref_id_last') } ); } } - else if(bFirstChunk) setFeedUpdateTimer(0.2); + else if(bFirstChunk) setFeedUpdateTimer(0.2, function(){updateFeed(bFirstChunk, bDiscrete, fCallback);}); } function focusOnPost(oFocusPost) { @@ -780,16 +782,10 @@ function focusOnPost(oFocusPost) { else console.log('Missing element ID '+sElemId); //Reset Hash - flushHash(['post', 'message']); + oSpot.flushHash(['post', 'message']); } } -function setFeedUpdateTimer(iSeconds, fCallback) { - fCallback = fCallback || function(){updateFeed(true);}; - if(typeof self.tmp('update_timer') != 'undefined') clearTimeout(self.tmp('update_timer')); - if(iSeconds > 0) self.tmp('update_timer', setTimeout(fCallback, iSeconds * 1000)); -} - function getPost(asPost) { asPost.headerless = asPost.headerless || false; var bLink = false; @@ -946,17 +942,41 @@ function getGoogleMapsLink(asInfo) { }).text(asInfo.lat_dms+' '+asInfo.lon_dms); } -function updateHash(sType, iId) { - sType = sType || ''; - iId = iId || 0; +/* +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]}; - var asHash = self.getHash(); - if(iId) self.setHash(asHash.page, [asHash.items[0], sType, iId]); -} + 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]+']'); -function flushHash(asTypes) { - asTypes = asTypes || []; - var asHash = self.getHash(); - if(asHash.items.length > 1 && (asTypes.length == 0 || asTypes.indexOf(asHash.items[1]) != -1)) self.setHash(asHash.page, [asHash.items[0]]); + //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); +} +*/ + \ No newline at end of file