More ES6 refactoring
This commit is contained in:
parent
3dd5c14273
commit
9f885f6068
35
css/main.css
35
css/main.css
@ -53,7 +53,7 @@ select {
|
||||
#menu input[type = "checkbox"] {
|
||||
width: auto;
|
||||
}
|
||||
.menulink {
|
||||
.menu-link {
|
||||
display: block;
|
||||
margin-top: .2em;
|
||||
}
|
||||
@ -117,7 +117,7 @@ label[for=user] {
|
||||
.section:first-child {
|
||||
padding-top: 1em;
|
||||
}
|
||||
#inputFile {
|
||||
#input-file {
|
||||
display: none;
|
||||
}
|
||||
#summary div {
|
||||
@ -201,7 +201,7 @@ label[for=user] {
|
||||
background-color: white;
|
||||
opacity: 0.8;
|
||||
}
|
||||
#chart_close {
|
||||
#chart-close {
|
||||
position: fixed;
|
||||
bottom: 175px;
|
||||
right: 175px;
|
||||
@ -308,7 +308,7 @@ button > * {
|
||||
-webkit-border-radius: 10px;
|
||||
}
|
||||
|
||||
.dropdown {
|
||||
#user-dropdown {
|
||||
display: none;
|
||||
position: absolute;
|
||||
background-color: gray;
|
||||
@ -317,7 +317,7 @@ button > * {
|
||||
border: 1px solid #888;
|
||||
}
|
||||
|
||||
.dropdown a {
|
||||
#user-dropdown a {
|
||||
display: block;
|
||||
padding-bottom: .5em;
|
||||
padding-top: .5em;
|
||||
@ -327,7 +327,7 @@ button > * {
|
||||
|
||||
.icon { height: 1.4em; }
|
||||
|
||||
.u { text-decoration: underline; }
|
||||
.menu-title { text-decoration: underline; }
|
||||
|
||||
.loader {
|
||||
animation: blink 1s linear infinite;
|
||||
@ -337,6 +337,29 @@ button > * {
|
||||
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 */
|
||||
.ol-popup {
|
||||
position: absolute;
|
||||
|
112
index.php
112
index.php
@ -85,10 +85,10 @@
|
||||
<div id="menu-content">
|
||||
|
||||
<?php if ($auth->isAuthenticated()): ?>
|
||||
<div id="user_menu">
|
||||
<a id="menu_head"><img class="icon" alt="<?= $lang["user"] ?>" src="images/user.svg"> <?= htmlspecialchars($auth->user->login) ?></a>
|
||||
<div id="user_dropdown" class="dropdown">
|
||||
<a id="menu_pass"><img class="icon" alt="<?= $lang["changepass"] ?>" src="images/lock.svg"> <?= $lang["changepass"] ?></a>
|
||||
<div>
|
||||
<a id="user-menu"><img class="icon" alt="<?= $lang["user"] ?>" src="images/user.svg"> <?= htmlspecialchars($auth->user->login) ?></a>
|
||||
<div id="user-dropdown">
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
@ -98,30 +98,26 @@
|
||||
|
||||
<div class="section">
|
||||
<?php if (!empty($usersArr)): ?>
|
||||
<label for="user" class="menutitle"><?= $lang["user"] ?></label>
|
||||
<form>
|
||||
<select id="user" name="user">
|
||||
<option value="0" disabled><?= $lang["suser"] ?></option>
|
||||
<?php foreach ($usersArr as $aUser): ?>
|
||||
<option <?= ($aUser->id == $displayUserId) ? "selected " : "" ?>value="<?= $aUser->id ?>"><?= htmlspecialchars($aUser->login) ?></option>
|
||||
<?php endforeach; ?>
|
||||
<label for="user"><?= $lang["user"] ?></label>
|
||||
<select id="user" name="user">
|
||||
<option value="0" disabled><?= $lang["suser"] ?></option>
|
||||
<?php foreach ($usersArr as $aUser): ?>
|
||||
<option <?= ($aUser->id == $displayUserId) ? "selected " : "" ?>value="<?= $aUser->id ?>"><?= htmlspecialchars($aUser->login) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<label for="track" class="menutitle"><?= $lang["track"] ?></label>
|
||||
<form>
|
||||
<select id="track" name="track">
|
||||
<?php foreach ($tracksArr as $aTrack): ?>
|
||||
<option value="<?= $aTrack->id ?>"><?= htmlspecialchars($aTrack->name) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
<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_time"><span id="auto"><?= uConfig::$interval ?></span></a> s)<br>
|
||||
</form>
|
||||
<a id="force_reload"> <?= $lang["reload"] ?></a><br>
|
||||
<label for="track"><?= $lang["track"] ?></label>
|
||||
<select id="track" name="track">
|
||||
<?php foreach ($tracksArr as $aTrack): ?>
|
||||
<option value="<?= $aTrack->id ?>"><?= htmlspecialchars($aTrack->name) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
<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>
|
||||
<a id="force-reload"> <?= $lang["reload"] ?></a><br>
|
||||
</div>
|
||||
|
||||
<div id="summary" class="section"></div>
|
||||
@ -131,60 +127,54 @@
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="api" class="menutitle"><?= $lang["api"] ?></label>
|
||||
<form>
|
||||
<select id="api" name="api">
|
||||
<option value="gmaps"<?= (uConfig::$mapapi == "gmaps") ? " selected" : "" ?>>Google Maps</option>
|
||||
<option value="openlayers"<?= (uConfig::$mapapi == "openlayers") ? " selected" : "" ?>>OpenLayers</option>
|
||||
</select>
|
||||
</form>
|
||||
<label for="api"><?= $lang["api"] ?></label>
|
||||
<select id="api" name="api">
|
||||
<option value="gmaps"<?= (uConfig::$mapapi == "gmaps") ? " selected" : "" ?>>Google Maps</option>
|
||||
<option value="openlayers"<?= (uConfig::$mapapi == "openlayers") ? " selected" : "" ?>>OpenLayers</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="lang" class="menutitle"><?= $lang["language"] ?></label>
|
||||
<form>
|
||||
<select id="lang" name="lang">
|
||||
<?php foreach ($langsArr as $langCode => $langName): ?>
|
||||
<option value="<?= $langCode ?>"<?= (uConfig::$lang == $langCode) ? " selected" : "" ?>><?= $langName ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</form>
|
||||
<label for="lang"><?= $lang["language"] ?></label>
|
||||
<select id="lang" name="lang">
|
||||
<?php foreach ($langsArr as $langCode => $langName): ?>
|
||||
<option value="<?= $langCode ?>"<?= (uConfig::$lang == $langCode) ? " selected" : "" ?>><?= $langName ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<label for="units" class="menutitle"><?= $lang["units"] ?></label>
|
||||
<form>
|
||||
<select id="units" name="units">
|
||||
<option value="metric"<?= (uConfig::$units == "metric") ? " selected" : "" ?>><?= $lang["metric"] ?></option>
|
||||
<option value="imperial"<?= (uConfig::$units == "imperial") ? " selected" : "" ?>><?= $lang["imperial"] ?></option>
|
||||
<option value="nautical"<?= (uConfig::$units == "nautical") ? " selected" : "" ?>><?= $lang["nautical"] ?></option>
|
||||
</select>
|
||||
</form>
|
||||
<label for="units"><?= $lang["units"] ?></label>
|
||||
<select id="units" name="units">
|
||||
<option value="metric"<?= (uConfig::$units == "metric") ? " selected" : "" ?>><?= $lang["metric"] ?></option>
|
||||
<option value="imperial"<?= (uConfig::$units == "imperial") ? " selected" : "" ?>><?= $lang["imperial"] ?></option>
|
||||
<option value="nautical"<?= (uConfig::$units == "nautical") ? " selected" : "" ?>><?= $lang["nautical"] ?></option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div id="export" class="section">
|
||||
<div class="menutitle u"><?= $lang["export"] ?></div>
|
||||
<a id="export_kml" class="menulink">kml</a>
|
||||
<a id="export_gpx" class="menulink">gpx</a>
|
||||
<div class="section">
|
||||
<div class="menu-title"><?= $lang["export"] ?></div>
|
||||
<a id="export-kml" class="menu-link">kml</a>
|
||||
<a id="export-gpx" class="menu-link">gpx</a>
|
||||
</div>
|
||||
|
||||
<?php if ($auth->isAuthenticated()): ?>
|
||||
<div id="import" class="section">
|
||||
<div class="menutitle u"><?= $lang["import"] ?></div>
|
||||
<form id="importForm" enctype="multipart/form-data" method="post">
|
||||
<div class="section">
|
||||
<div id="import" class="menu-title"><?= $lang["import"] ?></div>
|
||||
<form id="import-form" enctype="multipart/form-data" method="post">
|
||||
<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>
|
||||
<a id="import_gpx" class="menulink">gpx</a>
|
||||
<a id="import-gpx" class="menu-link">gpx</a>
|
||||
</div>
|
||||
|
||||
<div id="admin_menu">
|
||||
<div class="menutitle u"><?= $lang["adminmenu"] ?></div>
|
||||
<div id="admin-menu">
|
||||
<div class="menu-title"><?= $lang["adminmenu"] ?></div>
|
||||
<?php if ($auth->isAdmin()): ?>
|
||||
<a id="adduser" class="menulink"><?= $lang["adduser"] ?></a>
|
||||
<a id="edituser" class="menulink"><?= $lang["edituser"] ?></a>
|
||||
<a id="adduser" class="menu-link"><?= $lang["adduser"] ?></a>
|
||||
<a id="edituser" class="menu-link"><?= $lang["edituser"] ?></a>
|
||||
<?php endif; ?>
|
||||
<a id="edittrack" class="menulink"><?= $lang["edittrack"] ?></a>
|
||||
<a id="edittrack" class="menu-link"><?= $lang["edittrack"] ?></a>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
@ -197,7 +187,7 @@
|
||||
<div id="map-canvas"></div>
|
||||
<div id="bottom">
|
||||
<div id="chart"></div>
|
||||
<div id="chart_close"><?= $lang["close"] ?></div>
|
||||
<div id="chart-close"><?= $lang["close"] ?></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
27
js/ajax.js
27
js/ajax.js
@ -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';
|
||||
|
||||
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} [options] Optional options
|
||||
* @param {string} [options.method='GET'] Optional query method, default 'GET'
|
||||
* @param {HTMLElement} [options.loader] Optional element to animate during loading
|
||||
* @return {Promise<Document, string>}
|
||||
*/
|
||||
static ajax(url, data, options) {
|
||||
@ -36,7 +54,6 @@ export default class uAjax {
|
||||
data = data || {};
|
||||
options = options || {};
|
||||
let method = options.method || 'GET';
|
||||
const loader = options.loader;
|
||||
const xhr = new XMLHttpRequest();
|
||||
return new Promise((resolve, reject) => {
|
||||
xhr.onreadystatechange = function () {
|
||||
@ -63,9 +80,6 @@ export default class uAjax {
|
||||
if (error && reject && typeof reject === 'function') {
|
||||
reject(message);
|
||||
}
|
||||
if (loader) {
|
||||
// UI.removeLoader(loader);
|
||||
}
|
||||
};
|
||||
let body = null;
|
||||
if (data instanceof HTMLFormElement) {
|
||||
@ -90,9 +104,6 @@ export default class uAjax {
|
||||
xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
|
||||
}
|
||||
xhr.send(body);
|
||||
if (loader) {
|
||||
// UI.setLoader(loader);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
19
js/auth.js
19
js/auth.js
@ -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 { lang } from './constants.js';
|
||||
import uEvent from './event.js';
|
||||
|
19
js/binder.js
19
js/binder.js
@ -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';
|
||||
|
||||
export default class uBinder {
|
||||
|
80
js/chart.js
80
js/chart.js
@ -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 uEvent from './event.js';
|
||||
import uUtils from './utils.js';
|
||||
@ -13,10 +32,13 @@ export default class Chart {
|
||||
* @param {uBinder} 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.UI_READY, this);
|
||||
this._binder = binder;
|
||||
this._targetEl = null;
|
||||
this._points = null;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -57,22 +79,22 @@ export default class Chart {
|
||||
});
|
||||
|
||||
chart.on('created', () => {
|
||||
const points = document.querySelectorAll('.ct-chart-line .ct-point');
|
||||
for (let i = 0; i < points.length; i++) {
|
||||
this._points = document.querySelectorAll('.ct-chart-line .ct-point');
|
||||
const len = this._points.length;
|
||||
for (let i = 0; i < len; i++) {
|
||||
((id) => {
|
||||
points[id].addEventListener('click', () => {
|
||||
/** @todo trigger marker action */
|
||||
console.log(id);
|
||||
this._points[id].addEventListener('click', () => {
|
||||
this._binder.dispatchEvent(uEvent.CHART_CLICKED, id);
|
||||
});
|
||||
})(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
|
||||
if (this._targetEl.parentNode.style.display !== 'block') {
|
||||
if (!this.isVisible()) {
|
||||
const observer = new MutationObserver(() => {
|
||||
if (this._targetEl.parentNode.style.display === 'block') {
|
||||
if (this.isVisible()) {
|
||||
// eslint-disable-next-line no-underscore-dangle
|
||||
this._targetEl.__chartist__.update();
|
||||
observer.disconnect();
|
||||
@ -80,7 +102,10 @@ export default class Chart {
|
||||
});
|
||||
observer.observe(this._targetEl.parentNode, { attributes: true });
|
||||
}
|
||||
}
|
||||
|
||||
isVisible() {
|
||||
return this._targetEl && this._targetEl.parentNode && this._targetEl.parentNode.style.display === 'block';
|
||||
}
|
||||
|
||||
static onDomLoaded() {
|
||||
@ -104,7 +129,44 @@ export default class Chart {
|
||||
/** @type {uUI} */
|
||||
const ui = args;
|
||||
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'));
|
||||
}
|
||||
}
|
||||
|
25
js/config.js
25
js/config.js
@ -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';
|
||||
|
||||
export default class uConfig {
|
||||
@ -269,7 +288,9 @@ export default class uConfig {
|
||||
* @param {boolean} value
|
||||
*/
|
||||
set showLatest(value) {
|
||||
this._showLatest = value;
|
||||
this.notify('showLatest');
|
||||
if (this._showLatest !== value) {
|
||||
this._showLatest = value;
|
||||
this.notify('showLatest');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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 uConfig from './config.js';
|
||||
import uUser from './user.js';
|
||||
|
19
js/data.js
19
js/data.js
@ -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
|
||||
*/
|
||||
|
29
js/event.js
29
js/event.js
@ -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 */
|
||||
|
||||
/**
|
||||
* class uEvent
|
||||
* property {string} type
|
||||
@ -17,12 +35,17 @@ export default class uEvent {
|
||||
|
||||
static get ADD() { return 'µAdd'; }
|
||||
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 CHANGE() { return 'µChange'; }
|
||||
static get CHART_READY() { return 'µChartReady'; }
|
||||
static get EDIT() { return 'µEdit'; }
|
||||
static get EXPORT() { return 'µExport'; }
|
||||
static get OPEN_URL() { return 'µOpen'; }
|
||||
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 TRACK_READY() { return 'µTrackReady'; }
|
||||
static get UI_READY() { return 'µUiReady'; }
|
||||
@ -47,7 +70,7 @@ export default class uEvent {
|
||||
dispatch(args) {
|
||||
for (const listener of this.listeners) {
|
||||
(async () => {
|
||||
console.log(`${this.type}: ${args.constructor.name} => ${listener.name}`);
|
||||
console.log(`${this.type}: ${args ? args.constructor.name : ''}`);
|
||||
await listener(this, args);
|
||||
})();
|
||||
}
|
||||
|
89
js/list.js
89
js/list.js
@ -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 uEvent from './event.js';
|
||||
import uUtils from './utils.js';
|
||||
@ -20,7 +39,7 @@ export default class uList {
|
||||
/** @type {uBinder} */
|
||||
this.binder = binder;
|
||||
/** @type {boolean} */
|
||||
this.showAllOption = false;
|
||||
this._showAllOption = false;
|
||||
/** @type {boolean} */
|
||||
this.hasHead = false;
|
||||
this.headValue = '';
|
||||
@ -29,11 +48,10 @@ export default class uList {
|
||||
this.T = type || uData;
|
||||
/** @type {HTMLSelectElement} */
|
||||
this.domElement = document.querySelector(selector);
|
||||
if (this.domElement) {
|
||||
this.domElement.addEventListener('change', this, false);
|
||||
}
|
||||
if (this.binder) {
|
||||
this.binder.addEventListener(uEvent.ADD, this);
|
||||
this.binder.addEventListener(uEvent.CHANGE, this);
|
||||
this.binder.addEventListener(uEvent.CONFIG, this);
|
||||
this.binder.addEventListener(uEvent.EDIT, this);
|
||||
}
|
||||
|
||||
@ -61,6 +79,21 @@ export default class uList {
|
||||
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 {boolean=} skipUpdate
|
||||
@ -98,9 +131,6 @@ export default class uList {
|
||||
if (this.data.length) {
|
||||
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.onChange();
|
||||
}
|
||||
@ -114,7 +144,7 @@ export default class uList {
|
||||
}
|
||||
for (const option of this.domElement) {
|
||||
if (option.value === 'all') {
|
||||
this.showAllOption = true;
|
||||
this._showAllOption = true;
|
||||
} else if (!option.disabled) {
|
||||
const row = new this.T(parseInt(option.value), option.innerText);
|
||||
this.updateDataRow(row);
|
||||
@ -128,17 +158,19 @@ export default class uList {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {(Event|uEvent)} event
|
||||
* @param {uEvent} event
|
||||
* @param {*=} eventData
|
||||
*/
|
||||
handleEvent(event, eventData) {
|
||||
if (event.type === 'change') {
|
||||
this.selectedId = this.domElement.options[this.domElement.selectedIndex].value;
|
||||
if (event.type === uEvent.CHANGE && eventData.el === this.domElement) {
|
||||
this.selectedId = eventData.id;
|
||||
this.onChange();
|
||||
} else if (event.type === uEvent.EDIT && this.domElement === eventData) {
|
||||
} else if (event.type === uEvent.EDIT && eventData === this.domElement) {
|
||||
this.onEdit();
|
||||
} else if (event.type === uEvent.ADD && this.domElement === eventData) {
|
||||
} else if (event.type === uEvent.ADD && eventData === this.domElement) {
|
||||
this.onAdd();
|
||||
} else if (event.type === uEvent.CONFIG) {
|
||||
this.onConfigChange(eventData);
|
||||
}
|
||||
}
|
||||
|
||||
@ -175,16 +207,20 @@ export default class uList {
|
||||
const currentId = this.current.key;
|
||||
this.data.splice(this.data.findIndex((o) => o.key === id), 1);
|
||||
if (id === currentId) {
|
||||
if (this.data.length) {
|
||||
this.selectedId = this.data[0].key.toString();
|
||||
} else {
|
||||
this.selectedId = '';
|
||||
}
|
||||
this.selectDefault();
|
||||
this.onChange();
|
||||
}
|
||||
this.render();
|
||||
}
|
||||
|
||||
selectDefault() {
|
||||
if (this.data.length) {
|
||||
this.selectedId = this.data[0].key.toString();
|
||||
} else {
|
||||
this.selectedId = '';
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
this.domElement.options.length = 0;
|
||||
if (this.hasHead) {
|
||||
@ -192,7 +228,7 @@ export default class uList {
|
||||
head.disabled = true;
|
||||
this.domElement.options.add(head);
|
||||
}
|
||||
if (this.showAllOption) {
|
||||
if (this._showAllOption) {
|
||||
this.domElement.options.add(new Option(this.allValue, 'all'));
|
||||
}
|
||||
for (const item of this.data) {
|
||||
@ -223,10 +259,25 @@ export default class uList {
|
||||
onEdit() {
|
||||
}
|
||||
|
||||
/**
|
||||
* @abstract
|
||||
*/
|
||||
// eslint-disable-next-line no-unused-vars,no-empty-function,class-methods-use-this
|
||||
onReload() {
|
||||
}
|
||||
|
||||
/**
|
||||
* @abstract
|
||||
*/
|
||||
// eslint-disable-next-line no-unused-vars,no-empty-function,class-methods-use-this
|
||||
onAdd() {
|
||||
}
|
||||
|
||||
/**
|
||||
* @abstract
|
||||
* @param {string} property
|
||||
*/
|
||||
// eslint-disable-next-line no-unused-vars,no-empty-function,class-methods-use-this
|
||||
onConfigChange(property) {
|
||||
}
|
||||
}
|
||||
|
61
js/map.js
61
js/map.js
@ -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 olApi from './mapapi/api_openlayers.js';
|
||||
import { config, lang } from './constants.js';
|
||||
@ -5,6 +24,22 @@ import uEvent from './event.js';
|
||||
import { uLogger } from './ulogger.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
|
||||
* @property {number} loadTime
|
||||
@ -18,13 +53,17 @@ export default class uMap {
|
||||
* @param {uBinder} binder
|
||||
*/
|
||||
constructor(binder) {
|
||||
binder.addEventListener(uEvent.API_CHANGE, this);
|
||||
binder.addEventListener(uEvent.CHART_CLICKED, this);
|
||||
binder.addEventListener(uEvent.TRACK_READY, this);
|
||||
binder.addEventListener(uEvent.UI_READY, this);
|
||||
binder.addEventListener(uEvent.API_CHANGE, this);
|
||||
this.loadTime = 0;
|
||||
this.savedBounds = null;
|
||||
this.api = null;
|
||||
this.mapElement = null;
|
||||
this.lastTrackId = null;
|
||||
this._binder = binder;
|
||||
this.track = null;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -60,7 +99,7 @@ export default class uMap {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
this.api.init(this.mapElement);
|
||||
this.api.init(this._binder, this.mapElement);
|
||||
} catch (e) {
|
||||
setTimeout(() => {
|
||||
this.loadTime += 50;
|
||||
@ -69,17 +108,10 @@ export default class uMap {
|
||||
return;
|
||||
}
|
||||
this.loadTime = 0;
|
||||
let update = 1;
|
||||
if (this.savedBounds) {
|
||||
this.api.zoomToBounds(this.savedBounds);
|
||||
update = 0;
|
||||
}
|
||||
// if (latest && isSelectedAllUsers()) {
|
||||
// loadLastPositionAllUsers();
|
||||
// } else {
|
||||
// loadTrack(ns.userId, ns.trackId, update);
|
||||
uLogger.trackList.onChange();
|
||||
// }
|
||||
// save current api as default
|
||||
uUtils.setCookie('api', config.mapapi, 30);
|
||||
}
|
||||
@ -91,11 +123,12 @@ export default class uMap {
|
||||
*/
|
||||
handleEvent(event, args) {
|
||||
if (event.type === uEvent.TRACK_READY) {
|
||||
/** @type {uTrack} */
|
||||
const track = args;
|
||||
this.api.clearMap();
|
||||
/** @todo use update */
|
||||
const update = 1;
|
||||
this.api.displayTrack(track, update);
|
||||
const onlyReload = track.id !== this.lastTrackId;
|
||||
this.api.displayTrack(track, onlyReload);
|
||||
this.lastTrackId = track.id;
|
||||
} else if (event.type === uEvent.UI_READY) {
|
||||
/** @type {uUI} */
|
||||
const ui = args;
|
||||
@ -105,6 +138,10 @@ export default class uMap {
|
||||
/** @type {string} */
|
||||
const api = args;
|
||||
this.loadMapAPI(api);
|
||||
} else if (event.type === uEvent.CHART_CLICKED) {
|
||||
/** @type {number} */
|
||||
const id = args;
|
||||
this.api.animateMarker(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
* the terms of the GNU General Public License as published by
|
||||
@ -17,14 +18,21 @@
|
||||
*/
|
||||
|
||||
import { config, lang } from '../constants.js';
|
||||
import { uLogger } from '../ulogger.js';
|
||||
import uEvent from '../event.js';
|
||||
import uUI from '../ui.js';
|
||||
import uUtils from '../utils.js';
|
||||
|
||||
// google maps
|
||||
/**
|
||||
* Google Maps API module
|
||||
* @module gmApi
|
||||
* @implements {uMap.api}
|
||||
*/
|
||||
|
||||
/** @type {google.maps.Map} */
|
||||
let map = null;
|
||||
/** @type {uBinder} */
|
||||
let binder = null;
|
||||
/** @type {google.maps.Polyline[]} */
|
||||
const polies = [];
|
||||
/** @type {google.maps.Marker[]} */
|
||||
@ -32,7 +40,7 @@ const markers = [];
|
||||
/** @type {google.maps.InfoWindow[]} */
|
||||
const popups = [];
|
||||
/** @type {google.maps.InfoWindow} */
|
||||
let popup = null;
|
||||
let openPopup = null;
|
||||
/** @type {google.maps.PolylineOptions} */
|
||||
let polyOptions = null;
|
||||
/** @type {google.maps.MapOptions} */
|
||||
@ -45,9 +53,13 @@ let authError = false;
|
||||
|
||||
/**
|
||||
* Initialize map
|
||||
* @param {uBinder} b
|
||||
* @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';
|
||||
uUtils.addScript(url, 'mapapi_gmaps');
|
||||
if (!isLoaded) {
|
||||
@ -89,19 +101,21 @@ function cleanup() {
|
||||
polies.length = 0;
|
||||
markers.length = 0;
|
||||
popups.length = 0;
|
||||
map = null;
|
||||
polyOptions = null;
|
||||
mapOptions = null;
|
||||
popup = null;
|
||||
// ui.clearMapCanvas();
|
||||
openPopup = null;
|
||||
if (map && map.getDiv()) {
|
||||
map.getDiv().innerHTML = '';
|
||||
}
|
||||
map = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Display track
|
||||
* @param {uTrack} track
|
||||
* @param {boolean} update Should fit bounds if true
|
||||
*/
|
||||
function displayTrack(update) {
|
||||
const track = uLogger.trackList.current;
|
||||
function displayTrack(track, update) {
|
||||
if (!track) {
|
||||
return;
|
||||
}
|
||||
@ -113,10 +127,12 @@ function displayTrack(update) {
|
||||
let i = 0;
|
||||
for (const position of track.positions) {
|
||||
// set marker
|
||||
setMarker(i++);
|
||||
setMarker(i++, track);
|
||||
// update polyline
|
||||
const coordinates = new google.maps.LatLng(position.latitude, position.longitude);
|
||||
path.push(coordinates);
|
||||
if (track.continuous) {
|
||||
path.push(coordinates);
|
||||
}
|
||||
latlngbounds.extend(coordinates);
|
||||
}
|
||||
if (update) {
|
||||
@ -133,10 +149,6 @@ function displayTrack(update) {
|
||||
}
|
||||
}
|
||||
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) {
|
||||
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);
|
||||
markers[i].setMap(null);
|
||||
}
|
||||
@ -162,12 +175,13 @@ function clearMap() {
|
||||
|
||||
/**
|
||||
* Set marker
|
||||
* @param {uTrack} track
|
||||
* @param {number} id
|
||||
*/
|
||||
function setMarker(id) {
|
||||
function setMarker(id, track) {
|
||||
// marker
|
||||
const position = uLogger.trackList.current.positions[id];
|
||||
const posLen = uLogger.trackList.current.length;
|
||||
const position = track.positions[id];
|
||||
const posLen = track.length;
|
||||
// noinspection JSCheckFunctionSignatures
|
||||
const marker = new google.maps.Marker({
|
||||
position: new google.maps.LatLng(position.latitude, position.longitude),
|
||||
@ -184,37 +198,45 @@ function setMarker(id) {
|
||||
marker.setIcon('images/marker-white.png');
|
||||
}
|
||||
// popup
|
||||
const content = uUI.getPopupHtml(id);
|
||||
popup = new google.maps.InfoWindow();
|
||||
// noinspection JSUndefinedPropertyAssignment
|
||||
popup.listener = google.maps.event.addListener(marker, 'click', (function (_marker, _content) {
|
||||
return function () {
|
||||
popup.setContent(_content);
|
||||
popup.open(map, _marker);
|
||||
/** @todo handle chart */
|
||||
// ns.chartShowPosition(id);
|
||||
}
|
||||
})(marker, content));
|
||||
const popup = new google.maps.InfoWindow();
|
||||
|
||||
marker.addListener('click',
|
||||
((i) => () => {
|
||||
popup.setContent(uUI.getPopupHtml(i));
|
||||
popup.open(map, marker);
|
||||
binder.dispatchEvent(uEvent.MARKER_SELECT, i);
|
||||
openPopup = popup;
|
||||
popup.addListener('closeclick', () => {
|
||||
binder.dispatchEvent(uEvent.MARKER_SELECT);
|
||||
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);
|
||||
popups.push(popup);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add listener on chart to show position on map
|
||||
* @param {google.visualization.LineChart} chart
|
||||
* @param {google.visualization.DataTable} data
|
||||
*/
|
||||
function addChartEvent(chart, data) {
|
||||
google.visualization.events.addListener(chart, 'select', function () {
|
||||
if (popup) { popup.close(); clearTimeout(timeoutHandle); }
|
||||
const selection = chart.getSelection()[0];
|
||||
if (selection) {
|
||||
const id = data.getValue(selection.row, 0) - 1;
|
||||
const icon = markers[id].getIcon();
|
||||
markers[id].setIcon('images/marker-gold.png');
|
||||
timeoutHandle = setTimeout(function () { markers[id].setIcon(icon); }, 2000);
|
||||
}
|
||||
});
|
||||
function animateMarker(id) {
|
||||
if (openPopup) {
|
||||
openPopup.close();
|
||||
clearTimeout(timeoutHandle);
|
||||
}
|
||||
const icon = markers[id].getIcon();
|
||||
markers[id].setIcon('images/marker-gold.png');
|
||||
markers[id].setAnimation(google.maps.Animation.BOUNCE);
|
||||
timeoutHandle = setTimeout(() => {
|
||||
markers[id].setIcon(icon);
|
||||
markers[id].setAnimation(null);
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -270,14 +292,11 @@ export {
|
||||
cleanup,
|
||||
displayTrack,
|
||||
clearMap,
|
||||
setMarker,
|
||||
addChartEvent,
|
||||
animateMarker,
|
||||
getBounds,
|
||||
zoomToExtent,
|
||||
zoomToBounds,
|
||||
updateSize,
|
||||
setAuthError,
|
||||
setLoaded
|
||||
updateSize
|
||||
}
|
||||
|
||||
|
||||
|
@ -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
|
||||
* the terms of the GNU General Public License as published by
|
||||
@ -17,14 +18,21 @@
|
||||
*/
|
||||
|
||||
import { config } from '../constants.js';
|
||||
import { uLogger } from '../ulogger.js';
|
||||
import uEvent from '../event.js';
|
||||
import uUI from '../ui.js';
|
||||
import uUtils from '../utils.js';
|
||||
|
||||
// openlayers 3+
|
||||
/**
|
||||
* OpenLayers API module
|
||||
* @module olApi
|
||||
* @implements {uMap.api}
|
||||
*/
|
||||
|
||||
/** @type {ol.Map} */
|
||||
let map = null;
|
||||
/** @type {uBinder} */
|
||||
let binder = null;
|
||||
/** @type {ol.layer.Vector} */
|
||||
let layerTrack = null;
|
||||
/** @type {ol.layer.Vector} */
|
||||
@ -33,12 +41,19 @@ let layerMarkers = null;
|
||||
let selectedLayer = null;
|
||||
/** @type {ol.style.Style|{}} */
|
||||
let olStyles = {};
|
||||
/** @type {string} */
|
||||
const name = 'openlayers';
|
||||
/** @type {?number} */
|
||||
let pointOver = null;
|
||||
|
||||
/**
|
||||
* 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('js/lib/ol.js', 'mapapi_openlayers');
|
||||
@ -62,6 +77,41 @@ function init(target) {
|
||||
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
|
||||
const osm = new ol.layer.Tile({
|
||||
name: 'OpenStreetMap',
|
||||
@ -71,7 +121,7 @@ function init(target) {
|
||||
map.addLayer(osm);
|
||||
selectedLayer = osm;
|
||||
|
||||
// add extra layers
|
||||
// add extra tile layers
|
||||
for (const layerName in config.ol_layers) {
|
||||
if (config.ol_layers.hasOwnProperty(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({
|
||||
stroke: new ol.style.Stroke({
|
||||
color: uUtils.hexToRGBA(config.strokeColor, config.strokeOpacity),
|
||||
@ -107,7 +157,11 @@ function init(target) {
|
||||
map.addLayer(layerTrack);
|
||||
map.addLayer(layerMarkers);
|
||||
|
||||
// styles
|
||||
initLayerSwitcher();
|
||||
}
|
||||
|
||||
|
||||
function initStyles() {
|
||||
olStyles = {};
|
||||
const iconRed = new ol.style.Icon({
|
||||
anchor: [ 0.5, 1 ],
|
||||
@ -138,8 +192,9 @@ function init(target) {
|
||||
olStyles['gold'] = new ol.style.Style({
|
||||
image: iconGold
|
||||
});
|
||||
}
|
||||
|
||||
// popups
|
||||
function initPopups() {
|
||||
const popupContainer = document.createElement('div');
|
||||
popupContainer.id = 'popup';
|
||||
popupContainer.className = 'ol-popup';
|
||||
@ -183,25 +238,17 @@ function init(target) {
|
||||
popup.setPosition(coordinate);
|
||||
popupContent.innerHTML = uUI.getPopupHtml(feature.getId());
|
||||
map.addOverlay(popup);
|
||||
// ns.chartShowPosition(id);
|
||||
binder.dispatchEvent(uEvent.MARKER_SELECT, feature.getId());
|
||||
} else {
|
||||
// popup destroy
|
||||
// eslint-disable-next-line no-undefined
|
||||
popup.setPosition(undefined);
|
||||
binder.dispatchEvent(uEvent.MARKER_SELECT);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// change mouse cursor when over marker
|
||||
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
|
||||
function initLayerSwitcher() {
|
||||
const switcher = document.createElement('div');
|
||||
switcher.id = 'switcher';
|
||||
switcher.className = 'ol-control';
|
||||
@ -283,43 +330,45 @@ function init(target) {
|
||||
* Clean up API
|
||||
*/
|
||||
function cleanup() {
|
||||
map = null;
|
||||
layerTrack = null;
|
||||
layerMarkers = null;
|
||||
selectedLayer = null;
|
||||
olStyles = null;
|
||||
uUI.removeElementById('popup');
|
||||
uUI.removeElementById('switcher');
|
||||
// ui.clearMapCanvas();
|
||||
if (map && map.getTargetElement()) {
|
||||
map.getTargetElement().innerHTML = '';
|
||||
}
|
||||
map = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Display track
|
||||
* @param {uTrack} track Track
|
||||
* @param {boolean} update Should fit bounds if true
|
||||
*/
|
||||
function displayTrack(update) {
|
||||
const track = uLogger.trackList.current;
|
||||
function displayTrack(track, update) {
|
||||
if (!track) {
|
||||
return;
|
||||
}
|
||||
const points = [];
|
||||
let i = 0;
|
||||
const lineString = new ol.geom.LineString([]);
|
||||
for (const position of track.positions) {
|
||||
// set marker
|
||||
setMarker(i++);
|
||||
// update polyline
|
||||
const point = ol.proj.fromLonLat([ position.longitude, position.latitude ]);
|
||||
points.push(point);
|
||||
setMarker(i++, track);
|
||||
if (track.continuous) {
|
||||
// update polyline
|
||||
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({
|
||||
geometry: lineString
|
||||
});
|
||||
|
||||
layerTrack.getSource().addFeature(lineFeature);
|
||||
|
||||
let extent = layerTrack.getSource().getExtent();
|
||||
let extent = layerMarkers.getSource().getExtent();
|
||||
|
||||
map.getControls().forEach((el) => {
|
||||
if (el instanceof ol.control.ZoomToExtent) {
|
||||
@ -341,11 +390,6 @@ function displayTrack(update) {
|
||||
label: getExtentImg()
|
||||
});
|
||||
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
|
||||
* @param {number} id
|
||||
* @param {uTrack} track
|
||||
*/
|
||||
function setMarker(id) {
|
||||
function setMarker(id, track) {
|
||||
// marker
|
||||
const position = uLogger.trackList.current.positions[id];
|
||||
const posLen = uLogger.trackList.current.positions.length;
|
||||
const position = track.positions[id];
|
||||
const posLen = track.positions.length;
|
||||
const marker = new ol.Feature({
|
||||
geometry: new ol.geom.Point(ol.proj.fromLonLat([ position.longitude, position.latitude ]))
|
||||
});
|
||||
@ -384,31 +429,19 @@ function setMarker(id) {
|
||||
}
|
||||
marker.setStyle(iconStyle);
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add listener on chart to show position on map
|
||||
* @param {google.visualization.LineChart} chart
|
||||
* @param {google.visualization.DataTable} data
|
||||
* Animate marker
|
||||
* @param id Marker sequential id
|
||||
*/
|
||||
function addChartEvent(chart, data) {
|
||||
google.visualization.events.addListener(chart, 'select', () => {
|
||||
const selection = chart.getSelection()[0];
|
||||
if (selection) {
|
||||
const id = data.getValue(selection.row, 0) - 1;
|
||||
const marker = layerMarkers.getSource().getFeatureById(id);
|
||||
const initStyle = marker.getStyle();
|
||||
const iconStyle = olStyles['gold'];
|
||||
marker.setStyle(iconStyle);
|
||||
setTimeout(() => {
|
||||
marker.setStyle(initStyle);
|
||||
}, 2000);
|
||||
}
|
||||
});
|
||||
function animateMarker(id) {
|
||||
const marker = layerMarkers.getSource().getFeatureById(id);
|
||||
const initStyle = marker.getStyle();
|
||||
const iconStyle = olStyles['gold'];
|
||||
marker.setStyle(iconStyle);
|
||||
setTimeout(() => marker.setStyle(initStyle), 2000);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -466,8 +499,7 @@ export {
|
||||
cleanup,
|
||||
displayTrack,
|
||||
clearMap,
|
||||
setMarker,
|
||||
addChartEvent,
|
||||
animateMarker,
|
||||
getBounds,
|
||||
zoomToExtent,
|
||||
zoomToBounds,
|
||||
|
19
js/modal.js
19
js/modal.js
@ -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';
|
||||
|
||||
/**
|
||||
|
@ -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';
|
||||
|
||||
/**
|
||||
|
51
js/track.js
51
js/track.js
@ -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 uAjax from './ajax.js';
|
||||
import uData from './data.js';
|
||||
@ -10,6 +29,7 @@ import uPosition from './position.js';
|
||||
* @property {number} id
|
||||
* @property {string} name
|
||||
* @property {uUser} user
|
||||
* @property {boolean} continuous
|
||||
* @property {?uPosition[]} positions
|
||||
* @property {?Array<{x: number, y: number}>} plotData
|
||||
*/
|
||||
@ -27,6 +47,7 @@ export default class uTrack extends uData {
|
||||
this._plotData = null;
|
||||
this._maxId = 0;
|
||||
this._onlyLatest = false;
|
||||
this._continuous = true;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -50,6 +71,20 @@ export default class uTrack extends uData {
|
||||
return this._user;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {boolean} value
|
||||
*/
|
||||
set continuous(value) {
|
||||
this._continuous = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {boolean}
|
||||
*/
|
||||
get continuous() {
|
||||
return this._continuous;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {boolean} value
|
||||
*/
|
||||
@ -64,8 +99,8 @@ export default class uTrack extends uData {
|
||||
|
||||
/**
|
||||
* Get track data from xml
|
||||
* @param {XMLDocument} xml
|
||||
* @param {boolean} isUpdate
|
||||
* @param {XMLDocument} xml XML with positions data
|
||||
* @param {boolean} isUpdate If true append to old data
|
||||
*/
|
||||
fromXml(xml, isUpdate) {
|
||||
let positions = [];
|
||||
@ -139,16 +174,15 @@ export default class uTrack extends uData {
|
||||
} else {
|
||||
data.afterid = this._maxId;
|
||||
}
|
||||
return uAjax.get('utils/getpositions.php', data, {
|
||||
// loader: ui.trackTitle
|
||||
}).then((xml) => {
|
||||
return uAjax.get('utils/getpositions.php', data).then((xml) => {
|
||||
this.fromXml(xml, isUpdate);
|
||||
return this.render();
|
||||
this.render();
|
||||
return xml;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Save track data
|
||||
* @param {string} action
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
@ -161,6 +195,9 @@ export default class uTrack extends uData {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Render track
|
||||
*/
|
||||
render() {
|
||||
this.emit(uEvent.TRACK_READY);
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
/* μlogger
|
||||
/*
|
||||
* μlogger
|
||||
*
|
||||
* Copyright(C) 2019 Bartek Fabiszewski (www.fabiszewski.net)
|
||||
*
|
||||
|
144
js/tracklist.js
144
js/tracklist.js
@ -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 TrackDialog from './trackdialog.js';
|
||||
import uAjax from './ajax.js';
|
||||
@ -21,7 +40,6 @@ export default class TrackList extends uList {
|
||||
constructor(selector, binder) {
|
||||
super(selector, binder, uTrack);
|
||||
if (binder) {
|
||||
this.binder.addEventListener(uEvent.CONFIG, this);
|
||||
this.binder.addEventListener(uEvent.EXPORT, this);
|
||||
this.binder.addEventListener(uEvent.IMPORT, this);
|
||||
}
|
||||
@ -42,7 +60,7 @@ export default class TrackList extends uList {
|
||||
* @param {*=} data
|
||||
*/
|
||||
handleEvent(event, data) {
|
||||
if (event.type === 'change') {
|
||||
if (event.type === uEvent.CHANGE) {
|
||||
config.showLatest = false;
|
||||
}
|
||||
super.handleEvent(event, data);
|
||||
@ -50,12 +68,6 @@ export default class TrackList extends uList {
|
||||
this.current.export(data);
|
||||
} else if (event.type === uEvent.IMPORT) {
|
||||
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>}
|
||||
*/
|
||||
import(form) {
|
||||
return uAjax.post('utils/import.php', form,
|
||||
{
|
||||
// loader: ui.importTitle
|
||||
})
|
||||
this.emit(true, 'import');
|
||||
return uAjax.post('utils/import.php', form)
|
||||
.then((xml) => {
|
||||
const root = xml.getElementsByTagName('root');
|
||||
const trackCnt = uUtils.getNodeAsInt(root[0], 'trackcnt');
|
||||
if (trackCnt > 1) {
|
||||
alert(uUtils.sprintf(lang.strings['imultiple'], trackCnt));
|
||||
}
|
||||
const trackId = uUtils.getNodeAsInt(root[0], 'trackid');
|
||||
return this.fetch().then(() => this.select(trackId));
|
||||
}).catch((msg) => alert(`${lang.strings['actionfailure']}\n${msg}`));
|
||||
const root = xml.getElementsByTagName('root');
|
||||
const trackCnt = uUtils.getNodeAsInt(root[0], 'trackcnt');
|
||||
if (trackCnt > 1) {
|
||||
alert(uUtils.sprintf(lang.strings['imultiple'], trackCnt));
|
||||
}
|
||||
const trackId = uUtils.getNodeAsInt(root[0], 'trackid');
|
||||
this.emit(false, 'import');
|
||||
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
|
||||
* @throws
|
||||
* @return {Promise<Document, string>}
|
||||
*/
|
||||
fetch() {
|
||||
this.emit(true, 'track');
|
||||
return uAjax.get('utils/gettracks.php',
|
||||
{
|
||||
userid: uLogger.userList.current.id
|
||||
},
|
||||
{
|
||||
// loader: ui.trackTitle
|
||||
}).then((xml) => {
|
||||
this.clear();
|
||||
return this.fromXml(xml.getElementsByTagName('track'), 'trackid', 'trackname');
|
||||
});
|
||||
})
|
||||
.then((xml) => {
|
||||
this.clear();
|
||||
this.fromXml(xml.getElementsByTagName('track'), 'trackid', 'trackname');
|
||||
this.emit(false, 'track');
|
||||
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>}
|
||||
*/
|
||||
fetchLatest() {
|
||||
return uAjax.get('utils/getpositions.php', {
|
||||
userid: uLogger.userList.current.id,
|
||||
this.emit(true, 'track');
|
||||
const data = {
|
||||
last: 1
|
||||
}, {
|
||||
// loader: ui.trackTitle
|
||||
}).then((xml) => {
|
||||
const xmlPos = xml.getElementsByTagName('position');
|
||||
if (xmlPos.length === 1) {
|
||||
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;
|
||||
return this.current.render();
|
||||
};
|
||||
const allUsers = uLogger.userList.isSelectedAllOption;
|
||||
if (!allUsers) {
|
||||
data.userid = uLogger.userList.current.id;
|
||||
}
|
||||
return uAjax.get('utils/getpositions.php', data).then((xml) => {
|
||||
if (!allUsers) {
|
||||
const xmlPos = xml.getElementsByTagName('position');
|
||||
// single user
|
||||
if (xmlPos.length === 1) {
|
||||
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
|
||||
return this.fetch().fetchLatest();
|
||||
} else {
|
||||
// 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
|
||||
*/
|
||||
onChange() {
|
||||
this.fetchTrack();
|
||||
if (!config.showLatest) {
|
||||
this.fetchTrack();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch and render track
|
||||
*/
|
||||
fetchTrack() {
|
||||
if (this.current) {
|
||||
this.emit(true, 'track');
|
||||
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
144
js/ui.js
@ -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 uEvent from './event.js';
|
||||
import { uLogger } from './ulogger.js';
|
||||
@ -13,6 +32,8 @@ export default class uUI {
|
||||
binder.addEventListener(uEvent.CONFIG, this);
|
||||
binder.addEventListener(uEvent.CHART_READY, this);
|
||||
binder.addEventListener(uEvent.OPEN_URL, this);
|
||||
binder.addEventListener(uEvent.LOADER, this);
|
||||
binder.addEventListener(uEvent.TRACK_READY, this);
|
||||
document.addEventListener('DOMContentLoaded', () => { this.initUI(); });
|
||||
this.isLiveOn = false;
|
||||
}
|
||||
@ -24,11 +45,11 @@ export default class uUI {
|
||||
/** @type {HTMLElement} */
|
||||
this.menu = document.getElementById('menu');
|
||||
/** @type {?HTMLElement} */
|
||||
this.menuHead = document.getElementById('menu_head');
|
||||
this.userMenu = document.getElementById('user-menu');
|
||||
/** @type {?HTMLElement} */
|
||||
this.userDropdown = document.getElementById('user_dropdown');
|
||||
this.userDropdown = document.getElementById('user-dropdown');
|
||||
/** @type {?HTMLElement} */
|
||||
this.menuPass = document.getElementById('menu_pass');
|
||||
this.userPass = document.getElementById('user-pass');
|
||||
// noinspection JSValidateTypes
|
||||
/** @type {?HTMLSelectElement} */
|
||||
this.userSelect = function () {
|
||||
@ -41,17 +62,17 @@ export default class uUI {
|
||||
this.trackSelect = document.getElementsByName('track')[0];
|
||||
// noinspection JSValidateTypes
|
||||
/** @type {HTMLSelectElement} */
|
||||
this.api = document.getElementsByName('api')[0];
|
||||
this.apiSelect = document.getElementsByName('api')[0];
|
||||
// noinspection JSValidateTypes
|
||||
/** @type {HTMLSelectElement} */
|
||||
this.lang = document.getElementsByName('lang')[0];
|
||||
this.langSelect = document.getElementsByName('lang')[0];
|
||||
// noinspection JSValidateTypes
|
||||
/** @type {HTMLSelectElement} */
|
||||
this.units = document.getElementsByName('units')[0];
|
||||
this.unitsSelect = document.getElementsByName('units')[0];
|
||||
/** @type {HTMLElement} */
|
||||
this.chart = document.getElementById('chart');
|
||||
/** @type {HTMLElement} */
|
||||
this.chartClose = document.getElementById('chart_close');
|
||||
this.chartClose = document.getElementById('chart-close');
|
||||
/** @type {HTMLElement} */
|
||||
this.bottom = document.getElementById('bottom');
|
||||
/** @type {HTMLElement} */
|
||||
@ -63,31 +84,29 @@ export default class uUI {
|
||||
/** @type {HTMLElement} */
|
||||
this.track = document.getElementById('track');
|
||||
/** @type {HTMLElement} */
|
||||
this.trackTitle = this.track ? this.track.getElementsByClassName('menutitle')[0] : null;
|
||||
this.trackTitle = document.querySelector('label[for="track"]');
|
||||
/** @type {HTMLElement} */
|
||||
this.import = document.getElementById('import');
|
||||
/** @type {HTMLElement} */
|
||||
this.importTitle = this.import ? this.import.getElementsByClassName('menutitle')[0] : null;
|
||||
this.importTitle = document.getElementById('import') || null;
|
||||
/** @type {HTMLElement} */
|
||||
this.summary = document.getElementById('summary');
|
||||
/** @type {HTMLElement} */
|
||||
this.latest = document.getElementById('latest');
|
||||
/** @type {HTMLElement} */
|
||||
this.autoReload = document.getElementById('auto_reload');
|
||||
this.autoReload = document.getElementById('auto-reload');
|
||||
/** @type {HTMLElement} */
|
||||
this.forceReload = document.getElementById('force_reload');
|
||||
this.forceReload = document.getElementById('force-reload');
|
||||
/** @type {HTMLElement} */
|
||||
this.auto = document.getElementById('auto');
|
||||
this.interval = document.getElementById('interval');
|
||||
/** @type {HTMLElement} */
|
||||
this.setTime = document.getElementById('set_time');
|
||||
this.setInterval = document.getElementById('set-interval');
|
||||
/** @type {HTMLElement} */
|
||||
this.exportKml = document.getElementById('export_kml');
|
||||
this.exportKml = document.getElementById('export-kml');
|
||||
/** @type {HTMLElement} */
|
||||
this.exportGpx = document.getElementById('export_gpx');
|
||||
this.exportGpx = document.getElementById('export-gpx');
|
||||
/** @type {?HTMLElement} */
|
||||
this.inputFile = document.getElementById('inputFile');
|
||||
this.inputFile = document.getElementById('input-file');
|
||||
/** @type {HTMLElement} */
|
||||
this.importGpx = document.getElementById('import_gpx');
|
||||
this.importGpx = document.getElementById('import-gpx');
|
||||
/** @type {?HTMLElement} */
|
||||
this.addUser = document.getElementById('adduser');
|
||||
/** @type {?HTMLElement} */
|
||||
@ -99,29 +118,37 @@ export default class uUI {
|
||||
/** @type {HTMLElement} */
|
||||
this.head = document.getElementsByTagName('head')[0];
|
||||
|
||||
if (this.menuHead) {
|
||||
this.menuHead.onclick = () => this.showUserMenu();
|
||||
if (this.userMenu) {
|
||||
this.userMenu.onclick = () => this.showUserMenu();
|
||||
}
|
||||
if (this.menuPass) {
|
||||
this.menuPass.onclick = () => {
|
||||
if (this.userPass) {
|
||||
this.userPass.onclick = () => {
|
||||
this.emit(uEvent.PASSWORD);
|
||||
}
|
||||
}
|
||||
this.hideUserMenu = this.hideUserMenu.bind(this);
|
||||
this.latest.onchange = () => uUI.toggleLatest();
|
||||
this.autoReload.onchange = () => this.toggleAutoReload();
|
||||
this.setTime.onclick = () => this.setAutoReloadTime();
|
||||
this.setInterval.onclick = () => this.setAutoReloadTime();
|
||||
this.forceReload.onclick = () => this.trackReload();
|
||||
this.chartLink.onclick = () => this.toggleChart();
|
||||
this.api.onchange = () => {
|
||||
const api = this.api.options[this.api.selectedIndex].value;
|
||||
this.trackSelect.onchange = () => {
|
||||
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.lang.onchange = () => {
|
||||
uUI.setLang(this.lang.options[this.lang.selectedIndex].value);
|
||||
this.langSelect.onchange = () => {
|
||||
uUI.setLang(this.langSelect.options[this.langSelect.selectedIndex].value);
|
||||
};
|
||||
this.units.onchange = () => {
|
||||
uUI.setUnits(this.units.options[this.units.selectedIndex].value);
|
||||
this.unitsSelect.onchange = () => {
|
||||
uUI.setUnits(this.unitsSelect.options[this.unitsSelect.selectedIndex].value);
|
||||
};
|
||||
this.exportKml.onclick = () => {
|
||||
this.emit(uEvent.EXPORT, 'kml');
|
||||
@ -201,7 +228,7 @@ export default class uUI {
|
||||
const i = parseInt(prompt(lang.strings['newinterval']));
|
||||
if (!isNaN(i) && i !== config.interval) {
|
||||
config.interval = i;
|
||||
this.auto.innerHTML = config.interval.toString();
|
||||
this.interval.innerHTML = config.interval.toString();
|
||||
// if live tracking on, reload with new interval
|
||||
if (this.isLiveOn) {
|
||||
this.stopAutoReload();
|
||||
@ -313,13 +340,9 @@ export default class uUI {
|
||||
let date = '–––';
|
||||
let time = '–––';
|
||||
if (pos.timestamp > 0) {
|
||||
const d = new Date(pos.timestamp * 1000);
|
||||
date = `${d.getFullYear()}-${(`0${d.getMonth() + 1}`).slice(-2)}-${(`0${d.getDate()}`).slice(-2)}`;
|
||||
time = d.toTimeString();
|
||||
let offset;
|
||||
if ((offset = time.indexOf(' ')) >= 0) {
|
||||
time = `${time.substr(0, offset)} <span class="smaller">${time.substr(offset + 1)}</span>`;
|
||||
}
|
||||
const parts = uUtils.getTimeString(new Date(pos.timestamp * 1000));
|
||||
date = parts.date;
|
||||
time = `${parts.time}<span class="smaller">${parts.zone}</span>`;
|
||||
}
|
||||
let provider = '';
|
||||
if (pos.provider === 'gps') {
|
||||
@ -355,6 +378,37 @@ export default class uUI {
|
||||
</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
|
||||
*/
|
||||
@ -412,12 +466,26 @@ export default class uUI {
|
||||
} else {
|
||||
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) {
|
||||
window.location.assign(args);
|
||||
} else if (event.type === uEvent.CONFIG) {
|
||||
if (args === '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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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 TrackList from './tracklist.js';
|
||||
import UserList from './userlist.js';
|
||||
|
19
js/user.js
19
js/user.js
@ -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 uData from './data.js';
|
||||
import uUtils from './utils.js';
|
||||
|
@ -1,4 +1,5 @@
|
||||
/* μlogger
|
||||
/*
|
||||
* μlogger
|
||||
*
|
||||
* Copyright(C) 2019 Bartek Fabiszewski (www.fabiszewski.net)
|
||||
*
|
||||
|
@ -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 UserDialog from './userdialog.js';
|
||||
import uList from './list.js';
|
||||
@ -25,15 +44,28 @@ export default class UserList extends uList {
|
||||
* @override
|
||||
*/
|
||||
onChange() {
|
||||
if (this.isSelectedAllOption) {
|
||||
// clearOptions(ui.trackSelect);
|
||||
// loadLastPositionAllUsers();
|
||||
} else if (config.showLatest) {
|
||||
uLogger.trackList.fetchLatest()
|
||||
.catch((msg) => alert(`${lang.strings['actionfailure']}\n${msg}`));
|
||||
if (config.showLatest) {
|
||||
if (this.isSelectedAllOption) {
|
||||
uLogger.trackList.fetchLatest();
|
||||
} else {
|
||||
uLogger.trackList.fetch()
|
||||
.then(() => uLogger.trackList.fetchLatest());
|
||||
}
|
||||
} else {
|
||||
uLogger.trackList.fetch()
|
||||
.catch((msg) => alert(`${lang.strings['actionfailure']}\n${msg}`));
|
||||
uLogger.trackList.fetch();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
42
js/utils.js
42
js/utils.js
@ -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 {
|
||||
|
||||
@ -221,6 +239,30 @@ export default class uUtils {
|
||||
}
|
||||
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
|
||||
|
Loading…
x
Reference in New Issue
Block a user