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"] {
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
View File

@ -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>

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';
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);
}
});
}
}

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 { lang } from './constants.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';
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 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'));
}
}

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';
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');
}
}
}

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 uConfig from './config.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
*/

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 */
/**
* 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);
})();
}

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 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) {
}
}

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 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);
}
}
}

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
* 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
}

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
* 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,

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';
/**

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';
/**

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 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);
}

View File

@ -1,4 +1,5 @@
/* μlogger
/*
* μlogger
*
* 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 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
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 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);
}
}
}

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 TrackList from './tracklist.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 uData from './data.js';
import uUtils from './utils.js';

View File

@ -1,4 +1,5 @@
/* μlogger
/*
* μlogger
*
* 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 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;
}
}
}

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 {
@ -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