More ES6 refactoring

This commit is contained in:
Bartek Fabiszewski 2019-06-29 12:54:32 +02:00
parent 3dd5c14273
commit 9f885f6068
25 changed files with 995 additions and 339 deletions

View File

@ -53,7 +53,7 @@ select {
#menu input[type = "checkbox"] { #menu input[type = "checkbox"] {
width: auto; width: auto;
} }
.menulink { .menu-link {
display: block; display: block;
margin-top: .2em; margin-top: .2em;
} }
@ -117,7 +117,7 @@ label[for=user] {
.section:first-child { .section:first-child {
padding-top: 1em; padding-top: 1em;
} }
#inputFile { #input-file {
display: none; display: none;
} }
#summary div { #summary div {
@ -201,7 +201,7 @@ label[for=user] {
background-color: white; background-color: white;
opacity: 0.8; opacity: 0.8;
} }
#chart_close { #chart-close {
position: fixed; position: fixed;
bottom: 175px; bottom: 175px;
right: 175px; right: 175px;
@ -308,7 +308,7 @@ button > * {
-webkit-border-radius: 10px; -webkit-border-radius: 10px;
} }
.dropdown { #user-dropdown {
display: none; display: none;
position: absolute; position: absolute;
background-color: gray; background-color: gray;
@ -317,7 +317,7 @@ button > * {
border: 1px solid #888; border: 1px solid #888;
} }
.dropdown a { #user-dropdown a {
display: block; display: block;
padding-bottom: .5em; padding-bottom: .5em;
padding-top: .5em; padding-top: .5em;
@ -327,7 +327,7 @@ button > * {
.icon { height: 1.4em; } .icon { height: 1.4em; }
.u { text-decoration: underline; } .menu-title { text-decoration: underline; }
.loader { .loader {
animation: blink 1s linear infinite; animation: blink 1s linear infinite;
@ -337,6 +337,29 @@ button > * {
50% { opacity: 0; } 50% { opacity: 0; }
} }
/* chart */
.ct-point {
stroke-width: 5px !important;
transition: 0.3s;
}
.ct-point:hover {
stroke-width: 10px !important;
cursor: pointer;
}
.ct-point-hilight {
stroke-width: 10px !important;
}
.ct-point-selected {
stroke-width: 10px !important;
stroke: #f4c63d !important;
}
.ct-line {
stroke-width: 2px; !important;
}
/* openlayers 3 popup */ /* openlayers 3 popup */
.ol-popup { .ol-popup {
position: absolute; position: absolute;

112
index.php
View File

@ -85,10 +85,10 @@
<div id="menu-content"> <div id="menu-content">
<?php if ($auth->isAuthenticated()): ?> <?php if ($auth->isAuthenticated()): ?>
<div id="user_menu"> <div>
<a id="menu_head"><img class="icon" alt="<?= $lang["user"] ?>" src="images/user.svg"> <?= htmlspecialchars($auth->user->login) ?></a> <a id="user-menu"><img class="icon" alt="<?= $lang["user"] ?>" src="images/user.svg"> <?= htmlspecialchars($auth->user->login) ?></a>
<div id="user_dropdown" class="dropdown"> <div id="user-dropdown">
<a id="menu_pass"><img class="icon" alt="<?= $lang["changepass"] ?>" src="images/lock.svg"> <?= $lang["changepass"] ?></a> <a id="user-pass"><img class="icon" alt="<?= $lang["changepass"] ?>" src="images/lock.svg"> <?= $lang["changepass"] ?></a>
<a href="utils/logout.php"><img class="icon" alt="<?= $lang["logout"] ?>" src="images/poweroff.svg"> <?= $lang["logout"] ?></a> <a href="utils/logout.php"><img class="icon" alt="<?= $lang["logout"] ?>" src="images/poweroff.svg"> <?= $lang["logout"] ?></a>
</div> </div>
</div> </div>
@ -98,30 +98,26 @@
<div class="section"> <div class="section">
<?php if (!empty($usersArr)): ?> <?php if (!empty($usersArr)): ?>
<label for="user" class="menutitle"><?= $lang["user"] ?></label> <label for="user"><?= $lang["user"] ?></label>
<form> <select id="user" name="user">
<select id="user" name="user"> <option value="0" disabled><?= $lang["suser"] ?></option>
<option value="0" disabled><?= $lang["suser"] ?></option> <?php foreach ($usersArr as $aUser): ?>
<?php foreach ($usersArr as $aUser): ?> <option <?= ($aUser->id == $displayUserId) ? "selected " : "" ?>value="<?= $aUser->id ?>"><?= htmlspecialchars($aUser->login) ?></option>
<option <?= ($aUser->id == $displayUserId) ? "selected " : "" ?>value="<?= $aUser->id ?>"><?= htmlspecialchars($aUser->login) ?></option> <?php endforeach; ?>
<?php endforeach; ?>
</select> </select>
</form>
<?php endif; ?> <?php endif; ?>
</div> </div>
<div class="section"> <div class="section">
<label for="track" class="menutitle"><?= $lang["track"] ?></label> <label for="track"><?= $lang["track"] ?></label>
<form> <select id="track" name="track">
<select id="track" name="track"> <?php foreach ($tracksArr as $aTrack): ?>
<?php foreach ($tracksArr as $aTrack): ?> <option value="<?= $aTrack->id ?>"><?= htmlspecialchars($aTrack->name) ?></option>
<option value="<?= $aTrack->id ?>"><?= htmlspecialchars($aTrack->name) ?></option> <?php endforeach; ?>
<?php endforeach; ?> </select>
</select> <input id="latest" type="checkbox"> <label for="latest"><?= $lang["latest"] ?></label><br>
<input id="latest" type="checkbox"> <label for="latest"><?= $lang["latest"] ?></label><br> <input id="auto-reload" type="checkbox"> <label for="auto-reload"><?= $lang["autoreload"] ?></label> (<a id="set-interval"><span id="interval"><?= uConfig::$interval ?></span></a> s)<br>
<input id="auto_reload" type="checkbox"> <label for="auto_reload"><?= $lang["autoreload"] ?></label> (<a id="set_time"><span id="auto"><?= uConfig::$interval ?></span></a> s)<br> <a id="force-reload"> <?= $lang["reload"] ?></a><br>
</form>
<a id="force_reload"> <?= $lang["reload"] ?></a><br>
</div> </div>
<div id="summary" class="section"></div> <div id="summary" class="section"></div>
@ -131,60 +127,54 @@
</div> </div>
<div> <div>
<label for="api" class="menutitle"><?= $lang["api"] ?></label> <label for="api"><?= $lang["api"] ?></label>
<form> <select id="api" name="api">
<select id="api" name="api"> <option value="gmaps"<?= (uConfig::$mapapi == "gmaps") ? " selected" : "" ?>>Google Maps</option>
<option value="gmaps"<?= (uConfig::$mapapi == "gmaps") ? " selected" : "" ?>>Google Maps</option> <option value="openlayers"<?= (uConfig::$mapapi == "openlayers") ? " selected" : "" ?>>OpenLayers</option>
<option value="openlayers"<?= (uConfig::$mapapi == "openlayers") ? " selected" : "" ?>>OpenLayers</option> </select>
</select>
</form>
</div> </div>
<div> <div>
<label for="lang" class="menutitle"><?= $lang["language"] ?></label> <label for="lang"><?= $lang["language"] ?></label>
<form> <select id="lang" name="lang">
<select id="lang" name="lang"> <?php foreach ($langsArr as $langCode => $langName): ?>
<?php foreach ($langsArr as $langCode => $langName): ?> <option value="<?= $langCode ?>"<?= (uConfig::$lang == $langCode) ? " selected" : "" ?>><?= $langName ?></option>
<option value="<?= $langCode ?>"<?= (uConfig::$lang == $langCode) ? " selected" : "" ?>><?= $langName ?></option> <?php endforeach; ?>
<?php endforeach; ?> </select>
</select>
</form>
</div> </div>
<div class="section"> <div class="section">
<label for="units" class="menutitle"><?= $lang["units"] ?></label> <label for="units"><?= $lang["units"] ?></label>
<form> <select id="units" name="units">
<select id="units" name="units"> <option value="metric"<?= (uConfig::$units == "metric") ? " selected" : "" ?>><?= $lang["metric"] ?></option>
<option value="metric"<?= (uConfig::$units == "metric") ? " selected" : "" ?>><?= $lang["metric"] ?></option> <option value="imperial"<?= (uConfig::$units == "imperial") ? " selected" : "" ?>><?= $lang["imperial"] ?></option>
<option value="imperial"<?= (uConfig::$units == "imperial") ? " selected" : "" ?>><?= $lang["imperial"] ?></option> <option value="nautical"<?= (uConfig::$units == "nautical") ? " selected" : "" ?>><?= $lang["nautical"] ?></option>
<option value="nautical"<?= (uConfig::$units == "nautical") ? " selected" : "" ?>><?= $lang["nautical"] ?></option> </select>
</select>
</form>
</div> </div>
<div id="export" class="section"> <div class="section">
<div class="menutitle u"><?= $lang["export"] ?></div> <div class="menu-title"><?= $lang["export"] ?></div>
<a id="export_kml" class="menulink">kml</a> <a id="export-kml" class="menu-link">kml</a>
<a id="export_gpx" class="menulink">gpx</a> <a id="export-gpx" class="menu-link">gpx</a>
</div> </div>
<?php if ($auth->isAuthenticated()): ?> <?php if ($auth->isAuthenticated()): ?>
<div id="import" class="section"> <div class="section">
<div class="menutitle u"><?= $lang["import"] ?></div> <div id="import" class="menu-title"><?= $lang["import"] ?></div>
<form id="importForm" enctype="multipart/form-data" method="post"> <form id="import-form" enctype="multipart/form-data" method="post">
<input type="hidden" name="MAX_FILE_SIZE" value="<?= uUtils::getUploadMaxSize() ?>" /> <input type="hidden" name="MAX_FILE_SIZE" value="<?= uUtils::getUploadMaxSize() ?>" />
<input type="file" id="inputFile" name="gpx" /> <input type="file" id="input-file" name="gpx" />
</form> </form>
<a id="import_gpx" class="menulink">gpx</a> <a id="import-gpx" class="menu-link">gpx</a>
</div> </div>
<div id="admin_menu"> <div id="admin-menu">
<div class="menutitle u"><?= $lang["adminmenu"] ?></div> <div class="menu-title"><?= $lang["adminmenu"] ?></div>
<?php if ($auth->isAdmin()): ?> <?php if ($auth->isAdmin()): ?>
<a id="adduser" class="menulink"><?= $lang["adduser"] ?></a> <a id="adduser" class="menu-link"><?= $lang["adduser"] ?></a>
<a id="edituser" class="menulink"><?= $lang["edituser"] ?></a> <a id="edituser" class="menu-link"><?= $lang["edituser"] ?></a>
<?php endif; ?> <?php endif; ?>
<a id="edittrack" class="menulink"><?= $lang["edittrack"] ?></a> <a id="edittrack" class="menu-link"><?= $lang["edittrack"] ?></a>
</div> </div>
<?php endif; ?> <?php endif; ?>
@ -197,7 +187,7 @@
<div id="map-canvas"></div> <div id="map-canvas"></div>
<div id="bottom"> <div id="bottom">
<div id="chart"></div> <div id="chart"></div>
<div id="chart_close"><?= $lang["close"] ?></div> <div id="chart-close"><?= $lang["close"] ?></div>
</div> </div>
</div> </div>

View File

@ -1,3 +1,22 @@
/*
* μlogger
*
* Copyright(C) 2019 Bartek Fabiszewski (www.fabiszewski.net)
*
* This is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
import uUtils from './utils.js'; import uUtils from './utils.js';
export default class uAjax { export default class uAjax {
@ -28,7 +47,6 @@ export default class uAjax {
* @param {Object|HTMLFormElement} [data] Optional request parameters: key/value pairs or form element * @param {Object|HTMLFormElement} [data] Optional request parameters: key/value pairs or form element
* @param {Object} [options] Optional options * @param {Object} [options] Optional options
* @param {string} [options.method='GET'] Optional query method, default 'GET' * @param {string} [options.method='GET'] Optional query method, default 'GET'
* @param {HTMLElement} [options.loader] Optional element to animate during loading
* @return {Promise<Document, string>} * @return {Promise<Document, string>}
*/ */
static ajax(url, data, options) { static ajax(url, data, options) {
@ -36,7 +54,6 @@ export default class uAjax {
data = data || {}; data = data || {};
options = options || {}; options = options || {};
let method = options.method || 'GET'; let method = options.method || 'GET';
const loader = options.loader;
const xhr = new XMLHttpRequest(); const xhr = new XMLHttpRequest();
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
xhr.onreadystatechange = function () { xhr.onreadystatechange = function () {
@ -63,9 +80,6 @@ export default class uAjax {
if (error && reject && typeof reject === 'function') { if (error && reject && typeof reject === 'function') {
reject(message); reject(message);
} }
if (loader) {
// UI.removeLoader(loader);
}
}; };
let body = null; let body = null;
if (data instanceof HTMLFormElement) { if (data instanceof HTMLFormElement) {
@ -90,9 +104,6 @@ export default class uAjax {
xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded'); xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
} }
xhr.send(body); xhr.send(body);
if (loader) {
// UI.setLoader(loader);
}
}); });
} }
} }

View File

@ -1,3 +1,22 @@
/*
* μlogger
*
* Copyright(C) 2019 Bartek Fabiszewski (www.fabiszewski.net)
*
* This is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
import UserDialog from './userdialog.js'; import UserDialog from './userdialog.js';
import { lang } from './constants.js'; import { lang } from './constants.js';
import uEvent from './event.js'; import uEvent from './event.js';

View File

@ -1,3 +1,22 @@
/*
* μlogger
*
* Copyright(C) 2019 Bartek Fabiszewski (www.fabiszewski.net)
*
* This is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
import uEvent from './event.js'; import uEvent from './event.js';
export default class uBinder { export default class uBinder {

View File

@ -1,3 +1,22 @@
/*
* μlogger
*
* Copyright(C) 2019 Bartek Fabiszewski (www.fabiszewski.net)
*
* This is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
import { config, lang } from './constants.js'; import { config, lang } from './constants.js';
import uEvent from './event.js'; import uEvent from './event.js';
import uUtils from './utils.js'; import uUtils from './utils.js';
@ -13,10 +32,13 @@ export default class Chart {
* @param {uBinder} binder * @param {uBinder} binder
*/ */
constructor(binder) { constructor(binder) {
binder.addEventListener(uEvent.UI_READY, this); binder.addEventListener(uEvent.MARKER_OVER, this);
binder.addEventListener(uEvent.MARKER_SELECT, this);
binder.addEventListener(uEvent.TRACK_READY, this); binder.addEventListener(uEvent.TRACK_READY, this);
binder.addEventListener(uEvent.UI_READY, this);
this._binder = binder; this._binder = binder;
this._targetEl = null; this._targetEl = null;
this._points = null;
} }
/** /**
@ -57,22 +79,22 @@ export default class Chart {
}); });
chart.on('created', () => { chart.on('created', () => {
const points = document.querySelectorAll('.ct-chart-line .ct-point'); this._points = document.querySelectorAll('.ct-chart-line .ct-point');
for (let i = 0; i < points.length; i++) { const len = this._points.length;
for (let i = 0; i < len; i++) {
((id) => { ((id) => {
points[id].addEventListener('click', () => { this._points[id].addEventListener('click', () => {
/** @todo trigger marker action */ this._binder.dispatchEvent(uEvent.CHART_CLICKED, id);
console.log(id);
}); });
})(i); })(i);
} }
this._binder.dispatchEvent('chart ready', points.length); this._binder.dispatchEvent(uEvent.CHART_READY, len);
}); });
// need to update chart first time the container becomes visible // need to update chart first time the container becomes visible
if (this._targetEl.parentNode.style.display !== 'block') { if (!this.isVisible()) {
const observer = new MutationObserver(() => { const observer = new MutationObserver(() => {
if (this._targetEl.parentNode.style.display === 'block') { if (this.isVisible()) {
// eslint-disable-next-line no-underscore-dangle // eslint-disable-next-line no-underscore-dangle
this._targetEl.__chartist__.update(); this._targetEl.__chartist__.update();
observer.disconnect(); observer.disconnect();
@ -80,7 +102,10 @@ export default class Chart {
}); });
observer.observe(this._targetEl.parentNode, { attributes: true }); observer.observe(this._targetEl.parentNode, { attributes: true });
} }
}
isVisible() {
return this._targetEl && this._targetEl.parentNode && this._targetEl.parentNode.style.display === 'block';
} }
static onDomLoaded() { static onDomLoaded() {
@ -104,7 +129,44 @@ export default class Chart {
/** @type {uUI} */ /** @type {uUI} */
const ui = args; const ui = args;
this._targetEl = ui.chart; this._targetEl = ui.chart;
} else if (event.type === uEvent.MARKER_OVER) {
/** @type {number} */
const pointId = args;
if (pointId) {
this.pointOver(pointId);
} else {
this.pointOut();
}
} else if (event.type === uEvent.MARKER_SELECT) {
/** @type {number} */
const pointId = args;
if (pointId) {
this.pointSelect(pointId);
} else {
this.pointUnselect();
}
} }
} }
pointOver(pointId) {
if (this.isVisible()) {
const point = this._points[pointId];
point.classList.add('ct-point-hilight');
}
}
pointOut() {
this._targetEl.querySelectorAll('.ct-point-hilight').forEach((el) => el.classList.remove('ct-point-hilight'));
}
pointSelect(pointId) {
if (this.isVisible()) {
const point = this._points[pointId];
point.classList.add('ct-point-selected');
}
}
pointUnselect() {
this._targetEl.querySelectorAll('.ct-point-selected').forEach((el) => el.classList.remove('ct-point-selected'));
}
} }

