New settings panel & &subscription

This commit is contained in:
2020-04-05 18:56:42 +02:00
parent 9b59ec502e
commit 85e8c4a3d1
20 changed files with 887 additions and 241 deletions

View File

@@ -0,0 +1,14 @@
CREATE TABLE `users` (
`id_user` int(10) UNSIGNED auto_increment,
`name` VARCHAR(100),
`email` VARCHAR(320),
`language` VARCHAR(2),
`active` BOOLEAN,
`led` TIMESTAMP NOT NULL ON UPDATE CURRENT_TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id_user`),
UNIQUE KEY `uni_email` (`email`)
);
ALTER TABLE posts ADD COLUMN id_user int(10) UNSIGNED AFTER id_project;
ALTER TABLE posts ADD INDEX(`id_user`);
ALTER TABLE posts ADD FOREIGN KEY (`id_user`) REFERENCES users(`id_user`);

106
inc/email.php Normal file
View File

@@ -0,0 +1,106 @@
<?php
class Email extends PhpObject {
private $sServName;
private $sTemplate;
/**
*
* @var Translator[]
*/
private $asTranslators;
/**
*
* @var Mask[]
*/
private $asTemplates;
private $asDests;
public function __construct($sServName, $sTemplate='') {
$this->sServName = $sServName;
$this->setTemplate($sTemplate);
$this->asDests = array();
}
public function setTemplate($sTemplate) {
$this->sTemplate = $sTemplate;
$this->asTranslators = array();
$this->asTemplates = array();
}
private function getTemplate($sLanguage) {
if(!array_key_exists($sLanguage, $this->asTemplates)) {
$this->asTranslators[$sLanguage] = new Translator($sLanguage);
$this->buildTemplate($sLanguage);
}
return array('subject'=>$this->asTranslators[$sLanguage]->getTranslation('conf_subject'), 'email'=>$this->asTemplates[$sLanguage]);
}
private function buildTemplate($sLanguage) {
$oTemplate = new Mask($this->sTemplate, $this->asTranslators[$sLanguage]);
switch($this->sTemplate) {
case 'confirmation':
break;
case 'update':
break;
}
$this->asTemplates[$sLanguage] = $oTemplate;
}
/**
* Set Target User Info
* @param array $asDests Contains: id_user, name, email, language, active
*/
public function setDestInfo($asDests) {
if(array_key_exists('email', $asDests)) $asDests = array($asDests);
$this->asDests = $asDests;
}
public function send() {
$sEOL = "\r\n";
foreach($this->asDests as $asDest) {
//Message
$asTemplate = $this->getTemplate($asDest['language']);
$oEmail = $asTemplate['email'];
//Unsubscribe Link
$sUnsubLink = $this->sServName.'?a=unsubscribe_email&id='.$asDest['id_user'];
$oEmail->setTag('unsubscribe_link', $sUnsubLink);
//Email Content
$sHtmlMessage = $oEmail->getMask();
$sPlainMessage = strip_tags(str_replace('<br />', "\n", $sHtmlMessage));
//Email
$iBoundary = uniqid("HTMLEMAIL");
$sHeaders =
'From: Spotty <spot@lutran.fr>'.$sEOL.
'Reply-To: Spotty <spot@lutran.fr>'.$sEOL.
'List-Unsubscribe: <mailto:unsubscribe@'.parse_url($this->sServName)['host'].'?subject=unsubscribe>, <'.$sUnsubLink.'>'.$sEOL.
'List-Unsubscribe-Post: List-Unsubscribe=One-Click'.$sEOL.
'MIME-Version: 1.0'.$sEOL.
'Content-Type: multipart/alternative; boundary="'.$iBoundary.'"'.$sEOL;
$sBody =
'--'.$iBoundary.$sEOL. //Plain Message
'Content-Type: text/plain; charset=UTF-8'.$sEOL.
'Content-Transfer-Encoding: base64'.$sEOL.
chunk_split(base64_encode($sPlainMessage)).$sEOL.
'--'.$iBoundary.$sEOL. //HTML Message
'Content-Type: text/html; charset=UTF-8'.$sEOL.
'Content-Transfer-Encoding: base64'.$sEOL.
chunk_split(base64_encode($sHtmlMessage)).$sEOL.
'--'.$iBoundary.'--';
//Send
if(!mail($asDest['email'], $asTemplate['subject'], $sBody, $sHeaders)) $this->addError('Could not send '.$this->sTemplate.' email to '.$asDest['email']);
}
}
}

View File

@@ -101,7 +101,7 @@ class Feed extends PhpObject {
}
public function checkUpdateFeed($sProjectMode) {
//Feed updated once a day in Blog Mode
//Feed updated once every hour in Blog Mode
if($sProjectMode == Project::MODE_BLOG && date('Y-m-d-H', $this->iLastUpdate) != date('Y-m-d-H')) $this->updateFeed();
}

View File

@@ -21,6 +21,8 @@ class Spot extends Main
const FEED_CHUNK_SIZE = 15;
const DEFAULT_LANG = 'en';
/**
* Active Project
* @var Project
@@ -33,18 +35,27 @@ class Spot extends Main
*/
private $oMedia;
/**
* User
* @var User
*/
private $oUser;
public function __construct($oClassManagement, $sProcessPage, $sTimezone)
{
$asClasses = array(
array('name'=>'feed', 'project'=>true),
array('name'=>'project', 'project'=>true),
array('name'=>'media', 'project'=>true),
array('name'=>'converter', 'project'=>true)
array('name'=>'converter', 'project'=>true),
array('name'=>'user', 'project'=>true)
);
parent::__construct($oClassManagement, $sProcessPage, $asClasses, true, __FILE__, $sTimezone);
$this->oUser = new User($this->oDb);
$this->oClassManagement->incClass('translator');
$this->oLang = new Translator('', 'en');
$this->oLang = new Translator($this->oUser->getLang(), self::DEFAULT_LANG);
$this->oProject = new Project($this->oDb);
$this->oMedia = new Media($this->oDb, $this->oProject);
@@ -66,8 +77,9 @@ class Spot extends Main
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', 'timezone'),
self::POST_TABLE => array(Db::getId(Project::PROJ_TABLE), 'name', 'content', 'site_time'),
Media::MEDIA_TABLE => array(Db::getId(Project::PROJ_TABLE), 'filename', 'type', 'taken_on', 'posted_on', 'rotate', 'comment')
self::POST_TABLE => array(Db::getId(Project::PROJ_TABLE), Db::getId(User::USER_TABLE), 'name', 'content', 'site_time'),
Media::MEDIA_TABLE => array(Db::getId(Project::PROJ_TABLE), 'filename', 'type', 'taken_on', 'posted_on', 'rotate', 'comment'),
User::USER_TABLE => array('name', 'email', 'language', 'active')
),
'types' => array
(
@@ -95,7 +107,10 @@ class Spot extends Main
'taken_on' => "TIMESTAMP DEFAULT 0",
'posted_on' => "TIMESTAMP DEFAULT 0",
'rotate' => "SMALLINT",
'comment' => "LONGTEXT"
'comment' => "LONGTEXT",
'email' => "VARCHAR(320)",
'language' => "VARCHAR(2)",
'active' => "BOOLEAN"
),
'constraints' => array
(
@@ -103,11 +118,12 @@ class Spot extends Main
Feed::FEED_TABLE => array("UNIQUE KEY `uni_ref_feed_id` (`ref_feed_id`)", "INDEX(`ref_feed_id`)"),
Feed::SPOT_TABLE => array("UNIQUE KEY `uni_ref_spot_id` (`ref_spot_id`)", "INDEX(`ref_spot_id`)"),
Project::PROJ_TABLE => "UNIQUE KEY `uni_proj_name` (`codename`)",
Media::MEDIA_TABLE => "UNIQUE KEY `uni_file_name` (`filename`)"
Media::MEDIA_TABLE => "UNIQUE KEY `uni_file_name` (`filename`)",
User::USER_TABLE => "UNIQUE KEY `uni_email` (`email`)"
),
'cascading_delete' => array
(
Feed::SPOT_TABLE=>array(Feed::MSG_TABLE)
Feed::SPOT_TABLE => array(Feed::MSG_TABLE)
)
);
}
@@ -119,7 +135,8 @@ class Spot extends Main
'vars' => array(
'chunk_size' => self::FEED_CHUNK_SIZE,
'default_project_codename' => $this->oProject->getProjectCodeName(),
'projects' => $this->oProject->getProjects()
'projects' => $this->oProject->getProjects(),
'user' => $this->oUser->getUserInfo()
),
'consts' => array(
'geo_server' => Settings::GEO_SERVER,
@@ -155,7 +172,7 @@ class Spot extends Main
public function getMarkers()
{
$asMessages = $this->getSpotMessages();
$bSuccess = !empty($asMessages);
$bSuccess = !empty($this->getMedias('posted_on') + $asMessages + $this->getPosts());
$sDesc = $bSuccess?'':self::NO_DATA;
//Add medias
@@ -183,6 +200,35 @@ class Spot extends Main
return self::getJsonResult($bSuccess, $sDesc, $asMessages);
}
public function subscribe($sEmail) {
$asResult = $this->oUser->addUser($sEmail, $this->oLang->getLanguage());
//Send Confirmation Email
if($asResult['result']) {
$this->oClassManagement->incClass('email', true);
$oConfEmail = new Email($this->asContext['serv_name'],'email_confirmation');
$oConfEmail->setDestInfo($this->oUser->getUserInfo());
$oConfEmail->send();
}
return self::getJsonResult($asResult['result'], $asResult['desc'], $asResult['data']);
}
public function unsubscribe() {
$asResult = $this->oUser->removeUser();
return self::getJsonResult($asResult['result'], $asResult['desc'], $asResult['data']);
}
public function unsubscribeFromEmail($iUserId) {
$this->oUser->setUserId($iUserId);
$this->oLang->setLanguage($this->oUser->getLang(), self::DEFAULT_LANG);
$asResult = $this->oUser->removeUser();
$sDesc = $asResult['desc'];
if($sDesc=='') $sDesc = $this->oLang->getTranslation('nl_unsubscribed');
return $sDesc;
}
private function getSpotMessages()
{
$asMessages = array();
@@ -192,20 +238,22 @@ class Spot extends Main
foreach($asFeeds as $iFeedId) {
$oFeed = new Feed($this->oDb, $iFeedId);
$asMessages = $oFeed->getMessages($this->oProject->getActivePeriod());
foreach($asMessages as $iIndex=>&$asMessage)
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'] = $iIndex + 1;
$this->addTimeStamp($asMessage, $asMessage['unix_time']);
}
}
usort($asMessages, function($a, $b){return $a['unix_time'] > $b['unix_time'];});
return $asMessages;
$asSortedMessages = array_values($asMessages);
foreach($asSortedMessages as $iIndex=>&$asSortedMessage) $asSortedMessage['displayed_id'] = $iIndex + 1;
return $asSortedMessages;
}
/**
@@ -219,20 +267,23 @@ class Spot extends Main
{
$asMedias = $this->oMedia->getMediasInfo();
$asValidMedias = array();
foreach($asMedias as $iIndex=>$asMedia) {
foreach($asMedias as $asMedia) {
$sTimeRef = $asMedia[$sTimeRefField];
if($sTimeRef >= $this->oProject->getActivePeriod('from') && $sTimeRef <= $this->oProject->getActivePeriod('to')) {
$asMedia['taken_on_formatted'] = $this->getTimeFormat(strtotime($asMedia['taken_on']));
$asMedia['posted_on_formatted'] = $this->getTimeFormat(strtotime($asMedia['posted_on']));
$asMedia['displayed_id'] = $iIndex + 1;
$this->addTimeStamp($asMedia, strtotime($sTimeRef));
$asValidMedias[] = $asMedia;
}
}
usort($asValidMedias, function($a, $b){return $a['unix_time'] > $b['unix_time'];});
return $asValidMedias;
$asSortedMedias = array_values($asValidMedias);
foreach($asSortedMedias as $iIndex=>&$asSortedMedia) $asSortedMedia['displayed_id'] = $iIndex + 1;
return $asSortedMedias;
}
private function getPosts()
@@ -251,6 +302,7 @@ class Spot extends Main
foreach($asPosts as &$asPost) {
$iUnixTimeStamp = strtotime($asPost['site_time']); //assumes site timezone (Settings::TIMEZONE)
$asPost['formatted_name'] = Toolbox::mb_ucwords($asPost['name']);
unset($asPost[Db::getId(User::USER_TABLE)]);
$this->addTimeStamp($asPost, $iUnixTimeStamp);
}
@@ -315,11 +367,15 @@ class Spot extends Main
{
$asData = array(
Db::getId(Project::PROJ_TABLE) => $this->oProject->getProjectId(),
Db::getId(User::USER_TABLE) => $this->oUser->getUserId(),
'name' => mb_strtolower(trim($sName)),
'content' => trim($sPost),
'site_time' => date(Db::TIMESTAMP_FORMAT) //site time (Settings::TIMEZONE)
);
$iPostId = $this->oDb->insertRow(self::POST_TABLE, $asData);
$this->oUser->updateNickname($sName);
return self::getJsonResult(($iPostId > 0), '');
}

108
inc/user.php Normal file
View File

@@ -0,0 +1,108 @@
<?php
class User extends PhpObject {
//DB Tables
const USER_TABLE = 'users';
//Cookie
const COOKIE_ID_USER = 'subscriber';
/**
* Database Handle
* @var Db
*/
private $oDb;
//User Info
private $iUserId;
private $asUserInfo;
public function __construct(Db &$oDb) {
parent::__construct(__CLASS__, Settings::DEBUG);
$this->oDb = &$oDb;
$this->iUserId = 0;
$this->asUserInfo = array('name'=>'', 'email'=>'', 'language'=>'', 'active'=>0);
$this->checkUserCookie();
}
public function getLang() {
return $this->asUserInfo['language'];
}
public function addUser($sEmail, $sLang) {
$bSuccess = false;
$sDesc = '';
$sEmail = trim($sEmail);
//Check Email availability
$iUserId = $this->oDb->selectValue(self::USER_TABLE, Db::getId(self::USER_TABLE), array('email'=>$sEmail, 'active'=>true));
if($iUserId > 0) $sDesc = 'lang:nl_email_exists';
else {
//Add/Reactivate user
$iUserId = $this->oDb->insertUpdateRow(self::USER_TABLE, array('email'=>$sEmail, 'language'=>$sLang, 'active'=>true), array('email'));
if($iUserId==0) $sDesc = 'lang:error_commit_db';
else $bSuccess = true;
}
//Set Cookie (valid 1 year)
if($iUserId > 0) {
$this->setUserId($iUserId);
$this->updateCookie();
}
return Spot::getResult($bSuccess, $sDesc, $this->getUserInfo());
}
public function removeUser() {
$bSuccess = false;
$sDesc = '';
if($this->iUserId > 0) {
$iUserId = $this->oDb->updateRow(self::USER_TABLE, $this->iUserId, array('active'=>false));
if($iUserId==0) $sDesc = 'lang:error_commit_db';
else $bSuccess = true;
}
else $sDesc = 'lang:nl_unknown_email';
return Spot::getResult($bSuccess, $sDesc);
}
public function updateNickname($sNickname) {
if($this->iUserId > 0 && $sNickname!='') $this->oDb->updateRow(self::USER_TABLE, $this->iUserId, array('name'=>$sNickname));
}
private function checkUserCookie() {
if(isset($_COOKIE[self::COOKIE_ID_USER])){
$this->setUserId($_COOKIE[self::COOKIE_ID_USER]);
//Extend cookie life
if($this->iUserId > 0) $this->updateCookie();
}
}
public function getUserId() {
return $this->iUserId;
}
public function getUserInfo() {
$asUserInfo = $this->asUserInfo;
$asUserInfo[Db::getId(self::USER_TABLE)] = $this->iUserId;
return $asUserInfo;
}
public function setUserId($iUserId) {
$this->iUserId = 0;
$asUser = $this->oDb->selectRow(self::USER_TABLE, array(Db::getId(self::USER_TABLE)=>$iUserId, 'active'=>true), array_keys($this->asUserInfo));
if(!empty($asUser)) {
$this->iUserId = $iUserId;
$this->asUserInfo = $asUser;
}
}
private function updateCookie() {
setcookie(self::COOKIE_ID_USER, $this->iUserId, time() + 60 * 60 * 24 * 365);
}
}

View File

@@ -22,6 +22,7 @@ $sField = isset($_REQUEST['field'])?$_REQUEST['field']:'';
$oValue = isset($_REQUEST['value'])?$_REQUEST['value']:'';
$iId = isset($_REQUEST['id'])?$_REQUEST['id']:0;
$sType = isset($_REQUEST['type'])?$_REQUEST['type']:'';
$sEmail = isset($_REQUEST['email'])?$_REQUEST['email']:'';
//Initiate class
$oSpot = new Spot($oClassManagement, __FILE__, $sTimezone);
@@ -65,6 +66,15 @@ if($sAction!='')
case 'build_geojson':
$sResult = $oSpot->convertGpxToGeojson($sName);
break;
case 'subscribe':
$sResult = $oSpot->subscribe($sEmail);
break;
case 'unsubscribe':
$sResult = $oSpot->unsubscribe();
break;
case 'unsubscribe_email':
$sResult = $oSpot->unsubscribeFromEmail($iId);
break;
default:
$sResult = Main::getJsonResult(false, Main::NOT_FOUND);
}

View File

@@ -8,6 +8,7 @@ admin_save_success = Saved
track_main = Main track
track_offtrack = Off-track
track_hitchhiking = Hitchhiking
track_download = Download GPX Track
upload_title = Picture & Video Uploads
upload_wrong_mode = Project "$0" is not in blog mode. No upload allowed
@@ -20,6 +21,7 @@ post_new_message = New message
and = and
counter = #$0
maps = Base Maps
map_satellite = Satellite
map_otm = Open Topo Map
map_ign_france = IGN (France)
@@ -38,11 +40,13 @@ click_zoom = Click to zoom
media_count = Media $0 / $1
media_no_id = Missing Media ID in request
media_comment_update= Comment of media "$0" updated
see_on_google = See on Google Maps
city_time = $0 Time
project_id = Project ID
project = Project
projects = Projects
mode = Mode
code_name = Code name
start = Start
@@ -62,3 +66,25 @@ time_zone = Time zone
unit_day = day
unit_days = days
unit_hour = h
newsletter = Keep in touch!
nl_email_placeholder= my@email.com
nl_subscribed_desc = You're all set. We'll send you updates as soon as we get them
nl_unsubscribed_desc= Write down your email address and we'll send you François' position as soon as we get it :)
nl_email_exists = This email is already subscribed
nl_subscribe = Subscribe
nl_subscribed = Thanks! You'll receive a confirmation email shortly
nl_unsubscribe = Unsubscribe
nl_unsubscribed = Done. No more junk mail from us
nl_unknown_email = Unknown email address
conf_subject = Successful Registration
conf_preheader = Thanks for keeping in touch!
conf_thanks_sub = You're all set!
conf_body_para_1 = Thank you for checking in on my wanderings :). I'll make sure to keep you posted on my progress along the trail.
conf_body_para_2 = I usually check-in once a day, plus sometimes on special events, like successful peak ascents. I am using a GPS-based device (PLB) which does not require phone reception to work. Thus the messages should be pretty frequent, but, being awestruck by the beauty of nature, I could also just forget to send a signal once in a while. So do not worry if you don't receive anything for a couple of days.
conf_body_para_3 = If I've posted some pictures recently, you should also get them in this email.
conf_body_conclusion= Happy Trails!
conf_signature = --François
conf_unsubscribe = PS: Changed your mind?
conf_unsubscribe_btn= Unsubscribe

View File

@@ -8,6 +8,7 @@ admin_save_success = Sauvegardé
track_main = Trajet principal
track_offtrack = Variante
track_hitchhiking = Hors rando
track_download = Télécharger la trace GPX
upload_title = Uploader photos & vidéos
upload_wrong_mode = Le projet "$0" n'est pas en mode blog. Aucun upload possible
@@ -20,6 +21,7 @@ post_new_message = Nouveau message
and = et
counter = N° $0
maps = Fonds de carte
map_satellite = Satellite
map_otm = Open Topo Map
map_ign_france = IGN (France)
@@ -38,11 +40,13 @@ click_zoom = Click pour zoomer
media_count = Média $0 sur $1
media_no_id = ID du média manquant
media_comment_update= Commentaire du media "$0" mis-à-jour
see_on_google = Voir la position sur Google Maps
city_time = heure de $0
project_id = ID projet
project = Projet
projects = Projets
mode = Mode
code_name = Nom de code
start = Départ
@@ -62,3 +66,25 @@ time_zone = Fuseau horaire
unit_day = jour
unit_days = jours
unit_hour = h
newsletter = Rester en contact
nl_email_placeholder= mon@email.com
nl_subscribed_desc = C'est tout bon. On t'enverra les nouvelles posititions dès qu'on les reçoit
nl_unsubscribed_desc= Ajoute ton adresse email et on t'enverra la nouvelle position de François dès qu'on la reçoit :)
nl_email_exists = Cette adresse email est déjà enregistrée
nl_subscribe = S'abonner
nl_subscribed = Merci ! Tu vas recevoir un email de confirmation très bientôt
nl_unsubscribe = Se désinscrire
nl_unsubscribed = C'est fait. Fini le spam!
nl_unknown_email = Adresse email inconnue
conf_subject = Confirmation
conf_preheader = Merci de rester en contact !
conf_thanks_sub = C'est tout bon !
conf_body_para_1 = C'est gentil de venir voir où j'en suis. Promis, je vous tiendrais au courant de mon avancée.
conf_body_para_2 = En général, j'envoie un message une fois par jour. Lorsque je passe à des endroits sympas, j'en envoie un supplémentaire (ascension de sommets, ce genre de choses). J'utilise une balise GPS pour envoyer le signal, je n'ai donc pas besoin de réseau téléphonique pour que cela fonctionne. Cependant, il peut m'arriver d'appuyer sur le bouton. Donc pas de raison de s'inquiéter si vous ne recevez pas de messages pendant une journée ou deux.
conf_body_para_3 = If I've posted some pictures recently, you should also get them in this email.
conf_body_conclusion= A bientôt sur les chemins !
conf_signature = --François
conf_unsubscribe = PS: Trop d'emails ?
conf_unsubscribe_btn= Se désinscrire

View File

@@ -48,7 +48,7 @@
<tbody></tbody>
</table>
</div>
<div id="feedback"></div>
<div id="feedback" class="feedback"></div>
</div>
<script type="text/javascript">
oSpot.pageInit = function(asHash) {

View File

@@ -0,0 +1,33 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html charset=UTF-8" />
<title>[#]lang:conf_subject[#]</title>
</head>
<body>
<span style="color: transparent; display: none !important; height: 0; max-height: 0; max-width: 0; opacity: 0; overflow: hidden; mso-hide: all; visibility: hidden; width: 0;">[#]lang:conf_preheader[#]</span>
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="width:100%;max-width:600px;">
<tr>
<td width="20%"><img src="https://spot.lutran.fr/images/icons/mstile-144x144.png" width="90%" border="0" alt="logo" /></td>
<td><h1>[#]lang:conf_thanks_sub[#]</h1></td>
</tr>
<tr>
<td colspan="2">
<p align="justify">[#]lang:conf_body_para_1[#]</p>
<p align="justify">[#]lang:conf_body_para_2[#]</p>
<p align="justify">[#]lang:conf_body_para_3[#]</p>
</td>
</tr>
<tr>
<td colspan="2">
<p>[#]lang:conf_body_conclusion[#]<br />[#]lang:conf_signature[#]</p>
</td>
</tr>
<tr>
<td colspan="2">
<p>[#]lang:conf_unsubscribe[#] <a href="[#]unsubscribe_link[#]" target="_blank">[#]lang:conf_unsubscribe_btn[#]</a></p>
</td>
</tr>
</table>
</body>
</html>

View File

@@ -3,6 +3,24 @@
<div class="loader fa fa-fw fa-map flicker" id="map_loading"></div>
</div>
<div id="map"></div>
<div id="settings">
<div id="settings-sections">
<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">
<h1><i class="fa fa-fw push fa-project"></i>[#]lang:projects[#]</h1>
<div id="settings-projects"></div>
</div>
</div>
</div>
<div id="feed">
<div id="posts">
<div id="poster"></div>
@@ -11,6 +29,7 @@
</div>
</div>
<div id="elems">
<div id="settings-button"><i class="fa fa-menu fa-fw"></i></div>
<div id="post-button"><i class="fa fa-fw"></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>
@@ -40,7 +59,6 @@ oSpot.pageInit = function(asHash) {
};
oSpot.onResize = function() {
self.tmp('mobile', $('#mobile').is(':visible'));
self.tmp('feed_width', self.tmp('with_feed')?$('#feed').outerWidth(true):0);
self.tmp('map_offset', -1 * self.tmp('feed_width') / $('body').outerWidth(true));
@@ -64,7 +82,7 @@ oSpot.onKeydown = function(oEvent) {
}
}
function toggleFeedPanel(bShow, bAutoPan) {
function toggleFeedPanel(bShow, sMapAction) {
var $Container = $('#projects');
if(typeof bShow === 'undefined') $Container.toggleClass('with-feed');
else $Container.toggleClass('with-feed', bShow);
@@ -72,17 +90,36 @@ function toggleFeedPanel(bShow, bAutoPan) {
oSpot.tmp('with_feed', $Container.hasClass('with-feed'));
oSpot.onResize();
if(typeof bAutoPan === 'undefined') bAutoPan = true;
if(bAutoPan && typeof oSpot.tmp('map') != 'undefined') {
oSpot.tmp('map').panBy([(oSpot.tmp('with_feed')?1:-1)*$('#feed').outerWidth(true)/2, 0], {
duration: 0.5
});
if(isMobile()) $('#settings-button').toggle(!oSpot.tmp('with_feed'));
sMapAction = sMapAction || 'panTo';
switch(sMapAction) {
case 'none': break;
case 'panTo': oSpot.tmp('map').panBy([(oSpot.tmp('with_feed')?1:-1)*$('#feed').outerWidth(true)/2, 0], {duration: 0.5}); break;
case 'panToInstant': oSpot.tmp('map').panBy([(oSpot.tmp('with_feed')?1:-1)*$('#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_width') + 5, 5)}); break;
}
}
function toggleSettingsPanel(bShow) {
var $Container = $('#projects');
if(typeof bShow === 'undefined') $Container.toggleClass('with-settings');
else $Container.toggleClass('with-settings', bShow);
var bWithSettings = isSettingsPanelOpen();
oSpot.onResize();
if(isMobile()) $('#post-button').toggle(!bWithSettings);
oSpot.tmp('map').panBy([(bWithSettings?-1:1)*$('#settings').outerWidth(true)/2, 0], {duration: 0.5});
}
function isSettingsPanelOpen() {
return $('#projects').hasClass('with-settings');
}
function isMobile() {
self.tmp('mobile', $('#mobile').is(':visible'));
return self.tmp('mobile');
return $('#mobile').is(':visible');
}
function initPage(asHash) {
@@ -97,8 +134,7 @@ function initPage(asHash) {
self.tmp('trail-markers', 'object');
self.tmp('marker_size', {width: 32, height: 32});
toggleFeedPanel(!isMobile(), false);
oSpot.onResize();
toggleFeedPanel(false, 'none');
//Lightbox options
lightbox.option({
@@ -119,6 +155,7 @@ function initPage(asHash) {
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($('#posts')[0]));
self.tmp('simple-bar').getScrollElement().addEventListener('scroll', onFeedScroll);
@@ -131,6 +168,12 @@ function initPage(asHash) {
if(aiDelta.x > self.tmp('feed_width')/3 && aiDelta.x > Math.abs(aiDelta.y)) toggleFeedPanel(false);
});
//Feed Panel
initPosts();
//Settings Panel
initSettings();
//project Bootstrap
initProject(asHash.items[0]);
}
@@ -163,34 +206,36 @@ function initProject(sProjectCodeName){
mimeType: 'application/json'
})
).done(function(aoMessages, aoTracks) {
initSpotMessages(aoMessages[0]['data'] || [], aoTracks[0]);
initSpotMessages(aoMessages[0]['data'] || [], aoTracks[0], aoMessages[0]['desc']=='No Data');
});
//Posts
initPosts();
//Show/Hide Poster Panel
var bHistoMode = (self.vars(['project', 'mode']) == self.consts.modes.histo);
$('#poster').toggle(!bHistoMode);
//Feed auto-update
self.tmp('simple-bar').getScrollElement().scrollTop = 0;
if(!bHistoMode) onAutoUpdate(true);
else updateFeed(true);
}
function initPosts() {
var $Poster = $('#poster').empty();
if(self.vars(['project', 'mode'])==self.consts.modes.histo) $Poster.hide();
else {
var asPoster = {
type: 'poster',
formatted_time: '',
relative_time: oSpot.lang('post_new_message')
};
getPost(asPoster).appendTo($Poster.show());
getPost(asPoster).appendTo($('#poster'));
autosize($('#post'));
$('#submit').click(function(){
if($Poster.checkForm())
if($('#poster').checkForm())
{
self.get(
'add_post',
function()
{
$('#name').val('');
$('#post').val('');
updateFeed(true);
},
@@ -202,12 +247,73 @@ function initPosts() {
);
}
});
}
function initSettings(){
//Scrollbar
new SimpleBar($('#settings-sections')[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', 'this is not an email');
else {
oSpot.get(
sAction,
function(asData) {
settingsFeedback('success', oSpot.lang('nl_'+sAction+'d'));
oSpot.vars('user', asData);
setUserInterface();
},
{'email': sEmail},
function(sDesc) {settingsFeedback('error', sDesc);},
function(sState) {
var bLoading = (sState=='start');
$('#nl_btn')
.prop('disabled', bLoading)
.toggleClass('loading', bLoading);
}
);
}
});
//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
//Switch to subcribe mode
$('#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'));
//Reset nickname
$('#name').val('');
}
//Feed auto-update
self.tmp('simple-bar').getScrollElement().scrollTop = 0;
if(self.vars(['project', 'mode']) != self.consts.modes.histo) onAutoUpdate(true);
else updateFeed(true);
}
function onAutoUpdate(bFirstExec) {
@@ -218,11 +324,17 @@ function onAutoUpdate(bFirstExec) {
}
function getElevWidth() {
// Page widthFeed Panel Legend width (bottom left) Elevation right margin (from page side)
return $('#projects').width() - oSpot.tmp('feed_width') - $('.leaflet-bottom.leaflet-left > .leaflet-control-layers').outerWidth(true) - parseInt($('.leaflet-bottom.leaflet-right > .leaflet-control-scale').css('margin-right').slice(0, -2));
var
iPageWidth = $('#projects').width(),
iFeedPanelWidth = oSpot.tmp('feed_width'),
iSettingsPanelWidth = isSettingsPanelOpen()?$('#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 initSpotMessages(aoMessages, aoTracks) {
function initSpotMessages(aoMessages, aoTracks, bNoFeed) {
//Tile layers
aoLayers = {};
@@ -241,6 +353,13 @@ function initSpotMessages(aoMessages, aoTracks) {
});
self.tmp('map', oMap);
//Controls: Settings Panel
var oSettingsPanel = L.control({position: 'topleft'});
var $SettingsButton = $('#settings-button').clone();
$SettingsButton.click(toggleSettingsPanel);
oSettingsPanel.onAdd = function(oMap) {return $SettingsButton[0];};
oSettingsPanel.addTo(oMap);
//Controls: Feed Panel
var oFeedPanel = L.control({position: 'topright'});
var $PostButton = $('#post-button').clone();
@@ -254,27 +373,23 @@ function initSpotMessages(aoMessages, aoTracks) {
oLegend.addTo(oMap);
//Controls: Projects
var oProjects = L.control({position: 'bottomleft'});
oProjects.onAdd = function(oMap) {
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}).click(function(e){e.stopPropagation();}))
.append($('<a>', {'class':'fa fa-download push-left', href:asProject.gpxfilepath, title:oSpot.lang('track_download')}).click(function(e){e.stopPropagation();}))
);
$Labels.append($Label);
});
return $('<div>', {'class':'leaflet-control-layers leaflet-control leaflet-control-layers-expanded'}).append($('<section>').append($Labels))[0];
};
oProjects.addTo(oMap);
$('#settings-projects').empty().append($Labels);
//Controls: Scale
oScale = L.control.scale({imperial: false, 'position':'bottomright'}).addTo(oMap);
@@ -295,8 +410,9 @@ function initSpotMessages(aoMessages, aoTracks) {
}).addTo(oMap);
self.tmp('elev', oElev);
//Controls: Tiles (layers)
//Controls: Tiles (layers): Add & Move to Settings Panel
L.control.layers(aoLayers, null, {position: 'topleft'}).addTo(oMap);
$('#layers').empty().append($('.leaflet-control-layers-list .leaflet-control-layers-base'));
//Tracks, colors & popup
var oActualTracks = L.geoJson(aoTracks, {
@@ -305,7 +421,7 @@ function initSpotMessages(aoMessages, aoTracks) {
}
}).addTo(oMap);
var oTracks = L.geoJson(aoTracks, {
self.tmp('track', L.geoJson(aoTracks, {
style: {weight: 20, opacity: 0},
onEachFeature: function(feature, oLayer) {
var asProperties = feature.properties;
@@ -392,19 +508,18 @@ function initSpotMessages(aoMessages, aoTracks) {
(oElev.addData.bind(oElev))(feature, oLayer);
}
}
}).addTo(oMap);
}).addTo(oMap));
//Centering map
var bWithFeedPanel = (!bNoFeed && !isMobile());
if(self.vars(['project', 'mode'])==self.consts.modes.blog && aoMessages.length > 0)
{
//Zoom on last message
var oLastMsg = aoMessages[aoMessages.length-1];
oMap.setView(L.latLng(oLastMsg.latitude, oLastMsg.longitude), 15);
//Recenter map to be at the center of 70% (map_offset) of the page, 30% being used by posts
oMap.setOffsetView(self.tmp('map_offset'));
oMap.panBy([(bWithFeedPanel?1:0)*$('#feed').outerWidth(true)/2, 0]);
}
else oMap.fitBounds(oTracks.getBounds(), {paddingTopLeft: L.point(5, self.tmp('marker_size').height + 5), paddingBottomRight: L.point(self.tmp('feed_width') + 5, 5)});
else oMap.fitBounds(self.tmp('track').getBounds(), {paddingTopLeft: L.point(5, self.tmp('marker_size').height + 5), paddingBottomRight: L.point(5 + parseInt(bWithFeedPanel?$('#feed').outerWidth(true):0), 5)});
//Spot Messages
$.each(aoMessages, function(iKey, oMsg){
@@ -426,7 +541,7 @@ function initSpotMessages(aoMessages, aoTracks) {
.append(oMsg.formatted_time+(self.vars(['project', 'mode'])==self.consts.modes.blog?' ('+oMsg.relative_time+')':'')+self.tmp('site_tz_notice')))
.append($('<p>', {'class':'coordinates'})
.addIcon('fa-coords fa-fw fa-lg', false)
.append('Lat : '+oMsg.latitude+', Lng : '+oMsg.longitude));
.append(getGoogleMapsLink(oMsg)));
//Tooltip medias
if(oMsg.medias) {
@@ -440,14 +555,14 @@ function initSpotMessages(aoMessages, aoTracks) {
}
oMarker.bindPopup($Tooltip[0], {
maxWidth: 1000,
maxWidth: $('#projects').width(),
autoPan: false,
closeOnClick: true,
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) && self.tmp('mobile')) oMarker.openPopup();
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);
});
@@ -498,7 +613,7 @@ function updateFeed(bFirstChunk, bDiscrete) {
self.tmp('updatable', true);
if(bFirstChunk && !self.tmp('mobile') && !$.isEmptyObject(asData)) toggleFeedPanel(true, false);
if(bFirstChunk && !isMobile() && !$.isEmptyObject(asData)) toggleFeedPanel(true, 'none');
}, {
'project_id': self.vars(['project', 'id']),
'chunk': self.tmp('news_chunk')
@@ -517,7 +632,7 @@ function setFeedUpdateTimer(iSeconds, fCallback) {
function getPost(asPost) {
asPost.headerless = asPost.headerless || false;
var $Post = $('<div>', {'class':'post '+asPost.type+(asPost.headerless?' headerless':'')});
var sRelTime = (asPost.relative_time!='')?((self.vars(['project', 'mode'])==self.consts.modes.histo)?asPost.formatted_time.substr(0, 10):asPost.relative_time):'';
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 sType = asPost.subtype || asPost.type;
var $Body = {};
@@ -526,7 +641,7 @@ function getPost(asPost) {
case 'message':
$Body = $('<div>')
.data('id', asPost.id_message)
.append($('<p>').addIcon('fa-coords', true).append(asPost.lat_dms+' '+asPost.lon_dms))
.append($('<p>').addIcon('fa-coords', true).append(getGoogleMapsLink(asPost)))
.append($('<p>').addIcon('fa-time', true).append(sAbsTime+self.tmp('site_tz_notice')))
.append($('<a>', {'class':'drill'})
.append($('<img>', {'class':'staticmap', title: oSpot.lang('click_zoom'), src: getWmtsApiUrl('static', asPost.latitude, asPost.longitude, 13)}))
@@ -536,9 +651,17 @@ function getPost(asPost) {
)
.click(function(){
var oMarker = oSpot.tmp(['markers', $(this).parent().data('id')]);
self.tmp('map').setOffsetView(self.tmp('map_offset'), oMarker.getLatLng(), 13);
if(isMobile()) {
toggleFeedPanel(false, 'panToInstant');
self.tmp('map').setView(oMarker.getLatLng(), 15);
}
else {
var iOffset = $('#feed').outerWidth(true)/2 - ($('#projects').hasClass('with-settings')?1:-1)*$('#settings').outerWidth(true)/2;
var iRatio = -1 * iOffset / $('body').outerWidth(true);
self.tmp('map').setOffsetView(iRatio, oMarker.getLatLng(), 15);
}
if(!oMarker.isPopupOpen()) oMarker.openPopup();
if(self.tmp('mobile')) toggleFeedPanel(false);
})
)
.hover(
@@ -615,4 +738,13 @@ function getMediaLink(asData, sType) {
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'
}).text(asInfo.lat_dms+' '+asInfo.lon_dms);
}
</script>

View File

@@ -13,3 +13,4 @@
* Reset zoom on image closing (lightbox)
* Fix video fullscreen button on ios
* Fix lightbox portrait mode: push text under
* Subscribe to message feed

View File

@@ -65,12 +65,12 @@ function Spot(asGlobals)
/* Interface with server */
this.get = function(sAction, fOnSuccess, oVars, fOnError, bProcessIcon)
this.get = function(sAction, fOnSuccess, oVars, fOnError, fonProgress)
{
if(!oVars) oVars = {};
fOnError = fOnError || function(sError) {console.log(sError);};
bProcessIcon = bProcessIcon || false;
//if(bProcessIcon) self.addBufferIcon();
fonProgress = fonProgress || function(sState){};
fonProgress('start');
oVars['a'] = sAction;
oVars['t'] = self.consts.timezone;
@@ -82,18 +82,15 @@ function Spot(asGlobals)
})
.done(function(oData)
{
fonProgress('done');
if(oData.desc.substr(0, self.consts.lang_prefix.length)==self.consts.lang_prefix) oData.desc = self.lang(oData.desc.substr(5));
if(oData.result==self.consts.error) fOnError(oData.desc);
else
{
//if(bProcessIcon) self.resetIcon();
fOnSuccess(oData.data);
}
else fOnSuccess(oData.data);
})
.fail(function(jqXHR, textStatus, errorThrown)
{
//if(bProcessIcon) self.resetIcon();
fonProgress('fail');
fOnError(textStatus+' '+errorThrown);
});
};
@@ -171,7 +168,7 @@ function Spot(asGlobals)
self.onSamePageMove = function(asHash){return false};
self.onQuitPage = function(){return true};
self.onResize = function(){};
self.onFeedback = function(sType, sMsg){feedback(sType, sMsg);};
self.onFeedback = function(sType, sMsg){};
self.onKeydown = function(oEvent){};
};

View File

@@ -57,3 +57,26 @@ button {
cursor: pointer;
font-weight: bold;
}
input, textarea, button {
border: none;
padding: 0.5em 1em;
border-radius: 3px;
}
/* Feedback */
.feedback {
p {
margin: 0 0 1em 0;
&.error {
color: red;
}
&.warning {
color: orange;
}
&.success {
color: green;
}
}
}

View File

@@ -16,10 +16,10 @@ $fa-css-prefix: fa;
@extend .fas;
&.push {
margin-right: 0.5em;
margin-right: 0.5rem;
}
&.push-left {
margin-left: 0.5em;
margin-left: 0.5rem;
}
}
@@ -47,6 +47,15 @@ $fa-css-prefix: fa;
.#{$fa-css-prefix}-elev-gain:before { content: fa-content($fa-var-arrow-circle-up); }
.#{$fa-css-prefix}-download:before { content: fa-content($fa-var-file-download); }
/* Settings */
.#{$fa-css-prefix}-menu:before { content: fa-content($fa-var-bars); }
.#{$fa-css-prefix}-newsletter:before { content: fa-content($fa-var-wifi); }
.#{$fa-css-prefix}-project:before { content: fa-content($fa-var-hiking); }
.#{$fa-css-prefix}-error:before { content: fa-content($fa-var-exclamation-square); }
.#{$fa-css-prefix}-warning:before { content: fa-content($fa-var-exclamation-triangle); }
.#{$fa-css-prefix}-success:before { content: fa-content($fa-var-check-circle); }
.#{$fa-css-prefix}-unsubscribe:before { content: fa-content($fa-var-times); }
/* Feed */
.#{$fa-css-prefix}-post:before { content: fa-content($fa-var-comment); }
.#{$fa-css-prefix}-media:before { content: fa-content($fa-var-photo-video); }

View File

@@ -26,14 +26,4 @@
}
}
}
#feedback {
.error {
color: red;
}
.success {
color: green;
}
}
}

View File

@@ -1,34 +1,34 @@
//Feed width
$block-spacing: 1rem;
$feed-width: 30%;
$feed-width-max: "400px + 3 * #{$block-spacing}";
$panel-width: 30%;
$panel-width-max: "400px + 3 * #{$block-spacing}";
//Feed colors
$post-input-bg: #d9deff;
$post-color: #323268;
$post-bg: #B4BDFF;
$post-input-bg: #ffffff; //#d9deff;
$post-color: #333; //#323268;
$post-bg: rgba(255,255,255,.8); //#B4BDFF;
$message-color: #326526;
$message-bg: #6DFF58;
$media-color: #635C28;
$media-bg: #F3EC9F;
$media-color: #333; //#635C28;
$media-bg: rgba(255,255,255,.8); //#F3EC9F;
//Legend colors
$track-main-color: #00ff78;
$track-off-track-color: #0000ff;
$track-hitchhiking-color: #FF7814;
$legend-color: #222;
$legend-color: $post-color;
#projects {
&.with-feed {
#submap {
width: calc(100% - #{$feed-width});
min-width: calc(100% - #{$feed-width-max});
width: calc(100% - #{$panel-width});
min-width: calc(100% - #{$panel-width-max});
}
.leaflet-right {
width: calc(#{$feed-width});
max-width: calc(#{$feed-width-max});
width: calc(#{$panel-width});
max-width: calc(#{$panel-width-max});
}
#feed {
@@ -47,6 +47,39 @@ $legend-color: #222;
right: -100%;
}
}
&.with-settings {
#submap {
width: calc(100% - #{$panel-width});
min-width: calc(100% - #{$panel-width-max});
}
.leaflet-left {
width: calc(#{$panel-width});
max-width: calc(#{$panel-width-max});
}
#settings {
z-index: 999;
transition: none;
}
#settings-button {
.fa {
@extend .fa-prev;
}
}
}
&:not(.with-settings) {
#settings #settings-sections {
left: -100%;
}
}
&.with-feed.with-settings {
#submap {
width: calc(100% - #{$panel-width} * 2);
min-width: calc(100% - #{$panel-width-max} * 2);
}
}
#submap {
position: absolute;
@@ -103,19 +136,6 @@ $legend-color: #222;
margin-top: 0;
}
&.leaflet-control-layers-expanded {
color: $legend-color;
width: calc(100% - 2em - 16px);
a.fa-download {
color: $legend-color;
&:hover {
color: #0078A8;
}
}
}
&.leaflet-control-scale {
padding: 0.5em;
@@ -125,30 +145,27 @@ $legend-color: #222;
}
}
/* Pull right controls by $feed-width % */
.leaflet-right {
/* Pull right/left controls by $panel-width */
.leaflet-right, .leaflet-left {
transition: all 0.5s;
width: 0;
max-width: 0;
.leaflet-control {
}
.leaflet-right .leaflet-control {
left: -100%;
}
.leaflet-left .leaflet-control {
right: -100%;
}
/* Replace Layers image with font awesome icon */
.leaflet-control-layers-toggle {
@extend .control-icon;
@extend .fa-layers;
text-shadow: 0px 1px 1px rgba(0, 0, 0, 0.8);
}
.leaflet-control-layers-expanded .leaflet-control-layers-toggle {
/* Hide default layer control */
.leaflet-top.leaflet-left .leaflet-control-layers {
display: none;
}
#legend {
.track {
white-space: nowrap;
.line {
width: 2em;
height: 4px;
@@ -175,7 +192,7 @@ $legend-color: #222;
}
}
#post-button {
#post-button, #settings-button {
cursor: pointer;
text-shadow: 0px 1px 1px rgba(0,0,0,0.8);
width: 44px;
@@ -188,8 +205,13 @@ $legend-color: #222;
.fa {
color: #CCC;
@extend .control-icon;
}
}
#post-button .fa {
@extend .fa-post;
}
#settings-button .fa {
@extend .fa-menu;
}
/* Drill & Map icons */
@@ -235,25 +257,21 @@ $legend-color: #222;
}
}
/* Feed Panel */
/* Feed/Settings Panel */
#feed {
#feed, #settings {
position: absolute;
right: 0;
top: 0;
bottom: 0;
width: calc(#{$feed-width});
max-width: calc(#{$feed-width-max});
width: calc(#{$panel-width});
max-width: calc(#{$panel-width-max});
z-index: -1;
transition-property: z-index;
transition-duration: 0.1s;
transition-delay: 0.5s;
overflow: hidden;
input, textarea, button {
border: none;
padding: 0.5em 1em;
border-radius: 3px;
input, textarea {
background-color: $post-input-bg;
color: $post-color;
}
@@ -267,6 +285,9 @@ $legend-color: #222;
color: $post-color;
}
}
}
#feed {
right: 0;
#posts {
position: absolute;
@@ -391,6 +412,10 @@ $legend-color: #222;
margin: 0.5em 0;
}
a {
color: $message-color;
}
a.drill {
.drill-icon {
transform: translate(-16px, -32px);
@@ -432,13 +457,94 @@ $legend-color: #222;
}
}
}
#settings {
left: 0;
#settings-sections {
width: calc(100% - 3rem);
margin: 1rem;
padding: 1rem;
background: white;
border-radius: 3px;
box-shadow: 2px 2px 3px 0px rgba(0,0,0,.5);
position: absolute;
transition: all 0.5s;
top: 0;
bottom: 0;
left: 0;
color: $post-color;
background: rgba(255, 255, 255, 0.8);
.settings-section {
h1 {
margin: 1.5rem 0 1rem;
}
&:first-child h1 {
margin-top: 0;
}
label {
margin-bottom: .3em;
display: block;
cursor: pointer;
}
&.newsletter {
input#email {
width: calc(100% - 6em);
&:disabled {
color: #999;
background: rgba(255,255,255,0.2);
}
}
button#nl_btn {
margin-left: 1em;
margin-bottom: 1em;
&.subscribe .fa {
@extend .fa-send;
}
&.unsubscribe .fa {
@extend .fa-unsubscribe;
}
&.loading {
background-color: $message-color;
color: white;
span {
@extend .flicker;
}
}
}
}
#settings-projects {
a.fa-download {
color: $legend-color;
&:hover {
color: #0078A8;
}
}
}
}
}
}
/* Info Window */
.leaflet-popup-content {
margin: 0;
.info-window {
margin: 1rem;
h1 {
font-size: 1.2em;
margin: 1em 0;
margin: 1em 0 1.2em;
i {
margin-right: 0.3125em;
}
}
p {
@@ -448,15 +554,24 @@ $legend-color: #222;
i {
padding-right: 0.5em;
}
a {
color: $post-color;
}
}
.medias {
margin-top: 0.5em;
margin-top: -0.5rem;
line-height: 0;
a {
display: inline-block;
margin-right: 15px;
margin-right: 1rem;
margin-top: 1rem;
&:last-child {
margin-right: 0;
}
&.drill {
font-size: 2em;
@@ -485,6 +600,7 @@ $legend-color: #222;
}
}
}
}
}
#elems {

View File

@@ -5,12 +5,12 @@
#projects {
&.with-feed {
&.with-feed, &.with-settings {
#submap {
width: 100%;
}
.leaflet-right {
.leaflet-right, .leaflet-left {
width: calc(100% - 44px - 2 * #{$block-spacing});
}
@@ -20,14 +20,13 @@
}
.leaflet-control-container {
.leaflet-top.leaflet-left,
.leaflet-bottom.leaflet-left,
.leaflet-bottom.leaflet-right .leaflet-control.elevation {
display: none;
}
}
#feed {
#feed, #settings {
width: calc(100% - 44px - 2 * #{$block-spacing});
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long