Rebuild delta mode

This commit is contained in:
2021-08-31 20:21:39 +02:00
parent dbfebdf9be
commit 62bc25aade
5 changed files with 362 additions and 231 deletions

View File

@@ -69,16 +69,19 @@ class Media extends PhpObject {
return Spot::getResult(($sError==''), $sError, $asData); return Spot::getResult(($sError==''), $sError, $asData);
} }
public function getMediasInfo($iMediaId=0) { public function getMediasInfo($oMediaIds=null) {
$bOwnMedia = ($iMediaId > 0); $bOwnMedia = is_numeric($oMediaIds); //1 value
if($bOwnMedia && empty($this->asMedia) || !$bOwnMedia && empty($this->asMedias)) { $bConstraintArray = is_array($oMediaIds); //Custom Constraints
if($bOwnMedia && empty($this->asMedia) || !$bOwnMedia && empty($this->asMedias) || $bConstraintArray) {
if($this->oProject->getProjectId()) { if($this->oProject->getProjectId()) {
$asParams = array( $asParams = array(
'select' => array(Db::getId(self::MEDIA_TABLE), 'filename', 'taken_on', 'posted_on', 'timezone', 'rotate', 'type AS subtype', 'comment'), 'select' => array(Db::getId(self::MEDIA_TABLE), 'filename', 'taken_on', 'posted_on', 'timezone', 'rotate', 'type AS subtype', 'comment'),
'from' => self::MEDIA_TABLE, 'from' => self::MEDIA_TABLE,
'constraint'=> array(Db::getId(Project::PROJ_TABLE) => $this->oProject->getProjectId()) '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); $asMedias = $this->oDb->selectRows($asParams);
foreach($asMedias as &$asMedia) { foreach($asMedias as &$asMedia) {
@@ -86,13 +89,13 @@ class Media extends PhpObject {
$asMedia['thumb_path'] = $this->getMediaThumbnail($asMedia['filename']); $asMedia['thumb_path'] = $this->getMediaThumbnail($asMedia['filename']);
} }
if(!empty($asMedias)) { if(!empty($asMedias) && !$bConstraintArray) {
if($bOwnMedia) $this->asMedia = array_shift($asMedias); if($bOwnMedia) $this->asMedia = array_shift($asMedias);
else $this->asMedias = $asMedias; else $this->asMedias = $asMedias;
} }
} }
} }
return $bOwnMedia?$this->asMedia:$this->asMedias; return $bOwnMedia?$this->asMedia:$asMedias;
} }
public function getInfo() { public function getInfo() {

View File

@@ -164,6 +164,17 @@ class Project extends PhpObject {
return $iLastUpdate; 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() { public function getMaps() {
$sQuery = $sQuery =
"SELECT codename, geo_name, min_zoom, max_zoom, attribution ". "SELECT codename, geo_name, min_zoom, max_zoom, attribution ".

View File

@@ -88,7 +88,7 @@ class Spot extends Main
( (
'tables' => array '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::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'), Feed::SPOT_TABLE => array('ref_spot_id', 'name', 'model'),
Project::PROJ_TABLE => array('name', 'codename', 'active_from', 'active_to'), Project::PROJ_TABLE => array('name', 'codename', 'active_from', 'active_to'),
@@ -146,7 +146,10 @@ class Spot extends Main
), ),
'cascading_delete' => array '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()); $oEmail->setDestInfo($this->oUser->getActiveUsersInfo());
//Add Position //Add Position
$asMessages = $this->getSpotMessages(); $asLastMessage = array_shift($this->getSpotMessages(array($this->oProject->getLastMessageId($this->getFeedConstraints(Feed::MSG_TABLE)))));
$asLastMessage = end($asMessages);
$asLastMessage['token'] = Settings::GEO_SERVER_TOKEN; $asLastMessage['token'] = Settings::GEO_SERVER_TOKEN;
$oEmail->oTemplate->setTags($asLastMessage); $oEmail->oTemplate->setTags($asLastMessage);
$oEmail->oTemplate->setTag('date_time', 'time:'.$asLastMessage['unix_time'], 'd/m/Y, H:i'); $oEmail->oTemplate->setTag('date_time', 'time:'.$asLastMessage['unix_time'], 'd/m/Y, H:i');
//Add latest news feed //Add latest news feed
$asNews = $this->getNewsFeed(0, 0, true); $asNews = $this->getNextFeed(0, true);
$iPostCount = 0; $iPostCount = 0;
foreach($asNews as $asPost) { foreach($asNews as $asPost) {
if($asPost['type'] != 'message') { if($asPost['type'] != 'message') {
@@ -262,13 +264,15 @@ class Spot extends Main
return self::getJsonResult($bSuccess, ''); 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 //Add medias
if(!empty($asMessages)) { 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 //Assign medias to closest message
$iIndex = 0; $iIndex = 0;
@@ -277,6 +281,8 @@ class Spot extends Main
while($iIndex <= $iMaxIndex && $asMedia['unix_time'] > $asMessages[$iIndex]['unix_time']) { while($iIndex <= $iMaxIndex && $asMedia['unix_time'] > $asMessages[$iIndex]['unix_time']) {
$iIndex++; $iIndex++;
} }
//All medias before first message or after last message are assigned to first/last message respectively
if($iIndex == 0) $iMsgIndex = $iIndex; if($iIndex == 0) $iMsgIndex = $iIndex;
elseif($iIndex > $iMaxIndex) $iMsgIndex = $iMaxIndex; elseif($iIndex > $iMaxIndex) $iMsgIndex = $iMaxIndex;
else { else {
@@ -288,15 +294,17 @@ class Spot extends Main
} }
} }
//Add Project Last Update //Spot Last Update
$asLastUpdate = array(); $asLastUpdate = array();
$this->addTimeStamp($asLastUpdate, $this->oProject->getLastUpdate()); $this->addTimeStamp($asLastUpdate, $this->oProject->getLastUpdate());
return self::getJsonResult(true, '', array( $asResult = array(
'messages' => $asMessages, 'messages' => $asMessages,
'maps' => $this->oProject->getMaps(), 'maps' => $this->oProject->getMaps(),
'last_update' => $asLastUpdate 'last_update' => $asLastUpdate
)); );
return $bInternal?$asResult:self::getJsonResult(true, '', $asResult);
} }
public function subscribe($sEmail) { public function subscribe($sEmail) {
@@ -327,34 +335,35 @@ class Spot extends Main
return $this->oLang->getTranslation($sDesc); 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 //Get messages from all feeds belonging to the project
$asFeeds = $this->oProject->getFeedIds(); $asFeeds = $this->oProject->getFeedIds();
foreach($asFeeds as $iFeedId) { foreach($asFeeds as $iFeedId) {
$oFeed = new Feed($this->oDb, $iFeedId); $oFeed = new Feed($this->oDb, $iFeedId);
$asMessages = $oFeed->getMessages($this->oProject->getActivePeriod()); $asMessages = $oFeed->getMessages($asConstraints);
foreach($asMessages as &$asMessage) foreach($asMessages as $asMessage)
{ {
$asMessage['latitude'] = floatval($asMessage['latitude']); $asMessage['latitude'] = floatval($asMessage['latitude']);
$asMessage['longitude'] = floatval($asMessage['longitude']); $asMessage['longitude'] = floatval($asMessage['longitude']);
$asMessage['lat_dms'] = self::decToDms($asMessage['latitude'], 'lat'); $asMessage['lat_dms'] = self::decToDms($asMessage['latitude'], 'lat');
$asMessage['lon_dms'] = self::decToDms($asMessage['longitude'], 'lon'); $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']); $this->addTimeStamp($asMessage, $asMessage['unix_time'], $asMessage['timezone']);
$asCombinedMessages[] = $asMessage;
} }
} }
//Sort chronologically return $asCombinedMessages;
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;
} }
/** /**
@@ -364,48 +373,44 @@ class Spot extends Main
* @param String $sTimeRefField Field to calculate relative times: 'taken_on' or 'posted_on' * @param String $sTimeRefField Field to calculate relative times: 'taken_on' or 'posted_on'
* @return Array Medias info * @return Array Medias info
*/ */
private function getMedias($sTimeRefField) private function getMedias($sTimeRefField, $asMediaIds=array())
{ {
$asMedias = $this->oMedia->getMediasInfo(); //Constraints
$asValidMedias = array(); $asConstraints = $this->getFeedConstraints(Media::MEDIA_TABLE, $sTimeRefField);
foreach($asMedias as $asMedia) { if(!empty($asMediaIds)) {
$sTimeRef = $asMedia[$sTimeRefField]; $asConstraints['constraint'][Db::getId(Media::MEDIA_TABLE)] = $asMediaIds;
if($sTimeRef >= $this->oProject->getActivePeriod('from') && $sTimeRef <= $this->oProject->getActivePeriod('to')) { $asConstraints['constOpe'][Db::getId(Media::MEDIA_TABLE)] = 'IN';
}
$asMedias = $this->oMedia->getMediasInfo($asConstraints);
foreach($asMedias as &$asMedia) {
$iTimeStampTakenOn = strtotime($asMedia['taken_on']); $iTimeStampTakenOn = strtotime($asMedia['taken_on']);
$iTimeStampPostedOn = strtotime($asMedia['posted_on']); $iTimeStampPostedOn = strtotime($asMedia['posted_on']);
$asMedia['taken_on_formatted'] = $this->getTimeFormat($iTimeStampTakenOn); $asMedia['taken_on_formatted'] = $this->getTimeFormat($iTimeStampTakenOn);
$asMedia['taken_on_formatted_local'] = $this->getTimeFormat($iTimeStampTakenOn, $asMedia['timezone']); $asMedia['taken_on_formatted_local'] = $this->getTimeFormat($iTimeStampTakenOn, $asMedia['timezone']);
$asMedia['posted_on_formatted'] = $this->getTimeFormat($iTimeStampPostedOn); $asMedia['posted_on_formatted'] = $this->getTimeFormat($iTimeStampPostedOn);
$asMedia['posted_on_formatted_local'] = $this->getTimeFormat($iTimeStampPostedOn, $asMedia['timezone']); $asMedia['posted_on_formatted_local'] = $this->getTimeFormat($iTimeStampPostedOn, $asMedia['timezone']);
$asMedia['displayed_id'] = $asMedia[Db::getId(Media::MEDIA_TABLE)];
$this->addTimeStamp($asMedia, strtotime($sTimeRef), $asMedia['timezone']); $this->addTimeStamp($asMedia, strtotime($asMedia[$sTimeRefField]), $asMedia['timezone']);
$asValidMedias[] = $asMedia;
}
} }
usort($asValidMedias, function($a, $b){return $a['unix_time'] > $b['unix_time'];}); return $asMedias;
$asSortedMedias = array_values($asValidMedias);
foreach($asSortedMedias as $iIndex=>&$asSortedMedia) $asSortedMedia['displayed_id'] = $iIndex + 1;
return $asSortedMedias;
} }
private function getPosts() private function getPosts($asPostIds=array())
{ {
$asInfo = array( $asInfo = array(
'select' => array(Db::getFullColumnName(self::POST_TABLE, '*'), 'gravatar'), 'select' => array(Db::getFullColumnName(self::POST_TABLE, '*'), 'gravatar'),
'from' => self::POST_TABLE, 'from' => self::POST_TABLE,
'join' => array(User::USER_TABLE => Db::getId(User::USER_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' => "<="
)
); );
$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); $asPosts = $this->oDb->selectRows($asInfo);
foreach($asPosts as &$asPost) { foreach($asPosts as &$asPost) {
@@ -415,7 +420,6 @@ class Spot extends Main
$this->addTimeStamp($asPost, $iUnixTimeStamp, $asPost['timezone']); $this->addTimeStamp($asPost, $iUnixTimeStamp, $asPost['timezone']);
} }
usort($asPosts, function($a, $b){return $a['unix_time'] > $b['unix_time'];});
return $asPosts; return $asPosts;
} }
@@ -427,54 +431,145 @@ class Spot extends Main
if($sTimeZone != '') $asData['formatted_time_local'] = $this->getTimeFormat($iTime, $sTimeZone); if($sTimeZone != '') $asData['formatted_time_local'] = $this->getTimeFormat($iTime, $sTimeZone);
} }
public function getNewsFeed($iChunk=0, $iRefTimePoint=0, $bInternal=false) private function getFeedConstraints($sType, $sTimeField='site_time', $sReturnFormat='array') {
{ $asConsArray = array();
$bHistoMode = ($this->oProject->getMode() == Project::MODE_HISTO); $sConsSql = "";
$asFeeds = array(); $asActPeriod = $this->oProject->getActivePeriod();
$asFeedTypes = array(
'message' => array( //Filter on Project ID
'table' => Feed::MSG_TABLE, $sConsSql = "WHERE ".Db::getId(Project::PROJ_TABLE)." = ".$this->oProject->getProjectId();
'feed' => $this->getSpotMessages(), $asConsArray = array(
'priority' => 0 'constraint'=> array(Db::getId(Project::PROJ_TABLE) => $this->oProject->getProjectId()),
), 'constOpe' => array(Db::getId(Project::PROJ_TABLE) => "=")
'media' => array(
'table' => Media::MEDIA_TABLE,
'feed' => $this->getMedias('posted_on'),
'priority' => 1
),
'post' => array(
'table' => self::POST_TABLE,
'feed' => $this->getPosts(),
'priority' => 2
)
); );
foreach($asFeedTypes as $sFeedType=>$asFeedTypeInfo) {
foreach($asFeedTypeInfo['feed'] as $asFeed) {
$iTableId = $asFeed[Db::getId($asFeedTypeInfo['table'])];
//Build unique sorting ID //Time Filter
$iSort = $asFeed['unix_time'].'.'.$asFeedTypeInfo['priority'].$iTableId; switch($sType) {
case Feed::MSG_TABLE:
//Build feed (not including new feeds added in between chunk by concurrent access) $asConsArray['constraint'][$sTimeField] = $asActPeriod;
if($iChunk == 0 || $iSort <= $iRefTimePoint) { $asConsArray['constOpe'][$sTimeField] = "BETWEEN";
$asFeeds[$iSort] = $asFeed; $sConsSql .= " AND ".$sTimeField." BETWEEN '".$asActPeriod['from']."' AND '".$asActPeriod['to']."'";
$asFeeds[$iSort]['type'] = $sFeedType; break;
$asFeeds[$iSort]['id'] = $iTableId; 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;
} }
//Build Reference Time Point (latest item extracted by first chunk) return ($sReturnFormat=='array')?$asConsArray:$sConsSql;
if($iChunk == 0 && $iSort > $iRefTimePoint) $iRefTimePoint = $iSort; }
public function getNewFeed($iRefIdFirst) {
$asResult = array();
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;
} }
} }
//Sort by key //New Markers
if($bHistoMode) ksort($asFeeds); //Old first $asMarkers = $this->getMarkers(
else krsort($asFeeds); //New first empty($asMessageIds)?array(0):$asMessageIds,
empty($asMediaIds)?array(0):$asMediaIds,
true
);
//Split chunks $asResult = array_merge($asResult, $asMarkers);
$asFeeds = array_slice($asFeeds, $iChunk * self::FEED_CHUNK_SIZE, self::FEED_CHUNK_SIZE); }
return $bInternal?$asFeeds:self::getJsonResult(true, '', array('ref_id'=>$iRefTimePoint, 'feed' => $asFeeds)); 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() { public function syncMedias() {

View File

@@ -24,7 +24,6 @@ $sAction = isset($_REQUEST['a'])?$_REQUEST['a']:'';
$sTimezone = $_REQUEST['t'] ?? ''; $sTimezone = $_REQUEST['t'] ?? '';
$sName = isset($_GET['name'])?$_GET['name']:''; $sName = isset($_GET['name'])?$_GET['name']:'';
$sContent = isset($_GET['content'])?$_GET['content']:''; $sContent = isset($_GET['content'])?$_GET['content']:'';
$iChunk = isset($_GET['chunk'])?$_GET['chunk']:0;
$iProjectId = $_REQUEST['id_project'] ?? 0; $iProjectId = $_REQUEST['id_project'] ?? 0;
$sField = isset($_REQUEST['field'])?$_REQUEST['field']:''; $sField = isset($_REQUEST['field'])?$_REQUEST['field']:'';
$oValue = isset($_REQUEST['value'])?$_REQUEST['value']:''; $oValue = isset($_REQUEST['value'])?$_REQUEST['value']:'';
@@ -44,8 +43,11 @@ if($sAction!='')
case 'markers': case 'markers':
$sResult = $oSpot->getMarkers(); $sResult = $oSpot->getMarkers();
break; break;
case 'feed': case 'next_feed':
$sResult = $oSpot->getNewsFeed($iChunk, $iId); $sResult = $oSpot->getNextFeed($iId);
break;
case 'new_feed':
$sResult = $oSpot->getNewFeed($iId);
break; break;
case 'add_post': case 'add_post':
$sResult = $oSpot->addPost($sName, $sContent); $sResult = $oSpot->addPost($sName, $sContent);

View File

@@ -1,4 +1,5 @@
<div id="projects"> <div id="projects">
<div id="background"></div>
<div id="submap"> <div id="submap">
<div class="loader fa fa-fw fa-map flicker" id="map_loading"></div> <div class="loader fa fa-fw fa-map flicker" id="map_loading"></div>
</div> </div>
@@ -31,15 +32,15 @@
</div> </div>
</div> </div>
<div id="feed"> <div id="feed">
<div id="posts"> <div id="feed-panel">
<div id="poster"></div> <div id="poster"></div>
<div id="posts_list"></div> <div id="posts_list"></div>
<div id="loading"></div> <div id="loading"></div>
</div> </div>
</div> </div>
<div id="elems"> <div id="elems">
<div id="settings-button" class="spot-control"><i class="fa fa-menu fa-fw"></i></div> <div id="settings-button" class="spot-control"><i class="fa fa-menu"></i></div>
<div id="post-button" class="spot-control"><i class="fa fa-fw"></i></div> <div id="post-button" class="spot-control"><i class="fa"></i></div>
<div id="legend" class="leaflet-control-layers leaflet-control leaflet-control-layers-expanded"> <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 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 off-track"></span><span class="desc">[#]lang:track_off-track[#]</span></div>
@@ -56,7 +57,7 @@ oSpot.onSamePageMove = function(asHash) {
self.tmp('$Map').empty(); self.tmp('$Map').empty();
self.tmp('map', null); self.tmp('map', null);
self.tmp('$PostList').empty(); self.tmp('$PostList').empty();
setFeedUpdateTimer(false); setFeedUpdateTimer(-1);
initProject(asHash.items[0]); initProject(asHash.items[0]);
} }
return false; return false;
@@ -82,14 +83,18 @@ oSpot.onResize = function() {
}; };
oSpot.onQuitPage = function() { oSpot.onQuitPage = function() {
setFeedUpdateTimer(false); setFeedUpdateTimer(-1);
return true; return true;
} }
oSpot.onKeydown = function(oEvent) { oSpot.onKeydown = function(oEvent) {
switch(oEvent.which) { switch(oEvent.which) {
case 27: 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; break;
} }
} }
@@ -112,12 +117,17 @@ function isFeedPanelOpen() {
return self.tmp('$Projects').hasClass('with-feed'); 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); self.tmp('$Projects').toggleClass('with-settings', (typeof bShow === 'undefined')?null:bShow);
oSpot.onResize(); oSpot.onResize();
if(isMobile()) $('#post-button').toggle(!isSettingsPanelOpen()); 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() { function isSettingsPanelOpen() {
@@ -174,16 +184,18 @@ function initPage(asHash) {
//Post Panel one-off init (see initPosts for project related init) //Post Panel one-off init (see initPosts for project related init)
//Scrollbar //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); self.tmp('simple-bar').getScrollElement().addEventListener('scroll', onFeedScroll);
//Add "Loading" Post //Add "Loading" Post
getPost({type: 'loading', headerless: true, formatted_time: '', relative_time: ''}).appendTo($('#loading')); getPost({type: 'loading', headerless: true, formatted_time: '', relative_time: ''}).appendTo($('#loading'));
/*
//Mobile events //Mobile events
$("#feed").onSwipe(function(aiDelta){ $("#feed").onSwipe(function(aiDelta){
if(aiDelta.x > self.tmp('$Feed').outerWidth(true)/3 && aiDelta.x > Math.abs(aiDelta.y)) toggleFeedPanel(false); if(aiDelta.x > self.tmp('$Feed').outerWidth(true)/3 && aiDelta.x > Math.abs(aiDelta.y)) toggleFeedPanel(false);
}); });
*/
//Feed Panel //Feed Panel
initPosts(); initPosts();
@@ -257,7 +269,7 @@ function initPosts() {
function() function()
{ {
$('#post').val(''); $('#post').val('');
updateFeed(true); onAutoUpdate();
}, },
{ {
id_project: self.vars(['project', 'id']), 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() { function getElevWidth() {
var var
iPageWidth = self.tmp('$Projects').width(), iPageWidth = self.tmp('$Projects').width(),
@@ -518,27 +525,17 @@ function initSpotMessages(aoMessages, aoTracks) {
} }
//Conversion of hours into natural language //Conversion of hours into natural language
var iTimeMinutes = 0, iTimeHours = 0, iTimeDays = Math.floor(iTime/8); //8 hours a day var sDuration = oSpot.getNaturalDuration(iTime);
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
//Tooltip details info //Tooltip details info
$TooltipBody.append($('<div>', {'class':'separator'})); $TooltipBody
var $TooltipDetails = $('<div>', {'class':'details'}).appendTo($TooltipBody); .append($('<div>', {'class':'separator'}))
$('<p>', {'class':'detail'}).addIcon('fa-distance fa-fw', true).append(Math.round(iDistance/1000)+'km').appendTo($TooltipDetails); .append($('<div>', {'class':'details'})
$('<p>', {'class':'detail'}).addIcon('fa-time fa-fw', true).append(sDuration).appendTo($TooltipDetails); .append($('<p>', {'class':'detail'}).addIcon('fa-distance fa-fw', true).append(Math.round(iDistance/1000)+'km'))
$('<p>', {'class':'detail'}).addIcon('fa-elev-gain fa-fw', true).append(iElevGain+'m').appendTo($TooltipDetails); .append($('<p>', {'class':'detail'}).addIcon('fa-time fa-fw', true).append(sDuration))
$('<p>', {'class':'detail'}).addIcon('fa-elev-drop fa-fw', true).append(iElevDrop+'m').appendTo($TooltipDetails); .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 //Trail Start/End
self.tmp(['trail-markers', asProperties.name], { self.tmp(['trail-markers', asProperties.name], {
@@ -558,22 +555,43 @@ function initSpotMessages(aoMessages, aoTracks) {
} }
}).addTo(oMap)); }).addTo(oMap));
//Last message
var oLastMsg = (aoMessages.length > 0)?aoMessages[aoMessages.length-1]:{};
//Centering map //Centering map
var iPanelWidth = isMobile()?0:parseInt(self.tmp('$Feed').outerWidth(true)); var iPanelWidth = isMobile()?0:parseInt(self.tmp('$Feed').outerWidth(true));
if( if(
self.vars(['project', 'mode']) == self.consts.modes.blog && self.vars(['project', 'mode']) == self.consts.modes.blog &&
aoMessages.length > 0 && !$.isEmptyObject(oLastMsg) &&
self.getHash()[2] != 'message' self.getHash()[2] != 'message'
) { ) {
//Zoom on last message //Zoom on last message
var oLastMsg = aoMessages[aoMessages.length-1];
oMap.setView(L.latLng(oLastMsg.latitude, oLastMsg.longitude), 15); oMap.setView(L.latLng(oLastMsg.latitude, oLastMsg.longitude), 15);
oMap.panBy([iPanelWidth/2, 0]); 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)}); 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 //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){ $.each(aoMessages, function(iKey, oMsg){
//Marker //Marker
@@ -581,7 +599,7 @@ function initSpotMessages(aoMessages, aoTracks) {
id: oMsg.id_message, id: oMsg.id_message,
riseOnHover: true, riseOnHover: true,
icon: getDivIcon('message-in fa-rotate-270') icon: getDivIcon('message-in fa-rotate-270')
}).addTo(oMap); }).addTo(self.tmp('map'));
//Tooltip //Tooltip
$Tooltip = $('<div>', {'class':'info-window'}) $Tooltip = $('<div>', {'class':'info-window'})
@@ -621,21 +639,8 @@ function initSpotMessages(aoMessages, aoTracks) {
offset: new L.Point(0, -30) 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(['markers', oMsg.id_message], oMarker);
}); });
/*
oSpot.tmp('tracks', aoTracks);
next(24, 0, 0, 5);
*/
} }
function toggleSoftPopup(e, sMode) { 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) { function getDivIcon(sIcon) {
return L.divIcon({ return L.divIcon({
className: '', className: '',
@@ -715,12 +682,50 @@ function onFeedScroll() {
if(($Box.scrollTop() + $(window).height()) / $BoxContent.height() >= 0.8) updateFeed(); 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) { function updateFeed(bFirstChunk, bDiscrete, fCallback) {
bFirstChunk = bFirstChunk || false; bFirstChunk = bFirstChunk || false;
bDiscrete = bDiscrete || false; bDiscrete = bDiscrete || false;
fCallback = fCallback || function(){}; 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('updatable')) {
if(!self.tmp('out-of-data') || bFirstChunk) { if(!self.tmp('out-of-data') || bFirstChunk) {
@@ -729,39 +734,36 @@ function updateFeed(bFirstChunk, bDiscrete, fCallback) {
var $Posts = $('<div>'); var $Posts = $('<div>');
if(bFirstChunk===true) {
self.tmp('news_chunk', 0);
}
self.get( self.get(
'feed', 'next_feed',
function(asData) { function(asData) {
$('#loading').hide(); $('#loading').hide();
var iItemCount = Object.keys(asData.feed).length;
self.tmp('ref_id', asData.ref_id); //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
if(bFirstChunk === true) self.tmp('$PostList').empty();
$.each(asData.feed, function(iKey, asPost){ $.each(asData.feed, function(iKey, asPost){
$Posts.append(getPost(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').append($Posts.children());
self.tmp('$PostList').find('img').waitForImages(true).done(fCallback); self.tmp('$PostList').find('img').waitForImages(true).done(fCallback);
self.tmp('updatable', true); self.tmp('updatable', true);
}, { }, {
id_project: self.vars(['project', 'id']), id_project: self.vars(['project', 'id']),
chunk: self.tmp('news_chunk'), id: self.tmp('ref_id_last')
id: self.tmp('ref_id')
} }
); );
} }
} }
else if(bFirstChunk) setFeedUpdateTimer(0.2); else if(bFirstChunk) setFeedUpdateTimer(0.2, function(){updateFeed(bFirstChunk, bDiscrete, fCallback);});
} }
function focusOnPost(oFocusPost) { function focusOnPost(oFocusPost) {
@@ -780,16 +782,10 @@ function focusOnPost(oFocusPost) {
else console.log('Missing element ID '+sElemId); else console.log('Missing element ID '+sElemId);
//Reset Hash //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) { function getPost(asPost) {
asPost.headerless = asPost.headerless || false; asPost.headerless = asPost.headerless || false;
var bLink = false; var bLink = false;
@@ -946,17 +942,41 @@ function getGoogleMapsLink(asInfo) {
}).text(asInfo.lat_dms+' '+asInfo.lon_dms); }).text(asInfo.lat_dms+' '+asInfo.lon_dms);
} }
function updateHash(sType, iId) { /*
sType = sType || ''; function next(iCurrTrack, iCurrIndex, iCurrOffset, iCurrZoom) {
iId = iId || 0; 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(); console.log('Getting Track '+iCurrTrack+'/'+(aoTracks.features.length - 1)+', '+
if(iId) self.setHash(asHash.page, [asHash.items[0], sType, iId]); 'Point '+iCurrIndex+'/'+(aoTracks.features[iCurrTrack].geometry.coordinates.length - 1)+', '+
} 'Zoom '+iCurrZoom+'/14, '+
'Offset ['+aoOffset[iCurrOffset][0]+','+aoOffset[iCurrOffset][1]+']');
function flushHash(asTypes) { //Position map
asTypes = asTypes || []; var iLat = aoTracks.features[iCurrTrack].geometry.coordinates[iCurrIndex][1] + aoOffset[iCurrOffset][1] * 0.0347910214271;
var asHash = self.getHash(); var iLng = aoTracks.features[iCurrTrack].geometry.coordinates[iCurrIndex][0] + aoOffset[iCurrOffset][0] * 0.1016235351560;
if(asHash.items.length > 1 && (asTypes.length == 0 || asTypes.indexOf(asHash.items[1]) != -1)) self.setHash(asHash.page, [asHash.items[0]]); 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> </script>