View File

@ -1,3 +1,22 @@
/*
* μlogger
*
* Copyright(C) 2019 Bartek Fabiszewski (www.fabiszewski.net)
*
* This is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
import uEvent from './event.js'; import uEvent from './event.js';
export default class uConfig { export default class uConfig {
@ -269,7 +288,9 @@ export default class uConfig {
* @param {boolean} value * @param {boolean} value
*/ */
set showLatest(value) { set showLatest(value) {
this._showLatest = value; if (this._showLatest !== value) {
this.notify('showLatest'); this._showLatest = value;
this.notify('showLatest');
}
} }
} }

View File

@ -1,3 +1,22 @@
/*
* μlogger
*
* Copyright(C) 2019 Bartek Fabiszewski (www.fabiszewski.net)
*
* This is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
import uAuth from './auth.js'; import uAuth from './auth.js';
import uConfig from './config.js'; import uConfig from './config.js';
import uUser from './user.js'; import uUser from './user.js';

View File

@ -1,3 +1,22 @@
/*
* μlogger
*
* Copyright(C) 2019 Bartek Fabiszewski (www.fabiszewski.net)
*
* This is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
/** /**
* @abstract * @abstract
*/ */

View File

@ -1,4 +1,22 @@
/* μlogger
*
* Copyright(C) 2019 Bartek Fabiszewski (www.fabiszewski.net)
*
* This is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
/* eslint-disable lines-between-class-members */ /* eslint-disable lines-between-class-members */
/** /**
* class uEvent * class uEvent
* property {string} type * property {string} type
@ -17,12 +35,17 @@ export default class uEvent {
static get ADD() { return 'µAdd'; } static get ADD() { return 'µAdd'; }
static get API_CHANGE() { return 'µApiChange'; } static get API_CHANGE() { return 'µApiChange'; }
static get CHART_READY() { return 'µChartReady'; } static get CHART_CLICKED() { return 'µChartClicked'; }
static get CONFIG() { return 'µConfig'; } static get CONFIG() { return 'µConfig'; }
static get CHANGE() { return 'µChange'; }
static get CHART_READY() { return 'µChartReady'; }
static get EDIT() { return 'µEdit'; } static get EDIT() { return 'µEdit'; }
static get EXPORT() { return 'µExport'; } static get EXPORT() { return 'µExport'; }
static get OPEN_URL() { return 'µOpen'; }
static get IMPORT() { return 'µImport'; } static get IMPORT() { return 'µImport'; }
static get LOADER() { return 'µLoader'; }
static get MARKER_OVER() { return 'µMarkerOver'; }
static get MARKER_SELECT() { return 'µMarkerSelect'; }
static get OPEN_URL() { return 'µOpen'; }
static get PASSWORD() { return 'µPassword'; } static get PASSWORD() { return 'µPassword'; }
static get TRACK_READY() { return 'µTrackReady'; } static get TRACK_READY() { return 'µTrackReady'; }
static get UI_READY() { return 'µUiReady'; } static get UI_READY() { return 'µUiReady'; }
@ -47,7 +70,7 @@ export default class uEvent {
dispatch(args) { dispatch(args) {
for (const listener of this.listeners) { for (const listener of this.listeners) {
(async () => { (async () => {
console.log(`${this.type}: ${args.constructor.name} => ${listener.name}`); console.log(`${this.type}: ${args ? args.constructor.name : ''}`);
await listener(this, args); await listener(this, args);
})(); })();
} }

View File

@ -1,3 +1,22 @@
/*
* μlogger
*
* Copyright(C) 2019 Bartek Fabiszewski (www.fabiszewski.net)
*
* This is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
import uData from './data.js'; import uData from './data.js';
import uEvent from './event.js'; import uEvent from './event.js';
import uUtils from './utils.js'; import uUtils from './utils.js';
@ -20,7 +39,7 @@ export default class uList {
/** @type {uBinder} */ /** @type {uBinder} */
this.binder = binder; this.binder = binder;
/** @type {boolean} */ /** @type {boolean} */
this.showAllOption = false; this._showAllOption = false;
/** @type {boolean} */ /** @type {boolean} */
this.hasHead = false; this.hasHead = false;
this.headValue = ''; this.headValue = '';
@ -29,11 +48,10 @@ export default class uList {
this.T = type || uData; this.T = type || uData;
/** @type {HTMLSelectElement} */ /** @type {HTMLSelectElement} */
this.domElement = document.querySelector(selector); this.domElement = document.querySelector(selector);
if (this.domElement) {
this.domElement.addEventListener('change', this, false);
}
if (this.binder) { if (this.binder) {
this.binder.addEventListener(uEvent.ADD, this); this.binder.addEventListener(uEvent.ADD, this);
this.binder.addEventListener(uEvent.CHANGE, this);
this.binder.addEventListener(uEvent.CONFIG, this);
this.binder.addEventListener(uEvent.EDIT, this); this.binder.addEventListener(uEvent.EDIT, this);
} }
@ -61,6 +79,21 @@ export default class uList {
return this.selectedId === 'all'; return this.selectedId === 'all';
} }
get showAllOption() {
return this._showAllOption;
}
set showAllOption(value) {
if (this._showAllOption !== value) {
this._showAllOption = value;
if (value === false) {
this.selectDefault();
}
this.render();
this.onChange();
}
}
/** /**
* @param {number} id * @param {number} id
* @param {boolean=} skipUpdate * @param {boolean=} skipUpdate
@ -98,9 +131,6 @@ export default class uList {
if (this.data.length) { if (this.data.length) {
this.selectedId = this.data[0].key.toString(); this.selectedId = this.data[0].key.toString();
} }
/** @todo set defaults ?? */
// var defaultTrack = tid || getNodeAsInt(tracks[0], 'trackid');
// var defaultUser = uid || ns.userId;
this.render(); this.render();
this.onChange(); this.onChange();
} }
@ -114,7 +144,7 @@ export default class uList {
} }
for (const option of this.domElement) { for (const option of this.domElement) {
if (option.value === 'all') { if (option.value === 'all') {
this.showAllOption = true; this._showAllOption = true;
} else if (!option.disabled) { } else if (!option.disabled) {
const row = new this.T(parseInt(option.value), option.innerText); const row = new this.T(parseInt(option.value), option.innerText);
this.updateDataRow(row); this.updateDataRow(row);
@ -128,17 +158,19 @@ export default class uList {
} }
/** /**
* @param {(Event|uEvent)} event * @param {uEvent} event
* @param {*=} eventData * @param {*=} eventData
*/ */
handleEvent(event, eventData) { handleEvent(event, eventData) {
if (event.type === 'change') { if (event.type === uEvent.CHANGE && eventData.el === this.domElement) {
this.selectedId = this.domElement.options[this.domElement.selectedIndex].value; this.selectedId = eventData.id;
this.onChange(); this.onChange();
} else if (event.type === uEvent.EDIT && this.domElement === eventData) { } else if (event.type === uEvent.EDIT && eventData === this.domElement) {
this.onEdit(); this.onEdit();
} else if (event.type === uEvent.ADD && this.domElement === eventData) { } else if (event.type === uEvent.ADD && eventData === this.domElement) {
this.onAdd(); this.onAdd();
} else if (event.type === uEvent.CONFIG) {
this.onConfigChange(eventData);
} }
} }
@ -175,16 +207,20 @@ export default class uList {
const currentId = this.current.key; const currentId = this.current.key;
this.data.splice(this.data.findIndex((o) => o.key === id), 1); this.data.splice(this.data.findIndex((o) => o.key === id), 1);
if (id === currentId) { if (id === currentId) {
if (this.data.length) { this.selectDefault();
this.selectedId = this.data[0].key.toString();
} else {
this.selectedId = '';
}
this.onChange(); this.onChange();
} }
this.render(); this.render();
} }
selectDefault() {
if (this.data.length) {
this.selectedId = this.data[0].key.toString();
} else {
this.selectedId = '';
}
}
render() { render() {
this.domElement.options.length = 0; this.domElement.options.length = 0;
if (this.hasHead) { if (this.hasHead) {
@ -192,7 +228,7 @@ export default class uList {
head.disabled = true; head.disabled = true;
this.domElement.options.add(head); this.domElement.options.add(head);
} }
if (this.showAllOption) { if (this._showAllOption) {
this.domElement.options.add(new Option(this.allValue, 'all')); this.domElement.options.add(new Option(this.allValue, 'all'));
} }
for (const item of this.data) { for (const item of this.data) {
@ -223,10 +259,25 @@ export default class uList {
onEdit() { onEdit() {
} }
/**
* @abstract
*/
// eslint-disable-next-line no-unused-vars,no-empty-function,class-methods-use-this
onReload() {
}
/** /**
* @abstract * @abstract
*/ */
// eslint-disable-next-line no-unused-vars,no-empty-function,class-methods-use-this // eslint-disable-next-line no-unused-vars,no-empty-function,class-methods-use-this
onAdd() { onAdd() {
} }
/**
* @abstract
* @param {string} property
*/
// eslint-disable-next-line no-unused-vars,no-empty-function,class-methods-use-this
onConfigChange(property) {
}
} }

View File

@ -1,3 +1,22 @@
/*
* μlogger
*
* Copyright(C) 2019 Bartek Fabiszewski (www.fabiszewski.net)
*
* This is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
import * as gmApi from './mapapi/api_gmaps.js'; import * as gmApi from './mapapi/api_gmaps.js';
import * as olApi from './mapapi/api_openlayers.js'; import * as olApi from './mapapi/api_openlayers.js';
import { config, lang } from './constants.js'; import { config, lang } from './constants.js';
@ -5,6 +24,22 @@ import uEvent from './event.js';
import { uLogger } from './ulogger.js'; import { uLogger } from './ulogger.js';
import uUtils from './utils.js'; import uUtils from './utils.js';
/**
* @typedef {Object} uMap.api
* @memberOf uMap
* @type {Object}
* @property {string} name
* @property {function(uBinder, HTMLElement)} init
* @property {function} cleanup
* @property {function(uTrack, boolean)} displayTrack
* @property {function} clearMap
* @property {function(number)} animateMarker
* @property {function} getBounds
* @property {function} zoomToExtent
* @property {function} zoomToBounds
* @property {function} updateSize
*/
/** /**
* @class uMap * @class uMap
* @property {number} loadTime * @property {number} loadTime
@ -18,13 +53,17 @@ export default class uMap {
* @param {uBinder} binder * @param {uBinder} binder
*/ */
constructor(binder) { constructor(binder) {
binder.addEventListener(uEvent.API_CHANGE, this);
binder.addEventListener(uEvent.CHART_CLICKED, this);
binder.addEventListener(uEvent.TRACK_READY, this); binder.addEventListener(uEvent.TRACK_READY, this);
binder.addEventListener(uEvent.UI_READY, this); binder.addEventListener(uEvent.UI_READY, this);
binder.addEventListener(uEvent.API_CHANGE, this);
this.loadTime = 0; this.loadTime = 0;
this.savedBounds = null; this.savedBounds = null;
this.api = null; this.api = null;
this.mapElement = null; this.mapElement = null;
this.lastTrackId = null;
this._binder = binder;
this.track = null;
} }
/** /**
@ -60,7 +99,7 @@ export default class uMap {
return; return;
} }
try { try {
this.api.init(this.mapElement); this.api.init(this._binder, this.mapElement);
} catch (e) { } catch (e) {
setTimeout(() => { setTimeout(() => {
this.loadTime += 50; this.loadTime += 50;
@ -69,17 +108,10 @@ export default class uMap {
return; return;
} }
this.loadTime = 0; this.loadTime = 0;
let update = 1;
if (this.savedBounds) { if (this.savedBounds) {
this.api.zoomToBounds(this.savedBounds); this.api.zoomToBounds(this.savedBounds);
update = 0;
} }
// if (latest && isSelectedAllUsers()) {
// loadLastPositionAllUsers();
// } else {
// loadTrack(ns.userId, ns.trackId, update);
uLogger.trackList.onChange(); uLogger.trackList.onChange();
// }
// save current api as default // save current api as default
uUtils.setCookie('api', config.mapapi, 30); uUtils.setCookie('api', config.mapapi, 30);
} }
@ -91,11 +123,12 @@ export default class uMap {
*/ */
handleEvent(event, args) { handleEvent(event, args) {
if (event.type === uEvent.TRACK_READY) { if (event.type === uEvent.TRACK_READY) {
/** @type {uTrack} */
const track = args; const track = args;
this.api.clearMap(); this.api.clearMap();
/** @todo use update */ const onlyReload = track.id !== this.lastTrackId;
const update = 1; this.api.displayTrack(track, onlyReload);
this.api.displayTrack(track, update); this.lastTrackId = track.id;
} else if (event.type === uEvent.UI_READY) { } else if (event.type === uEvent.UI_READY) {
/** @type {uUI} */ /** @type {uUI} */
const ui = args; const ui = args;
@ -105,6 +138,10 @@ export default class uMap {
/** @type {string} */ /** @type {string} */
const api = args; const api = args;
this.loadMapAPI(api); this.loadMapAPI(api);
} else if (event.type === uEvent.CHART_CLICKED) {
/** @type {number} */
const id = args;
this.api.animateMarker(id);
} }
} }
} }

View File

@ -1,6 +1,7 @@
/* μlogger /*
* μlogger
* *
* Copyright(C) 2017 Bartek Fabiszewski (www.fabiszewski.net) * Copyright(C) 2019 Bartek Fabiszewski (www.fabiszewski.net)
* *
* This is free software; you can redistribute it and/or modify it under * This is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by * the terms of the GNU General Public License as published by
@ -17,14 +18,21 @@
*/ */
import { config, lang } from '../constants.js'; import { config, lang } from '../constants.js';
import { uLogger } from '../ulogger.js'; import uEvent from '../event.js';
import uUI from '../ui.js'; import uUI from '../ui.js';
import uUtils from '../utils.js'; import uUtils from '../utils.js';
// google maps // google maps
/**
* Google Maps API module
* @module gmApi
* @implements {uMap.api}
*/
/** @type {google.maps.Map} */ /** @type {google.maps.Map} */
let map = null; let map = null;
/** @type {uBinder} */
let binder = null;
/** @type {google.maps.Polyline[]} */ /** @type {google.maps.Polyline[]} */
const polies = []; const polies = [];
/** @type {google.maps.Marker[]} */ /** @type {google.maps.Marker[]} */
@ -32,7 +40,7 @@ const markers = [];
/** @type {google.maps.InfoWindow[]} */ /** @type {google.maps.InfoWindow[]} */
const popups = []; const popups = [];
/** @type {google.maps.InfoWindow} */ /** @type {google.maps.InfoWindow} */
let popup = null; let openPopup = null;
/** @type {google.maps.PolylineOptions} */ /** @type {google.maps.PolylineOptions} */
let polyOptions = null; let polyOptions = null;
/** @type {google.maps.MapOptions} */ /** @type {google.maps.MapOptions} */
@ -45,9 +53,13 @@ let authError = false;
/** /**
* Initialize map * Initialize map
* @param {uBinder} b
* @param {HTMLElement} el * @param {HTMLElement} el
*/ */
function init(el) { function init(b, el) {
binder = b;
const url = '//maps.googleapis.com/maps/api/js?' + ((config.gkey != null) ? ('key=' + config.gkey + '&') : '') + 'callback=gm_loaded'; const url = '//maps.googleapis.com/maps/api/js?' + ((config.gkey != null) ? ('key=' + config.gkey + '&') : '') + 'callback=gm_loaded';
uUtils.addScript(url, 'mapapi_gmaps'); uUtils.addScript(url, 'mapapi_gmaps');
if (!isLoaded) { if (!isLoaded) {
@ -89,19 +101,21 @@ function cleanup() {
polies.length = 0; polies.length = 0;
markers.length = 0; markers.length = 0;
popups.length = 0; popups.length = 0;
map = null;
polyOptions = null; polyOptions = null;
mapOptions = null; mapOptions = null;
popup = null; openPopup = null;
// ui.clearMapCanvas(); if (map && map.getDiv()) {
map.getDiv().innerHTML = '';
}
map = null;
} }
/** /**
* Display track * Display track
* @param {uTrack} track
* @param {boolean} update Should fit bounds if true * @param {boolean} update Should fit bounds if true
*/ */
function displayTrack(update) { function displayTrack(track, update) {
const track = uLogger.trackList.current;
if (!track) { if (!track) {
return; return;
} }
@ -113,10 +127,12 @@ function displayTrack(update) {
let i = 0; let i = 0;
for (const position of track.positions) { for (const position of track.positions) {
// set marker // set marker
setMarker(i++); setMarker(i++, track);
// update polyline // update polyline
const coordinates = new google.maps.LatLng(position.latitude, position.longitude); const coordinates = new google.maps.LatLng(position.latitude, position.longitude);
path.push(coordinates); if (track.continuous) {
path.push(coordinates);
}
latlngbounds.extend(coordinates); latlngbounds.extend(coordinates);
} }
if (update) { if (update) {
@ -133,10 +149,6 @@ function displayTrack(update) {
} }
} }
polies.push(poly); polies.push(poly);
/** @todo handle summary and chart in track */
// ns.updateSummary(p.timestamp, totalDistance, totalSeconds);
// ns.updateChart();
} }
/** /**
@ -150,7 +162,8 @@ function clearMap() {
} }
if (markers) { if (markers) {
for (let i = 0; i < markers.length; i++) { for (let i = 0; i < markers.length; i++) {
google.maps.event.removeListener(popups[i].listener); // google.maps.event.removeListener(popups[i].listener);
google.maps.event.clearInstanceListeners(popups[i]);
popups[i].setMap(null); popups[i].setMap(null);
markers[i].setMap(null); markers[i].setMap(null);
} }
@ -162,12 +175,13 @@ function clearMap() {
/** /**
* Set marker * Set marker
* @param {uTrack} track
* @param {number} id * @param {number} id
*/ */
function setMarker(id) { function setMarker(id, track) {
// marker // marker
const position = uLogger.trackList.current.positions[id]; const position = track.positions[id];
const posLen = uLogger.trackList.current.length; const posLen = track.length;
// noinspection JSCheckFunctionSignatures // noinspection JSCheckFunctionSignatures
const marker = new google.maps.Marker({ const marker = new google.maps.Marker({
position: new google.maps.LatLng(position.latitude, position.longitude), position: new google.maps.LatLng(position.latitude, position.longitude),
@ -184,37 +198,45 @@ function setMarker(id) {
marker.setIcon('images/marker-white.png'); marker.setIcon('images/marker-white.png');
} }
// popup // popup
const content = uUI.getPopupHtml(id); const popup = new google.maps.InfoWindow();
popup = new google.maps.InfoWindow();
// noinspection JSUndefinedPropertyAssignment marker.addListener('click',
popup.listener = google.maps.event.addListener(marker, 'click', (function (_marker, _content) { ((i) => () => {
return function () { popup.setContent(uUI.getPopupHtml(i));
popup.setContent(_content); popup.open(map, marker);
popup.open(map, _marker); binder.dispatchEvent(uEvent.MARKER_SELECT, i);
/** @todo handle chart */ openPopup = popup;
// ns.chartShowPosition(id); popup.addListener('closeclick', () => {
} binder.dispatchEvent(uEvent.MARKER_SELECT);
})(marker, content)); google.maps.event.clearListeners(popup, 'closeclick');
openPopup = null;
});
})(id));
marker.addListener('mouseover',
((i) => () => {
binder.dispatchEvent(uEvent.MARKER_OVER, i);
})(id));
marker.addListener('mouseout',
() => {
binder.dispatchEvent(uEvent.MARKER_OVER);
});
markers.push(marker); markers.push(marker);
popups.push(popup); popups.push(popup);
} }
/** function animateMarker(id) {
* Add listener on chart to show position on map if (openPopup) {
* @param {google.visualization.LineChart} chart openPopup.close();
* @param {google.visualization.DataTable} data clearTimeout(timeoutHandle);
*/ }
function addChartEvent(chart, data) { const icon = markers[id].getIcon();
google.visualization.events.addListener(chart, 'select', function () { markers[id].setIcon('images/marker-gold.png');
if (popup) { popup.close(); clearTimeout(timeoutHandle); } markers[id].setAnimation(google.maps.Animation.BOUNCE);
const selection = chart.getSelection()[0]; timeoutHandle = setTimeout(() => {
if (selection) { markers[id].setIcon(icon);
const id = data.getValue(selection.row, 0) - 1; markers[id].setAnimation(null);
const icon = markers[id].getIcon(); }, 2000);
markers[id].setIcon('images/marker-gold.png');
timeoutHandle = setTimeout(function () { markers[id].setIcon(icon); }, 2000);
}
});
} }
/** /**
@ -270,14 +292,11 @@ export {
cleanup, cleanup,
displayTrack, displayTrack,
clearMap, clearMap,
setMarker, animateMarker,
addChartEvent,
getBounds, getBounds,
zoomToExtent, zoomToExtent,
zoomToBounds, zoomToBounds,
updateSize, updateSize
setAuthError,
setLoaded
} }

View File

@ -1,6 +1,7 @@
/* μlogger /*
* μlogger
* *
* Copyright(C) 2017 Bartek Fabiszewski (www.fabiszewski.net) * Copyright(C) 2019 Bartek Fabiszewski (www.fabiszewski.net)
* *
* This is free software; you can redistribute it and/or modify it under * This is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by * the terms of the GNU General Public License as published by
@ -17,14 +18,21 @@
*/ */
import { config } from '../constants.js'; import { config } from '../constants.js';
import { uLogger } from '../ulogger.js'; import uEvent from '../event.js';
import uUI from '../ui.js'; import uUI from '../ui.js';
import uUtils from '../utils.js'; import uUtils from '../utils.js';
// openlayers 3+ // openlayers 3+
/**
* OpenLayers API module
* @module olApi
* @implements {uMap.api}
*/
/** @type {ol.Map} */ /** @type {ol.Map} */
let map = null; let map = null;
/** @type {uBinder} */
let binder = null;
/** @type {ol.layer.Vector} */ /** @type {ol.layer.Vector} */
let layerTrack = null; let layerTrack = null;
/** @type {ol.layer.Vector} */ /** @type {ol.layer.Vector} */
@ -33,12 +41,19 @@ let layerMarkers = null;
let selectedLayer = null; let selectedLayer = null;
/** @type {ol.style.Style|{}} */ /** @type {ol.style.Style|{}} */
let olStyles = {}; let olStyles = {};
/** @type {string} */
const name = 'openlayers'; const name = 'openlayers';
/** @type {?number} */
let pointOver = null;
/** /**
* Initialize map * Initialize map
* @param {uBinder} b
* @param {HTMLElement} target
*/ */
function init(target) { function init(b, target) {
binder = b;
uUtils.addScript('//cdn.polyfill.io/v2/polyfill.min.js?features=requestAnimationFrame,Element.prototype.classList', 'mapapi_openlayers_polyfill'); uUtils.addScript('//cdn.polyfill.io/v2/polyfill.min.js?features=requestAnimationFrame,Element.prototype.classList', 'mapapi_openlayers_polyfill');
uUtils.addScript('js/lib/ol.js', 'mapapi_openlayers'); uUtils.addScript('js/lib/ol.js', 'mapapi_openlayers');
@ -62,6 +77,41 @@ function init(target) {
view: view view: view
}); });
map.on('pointermove', (e) => {
const feature = map.forEachFeatureAtPixel(e.pixel,
(_feature, _layer) => {
if (_layer.get('name') === 'Markers') {
return _feature;
}
return null;
});
// emit mouse over marker event
/** @type {?number} */
const id = feature ? feature.getId() : null;
if (id !== pointOver) {
binder.dispatchEvent(uEvent.MARKER_OVER);
pointOver = id;
if (id) {
binder.dispatchEvent(uEvent.MARKER_OVER, id);
}
}
// change mouse cursor when over marker
if (feature) {
map.getTargetElement().style.cursor = 'pointer';
} else {
map.getTargetElement().style.cursor = '';
}
});
initLayers();
initStyles();
initPopups();
}
/**
* Initialize map layers
*/
function initLayers() {
// default layer: OpenStreetMap // default layer: OpenStreetMap
const osm = new ol.layer.Tile({ const osm = new ol.layer.Tile({
name: 'OpenStreetMap', name: 'OpenStreetMap',
@ -71,7 +121,7 @@ function init(target) {
map.addLayer(osm); map.addLayer(osm);
selectedLayer = osm; selectedLayer = osm;
// add extra layers // add extra tile layers
for (const layerName in config.ol_layers) { for (const layerName in config.ol_layers) {
if (config.ol_layers.hasOwnProperty(layerName)) { if (config.ol_layers.hasOwnProperty(layerName)) {
const layerUrl = config.ol_layers[layerName]; const layerUrl = config.ol_layers[layerName];
@ -86,7 +136,7 @@ function init(target) {
} }
} }
// init layers // add track and markers layers
const lineStyle = new ol.style.Style({ const lineStyle = new ol.style.Style({
stroke: new ol.style.Stroke({ stroke: new ol.style.Stroke({
color: uUtils.hexToRGBA(config.strokeColor, config.strokeOpacity), color: uUtils.hexToRGBA(config.strokeColor, config.strokeOpacity),
@ -107,7 +157,11 @@ function init(target) {
map.addLayer(layerTrack); map.addLayer(layerTrack);
map.addLayer(layerMarkers); map.addLayer(layerMarkers);
// styles initLayerSwitcher();
}
function initStyles() {
olStyles = {}; olStyles = {};
const iconRed = new ol.style.Icon({ const iconRed = new ol.style.Icon({
anchor: [ 0.5, 1 ], anchor: [ 0.5, 1 ],
@ -138,8 +192,9 @@ function init(target) {
olStyles['gold'] = new ol.style.Style({ olStyles['gold'] = new ol.style.Style({
image: iconGold image: iconGold
}); });
}
// popups function initPopups() {
const popupContainer = document.createElement('div'); const popupContainer = document.createElement('div');
popupContainer.id = 'popup'; popupContainer.id = 'popup';
popupContainer.className = 'ol-popup'; popupContainer.className = 'ol-popup';
@ -183,25 +238,17 @@ function init(target) {
popup.setPosition(coordinate); popup.setPosition(coordinate);
popupContent.innerHTML = uUI.getPopupHtml(feature.getId()); popupContent.innerHTML = uUI.getPopupHtml(feature.getId());
map.addOverlay(popup); map.addOverlay(popup);
// ns.chartShowPosition(id); binder.dispatchEvent(uEvent.MARKER_SELECT, feature.getId());
} else { } else {
// popup destroy // popup destroy
// eslint-disable-next-line no-undefined // eslint-disable-next-line no-undefined
popup.setPosition(undefined); popup.setPosition(undefined);
binder.dispatchEvent(uEvent.MARKER_SELECT);
} }
}); });
}
// change mouse cursor when over marker function initLayerSwitcher() {
map.on('pointermove', function({ pixel }) {
const hit = map.forEachFeatureAtPixel(pixel, (_feature, _layer) => _layer.get('name') === 'Markers');
if (hit) {
this.getTargetElement().style.cursor = 'pointer';
} else {
this.getTargetElement().style.cursor = '';
}
});
// layer switcher
const switcher = document.createElement('div'); const switcher = document.createElement('div');
switcher.id = 'switcher'; switcher.id = 'switcher';
switcher.className = 'ol-control'; switcher.className = 'ol-control';
@ -283,43 +330,45 @@ function init(target) {
* Clean up API * Clean up API
*/ */
function cleanup() { function cleanup() {
map = null;
layerTrack = null; layerTrack = null;
layerMarkers = null; layerMarkers = null;
selectedLayer = null; selectedLayer = null;
olStyles = null; olStyles = null;
uUI.removeElementById('popup'); uUI.removeElementById('popup');
uUI.removeElementById('switcher'); uUI.removeElementById('switcher');
// ui.clearMapCanvas(); if (map && map.getTargetElement()) {
map.getTargetElement().innerHTML = '';
}
map = null;
} }
/** /**
* Display track * Display track
* @param {uTrack} track Track
* @param {boolean} update Should fit bounds if true * @param {boolean} update Should fit bounds if true
*/ */
function displayTrack(update) { function displayTrack(track, update) {
const track = uLogger.trackList.current;
if (!track) { if (!track) {
return; return;
} }
const points = [];
let i = 0; let i = 0;
const lineString = new ol.geom.LineString([]);
for (const position of track.positions) { for (const position of track.positions) {
// set marker // set marker
setMarker(i++); setMarker(i++, track);
// update polyline if (track.continuous) {
const point = ol.proj.fromLonLat([ position.longitude, position.latitude ]); // update polyline
points.push(point); lineString.appendCoordinate(ol.proj.fromLonLat([ position.longitude, position.latitude ]));
}
}
if (lineString.getLength() > 0) {
const lineFeature = new ol.Feature({
geometry: lineString
});
layerTrack.getSource().addFeature(lineFeature);
} }
const lineString = new ol.geom.LineString(points);
const lineFeature = new ol.Feature({ let extent = layerMarkers.getSource().getExtent();
geometry: lineString
});
layerTrack.getSource().addFeature(lineFeature);
let extent = layerTrack.getSource().getExtent();
map.getControls().forEach((el) => { map.getControls().forEach((el) => {
if (el instanceof ol.control.ZoomToExtent) { if (el instanceof ol.control.ZoomToExtent) {
@ -341,11 +390,6 @@ function displayTrack(update) {
label: getExtentImg() label: getExtentImg()
}); });
map.addControl(zoomToExtentControl); map.addControl(zoomToExtentControl);
/** @todo handle summary and chart in track */
// ns.updateSummary(p.timestamp, totalDistance, totalSeconds);
// ns.updateChart();
} }
/** /**
@ -363,11 +407,12 @@ function clearMap() {
/** /**
* Set marker * Set marker
* @param {number} id * @param {number} id
* @param {uTrack} track
*/ */
function setMarker(id) { function setMarker(id, track) {
// marker // marker
const position = uLogger.trackList.current.positions[id]; const position = track.positions[id];
const posLen = uLogger.trackList.current.positions.length; const posLen = track.positions.length;
const marker = new ol.Feature({ const marker = new ol.Feature({
geometry: new ol.geom.Point(ol.proj.fromLonLat([ position.longitude, position.latitude ])) geometry: new ol.geom.Point(ol.proj.fromLonLat([ position.longitude, position.latitude ]))
}); });
@ -384,31 +429,19 @@ function setMarker(id) {
} }
marker.setStyle(iconStyle); marker.setStyle(iconStyle);
marker.setId(id); marker.setId(id);
/** @todo why set position in marker? ID should be enough */
marker.set('p', position);
marker.set('posLen', posLen);
layerMarkers.getSource().addFeature(marker); layerMarkers.getSource().addFeature(marker);
} }
/** /**
* Add listener on chart to show position on map * Animate marker
* @param {google.visualization.LineChart} chart * @param id Marker sequential id
* @param {google.visualization.DataTable} data
*/ */
function addChartEvent(chart, data) { function animateMarker(id) {
google.visualization.events.addListener(chart, 'select', () => { const marker = layerMarkers.getSource().getFeatureById(id);
const selection = chart.getSelection()[0]; const initStyle = marker.getStyle();
if (selection) { const iconStyle = olStyles['gold'];
const id = data.getValue(selection.row, 0) - 1; marker.setStyle(iconStyle);
const marker = layerMarkers.getSource().getFeatureById(id); setTimeout(() => marker.setStyle(initStyle), 2000);
const initStyle = marker.getStyle();
const iconStyle = olStyles['gold'];
marker.setStyle(iconStyle);
setTimeout(() => {
marker.setStyle(initStyle);
}, 2000);
}
});
} }
/** /**
@ -466,8 +499,7 @@ export {
cleanup, cleanup,
displayTrack, displayTrack,
clearMap, clearMap,
setMarker, animateMarker,
addChartEvent,
getBounds, getBounds,
zoomToExtent, zoomToExtent,
zoomToBounds, zoomToBounds,

View File

@ -1,3 +1,22 @@
/*
* μlogger
*
* Copyright(C) 2019 Bartek Fabiszewski (www.fabiszewski.net)
*
* This is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
import { lang } from './constants.js'; import { lang } from './constants.js';
/** /**

View File

@ -1,3 +1,22 @@
/*
* μlogger
*
* Copyright(C) 2019 Bartek Fabiszewski (www.fabiszewski.net)
*
* This is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
import uUtils from './utils.js'; import uUtils from './utils.js';
/** /**

View File

@ -1,3 +1,22 @@
/*
* μlogger
*
* Copyright(C) 2019 Bartek Fabiszewski (www.fabiszewski.net)
*
* This is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
import { config } from './constants.js'; import { config } from './constants.js';
import uAjax from './ajax.js'; import uAjax from './ajax.js';
import uData from './data.js'; import uData from './data.js';
@ -10,6 +29,7 @@ import uPosition from './position.js';
* @property {number} id * @property {number} id
* @property {string} name * @property {string} name
* @property {uUser} user * @property {uUser} user
* @property {boolean} continuous
* @property {?uPosition[]} positions * @property {?uPosition[]} positions
* @property {?Array<{x: number, y: number}>} plotData * @property {?Array<{x: number, y: number}>} plotData
*/ */
@ -27,6 +47,7 @@ export default class uTrack extends uData {
this._plotData = null; this._plotData = null;
this._maxId = 0; this._maxId = 0;
this._onlyLatest = false; this._onlyLatest = false;
this._continuous = true;
} }
/** /**
@ -50,6 +71,20 @@ export default class uTrack extends uData {
return this._user; return this._user;
} }
/**
* @param {boolean} value
*/
set continuous(value) {
this._continuous = value;
}
/**
* @return {boolean}
*/
get continuous() {
return this._continuous;
}
/** /**
* @param {boolean} value * @param {boolean} value
*/ */
@ -64,8 +99,8 @@ export default class uTrack extends uData {
/** /**
* Get track data from xml * Get track data from xml
* @param {XMLDocument} xml * @param {XMLDocument} xml XML with positions data
* @param {boolean} isUpdate * @param {boolean} isUpdate If true append to old data
*/ */
fromXml(xml, isUpdate) { fromXml(xml, isUpdate) {
let positions = []; let positions = [];
@ -139,16 +174,15 @@ export default class uTrack extends uData {
} else { } else {
data.afterid = this._maxId; data.afterid = this._maxId;
} }
return uAjax.get('utils/getpositions.php', data, { return uAjax.get('utils/getpositions.php', data).then((xml) => {
// loader: ui.trackTitle
}).then((xml) => {
this.fromXml(xml, isUpdate); this.fromXml(xml, isUpdate);
return this.render(); this.render();
return xml;
}); });
} }
/** /**
* * Save track data
* @param {string} action * @param {string} action
* @return {Promise<void>} * @return {Promise<void>}
*/ */
@ -161,6 +195,9 @@ export default class uTrack extends uData {
}); });
} }
/**
* Render track
*/
render() { render() {
this.emit(uEvent.TRACK_READY); this.emit(uEvent.TRACK_READY);
} }

View File

@ -1,4 +1,5 @@
/* μlogger /*
* μlogger
* *
* Copyright(C) 2019 Bartek Fabiszewski (www.fabiszewski.net) * Copyright(C) 2019 Bartek Fabiszewski (www.fabiszewski.net)
* *

View File

@ -1,3 +1,22 @@
/*
* μlogger
*
* Copyright(C) 2019 Bartek Fabiszewski (www.fabiszewski.net)
*
* This is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
import { auth, config, lang } from './constants.js'; import { auth, config, lang } from './constants.js';
import TrackDialog from './trackdialog.js'; import TrackDialog from './trackdialog.js';
import uAjax from './ajax.js'; import uAjax from './ajax.js';
@ -21,7 +40,6 @@ export default class TrackList extends uList {
constructor(selector, binder) { constructor(selector, binder) {
super(selector, binder, uTrack); super(selector, binder, uTrack);
if (binder) { if (binder) {
this.binder.addEventListener(uEvent.CONFIG, this);
this.binder.addEventListener(uEvent.EXPORT, this); this.binder.addEventListener(uEvent.EXPORT, this);
this.binder.addEventListener(uEvent.IMPORT, this); this.binder.addEventListener(uEvent.IMPORT, this);
} }
@ -42,7 +60,7 @@ export default class TrackList extends uList {
* @param {*=} data * @param {*=} data
*/ */
handleEvent(event, data) { handleEvent(event, data) {
if (event.type === 'change') { if (event.type === uEvent.CHANGE) {
config.showLatest = false; config.showLatest = false;
} }
super.handleEvent(event, data); super.handleEvent(event, data);
@ -50,12 +68,6 @@ export default class TrackList extends uList {
this.current.export(data); this.current.export(data);
} else if (event.type === uEvent.IMPORT) { } else if (event.type === uEvent.IMPORT) {
this.import(data).catch((msg) => alert(`${lang.strings['actionfailure']}\n${msg}`)); this.import(data).catch((msg) => alert(`${lang.strings['actionfailure']}\n${msg}`));
} else if (event.type === uEvent.CONFIG && data === 'showLatest') {
if (config.showLatest) {
this.fetchLatest().catch((msg) => alert(`${lang.strings['actionfailure']}\n${msg}`));
} else {
this.fetchTrack();
}
} }
} }
@ -64,36 +76,47 @@ export default class TrackList extends uList {
* @return {Promise<void>} * @return {Promise<void>}
*/ */
import(form) { import(form) {
return uAjax.post('utils/import.php', form, this.emit(true, 'import');
{ return uAjax.post('utils/import.php', form)
// loader: ui.importTitle
})
.then((xml) => { .then((xml) => {
const root = xml.getElementsByTagName('root'); const root = xml.getElementsByTagName('root');
const trackCnt = uUtils.getNodeAsInt(root[0], 'trackcnt'); const trackCnt = uUtils.getNodeAsInt(root[0], 'trackcnt');
if (trackCnt > 1) { if (trackCnt > 1) {
alert(uUtils.sprintf(lang.strings['imultiple'], trackCnt)); alert(uUtils.sprintf(lang.strings['imultiple'], trackCnt));
} }
const trackId = uUtils.getNodeAsInt(root[0], 'trackid'); const trackId = uUtils.getNodeAsInt(root[0], 'trackid');
return this.fetch().then(() => this.select(trackId)); this.emit(false, 'import');
}).catch((msg) => alert(`${lang.strings['actionfailure']}\n${msg}`)); return this.fetch().then(() => this.select(trackId));
}).catch((msg) => {
this.emit(false, 'import');
alert(`${lang.strings['actionfailure']}\n${msg}`);
});
}
emit(on, action) {
this.binder.dispatchEvent(uEvent.LOADER, { on: on, action: action });
} }
/** /**
* Fetch tracks for current user * Fetch tracks for current user
* @throws
* @return {Promise<Document, string>} * @return {Promise<Document, string>}
*/ */
fetch() { fetch() {
this.emit(true, 'track');
return uAjax.get('utils/gettracks.php', return uAjax.get('utils/gettracks.php',
{ {
userid: uLogger.userList.current.id userid: uLogger.userList.current.id
}, })
{ .then((xml) => {
// loader: ui.trackTitle this.clear();
}).then((xml) => { this.fromXml(xml.getElementsByTagName('track'), 'trackid', 'trackname');
this.clear(); this.emit(false, 'track');
return this.fromXml(xml.getElementsByTagName('track'), 'trackid', 'trackname'); return xml;
}); }).catch((msg) => {
this.emit(false, 'track');
alert(`${lang.strings['actionfailure']}\n${msg}`);
});
} }
/** /**
@ -102,25 +125,46 @@ export default class TrackList extends uList {
* @return {Promise<Document, string>} * @return {Promise<Document, string>}
*/ */
fetchLatest() { fetchLatest() {
return uAjax.get('utils/getpositions.php', { this.emit(true, 'track');
userid: uLogger.userList.current.id, const data = {
last: 1 last: 1
}, { };
// loader: ui.trackTitle const allUsers = uLogger.userList.isSelectedAllOption;
}).then((xml) => { if (!allUsers) {
const xmlPos = xml.getElementsByTagName('position'); data.userid = uLogger.userList.current.id;
if (xmlPos.length === 1) { }
const position = uPosition.fromXml(xmlPos[0]); return uAjax.get('utils/getpositions.php', data).then((xml) => {
if (this.has(position.trackid)) { if (!allUsers) {
this.select(position.trackid, true); const xmlPos = xml.getElementsByTagName('position');
this.current.fromXml(xml, false); // single user
this.current.onlyLatest = true; if (xmlPos.length === 1) {
return this.current.render(); const position = uPosition.fromXml(xmlPos[0]);
if (this.has(position.trackid)) {
this.select(position.trackid, true);
this.current.fromXml(xml, false);
this.current.onlyLatest = true;
this.current.render();
} else {
// tracklist needs update
return this.fetch().then(() => this.fetchLatest());
}
} }
// tracklist needs update } else {
return this.fetch().fetchLatest(); // all users
this.clear();
const track = new uTrack(0, '', null);
track.binder = this.binder;
track.continuous = false;
track.fromXml(xml, false);
this.add(track);
this.select(0, true);
this.current.render();
} }
return false; this.emit(false, 'track');
return xml;
}).catch((msg) => {
this.emit(false, 'track');
alert(`${lang.strings['actionfailure']}\n${msg}`);
}); });
} }
@ -128,13 +172,23 @@ export default class TrackList extends uList {
* @override * @override
*/ */
onChange() { onChange() {
this.fetchTrack(); if (!config.showLatest) {
this.fetchTrack();
}
} }
/**
* Fetch and render track
*/
fetchTrack() { fetchTrack() {
if (this.current) { if (this.current) {
this.emit(true, 'track');
this.current.fetch() this.current.fetch()
.catch((msg) => alert(`${lang.strings['actionfailure']}\n${msg}`)); .then(() => this.emit(false, 'track'))
.catch((msg) => {
this.emit(false, 'track');
alert(`${lang.strings['actionfailure']}\n${msg}`);
});
} }
} }

144
js/ui.js
View File

@ -1,3 +1,22 @@
/*
* μlogger
*
* Copyright(C) 2019 Bartek Fabiszewski (www.fabiszewski.net)
*
* This is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
import { config, lang } from './constants.js'; import { config, lang } from './constants.js';
import uEvent from './event.js'; import uEvent from './event.js';
import { uLogger } from './ulogger.js'; import { uLogger } from './ulogger.js';
@ -13,6 +32,8 @@ export default class uUI {
binder.addEventListener(uEvent.CONFIG, this); binder.addEventListener(uEvent.CONFIG, this);
binder.addEventListener(uEvent.CHART_READY, this); binder.addEventListener(uEvent.CHART_READY, this);
binder.addEventListener(uEvent.OPEN_URL, this); binder.addEventListener(uEvent.OPEN_URL, this);
binder.addEventListener(uEvent.LOADER, this);
binder.addEventListener(uEvent.TRACK_READY, this);
document.addEventListener('DOMContentLoaded', () => { this.initUI(); }); document.addEventListener('DOMContentLoaded', () => { this.initUI(); });
this.isLiveOn = false; this.isLiveOn = false;
} }
@ -24,11 +45,11 @@ export default class uUI {
/** @type {HTMLElement} */ /** @type {HTMLElement} */
this.menu = document.getElementById('menu'); this.menu = document.getElementById('menu');
/** @type {?HTMLElement} */ /** @type {?HTMLElement} */
this.menuHead = document.getElementById('menu_head'); this.userMenu = document.getElementById('user-menu');
/** @type {?HTMLElement} */ /** @type {?HTMLElement} */
this.userDropdown = document.getElementById('user_dropdown'); this.userDropdown = document.getElementById('user-dropdown');
/** @type {?HTMLElement} */ /** @type {?HTMLElement} */
this.menuPass = document.getElementById('menu_pass'); this.userPass = document.getElementById('user-pass');
// noinspection JSValidateTypes // noinspection JSValidateTypes
/** @type {?HTMLSelectElement} */ /** @type {?HTMLSelectElement} */
this.userSelect = function () { this.userSelect = function () {
@ -41,17 +62,17 @@ export default class uUI {
this.trackSelect = document.getElementsByName('track')[0]; this.trackSelect = document.getElementsByName('track')[0];
// noinspection JSValidateTypes // noinspection JSValidateTypes
/** @type {HTMLSelectElement} */ /** @type {HTMLSelectElement} */
this.api = document.getElementsByName('api')[0]; this.apiSelect = document.getElementsByName('api')[0];
// noinspection JSValidateTypes // noinspection JSValidateTypes
/** @type {HTMLSelectElement} */ /** @type {HTMLSelectElement} */
this.lang = document.getElementsByName('lang')[0]; this.langSelect = document.getElementsByName('lang')[0];
// noinspection JSValidateTypes // noinspection JSValidateTypes
/** @type {HTMLSelectElement} */ /** @type {HTMLSelectElement} */
this.units = document.getElementsByName('units')[0]; this.unitsSelect = document.getElementsByName('units')[0];
/** @type {HTMLElement} */ /** @type {HTMLElement} */
this.chart = document.getElementById('chart'); this.chart = document.getElementById('chart');
/** @type {HTMLElement} */ /** @type {HTMLElement} */
this.chartClose = document.getElementById('chart_close'); this.chartClose = document.getElementById('chart-close');
/** @type {HTMLElement} */ /** @type {HTMLElement} */
this.bottom = document.getElementById('bottom'); this.bottom = document.getElementById('bottom');
/** @type {HTMLElement} */ /** @type {HTMLElement} */
@ -63,31 +84,29 @@ export default class uUI {
/** @type {HTMLElement} */ /** @type {HTMLElement} */
this.track = document.getElementById('track'); this.track = document.getElementById('track');
/** @type {HTMLElement} */ /** @type {HTMLElement} */
this.trackTitle = this.track ? this.track.getElementsByClassName('menutitle')[0] : null; this.trackTitle = document.querySelector('label[for="track"]');
/** @type {HTMLElement} */ /** @type {HTMLElement} */
this.import = document.getElementById('import'); this.importTitle = document.getElementById('import') || null;
/** @type {HTMLElement} */
this.importTitle = this.import ? this.import.getElementsByClassName('menutitle')[0] : null;
/** @type {HTMLElement} */ /** @type {HTMLElement} */
this.summary = document.getElementById('summary'); this.summary = document.getElementById('summary');
/** @type {HTMLElement} */ /** @type {HTMLElement} */
this.latest = document.getElementById('latest'); this.latest = document.getElementById('latest');
/** @type {HTMLElement} */ /** @type {HTMLElement} */
this.autoReload = document.getElementById('auto_reload'); this.autoReload = document.getElementById('auto-reload');
/** @type {HTMLElement} */ /** @type {HTMLElement} */
this.forceReload = document.getElementById('force_reload'); this.forceReload = document.getElementById('force-reload');
/** @type {HTMLElement} */ /** @type {HTMLElement} */
this.auto = document.getElementById('auto'); this.interval = document.getElementById('interval');
/** @type {HTMLElement} */ /** @type {HTMLElement} */
this.setTime = document.getElementById('set_time'); this.setInterval = document.getElementById('set-interval');
/** @type {HTMLElement} */ /** @type {HTMLElement} */
this.exportKml = document.getElementById('export_kml'); this.exportKml = document.getElementById('export-kml');
/** @type {HTMLElement} */ /** @type {HTMLElement} */
this.exportGpx = document.getElementById('export_gpx'); this.exportGpx = document.getElementById('export-gpx');
/** @type {?HTMLElement} */ /** @type {?HTMLElement} */
this.inputFile = document.getElementById('inputFile'); this.inputFile = document.getElementById('input-file');
/** @type {HTMLElement} */ /** @type {HTMLElement} */
this.importGpx = document.getElementById('import_gpx'); this.importGpx = document.getElementById('import-gpx');
/** @type {?HTMLElement} */ /** @type {?HTMLElement} */
this.addUser = document.getElementById('adduser'); this.addUser = document.getElementById('adduser');
/** @type {?HTMLElement} */ /** @type {?HTMLElement} */
@ -99,29 +118,37 @@ export default class uUI {
/** @type {HTMLElement} */ /** @type {HTMLElement} */
this.head = document.getElementsByTagName('head')[0]; this.head = document.getElementsByTagName('head')[0];
if (this.menuHead) { if (this.userMenu) {
this.menuHead.onclick = () => this.showUserMenu(); this.userMenu.onclick = () => this.showUserMenu();
} }
if (this.menuPass) { if (this.userPass) {
this.menuPass.onclick = () => { this.userPass.onclick = () => {
this.emit(uEvent.PASSWORD); this.emit(uEvent.PASSWORD);
} }
} }
this.hideUserMenu = this.hideUserMenu.bind(this); this.hideUserMenu = this.hideUserMenu.bind(this);
this.latest.onchange = () => uUI.toggleLatest(); this.latest.onchange = () => uUI.toggleLatest();
this.autoReload.onchange = () => this.toggleAutoReload(); this.autoReload.onchange = () => this.toggleAutoReload();
this.setTime.onclick = () => this.setAutoReloadTime(); this.setInterval.onclick = () => this.setAutoReloadTime();
this.forceReload.onclick = () => this.trackReload(); this.forceReload.onclick = () => this.trackReload();
this.chartLink.onclick = () => this.toggleChart(); this.chartLink.onclick = () => this.toggleChart();
this.api.onchange = () => { this.trackSelect.onchange = () => {
const api = this.api.options[this.api.selectedIndex].value; const trackId = this.trackSelect.options[this.trackSelect.selectedIndex].value;
this.emit(uEvent.CHANGE, { el: this.trackSelect, id: trackId });
};
this.userSelect.onchange = () => {
const userId = this.userSelect.options[this.userSelect.selectedIndex].value;
this.emit(uEvent.CHANGE, { el: this.userSelect, id: userId });
};
this.apiSelect.onchange = () => {
const api = this.apiSelect.options[this.apiSelect.selectedIndex].value;
this.emit(uEvent.API_CHANGE, api); this.emit(uEvent.API_CHANGE, api);
}; };
this.lang.onchange = () => { this.langSelect.onchange = () => {
uUI.setLang(this.lang.options[this.lang.selectedIndex].value); uUI.setLang(this.langSelect.options[this.langSelect.selectedIndex].value);
}; };
this.units.onchange = () => { this.unitsSelect.onchange = () => {
uUI.setUnits(this.units.options[this.units.selectedIndex].value); uUI.setUnits(this.unitsSelect.options[this.unitsSelect.selectedIndex].value);
}; };
this.exportKml.onclick = () => { this.exportKml.onclick = () => {
this.emit(uEvent.EXPORT, 'kml'); this.emit(uEvent.EXPORT, 'kml');
@ -201,7 +228,7 @@ export default class uUI {
const i = parseInt(prompt(lang.strings['newinterval'])); const i = parseInt(prompt(lang.strings['newinterval']));
if (!isNaN(i) && i !== config.interval) { if (!isNaN(i) && i !== config.interval) {
config.interval = i; config.interval = i;
this.auto.innerHTML = config.interval.toString(); this.interval.innerHTML = config.interval.toString();
// if live tracking on, reload with new interval // if live tracking on, reload with new interval
if (this.isLiveOn) { if (this.isLiveOn) {
this.stopAutoReload(); this.stopAutoReload();
@ -313,13 +340,9 @@ export default class uUI {
let date = ''; let date = '';
let time = ''; let time = '';
if (pos.timestamp > 0) { if (pos.timestamp > 0) {
const d = new Date(pos.timestamp * 1000); const parts = uUtils.getTimeString(new Date(pos.timestamp * 1000));
date = `${d.getFullYear()}-${(`0${d.getMonth() + 1}`).slice(-2)}-${(`0${d.getDate()}`).slice(-2)}`; date = parts.date;
time = d.toTimeString(); time = `${parts.time}<span class="smaller">${parts.zone}</span>`;
let offset;
if ((offset = time.indexOf(' ')) >= 0) {
time = `${time.substr(0, offset)} <span class="smaller">${time.substr(offset + 1)}</span>`;
}
} }
let provider = ''; let provider = '';
if (pos.provider === 'gps') { if (pos.provider === 'gps') {
@ -355,6 +378,37 @@ export default class uUI {
</div>`; </div>`;
} }
/**
* Update track summary
* @param {number} timestamp
* @param {number=} totalDistance Total distance (m)
* @param {number=} totalTime Total time (s)
*/
updateSummary(timestamp, totalDistance, totalTime) {
if (config.showLatest) {
const today = new Date();
const date = new Date(timestamp * 1000);
let dateString = '';
if (date.toDateString() !== today.toDateString()) {
dateString = `${date.getFullYear()}-${(`0${date.getMonth() + 1}`).slice(-2)}-${(`0${date.getDate()}`).slice(-2)}<br>`;
}
let timeString = date.toTimeString();
let offset;
if ((offset = timeString.indexOf(' ')) >= 0) {
timeString = `${timeString.substr(0, offset)} <span style="font-weight:normal">${timeString.substr(offset + 1)}</span>`;
}
this.summary.innerHTML = `
<div class="menu-title">${lang.strings['latest']}:</div>
${dateString}
${timeString}`;
} else {
this.summary.innerHTML = `
<div class="menu-title">${lang.strings['summary']}</div>
<div><img class="icon" alt="${lang.strings['tdistance']}" title="${lang.strings['tdistance']}" src="images/distance.svg"> ${(totalDistance.toKm() * config.factor_km).toFixed(2)} ${config.unit_km}</div>
<div><img class="icon" alt="${lang.strings['ttime']}" title="${lang.strings['ttime']}" src="images/time.svg"> ${totalTime.toHMS()}</div>`;
}
}
/** /**
* Clear map canvas * Clear map canvas
*/ */
@ -412,12 +466,26 @@ export default class uUI {
} else { } else {
this.chartLink.style.visibility = 'hidden'; this.chartLink.style.visibility = 'hidden';
} }
} else if (event.type === uEvent.TRACK_READY) {
/** @type {uTrack} */
const track = args;
if (track.hasPositions) {
const position = track.positions[track.positions.length - 1];
this.updateSummary(position.timestamp, position.totalDistance, position.totalSeconds);
}
} else if (event.type === uEvent.OPEN_URL) { } else if (event.type === uEvent.OPEN_URL) {
window.location.assign(args); window.location.assign(args);
} else if (event.type === uEvent.CONFIG) { } else if (event.type === uEvent.CONFIG) {
if (args === 'showLatest') { if (args === 'showLatest') {
this.latest.checked = config.showLatest; this.latest.checked = config.showLatest;
} }
} else if (event.type === uEvent.LOADER) {
const el = args.action === 'track' ? this.trackTitle : this.importTitle;
if (args.on) {
uUI.setLoader(el);
} else {
uUI.removeLoader(el);
}
} }
} }

View File

@ -1,3 +1,22 @@
/*
* μlogger
*
* Copyright(C) 2019 Bartek Fabiszewski (www.fabiszewski.net)
*
* This is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
import { auth, config } from './constants.js'; import { auth, config } from './constants.js';
import TrackList from './tracklist.js'; import TrackList from './tracklist.js';
import UserList from './userlist.js'; import UserList from './userlist.js';

View File

@ -1,3 +1,22 @@
/*
* μlogger
*
* Copyright(C) 2019 Bartek Fabiszewski (www.fabiszewski.net)
*
* This is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
import uAjax from './ajax.js'; import uAjax from './ajax.js';
import uData from './data.js'; import uData from './data.js';
import uUtils from './utils.js'; import uUtils from './utils.js';

View File

@ -1,4 +1,5 @@
/* μlogger /*
* μlogger
* *
* Copyright(C) 2019 Bartek Fabiszewski (www.fabiszewski.net) * Copyright(C) 2019 Bartek Fabiszewski (www.fabiszewski.net)
* *

View File

@ -1,3 +1,22 @@
/*
* μlogger
*
* Copyright(C) 2019 Bartek Fabiszewski (www.fabiszewski.net)
*
* This is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
import { auth, config, lang } from './constants.js'; import { auth, config, lang } from './constants.js';
import UserDialog from './userdialog.js'; import UserDialog from './userdialog.js';
import uList from './list.js'; import uList from './list.js';
@ -25,15 +44,28 @@ export default class UserList extends uList {
* @override * @override
*/ */
onChange() { onChange() {
if (this.isSelectedAllOption) { if (config.showLatest) {
// clearOptions(ui.trackSelect); if (this.isSelectedAllOption) {
// loadLastPositionAllUsers(); uLogger.trackList.fetchLatest();
} else if (config.showLatest) { } else {
uLogger.trackList.fetchLatest() uLogger.trackList.fetch()
.catch((msg) => alert(`${lang.strings['actionfailure']}\n${msg}`)); .then(() => uLogger.trackList.fetchLatest());
}
} else { } else {
uLogger.trackList.fetch() uLogger.trackList.fetch();
.catch((msg) => alert(`${lang.strings['actionfailure']}\n${msg}`)); }
}
/**
* @override
*/
onConfigChange(property) {
if (property === 'showLatest') {
if (config.showLatest && this.data.length > 1) {
this.showAllOption = true;
} else if (!config.showLatest && this.showAllOption) {
this.showAllOption = false;
}
} }
} }

View File

@ -1,3 +1,21 @@
/*
* μlogger
*
* Copyright(C) 2019 Bartek Fabiszewski (www.fabiszewski.net)
*
* This is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
export default class uUtils { export default class uUtils {
@ -221,6 +239,30 @@ export default class uUtils {
} }
return null; return null;
} }
/**
* Format date to date, time and time zone strings
* Simplify zone name, eg.
* date: 2017-06-14, time: 11:42:19, zone: GMT+2 CEST
* @param {Date} date
* @return {{date: string, time: string, zone: string}}
*/
static getTimeString(date) {
let timeZone = '';
const dateStr = `${date.getFullYear()}-${(`0${date.getMonth() + 1}`).slice(-2)}-${(`0${date.getDate()}`).slice(-2)}`;
const timeStr = date.toTimeString().replace(/^\s*([^ ]+)([^(]*)(\([^)]*\))*/,
// eslint-disable-next-line max-params
(_, hours, zone, dst) => {
if (zone) {
timeZone = zone.replace(/(0(?=[1-9]00))|(00\b)/g, '');
if (dst && (/[A-Z]/).test(dst)) {
timeZone += dst.match(/\b[A-Z]+/g).join('');
}
}
return hours;
});
return { date: dateStr, time: timeStr, zone: timeZone };
}
} }
// seconds to (d) H:M:S // seconds to (d) H:M:S