Remove obsolete files
This commit is contained in:
parent
8e2356aca0
commit
83a1546e28
109
js/ajax.js
109
js/ajax.js
@ -1,109 +0,0 @@
|
|||||||
/*
|
|
||||||
* μ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 {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Perform POST HTTP request
|
|
||||||
* @alias ajax
|
|
||||||
*/
|
|
||||||
static post(url, data, options) {
|
|
||||||
const params = options || {};
|
|
||||||
params.method = 'POST';
|
|
||||||
return this.ajax(url, data, params);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Perform GET HTTP request
|
|
||||||
* @alias ajax
|
|
||||||
*/
|
|
||||||
static get(url, data, options) {
|
|
||||||
const params = options || {};
|
|
||||||
params.method = 'GET';
|
|
||||||
return this.ajax(url, data, params);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Perform ajax HTTP request
|
|
||||||
* @param {string} url Request URL
|
|
||||||
* @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'
|
|
||||||
* @return {Promise<Document, string>}
|
|
||||||
*/
|
|
||||||
static ajax(url, data, options) {
|
|
||||||
const params = [];
|
|
||||||
data = data || {};
|
|
||||||
options = options || {};
|
|
||||||
let method = options.method || 'GET';
|
|
||||||
const xhr = new XMLHttpRequest();
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
xhr.onreadystatechange = function () {
|
|
||||||
if (xhr.readyState !== 4) { return; }
|
|
||||||
let message = '';
|
|
||||||
let error = true;
|
|
||||||
if (xhr.status === 200) {
|
|
||||||
const xml = xhr.responseXML;
|
|
||||||
if (xml) {
|
|
||||||
const root = xml.getElementsByTagName('root');
|
|
||||||
if (root.length && uUtils.getNode(root[0], 'error') !== '1') {
|
|
||||||
if (resolve && typeof resolve === 'function') {
|
|
||||||
resolve(xml);
|
|
||||||
}
|
|
||||||
error = false;
|
|
||||||
} else if (root.length) {
|
|
||||||
const errorMsg = uUtils.getNode(root[0], 'message');
|
|
||||||
if (errorMsg) {
|
|
||||||
message = errorMsg;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (error && reject && typeof reject === 'function') {
|
|
||||||
reject(message);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let body = null;
|
|
||||||
if (data instanceof HTMLFormElement) {
|
|
||||||
// noinspection JSCheckFunctionSignatures
|
|
||||||
body = new FormData(data);
|
|
||||||
method = 'POST';
|
|
||||||
} else {
|
|
||||||
for (const key in data) {
|
|
||||||
if (data.hasOwnProperty(key)) {
|
|
||||||
params.push(key + '=' + encodeURIComponent(data[key]));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
body = params.join('&');
|
|
||||||
body = body.replace(/%20/g, '+');
|
|
||||||
}
|
|
||||||
if (method === 'GET' && params.length) {
|
|
||||||
url += '?' + body;
|
|
||||||
body = null;
|
|
||||||
}
|
|
||||||
xhr.open(method, url, true);
|
|
||||||
if (method === 'POST' && !(data instanceof HTMLFormElement)) {
|
|
||||||
xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
|
|
||||||
}
|
|
||||||
xhr.send(body);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
96
js/auth.js
96
js/auth.js
@ -1,96 +0,0 @@
|
|||||||
/*
|
|
||||||
* μ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';
|
|
||||||
|
|
||||||
export default class uAuth {
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
/** @type {boolean} */
|
|
||||||
this._isAdmin = false;
|
|
||||||
/** @type {boolean} */
|
|
||||||
this._isAuthenticated = false;
|
|
||||||
/** @type {?uUser} */
|
|
||||||
this._user = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {uUser} user
|
|
||||||
*/
|
|
||||||
set user(user) {
|
|
||||||
this._user = user;
|
|
||||||
this._isAuthenticated = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {boolean} isAdmin
|
|
||||||
*/
|
|
||||||
set isAdmin(isAdmin) {
|
|
||||||
this._isAdmin = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return {boolean}
|
|
||||||
*/
|
|
||||||
get isAdmin() {
|
|
||||||
return this._isAdmin;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return {boolean}
|
|
||||||
*/
|
|
||||||
get isAuthenticated() {
|
|
||||||
return this._isAuthenticated;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return {?uUser}
|
|
||||||
*/
|
|
||||||
get user() {
|
|
||||||
return this._user;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {uEvent} event
|
|
||||||
*/
|
|
||||||
handleEvent(event) {
|
|
||||||
if (event.type === uEvent.PASSWORD && this.isAuthenticated) {
|
|
||||||
this.changePassword();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {UserDialog=} modal
|
|
||||||
*/
|
|
||||||
changePassword(modal) {
|
|
||||||
const dialog = modal || new UserDialog('pass', this.user);
|
|
||||||
dialog.show()
|
|
||||||
.then((result) => this.user.changePass(result.data.password, result.data.oldPassword))
|
|
||||||
.then(() => {
|
|
||||||
alert(lang.strings['actionsuccess']);
|
|
||||||
dialog.hide();
|
|
||||||
})
|
|
||||||
.catch((msg) => {
|
|
||||||
alert(`${lang.strings['actionfailure']}\n${msg}`);
|
|
||||||
this.changePassword(dialog);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
77
js/binder.js
77
js/binder.js
@ -1,77 +0,0 @@
|
|||||||
/*
|
|
||||||
* μ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 {
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
/** @type {Map<string, uEvent>} */
|
|
||||||
this.events = new Map();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string} type
|
|
||||||
*/
|
|
||||||
addEvent(type) {
|
|
||||||
this.events.set(type, new uEvent(type));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string} type
|
|
||||||
* @param {(Object|Function)} listener
|
|
||||||
*/
|
|
||||||
addEventListener(type, listener) {
|
|
||||||
if (!this.events.has(type)) {
|
|
||||||
this.addEvent(type);
|
|
||||||
}
|
|
||||||
if ((typeof listener === 'object') &&
|
|
||||||
(typeof listener.handleEvent === 'function')) {
|
|
||||||
listener = listener.handleEvent.bind(listener);
|
|
||||||
}
|
|
||||||
if (typeof listener !== 'function') {
|
|
||||||
throw new Error(`Wrong listener type: ${typeof listener}`);
|
|
||||||
}
|
|
||||||
this.events.get(type).addListener(listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string} type
|
|
||||||
* @param {(Object|Function)} listener
|
|
||||||
*/
|
|
||||||
removeEventListener(type, listener) {
|
|
||||||
if (this.events.has(type)) {
|
|
||||||
if ((typeof listener === 'object') &&
|
|
||||||
(typeof listener.handleEvent === 'function')) {
|
|
||||||
listener = listener.handleEvent;
|
|
||||||
}
|
|
||||||
this.events.get(type).removeListener(listener);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string} type
|
|
||||||
* @param {*=} args
|
|
||||||
*/
|
|
||||||
dispatchEvent(type, args) {
|
|
||||||
if (this.events.has(type)) {
|
|
||||||
this.events.get(type).dispatch(args);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
File diff suppressed because one or more lines are too long
172
js/chart.js
172
js/chart.js
@ -1,172 +0,0 @@
|
|||||||
/*
|
|
||||||
* μ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';
|
|
||||||
|
|
||||||
/* global Chartist */
|
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
|
||||||
Chart.onDomLoaded();
|
|
||||||
});
|
|
||||||
|
|
||||||
export default class Chart {
|
|
||||||
/**
|
|
||||||
* @param {uBinder} binder
|
|
||||||
*/
|
|
||||||
constructor(binder) {
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return {Array<{x: number, y: number}>}
|
|
||||||
*/
|
|
||||||
get data() {
|
|
||||||
return this._data;
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
if (!this._targetEl) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const chart = new Chartist.Line(this._targetEl, {
|
|
||||||
series: [ this.data ]
|
|
||||||
}, {
|
|
||||||
lineSmooth: true,
|
|
||||||
showArea: true,
|
|
||||||
axisX: {
|
|
||||||
type: Chartist.AutoScaleAxis,
|
|
||||||
onlyInteger: true,
|
|
||||||
showLabel: false
|
|
||||||
},
|
|
||||||
plugins: [
|
|
||||||
Chartist.plugins.ctAxisTitle({
|
|
||||||
axisY: {
|
|
||||||
axisTitle: `${lang.strings['altitude']} (${config.unit_m})`,
|
|
||||||
axisClass: 'ct-axis-title',
|
|
||||||
offset: {
|
|
||||||
x: 0,
|
|
||||||
y: 20
|
|
||||||
},
|
|
||||||
textAnchor: 'middle',
|
|
||||||
flipTitle: true
|
|
||||||
}
|
|
||||||
})
|
|
||||||
]
|
|
||||||
});
|
|
||||||
|
|
||||||
chart.on('created', () => {
|
|
||||||
this._points = document.querySelectorAll('.ct-chart-line .ct-point');
|
|
||||||
const len = this._points.length;
|
|
||||||
for (let i = 0; i < len; i++) {
|
|
||||||
((id) => {
|
|
||||||
this._points[id].addEventListener('click', () => {
|
|
||||||
this._binder.dispatchEvent(uEvent.CHART_CLICKED, id);
|
|
||||||
});
|
|
||||||
})(i);
|
|
||||||
}
|
|
||||||
this._binder.dispatchEvent(uEvent.CHART_READY, len);
|
|
||||||
});
|
|
||||||
|
|
||||||
// need to update chart first time the container becomes visible
|
|
||||||
if (!this.isVisible()) {
|
|
||||||
const observer = new MutationObserver(() => {
|
|
||||||
if (this.isVisible()) {
|
|
||||||
// eslint-disable-next-line no-underscore-dangle
|
|
||||||
this._targetEl.__chartist__.update();
|
|
||||||
observer.disconnect();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
observer.observe(this._targetEl.parentNode, { attributes: true });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
isVisible() {
|
|
||||||
return this._targetEl && this._targetEl.parentNode && this._targetEl.parentNode.style.display === 'block';
|
|
||||||
}
|
|
||||||
|
|
||||||
static onDomLoaded() {
|
|
||||||
uUtils.addScript('js/lib/chartist.min.js', 'chartist_js', () => {
|
|
||||||
uUtils.addScript('js/lib/chartist-plugin-axistitle.min.js', 'chartist_axistitle_js');
|
|
||||||
});
|
|
||||||
uUtils.addCss('css/chartist.min.css', 'chartist_css');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {uEvent} event
|
|
||||||
* @param {*=} args
|
|
||||||
*/
|
|
||||||
handleEvent(event, args) {
|
|
||||||
if (event.type === uEvent.TRACK_READY) {
|
|
||||||
/** @type {uTrack} */
|
|
||||||
const track = args;
|
|
||||||
this._data = track.plotData;
|
|
||||||
this.render()
|
|
||||||
} else if (event.type === uEvent.UI_READY) {
|
|
||||||
/** @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'));
|
|
||||||
}
|
|
||||||
}
|
|
371
js/config.js
371
js/config.js
@ -1,371 +0,0 @@
|
|||||||
/*
|
|
||||||
* μ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 {
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
this.inititialize();
|
|
||||||
}
|
|
||||||
|
|
||||||
inititialize() {
|
|
||||||
this.interval = 10;
|
|
||||||
this.units = 'metric';
|
|
||||||
this.mapapi = 'openlayers';
|
|
||||||
this.gkey = null;
|
|
||||||
this.ol_layers = {};
|
|
||||||
this.init_latitude = 52.23;
|
|
||||||
this.init_longitude = 21.01;
|
|
||||||
this.pass_regex = new RegExp('(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.{12,})');
|
|
||||||
this.strokeWeight = 2;
|
|
||||||
this.strokeColor = '#ff0000';
|
|
||||||
this.strokeOpacity = 1;
|
|
||||||
this.showLatest = false;
|
|
||||||
// marker colors
|
|
||||||
this.colorNormal = '#fff';
|
|
||||||
this.colorStart = '#55b500';
|
|
||||||
this.colorStop = '#ff6a00';
|
|
||||||
this.colorExtra = '#ccc';
|
|
||||||
this.colorHilite = '#feff6a';
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param {uBinder} binder
|
|
||||||
*/
|
|
||||||
set binder(binder) {
|
|
||||||
this._binder = binder;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Dispatch event
|
|
||||||
* @param {string} property
|
|
||||||
*/
|
|
||||||
notify(property) {
|
|
||||||
if (this._binder) {
|
|
||||||
this._binder.dispatchEvent(uEvent.CONFIG, property);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return {number}
|
|
||||||
*/
|
|
||||||
get interval() {
|
|
||||||
return this._interval;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {number} value
|
|
||||||
*/
|
|
||||||
set interval(value) {
|
|
||||||
this._interval = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return {string}
|
|
||||||
*/
|
|
||||||
get units() {
|
|
||||||
return this._units;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string} value
|
|
||||||
*/
|
|
||||||
set units(value) {
|
|
||||||
this._units = value;
|
|
||||||
if (this._units === 'imperial') {
|
|
||||||
this._factor_kmh = 0.62; // to mph
|
|
||||||
this._unit_kmh = 'mph';
|
|
||||||
this._factor_m = 3.28; // to feet
|
|
||||||
this._unit_m = 'ft';
|
|
||||||
this._factor_km = 0.62; // to miles
|
|
||||||
this._unit_km = 'mi';
|
|
||||||
} else if (this._units === 'nautical') {
|
|
||||||
this._factor_kmh = 0.54; // to knots
|
|
||||||
this._unit_kmh = 'kt';
|
|
||||||
this._factor_m = 1; // meters
|
|
||||||
this._unit_m = 'm';
|
|
||||||
this._factor_km = 0.54; // to nautical miles
|
|
||||||
this._unit_km = 'nm';
|
|
||||||
} else {
|
|
||||||
this._factor_kmh = 1;
|
|
||||||
this._unit_kmh = 'km/h';
|
|
||||||
this._factor_m = 1;
|
|
||||||
this._unit_m = 'm';
|
|
||||||
this._factor_km = 1;
|
|
||||||
this._unit_km = 'km';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return {string}
|
|
||||||
*/
|
|
||||||
get mapapi() {
|
|
||||||
return this._mapapi;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string} value
|
|
||||||
*/
|
|
||||||
set mapapi(value) {
|
|
||||||
this._mapapi = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return {?string}
|
|
||||||
*/
|
|
||||||
get gkey() {
|
|
||||||
return this._gkey;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {?string} value
|
|
||||||
*/
|
|
||||||
set gkey(value) {
|
|
||||||
this._gkey = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return {Object.<string, string>}
|
|
||||||
*/
|
|
||||||
get ol_layers() {
|
|
||||||
return this._ol_layers;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {Object.<string, string>} value
|
|
||||||
*/
|
|
||||||
set ol_layers(value) {
|
|
||||||
this._ol_layers = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return {number}
|
|
||||||
*/
|
|
||||||
get init_latitude() {
|
|
||||||
return this._init_latitude;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {number} value
|
|
||||||
*/
|
|
||||||
set init_latitude(value) {
|
|
||||||
this._init_latitude = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return {number}
|
|
||||||
*/
|
|
||||||
get init_longitude() {
|
|
||||||
return this._init_longitude;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {number} value
|
|
||||||
*/
|
|
||||||
set init_longitude(value) {
|
|
||||||
this._init_longitude = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return {RegExp}
|
|
||||||
*/
|
|
||||||
get pass_regex() {
|
|
||||||
return this._pass_regex;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {RegExp} value
|
|
||||||
*/
|
|
||||||
set pass_regex(value) {
|
|
||||||
this._pass_regex = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return {number}
|
|
||||||
*/
|
|
||||||
get strokeWeight() {
|
|
||||||
return this._strokeWeight;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {number} value
|
|
||||||
*/
|
|
||||||
set strokeWeight(value) {
|
|
||||||
this._strokeWeight = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return {string}
|
|
||||||
*/
|
|
||||||
get strokeColor() {
|
|
||||||
return this._strokeColor;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string} value
|
|
||||||
*/
|
|
||||||
set strokeColor(value) {
|
|
||||||
this._strokeColor = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return {number}
|
|
||||||
*/
|
|
||||||
get strokeOpacity() {
|
|
||||||
return this._strokeOpacity;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {number} value
|
|
||||||
*/
|
|
||||||
set strokeOpacity(value) {
|
|
||||||
this._strokeOpacity = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return {number}
|
|
||||||
*/
|
|
||||||
get factor_kmh() {
|
|
||||||
return this._factor_kmh;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return {string}
|
|
||||||
*/
|
|
||||||
get unit_kmh() {
|
|
||||||
return this._unit_kmh;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return {number}
|
|
||||||
*/
|
|
||||||
get factor_m() {
|
|
||||||
return this._factor_m;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return {string}
|
|
||||||
*/
|
|
||||||
get unit_m() {
|
|
||||||
return this._unit_m;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return {number}
|
|
||||||
*/
|
|
||||||
get factor_km() {
|
|
||||||
return this._factor_km;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return {string}
|
|
||||||
*/
|
|
||||||
get unit_km() {
|
|
||||||
return this._unit_km;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return {boolean}
|
|
||||||
*/
|
|
||||||
get showLatest() {
|
|
||||||
return this._showLatest;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {boolean} value
|
|
||||||
*/
|
|
||||||
set showLatest(value) {
|
|
||||||
if (this._showLatest !== value) {
|
|
||||||
this._showLatest = value;
|
|
||||||
this.notify('showLatest');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return {string}
|
|
||||||
*/
|
|
||||||
get colorNormal() {
|
|
||||||
return this._colorNormal;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string} value
|
|
||||||
*/
|
|
||||||
set colorNormal(value) {
|
|
||||||
this._colorNormal = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return {string}
|
|
||||||
*/
|
|
||||||
get colorStart() {
|
|
||||||
return this._colorStart;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string} value
|
|
||||||
*/
|
|
||||||
set colorStart(value) {
|
|
||||||
this._colorStart = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return {string}
|
|
||||||
*/
|
|
||||||
get colorStop() {
|
|
||||||
return this._colorStop;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string} value
|
|
||||||
*/
|
|
||||||
set colorStop(value) {
|
|
||||||
this._colorStop = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return {string}
|
|
||||||
*/
|
|
||||||
get colorExtra() {
|
|
||||||
return this._colorExtra;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string} value
|
|
||||||
*/
|
|
||||||
set colorExtra(value) {
|
|
||||||
this._colorExtra = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return {string}
|
|
||||||
*/
|
|
||||||
get colorHilite() {
|
|
||||||
return this._colorHilite;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string} value
|
|
||||||
*/
|
|
||||||
set colorHilite(value) {
|
|
||||||
this._colorHilite = value;
|
|
||||||
}
|
|
||||||
}
|
|
117
js/constants.js
117
js/constants.js
@ -1,117 +0,0 @@
|
|||||||
/*
|
|
||||||
* μ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';
|
|
||||||
import uUtils from './utils.js';
|
|
||||||
|
|
||||||
class uConstants {
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
this.auth = {};
|
|
||||||
this.config = {};
|
|
||||||
this.lang = {};
|
|
||||||
if (!this.loaded) {
|
|
||||||
this.initialize();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return {?XMLDocument}
|
|
||||||
*/
|
|
||||||
static fetch() {
|
|
||||||
let xml = null;
|
|
||||||
const request = new XMLHttpRequest();
|
|
||||||
request.open('GET', 'utils/getconstants.php', false);
|
|
||||||
request.send(null);
|
|
||||||
if (request.status === 200) {
|
|
||||||
xml = request.responseXML;
|
|
||||||
}
|
|
||||||
return xml;
|
|
||||||
}
|
|
||||||
|
|
||||||
initialize() {
|
|
||||||
const xml = uConstants.fetch();
|
|
||||||
if (xml) {
|
|
||||||
this.initAuth(xml);
|
|
||||||
this.initConfig(xml);
|
|
||||||
this.initLang(xml);
|
|
||||||
this.loaded = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {XMLDocument} xml
|
|
||||||
*/
|
|
||||||
initAuth(xml) {
|
|
||||||
this.auth = new uAuth();
|
|
||||||
const authNode = xml.getElementsByTagName('auth');
|
|
||||||
if (authNode.length) {
|
|
||||||
const isAuthenticated = uUtils.getNodeAsInt(authNode[0], 'isAuthenticated') === 1;
|
|
||||||
if (isAuthenticated) {
|
|
||||||
const id = uUtils.getNodeAsInt(authNode[0], 'userId');
|
|
||||||
const login = uUtils.getNode(authNode[0], 'userLogin');
|
|
||||||
this.auth.user = new uUser(id, login);
|
|
||||||
this.auth.isAdmin = uUtils.getNodeAsInt(authNode[0], 'isAdmin') === 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {XMLDocument} xml
|
|
||||||
*/
|
|
||||||
initLang(xml) {
|
|
||||||
const langNode = xml.getElementsByTagName('lang');
|
|
||||||
if (langNode.length) {
|
|
||||||
/** @type {Object<string, string>} */
|
|
||||||
this.lang.strings = uUtils.getNodesArray(langNode[0], 'strings');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {XMLDocument} xml
|
|
||||||
*/
|
|
||||||
initConfig(xml) {
|
|
||||||
this.config = new uConfig();
|
|
||||||
const configNode = xml.getElementsByTagName('config');
|
|
||||||
if (configNode.length) {
|
|
||||||
this.config.interval = uUtils.getNodeAsInt(configNode[0], 'interval');
|
|
||||||
this.config.units = uUtils.getNode(configNode[0], 'units');
|
|
||||||
this.config.mapapi = uUtils.getNode(configNode[0], 'mapapi');
|
|
||||||
this.config.gkey = uUtils.getNode(configNode[0], 'gkey');
|
|
||||||
this.config.ol_layers = uUtils.getNodesArray(configNode[0], 'ol_layers');
|
|
||||||
this.config.init_latitude = uUtils.getNodeAsFloat(configNode[0], 'init_latitude');
|
|
||||||
this.config.init_longitude = uUtils.getNodeAsFloat(configNode[0], 'init_longitude');
|
|
||||||
const re = uUtils.getNode(configNode[0], 'pass_regex');
|
|
||||||
this.config.pass_regex = new RegExp(re.substr(1, re.length - 2));
|
|
||||||
this.config.strokeWeight = uUtils.getNodeAsInt(configNode[0], 'strokeWeight');
|
|
||||||
this.config.strokeColor = uUtils.getNode(configNode[0], 'strokeColor');
|
|
||||||
this.config.strokeOpacity = uUtils.getNodeAsInt(configNode[0], 'strokeOpacity');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const constants = new uConstants();
|
|
||||||
/** @type {uConfig} */
|
|
||||||
export const config = constants.config;
|
|
||||||
/** @type {{strings: Object<string, string>}} */
|
|
||||||
export const lang = constants.lang;
|
|
||||||
/** @type {uAuth} */
|
|
||||||
export const auth = constants.auth;
|
|
69
js/data.js
69
js/data.js
@ -1,69 +0,0 @@
|
|||||||
/*
|
|
||||||
* μ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
|
|
||||||
*/
|
|
||||||
export default class uData {
|
|
||||||
/**
|
|
||||||
* @param {number} key
|
|
||||||
* @param {string} value
|
|
||||||
* @param {string} keyProperty
|
|
||||||
* @param {string} valueProperty
|
|
||||||
*/
|
|
||||||
// eslint-disable-next-line max-params
|
|
||||||
constructor(key, value, keyProperty, valueProperty) {
|
|
||||||
this[keyProperty] = key;
|
|
||||||
this[valueProperty] = value;
|
|
||||||
Object.defineProperty(this, 'key', {
|
|
||||||
get() {
|
|
||||||
return this[keyProperty];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
Object.defineProperty(this, 'value', {
|
|
||||||
get() {
|
|
||||||
return this[valueProperty];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {uBinder} binder
|
|
||||||
*/
|
|
||||||
set binder(binder) {
|
|
||||||
this._binder = binder;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @returns {uBinder}
|
|
||||||
*/
|
|
||||||
get binder() {
|
|
||||||
return this._binder;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Dispatch event
|
|
||||||
* @param {string} type
|
|
||||||
* @param {*=} args Defaults to this
|
|
||||||
*/
|
|
||||||
emit(type, args) {
|
|
||||||
const data = args || this;
|
|
||||||
this.binder.dispatchEvent(type, data);
|
|
||||||
}
|
|
||||||
}
|
|
78
js/event.js
78
js/event.js
@ -1,78 +0,0 @@
|
|||||||
/* μ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
|
|
||||||
* property {Set<Function>} listeners
|
|
||||||
*/
|
|
||||||
export default class uEvent {
|
|
||||||
/**
|
|
||||||
* @param {string} type
|
|
||||||
*/
|
|
||||||
constructor(type) {
|
|
||||||
/** type {string} */
|
|
||||||
this.type = type;
|
|
||||||
/** type {Set<Function>} */
|
|
||||||
this.listeners = new Set();
|
|
||||||
}
|
|
||||||
|
|
||||||
static get ADD() { return 'µAdd'; }
|
|
||||||
static get API_CHANGE() { return 'µApiChange'; }
|
|
||||||
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 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'; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {Function} listener
|
|
||||||
*/
|
|
||||||
addListener(listener) {
|
|
||||||
this.listeners.add(listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {Function} listener
|
|
||||||
*/
|
|
||||||
removeListener(listener) {
|
|
||||||
this.listeners.delete(listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {*=} args
|
|
||||||
*/
|
|
||||||
dispatch(args) {
|
|
||||||
for (const listener of this.listeners) {
|
|
||||||
(async () => {
|
|
||||||
console.log(`${this.type}: ${args ? args.constructor.name : ''}`);
|
|
||||||
await listener(this, args);
|
|
||||||
})();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
283
js/list.js
283
js/list.js
@ -1,283 +0,0 @@
|
|||||||
/*
|
|
||||||
* μ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';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @class uList
|
|
||||||
* @template T
|
|
||||||
*/
|
|
||||||
export default class uList {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string} selector
|
|
||||||
* @param {uBinder} binder
|
|
||||||
* @param {Class<T>} type
|
|
||||||
* @template T
|
|
||||||
*/
|
|
||||||
constructor(selector, binder, type) {
|
|
||||||
/** @type {T[]} */
|
|
||||||
this.data = [];
|
|
||||||
/** @type {uBinder} */
|
|
||||||
this.binder = binder;
|
|
||||||
/** @type {boolean} */
|
|
||||||
this._showAllOption = false;
|
|
||||||
/** @type {boolean} */
|
|
||||||
this.hasHead = false;
|
|
||||||
this.headValue = '';
|
|
||||||
this.allValue = '';
|
|
||||||
/** @type {(T|uData)} */
|
|
||||||
this.T = type || uData;
|
|
||||||
/** @type {HTMLSelectElement} */
|
|
||||||
this.domElement = document.querySelector(selector);
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @type {string} */
|
|
||||||
this.selectedId = '';
|
|
||||||
this.fromDom();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return {(T|null)}
|
|
||||||
* @template T
|
|
||||||
*/
|
|
||||||
get current() {
|
|
||||||
const i = parseInt(this.selectedId);
|
|
||||||
if (!isNaN(i)) {
|
|
||||||
return this.data.find((item) => item.key === i);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return {boolean}
|
|
||||||
*/
|
|
||||||
get isSelectedAllOption() {
|
|
||||||
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
|
|
||||||
*/
|
|
||||||
select(id, skipUpdate) {
|
|
||||||
this.selectedId = id.toString();
|
|
||||||
this.render();
|
|
||||||
if (!skipUpdate) {
|
|
||||||
this.onChange();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
clear() {
|
|
||||||
this.domElement.options.length = 0;
|
|
||||||
this.data.length = 0;
|
|
||||||
this.selectedId = '';
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get list from XML structure
|
|
||||||
* @param {Element|Document} xml
|
|
||||||
* @param {string} key Name of key node
|
|
||||||
* @param {string} value Name of value node
|
|
||||||
*/
|
|
||||||
fromXml(xml, key, value) {
|
|
||||||
if (!xml) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
for (const item of xml) {
|
|
||||||
const row = new this.T(uUtils.getNodeAsInt(item, key), uUtils.getNode(item, value));
|
|
||||||
this.updateDataRow(row);
|
|
||||||
row.binder = this.binder;
|
|
||||||
this.data.push(row);
|
|
||||||
}
|
|
||||||
if (this.data.length) {
|
|
||||||
this.selectedId = this.data[0].key.toString();
|
|
||||||
}
|
|
||||||
this.render();
|
|
||||||
this.onChange();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize list from DOM select element options
|
|
||||||
*/
|
|
||||||
fromDom() {
|
|
||||||
if (!this.domElement) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
for (const option of this.domElement) {
|
|
||||||
if (option.value === 'all') {
|
|
||||||
this._showAllOption = true;
|
|
||||||
} else if (!option.disabled) {
|
|
||||||
const row = new this.T(parseInt(option.value), option.innerText);
|
|
||||||
this.updateDataRow(row);
|
|
||||||
row.binder = this.binder;
|
|
||||||
this.data.push(row);
|
|
||||||
}
|
|
||||||
if (option.selected) {
|
|
||||||
this.selectedId = option.value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {uEvent} event
|
|
||||||
* @param {*=} eventData
|
|
||||||
*/
|
|
||||||
handleEvent(event, eventData) {
|
|
||||||
if (event.type === uEvent.CHANGE && eventData.el === this.domElement) {
|
|
||||||
this.selectedId = eventData.id;
|
|
||||||
this.onChange();
|
|
||||||
} else if (event.type === uEvent.EDIT && eventData === this.domElement) {
|
|
||||||
this.onEdit();
|
|
||||||
} else if (event.type === uEvent.ADD && eventData === this.domElement) {
|
|
||||||
this.onAdd();
|
|
||||||
} else if (event.type === uEvent.CONFIG) {
|
|
||||||
this.onConfigChange(eventData);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// /**
|
|
||||||
// * @param {T[]} data
|
|
||||||
// */
|
|
||||||
// set list(data) {
|
|
||||||
// this.data = data;
|
|
||||||
// }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add item
|
|
||||||
* @param {T} item
|
|
||||||
* @template T
|
|
||||||
*/
|
|
||||||
add(item) {
|
|
||||||
this.data.push(item);
|
|
||||||
this.render();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {number} id
|
|
||||||
* @return {boolean}
|
|
||||||
*/
|
|
||||||
has(id) {
|
|
||||||
return this.data.findIndex((o) => o.key === id) !== -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove item
|
|
||||||
* @param {number} id
|
|
||||||
*/
|
|
||||||
remove(id) {
|
|
||||||
const currentId = this.current.key;
|
|
||||||
this.data.splice(this.data.findIndex((o) => o.key === id), 1);
|
|
||||||
if (id === currentId) {
|
|
||||||
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) {
|
|
||||||
const head = new Option(this.headValue, '0', true, this.selectedId === '0');
|
|
||||||
head.disabled = true;
|
|
||||||
this.domElement.options.add(head);
|
|
||||||
}
|
|
||||||
if (this._showAllOption) {
|
|
||||||
this.domElement.options.add(new Option(this.allValue, 'all'));
|
|
||||||
}
|
|
||||||
for (const item of this.data) {
|
|
||||||
this.domElement.options.add(new Option(item.value, item.key.toString(), false, item.key.toString() === this.selectedId));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {T} row
|
|
||||||
* @template T
|
|
||||||
*/
|
|
||||||
// eslint-disable-next-line no-unused-vars,no-empty-function,class-methods-use-this
|
|
||||||
updateDataRow(row) {
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @abstract
|
|
||||||
*/
|
|
||||||
// eslint-disable-next-line no-unused-vars,no-empty-function,class-methods-use-this
|
|
||||||
onChange() {
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @abstract
|
|
||||||
*/
|
|
||||||
// eslint-disable-next-line no-unused-vars,no-empty-function,class-methods-use-this
|
|
||||||
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) {
|
|
||||||
}
|
|
||||||
}
|
|
147
js/map.js
147
js/map.js
@ -1,147 +0,0 @@
|
|||||||
/*
|
|
||||||
* μ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';
|
|
||||||
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
|
|
||||||
* @property {?Array<number>} savedBounds
|
|
||||||
* @property {?(gmApi|olApi)} api
|
|
||||||
* @property {?HTMLElement} mapElement
|
|
||||||
*/
|
|
||||||
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);
|
|
||||||
this.loadTime = 0;
|
|
||||||
this.savedBounds = null;
|
|
||||||
this.api = null;
|
|
||||||
this.mapElement = null;
|
|
||||||
this.lastTrackId = null;
|
|
||||||
this._binder = binder;
|
|
||||||
this.track = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Dynamic change of map api
|
|
||||||
* @param {string=} apiName API name
|
|
||||||
*/
|
|
||||||
loadMapAPI(apiName) {
|
|
||||||
if (apiName) {
|
|
||||||
config.mapapi = apiName;
|
|
||||||
try {
|
|
||||||
this.savedBounds = this.api.getBounds();
|
|
||||||
} catch (e) {
|
|
||||||
this.savedBounds = null;
|
|
||||||
}
|
|
||||||
this.api.cleanup();
|
|
||||||
}
|
|
||||||
if (config.mapapi === 'gmaps') {
|
|
||||||
this.api = gmApi;
|
|
||||||
} else {
|
|
||||||
this.api = olApi;
|
|
||||||
}
|
|
||||||
this.waitAndInit();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Try to initialize map engine
|
|
||||||
*/
|
|
||||||
waitAndInit() {
|
|
||||||
// wait till main api loads
|
|
||||||
if (this.loadTime > 10000) {
|
|
||||||
this.loadTime = 0;
|
|
||||||
alert(uUtils.sprintf(lang.strings['apifailure'], config.mapapi));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
this.api.init(this._binder, this.mapElement);
|
|
||||||
} catch (e) {
|
|
||||||
setTimeout(() => {
|
|
||||||
this.loadTime += 50;
|
|
||||||
this.waitAndInit();
|
|
||||||
}, 50);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.loadTime = 0;
|
|
||||||
if (this.savedBounds) {
|
|
||||||
this.api.zoomToBounds(this.savedBounds);
|
|
||||||
}
|
|
||||||
uLogger.trackList.onChange();
|
|
||||||
// save current api as default
|
|
||||||
uUtils.setCookie('api', config.mapapi, 30);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param {uEvent} event
|
|
||||||
* @param {*=} args
|
|
||||||
*/
|
|
||||||
handleEvent(event, args) {
|
|
||||||
if (event.type === uEvent.TRACK_READY) {
|
|
||||||
/** @type {uTrack} */
|
|
||||||
const track = args;
|
|
||||||
this.api.clearMap();
|
|
||||||
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;
|
|
||||||
this.mapElement = ui.map;
|
|
||||||
this.loadMapAPI();
|
|
||||||
} else if (event.type === uEvent.API_CHANGE) {
|
|
||||||
/** @type {string} */
|
|
||||||
const api = args;
|
|
||||||
this.loadMapAPI(api);
|
|
||||||
} else if (event.type === uEvent.CHART_CLICKED) {
|
|
||||||
/** @type {number} */
|
|
||||||
const id = args;
|
|
||||||
this.api.animateMarker(id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
145
js/modal.js
145
js/modal.js
@ -1,145 +0,0 @@
|
|||||||
/*
|
|
||||||
* μ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';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @typedef {Object} ModalResult
|
|
||||||
* @property {boolean} cancelled Was dialog cancelled
|
|
||||||
* @property {string} [action] Click action name
|
|
||||||
* @property {Object} [data] Additional data
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @callback ModalCallback
|
|
||||||
* @param {ModalResult} result
|
|
||||||
*/
|
|
||||||
|
|
||||||
export default class uModal {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Builds modal dialog
|
|
||||||
* Positive click handlers bound to elements with class 'button-resolve'.
|
|
||||||
* Negative click handlers bound to elements with class 'button-reject'.
|
|
||||||
* Optional attribute 'data-action' value is returned in {@link ModalResult.action}
|
|
||||||
* @param {(string|Node|NodeList|Array.<Node>)} content
|
|
||||||
*/
|
|
||||||
constructor(content) {
|
|
||||||
const modal = document.createElement('div');
|
|
||||||
modal.setAttribute('id', 'modal');
|
|
||||||
const modalHeader = document.createElement('div');
|
|
||||||
modalHeader.setAttribute('id', 'modal-header');
|
|
||||||
const buttonClose = document.createElement('button');
|
|
||||||
buttonClose.setAttribute('id', 'modal-close');
|
|
||||||
buttonClose.setAttribute('type', 'button');
|
|
||||||
buttonClose.setAttribute('class', 'button-reject');
|
|
||||||
const img = document.createElement('img');
|
|
||||||
img.setAttribute('src', 'images/close.svg');
|
|
||||||
img.setAttribute('alt', lang.strings['close']);
|
|
||||||
buttonClose.append(img);
|
|
||||||
modalHeader.append(buttonClose);
|
|
||||||
modal.append(modalHeader);
|
|
||||||
const modalBody = document.createElement('div');
|
|
||||||
modalBody.setAttribute('id', 'modal-body');
|
|
||||||
if (typeof content === 'string') {
|
|
||||||
modalBody.innerHTML = content;
|
|
||||||
} else if (content instanceof NodeList || content instanceof Array) {
|
|
||||||
for (const node of content) {
|
|
||||||
modalBody.append(node);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
modalBody.append(content);
|
|
||||||
}
|
|
||||||
modal.append(modalBody);
|
|
||||||
this._modal = modal;
|
|
||||||
this.visible = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return {HTMLDivElement}
|
|
||||||
*/
|
|
||||||
get modal() {
|
|
||||||
return this._modal;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Show modal dialog
|
|
||||||
* @returns {Promise<ModalResult>}
|
|
||||||
*/
|
|
||||||
show() {
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
this.addListeners(resolve);
|
|
||||||
if (!this.visible) {
|
|
||||||
document.body.append(this._modal);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add listeners
|
|
||||||
* @param {ModalCallback} resolve callback
|
|
||||||
*/
|
|
||||||
addListeners(resolve) {
|
|
||||||
this._modal.querySelectorAll('.button-resolve').forEach((el) => {
|
|
||||||
el.addEventListener('click', () => {
|
|
||||||
uModal.onClick(el, resolve, { cancelled: false, action: el.getAttribute('data-action') });
|
|
||||||
});
|
|
||||||
});
|
|
||||||
this._modal.querySelectorAll('.button-reject').forEach((el) => {
|
|
||||||
el.addEventListener('click', () => {
|
|
||||||
uModal.onClick(el, resolve, { cancelled: true });
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* On click action
|
|
||||||
* Handles optional confirmation dialog
|
|
||||||
* @param {Element} el Clicked element
|
|
||||||
* @param {ModalCallback} resolve callback
|
|
||||||
* @param {ModalResult} result
|
|
||||||
*/
|
|
||||||
static onClick(el, resolve, result) {
|
|
||||||
const confirm = el.getAttribute('data-confirm');
|
|
||||||
let proceed = true;
|
|
||||||
if (confirm) {
|
|
||||||
proceed = this.isConfirmed(confirm);
|
|
||||||
}
|
|
||||||
if (proceed) {
|
|
||||||
resolve(result);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Show confirmation dialog and return user decision
|
|
||||||
* @param {string} message
|
|
||||||
* @return {boolean} True if confirmed, false otherwise
|
|
||||||
*/
|
|
||||||
static isConfirmed(message) {
|
|
||||||
return confirm(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove modal dialog
|
|
||||||
*/
|
|
||||||
hide() {
|
|
||||||
document.body.removeChild(this._modal);
|
|
||||||
this.visible = false
|
|
||||||
}
|
|
||||||
}
|
|
File diff suppressed because one or more lines are too long
@ -1,85 +0,0 @@
|
|||||||
/*
|
|
||||||
* μ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';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @class uPosition
|
|
||||||
* @property {number} id
|
|
||||||
* @property {number} latitude
|
|
||||||
* @property {number} longitude
|
|
||||||
* @property {?number} altitude
|
|
||||||
* @property {?number} speed
|
|
||||||
* @property {?number} bearing
|
|
||||||
* @property {?number} accuracy
|
|
||||||
* @property {?string} provider
|
|
||||||
* @property {?string} comment
|
|
||||||
* @property {?string} image
|
|
||||||
* @property {string} username
|
|
||||||
* @property {string} trackname
|
|
||||||
* @property {number} trackid
|
|
||||||
* @property {number} timestamp
|
|
||||||
* @property {number} distance
|
|
||||||
* @property {number} seconds
|
|
||||||
* @property {number} totalDistance
|
|
||||||
* @property {number} totalSeconds
|
|
||||||
*/
|
|
||||||
export default class uPosition {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {Element|Document} xml
|
|
||||||
* @returns {uPosition}
|
|
||||||
*/
|
|
||||||
static fromXml(xml) {
|
|
||||||
const position = new uPosition();
|
|
||||||
position.id = uUtils.getAttributeAsInt(xml, 'id');
|
|
||||||
position.latitude = uUtils.getNodeAsFloat(xml, 'latitude');
|
|
||||||
position.longitude = uUtils.getNodeAsFloat(xml, 'longitude');
|
|
||||||
position.altitude = uUtils.getNodeAsInt(xml, 'altitude'); // may be null
|
|
||||||
position.speed = uUtils.getNodeAsInt(xml, 'speed'); // may be null
|
|
||||||
position.bearing = uUtils.getNodeAsInt(xml, 'bearing'); // may be null
|
|
||||||
position.accuracy = uUtils.getNodeAsInt(xml, 'accuracy'); // may be null
|
|
||||||
position.provider = uUtils.getNode(xml, 'provider'); // may be null
|
|
||||||
position.comment = uUtils.getNode(xml, 'comment'); // may be null
|
|
||||||
position.image = uUtils.getNode(xml, 'image'); // may be null
|
|
||||||
position.username = uUtils.getNode(xml, 'username');
|
|
||||||
position.trackname = uUtils.getNode(xml, 'trackname');
|
|
||||||
position.trackid = uUtils.getNodeAsInt(xml, 'trackid');
|
|
||||||
position.timestamp = uUtils.getNodeAsInt(xml, 'timestamp');
|
|
||||||
position.distance = uUtils.getNodeAsInt(xml, 'distance');
|
|
||||||
position.seconds = uUtils.getNodeAsInt(xml, 'seconds');
|
|
||||||
position.totalDistance = 0;
|
|
||||||
position.totalSeconds = 0;
|
|
||||||
return position;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return {boolean}
|
|
||||||
*/
|
|
||||||
hasComment() {
|
|
||||||
return (this.comment != null && this.comment.length);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return {boolean}
|
|
||||||
*/
|
|
||||||
hasImage() {
|
|
||||||
return (this.image != null && this.image.length);
|
|
||||||
}
|
|
||||||
}
|
|
213
js/track.js
213
js/track.js
@ -1,213 +0,0 @@
|
|||||||
/*
|
|
||||||
* μ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';
|
|
||||||
import uEvent from './event.js';
|
|
||||||
import uPosition from './position.js';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @class uTrack
|
|
||||||
* @extends {uData}
|
|
||||||
* @property {number} id
|
|
||||||
* @property {string} name
|
|
||||||
* @property {uUser} user
|
|
||||||
* @property {boolean} continuous
|
|
||||||
* @property {?uPosition[]} positions
|
|
||||||
* @property {?Array<{x: number, y: number}>} plotData
|
|
||||||
*/
|
|
||||||
export default class uTrack extends uData {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {number} id
|
|
||||||
* @param {string} name
|
|
||||||
* @param {uUser} user
|
|
||||||
*/
|
|
||||||
constructor(id, name, user) {
|
|
||||||
super(id, name, 'id', 'name');
|
|
||||||
this._user = user;
|
|
||||||
this._positions = null;
|
|
||||||
this._plotData = null;
|
|
||||||
this._maxId = 0;
|
|
||||||
this._onlyLatest = false;
|
|
||||||
this._continuous = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return {?uPosition[]}
|
|
||||||
*/
|
|
||||||
get positions() {
|
|
||||||
return this._positions;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {uUser} user
|
|
||||||
*/
|
|
||||||
set user(user) {
|
|
||||||
this._user = user;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return {uUser}
|
|
||||||
*/
|
|
||||||
get user() {
|
|
||||||
return this._user;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {boolean} value
|
|
||||||
*/
|
|
||||||
set continuous(value) {
|
|
||||||
this._continuous = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return {boolean}
|
|
||||||
*/
|
|
||||||
get continuous() {
|
|
||||||
return this._continuous;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {boolean} value
|
|
||||||
*/
|
|
||||||
set onlyLatest(value) {
|
|
||||||
this._onlyLatest = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
clear() {
|
|
||||||
this._positions = null;
|
|
||||||
this._plotData = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get track data from xml
|
|
||||||
* @param {XMLDocument} xml XML with positions data
|
|
||||||
* @param {boolean} isUpdate If true append to old data
|
|
||||||
*/
|
|
||||||
fromXml(xml, isUpdate) {
|
|
||||||
let positions = [];
|
|
||||||
let plotData = [];
|
|
||||||
let totalDistance = 0;
|
|
||||||
let totalSeconds = 0;
|
|
||||||
if (isUpdate && this._positions) {
|
|
||||||
positions = this._positions;
|
|
||||||
plotData = this._plotData;
|
|
||||||
totalDistance = positions[positions.length - 1].totalDistance;
|
|
||||||
totalSeconds = positions[positions.length - 1].totalSeconds;
|
|
||||||
}
|
|
||||||
const xmlPos = xml.getElementsByTagName('position');
|
|
||||||
for (xml of xmlPos) {
|
|
||||||
const position = uPosition.fromXml(xml);
|
|
||||||
totalDistance += position.distance;
|
|
||||||
totalSeconds += position.seconds;
|
|
||||||
position.totalDistance = totalDistance;
|
|
||||||
position.totalSeconds = totalSeconds;
|
|
||||||
positions.push(position);
|
|
||||||
if (position.altitude != null) {
|
|
||||||
plotData.push({ x: position.totalDistance, y: position.altitude * config.factor_m });
|
|
||||||
}
|
|
||||||
if (position.id > this._maxId) {
|
|
||||||
this._maxId = position.id;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this._positions = positions;
|
|
||||||
this._plotData = plotData;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return {?Array<{x: number, y: number}>}
|
|
||||||
*/
|
|
||||||
get plotData() {
|
|
||||||
return this._plotData;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return {number}
|
|
||||||
*/
|
|
||||||
get length() {
|
|
||||||
return this._positions ? this._positions.length : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return {boolean}
|
|
||||||
*/
|
|
||||||
get hasPositions() {
|
|
||||||
return this._positions !== null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @throws
|
|
||||||
* @return {Promise<void>}
|
|
||||||
*/
|
|
||||||
fetch() {
|
|
||||||
const data = {
|
|
||||||
userid: this._user.id
|
|
||||||
};
|
|
||||||
let isUpdate = this.hasPositions;
|
|
||||||
if (config.showLatest) {
|
|
||||||
data.last = 1;
|
|
||||||
isUpdate = false;
|
|
||||||
} else {
|
|
||||||
data.trackid = this.id;
|
|
||||||
}
|
|
||||||
if (this._onlyLatest !== config.showLatest) {
|
|
||||||
this._onlyLatest = config.showLatest;
|
|
||||||
isUpdate = false;
|
|
||||||
} else {
|
|
||||||
data.afterid = this._maxId;
|
|
||||||
}
|
|
||||||
return uAjax.get('utils/getpositions.php', data).then((xml) => {
|
|
||||||
this.fromXml(xml, isUpdate);
|
|
||||||
this.render();
|
|
||||||
return xml;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Save track data
|
|
||||||
* @param {string} action
|
|
||||||
* @return {Promise<void>}
|
|
||||||
*/
|
|
||||||
update(action) {
|
|
||||||
return uAjax.post('utils/handletrack.php',
|
|
||||||
{
|
|
||||||
action: action,
|
|
||||||
trackid: this.id,
|
|
||||||
trackname: this.name
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Render track
|
|
||||||
*/
|
|
||||||
render() {
|
|
||||||
this.emit(uEvent.TRACK_READY);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Export to file
|
|
||||||
* @param {string} type File type
|
|
||||||
*/
|
|
||||||
export(type) {
|
|
||||||
const url = `utils/export.php?type=${type}&userid=${this._user.id}&trackid=${this.id}`;
|
|
||||||
this.emit(uEvent.OPEN_URL, url);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,109 +0,0 @@
|
|||||||
/*
|
|
||||||
* μlogger
|
|
||||||
*
|
|
||||||
* Copyright(C) 2019 Bartek Fabiszewski (www.fabiszewski.net)
|
|
||||||
*
|
|
||||||
* This is free software; you can redistribute it and/or modify it under
|
|
||||||
* the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation; either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful, but
|
|
||||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
||||||
* General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program; if not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { lang } from './constants.js';
|
|
||||||
import uModal from './modal.js';
|
|
||||||
import uUtils from './utils.js';
|
|
||||||
|
|
||||||
export default class TrackDialog {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {uTrack} track
|
|
||||||
*/
|
|
||||||
constructor(track) {
|
|
||||||
this.track = track;
|
|
||||||
const html = `<div style="float:left">${uUtils.sprintf(lang.strings['editingtrack'], `<b>${uUtils.htmlEncode(this.track.name)}</b>`)}</div>
|
|
||||||
<div class="red-button button-resolve" data-action="delete" data-confirm="${uUtils.sprintf(lang.strings['trackdelwarn'], uUtils.htmlEncode(this.track.name))}"><b><a>${lang.strings['deltrack']}</a></b></div>
|
|
||||||
<div style="clear: both; padding-bottom: 1em;"></div>
|
|
||||||
<form id="trackForm">
|
|
||||||
<label><b>${lang.strings['trackname']}</b></label>
|
|
||||||
<input type="text" placeholder="${lang.strings['trackname']}" name="trackname" value="${uUtils.htmlEncode(this.track.name)}" required>
|
|
||||||
<div class="buttons">
|
|
||||||
<button class="button-reject" type="button">${lang.strings['cancel']}</button>
|
|
||||||
<button class="button-resolve" type="submit" data-action="update">${lang.strings['submit']}</button>
|
|
||||||
</div>
|
|
||||||
</form>`;
|
|
||||||
this.dialog = new uModal(html);
|
|
||||||
this.form = this.dialog.modal.querySelector('#trackForm');
|
|
||||||
this.form.onsubmit = () => false;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Show edit track dialog
|
|
||||||
* @see {uModal}
|
|
||||||
* @returns {Promise<ModalResult>}
|
|
||||||
*/
|
|
||||||
show() {
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
this.resolveModal(resolve);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {ModalCallback} resolve
|
|
||||||
*/
|
|
||||||
resolveModal(resolve) {
|
|
||||||
this.dialog.show().then((result) => {
|
|
||||||
if (result.cancelled) {
|
|
||||||
return this.hide();
|
|
||||||
}
|
|
||||||
if (result.action === 'update') {
|
|
||||||
if (!this.validate()) {
|
|
||||||
return this.resolveModal(resolve);
|
|
||||||
}
|
|
||||||
result.data = this.getData();
|
|
||||||
}
|
|
||||||
return resolve(result);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Hide dialog
|
|
||||||
*/
|
|
||||||
hide() {
|
|
||||||
this.dialog.hide();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get data from track form
|
|
||||||
* @return {{name: string}}
|
|
||||||
*/
|
|
||||||
getData() {
|
|
||||||
const trackName = this.form.elements['trackname'].value.trim();
|
|
||||||
return { name: trackName };
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validate form
|
|
||||||
* @return {boolean} True if valid
|
|
||||||
*/
|
|
||||||
validate() {
|
|
||||||
const trackName = this.form.elements['trackname'].value.trim();
|
|
||||||
if (trackName === this.track.name) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!trackName) {
|
|
||||||
alert(lang.strings['allrequired']);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
242
js/tracklist.js
242
js/tracklist.js
@ -1,242 +0,0 @@
|
|||||||
/*
|
|
||||||
* μ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';
|
|
||||||
import uEvent from './event.js';
|
|
||||||
import uList from './list.js';
|
|
||||||
import { uLogger } from './ulogger.js';
|
|
||||||
import uPosition from './position.js';
|
|
||||||
import uTrack from './track.js';
|
|
||||||
import uUtils from './utils.js';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @class TrackList
|
|
||||||
* @extends {uList<uTrack>}
|
|
||||||
*/
|
|
||||||
export default class TrackList extends uList {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string} selector
|
|
||||||
* @param {uBinder} binder
|
|
||||||
*/
|
|
||||||
constructor(selector, binder) {
|
|
||||||
super(selector, binder, uTrack);
|
|
||||||
if (binder) {
|
|
||||||
this.binder.addEventListener(uEvent.EXPORT, this);
|
|
||||||
this.binder.addEventListener(uEvent.IMPORT, this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @override
|
|
||||||
* @param {uTrack} row
|
|
||||||
*/
|
|
||||||
// eslint-disable-next-line class-methods-use-this
|
|
||||||
updateDataRow(row) {
|
|
||||||
row.user = uLogger.userList.current;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @override
|
|
||||||
* @param {(Event|uEvent)} event
|
|
||||||
* @param {*=} data
|
|
||||||
*/
|
|
||||||
handleEvent(event, data) {
|
|
||||||
if (event.type === uEvent.CHANGE) {
|
|
||||||
config.showLatest = false;
|
|
||||||
}
|
|
||||||
super.handleEvent(event, data);
|
|
||||||
if (event.type === uEvent.EXPORT) {
|
|
||||||
this.current.export(data);
|
|
||||||
} else if (event.type === uEvent.IMPORT) {
|
|
||||||
this.import(data).catch((msg) => alert(`${lang.strings['actionfailure']}\n${msg}`));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {HTMLFormElement} form
|
|
||||||
* @return {Promise<void>}
|
|
||||||
*/
|
|
||||||
import(form) {
|
|
||||||
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');
|
|
||||||
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
|
|
||||||
})
|
|
||||||
.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}`);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetch track with latest position for current user
|
|
||||||
* @throws
|
|
||||||
* @return {Promise<Document, string>}
|
|
||||||
*/
|
|
||||||
fetchLatest() {
|
|
||||||
this.emit(true, 'track');
|
|
||||||
const data = {
|
|
||||||
last: 1
|
|
||||||
};
|
|
||||||
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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} 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();
|
|
||||||
}
|
|
||||||
this.emit(false, 'track');
|
|
||||||
return xml;
|
|
||||||
}).catch((msg) => {
|
|
||||||
this.emit(false, 'track');
|
|
||||||
alert(`${lang.strings['actionfailure']}\n${msg}`);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @override
|
|
||||||
*/
|
|
||||||
onChange() {
|
|
||||||
if (!config.showLatest) {
|
|
||||||
this.fetchTrack();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetch and render track
|
|
||||||
*/
|
|
||||||
fetchTrack() {
|
|
||||||
if (this.current) {
|
|
||||||
this.emit(true, 'track');
|
|
||||||
this.current.fetch()
|
|
||||||
.then(() => this.emit(false, 'track'))
|
|
||||||
.catch((msg) => {
|
|
||||||
this.emit(false, 'track');
|
|
||||||
alert(`${lang.strings['actionfailure']}\n${msg}`);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @override
|
|
||||||
*/
|
|
||||||
onEdit() {
|
|
||||||
if (this.current) {
|
|
||||||
if (this.current.user.login !== auth.user.login && !auth.isAdmin) {
|
|
||||||
alert(lang.strings['owntrackswarn']);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.editTrack();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {TrackDialog=} modal
|
|
||||||
*/
|
|
||||||
editTrack(modal) {
|
|
||||||
const dialog = modal || new TrackDialog(this.current);
|
|
||||||
dialog.show()
|
|
||||||
.then((result) => {
|
|
||||||
switch (result.action) {
|
|
||||||
case 'update':
|
|
||||||
this.current.name = result.data.name;
|
|
||||||
return this.current.update('update').then(() => this.render());
|
|
||||||
case 'delete':
|
|
||||||
return this.current.update('delete').then(() => this.remove(this.current.id));
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
throw new Error();
|
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
alert(lang.strings['actionsuccess']);
|
|
||||||
dialog.hide();
|
|
||||||
})
|
|
||||||
.catch((msg) => {
|
|
||||||
alert(`${lang.strings['actionfailure']}\n${msg}`);
|
|
||||||
this.editTrack(dialog);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @override
|
|
||||||
*/
|
|
||||||
// eslint-disable-next-line no-empty-function,class-methods-use-this
|
|
||||||
onAdd() {
|
|
||||||
}
|
|
||||||
}
|
|
559
js/ui.js
559
js/ui.js
@ -1,559 +0,0 @@
|
|||||||
/*
|
|
||||||
* μ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';
|
|
||||||
import uUtils from './utils.js';
|
|
||||||
|
|
||||||
export default class uUI {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {uBinder} binder
|
|
||||||
*/
|
|
||||||
constructor(binder) {
|
|
||||||
this._binder = binder;
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize uUI elements
|
|
||||||
*/
|
|
||||||
initUI() {
|
|
||||||
/** @type {HTMLElement} */
|
|
||||||
this.menu = document.getElementById('menu');
|
|
||||||
/** @type {?HTMLElement} */
|
|
||||||
this.userMenu = document.getElementById('user-menu');
|
|
||||||
/** @type {?HTMLElement} */
|
|
||||||
this.userDropdown = document.getElementById('user-dropdown');
|
|
||||||
/** @type {?HTMLElement} */
|
|
||||||
this.userPass = document.getElementById('user-pass');
|
|
||||||
// noinspection JSValidateTypes
|
|
||||||
/** @type {?HTMLSelectElement} */
|
|
||||||
this.userSelect = function () {
|
|
||||||
const list = document.getElementsByName('user');
|
|
||||||
if (list.length) { return list[0]; }
|
|
||||||
return null;
|
|
||||||
}();
|
|
||||||
// noinspection JSValidateTypes
|
|
||||||
/** @type {HTMLSelectElement} */
|
|
||||||
this.trackSelect = document.getElementsByName('track')[0];
|
|
||||||
// noinspection JSValidateTypes
|
|
||||||
/** @type {HTMLSelectElement} */
|
|
||||||
this.apiSelect = document.getElementsByName('api')[0];
|
|
||||||
// noinspection JSValidateTypes
|
|
||||||
/** @type {HTMLSelectElement} */
|
|
||||||
this.langSelect = document.getElementsByName('lang')[0];
|
|
||||||
// noinspection JSValidateTypes
|
|
||||||
/** @type {HTMLSelectElement} */
|
|
||||||
this.unitsSelect = document.getElementsByName('units')[0];
|
|
||||||
/** @type {HTMLElement} */
|
|
||||||
this.chart = document.getElementById('chart');
|
|
||||||
/** @type {HTMLElement} */
|
|
||||||
this.chartClose = document.getElementById('chart-close');
|
|
||||||
/** @type {HTMLElement} */
|
|
||||||
this.bottom = document.getElementById('bottom');
|
|
||||||
/** @type {HTMLElement} */
|
|
||||||
this.chartLink = document.getElementById('altitudes');
|
|
||||||
/** @type {HTMLElement} */
|
|
||||||
this.main = document.getElementById('main');
|
|
||||||
/** @type {HTMLElement} */
|
|
||||||
this.menuClose = document.getElementById('menu-close');
|
|
||||||
/** @type {HTMLElement} */
|
|
||||||
this.track = document.getElementById('track');
|
|
||||||
/** @type {HTMLElement} */
|
|
||||||
this.trackTitle = document.querySelector('label[for="track"]');
|
|
||||||
/** @type {HTMLElement} */
|
|
||||||
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');
|
|
||||||
/** @type {HTMLElement} */
|
|
||||||
this.forceReload = document.getElementById('force-reload');
|
|
||||||
/** @type {HTMLElement} */
|
|
||||||
this.interval = document.getElementById('interval');
|
|
||||||
/** @type {HTMLElement} */
|
|
||||||
this.setInterval = document.getElementById('set-interval');
|
|
||||||
/** @type {HTMLElement} */
|
|
||||||
this.exportKml = document.getElementById('export-kml');
|
|
||||||
/** @type {HTMLElement} */
|
|
||||||
this.exportGpx = document.getElementById('export-gpx');
|
|
||||||
/** @type {?HTMLElement} */
|
|
||||||
this.inputFile = document.getElementById('input-file');
|
|
||||||
/** @type {HTMLElement} */
|
|
||||||
this.importGpx = document.getElementById('import-gpx');
|
|
||||||
/** @type {?HTMLElement} */
|
|
||||||
this.addUser = document.getElementById('adduser');
|
|
||||||
/** @type {?HTMLElement} */
|
|
||||||
this.editUser = document.getElementById('edituser');
|
|
||||||
/** @type {?HTMLElement} */
|
|
||||||
this.editTrack = document.getElementById('edittrack');
|
|
||||||
/** @type {HTMLElement} */
|
|
||||||
this.map = document.getElementById('map-canvas');
|
|
||||||
/** @type {HTMLElement} */
|
|
||||||
this.head = document.getElementsByTagName('head')[0];
|
|
||||||
|
|
||||||
if (this.userMenu) {
|
|
||||||
this.userMenu.onclick = () => this.showUserMenu();
|
|
||||||
}
|
|
||||||
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.setInterval.onclick = () => this.setAutoReloadTime();
|
|
||||||
this.forceReload.onclick = () => this.trackReload();
|
|
||||||
this.chartLink.onclick = () => this.toggleChart();
|
|
||||||
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.langSelect.onchange = () => {
|
|
||||||
uUI.setLang(this.langSelect.options[this.langSelect.selectedIndex].value);
|
|
||||||
};
|
|
||||||
this.unitsSelect.onchange = () => {
|
|
||||||
uUI.setUnits(this.unitsSelect.options[this.unitsSelect.selectedIndex].value);
|
|
||||||
};
|
|
||||||
this.exportKml.onclick = () => {
|
|
||||||
this.emit(uEvent.EXPORT, 'kml');
|
|
||||||
};
|
|
||||||
this.exportGpx.onclick = () => {
|
|
||||||
this.emit(uEvent.EXPORT, 'gpx');
|
|
||||||
};
|
|
||||||
if (this.inputFile) {
|
|
||||||
this.inputFile.onchange = () => {
|
|
||||||
const form = this.inputFile.parentElement;
|
|
||||||
const sizeMax = form.elements['MAX_FILE_SIZE'].value;
|
|
||||||
if (this.inputFile.files && this.inputFile.files.length === 1 && this.inputFile.files[0].size > sizeMax) {
|
|
||||||
alert(uUtils.sprintf(lang.strings['isizefailure'], sizeMax));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.emit(uEvent.IMPORT, form);
|
|
||||||
};
|
|
||||||
this.importGpx.onclick = () => {
|
|
||||||
this.inputFile.click();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
if (this.addUser) {
|
|
||||||
this.addUser.onclick = () => {
|
|
||||||
this.emit(uEvent.ADD, this.userSelect);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (this.editUser) {
|
|
||||||
this.editUser.onclick = () => {
|
|
||||||
this.emit(uEvent.EDIT, this.userSelect);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (this.editTrack) {
|
|
||||||
this.editTrack.onclick = () => {
|
|
||||||
this.emit(uEvent.EDIT, this.trackSelect);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.menuClose.onclick = () => this.toggleSideMenu();
|
|
||||||
this.chartClose.onclick = () => this.hideChart();
|
|
||||||
this.emit(uEvent.UI_READY);
|
|
||||||
}
|
|
||||||
|
|
||||||
trackReload() {
|
|
||||||
uUI.emitDom(this.trackSelect, 'change');
|
|
||||||
}
|
|
||||||
|
|
||||||
userReload() {
|
|
||||||
uUI.emitDom(this.userSelect, 'change');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Toggle auto-reload
|
|
||||||
*/
|
|
||||||
toggleAutoReload() {
|
|
||||||
if (this.isLiveOn) {
|
|
||||||
this.stopAutoReload();
|
|
||||||
} else {
|
|
||||||
this.startAutoReload();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
startAutoReload() {
|
|
||||||
this.isLiveOn = true;
|
|
||||||
this.liveInterval = setInterval(() => {
|
|
||||||
this.trackReload();
|
|
||||||
}, config.interval * 1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
stopAutoReload() {
|
|
||||||
this.isLiveOn = false;
|
|
||||||
clearInterval(this.liveInterval);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set new interval from user dialog
|
|
||||||
*/
|
|
||||||
setAutoReloadTime() {
|
|
||||||
const i = parseInt(prompt(lang.strings['newinterval']));
|
|
||||||
if (!isNaN(i) && i !== config.interval) {
|
|
||||||
config.interval = i;
|
|
||||||
this.interval.innerHTML = config.interval.toString();
|
|
||||||
// if live tracking on, reload with new interval
|
|
||||||
if (this.isLiveOn) {
|
|
||||||
this.stopAutoReload();
|
|
||||||
this.startAutoReload();
|
|
||||||
}
|
|
||||||
// save current state as default
|
|
||||||
uUtils.setCookie('interval', config.interval, 30);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Toggle side menu
|
|
||||||
*/
|
|
||||||
toggleSideMenu() {
|
|
||||||
if (this.menuClose.innerHTML === '»') {
|
|
||||||
this.menu.style.width = '0';
|
|
||||||
this.main.style.marginRight = '0';
|
|
||||||
this.menuClose.style.right = '0';
|
|
||||||
this.menuClose.innerHTML = '«';
|
|
||||||
} else {
|
|
||||||
this.menu.style.width = '165px';
|
|
||||||
this.main.style.marginRight = '165px';
|
|
||||||
this.menuClose.style.right = '165px';
|
|
||||||
this.menuClose.innerHTML = '»';
|
|
||||||
}
|
|
||||||
uUI.emitDom(window, 'resize');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Dispatch event at specified target
|
|
||||||
* @param {(Element|Document|Window)} el Target element
|
|
||||||
* @param {string} event Event name
|
|
||||||
*/
|
|
||||||
static emitDom(el, event) {
|
|
||||||
el.dispatchEvent(new Event(event));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Dispatch event
|
|
||||||
* @param {string} type
|
|
||||||
* @param {*=} args Defaults to this
|
|
||||||
*/
|
|
||||||
emit(type, args) {
|
|
||||||
const data = args || this;
|
|
||||||
this._binder.dispatchEvent(type, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Is chart visible
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
isChartVisible() {
|
|
||||||
return this.bottom.style.display === 'block';
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Show chart
|
|
||||||
*/
|
|
||||||
showChart() {
|
|
||||||
this.bottom.style.display = 'block';
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Hide chart
|
|
||||||
*/
|
|
||||||
hideChart() {
|
|
||||||
this.bottom.style.display = 'none';
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Toggle chart visibility
|
|
||||||
*/
|
|
||||||
toggleChart() {
|
|
||||||
if (this.isChartVisible()) {
|
|
||||||
this.hideChart();
|
|
||||||
} else {
|
|
||||||
this.showChart();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Animate element text
|
|
||||||
* @param {HTMLElement} el
|
|
||||||
*/
|
|
||||||
static setLoader(el) {
|
|
||||||
const str = el.textContent;
|
|
||||||
el.innerHTML = '';
|
|
||||||
for (const c of str) {
|
|
||||||
el.innerHTML += `<span class="loader">${c}</span>`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Stop animation
|
|
||||||
* @param {HTMLElement} el
|
|
||||||
*/
|
|
||||||
static removeLoader(el) {
|
|
||||||
el.innerHTML = el.textContent;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get popup html
|
|
||||||
* @param {number} id Position ID
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
static getPopupHtml(id) {
|
|
||||||
const pos = uLogger.trackList.current.positions[id];
|
|
||||||
const count = uLogger.trackList.current.positions.length;
|
|
||||||
let date = '–––';
|
|
||||||
let time = '–––';
|
|
||||||
if (pos.timestamp > 0) {
|
|
||||||
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') {
|
|
||||||
provider = ` (<img class="icon" alt="${lang.strings['gps']}" title="${lang.strings['gps']}" src="images/gps_dark.svg">)`;
|
|
||||||
} else if (pos.provider === 'network') {
|
|
||||||
provider = ` (<img class="icon" alt="${lang.strings['network']}" title="${lang.strings['network']}" src="images/network_dark.svg">)`;
|
|
||||||
}
|
|
||||||
let stats = '';
|
|
||||||
if (!config.showLatest) {
|
|
||||||
stats =
|
|
||||||
`<div id="pright">
|
|
||||||
<img class="icon" alt="${lang.strings['track']}" src="images/stats_blue.svg" style="padding-left: 3em;"><br>
|
|
||||||
<img class="icon" alt="${lang.strings['ttime']}" title="${lang.strings['ttime']}" src="images/time_blue.svg"> ${pos.totalSeconds.toHMS()}<br>
|
|
||||||
<img class="icon" alt="${lang.strings['aspeed']}" title="${lang.strings['aspeed']}" src="images/speed_blue.svg"> ${(pos.totalSeconds > 0) ? ((pos.totalDistance / pos.totalSeconds).toKmH() * config.factor_kmh).toFixed() : 0} ${config.unit_kmh}<br>
|
|
||||||
<img class="icon" alt="${lang.strings['tdistance']}" title="${lang.strings['tdistance']}" src="images/distance_blue.svg"> ${(pos.totalDistance.toKm() * config.factor_km).toFixed(2)} ${config.unit_km}<br>
|
|
||||||
</div>`;
|
|
||||||
}
|
|
||||||
return `<div id="popup">
|
|
||||||
<div id="pheader">
|
|
||||||
<div><img alt="${lang.strings['user']}" title="${lang.strings['user']}" src="images/user_dark.svg"> ${uUtils.htmlEncode(pos.username)}</div>
|
|
||||||
<div><img alt="${lang.strings['track']}" title="${lang.strings['track']}" src="images/route_dark.svg"> ${uUtils.htmlEncode(pos.trackname)}</div>
|
|
||||||
</div>
|
|
||||||
<div id="pbody">
|
|
||||||
${(pos.hasComment()) ? `<div id="pcomments">${uUtils.htmlEncode(pos.comment)}</div>` : ''}
|
|
||||||
${(pos.hasImage()) ? `<div id="pimage"><img src="uploads/${pos.image}" alt="image"></div>` : ''}
|
|
||||||
<div id="pleft">
|
|
||||||
<img class="icon" alt="${lang.strings['time']}" title="${lang.strings['time']}" src="images/calendar_dark.svg"> ${date}<br>
|
|
||||||
<img class="icon" alt="${lang.strings['time']}" title="${lang.strings['time']}" src="images/clock_dark.svg"> ${time}<br>
|
|
||||||
${(pos.speed != null) ? `<img class="icon" alt="${lang.strings['speed']}" title="${lang.strings['speed']}" src="images/speed_dark.svg">${pos.speed.toKmH() * config.factor_kmh} ${config.unit_kmh}<br>` : ''}
|
|
||||||
${(pos.altitude != null) ? `<img class="icon" alt="${lang.strings['altitude']}" title="${lang.strings['altitude']}" src="images/altitude_dark.svg">${(pos.altitude * config.factor_m).toFixed()} ${config.unit_m}<br>` : ''}
|
|
||||||
${(pos.accuracy != null) ? `<img class="icon" alt="${lang.strings['accuracy']}" title="${lang.strings['accuracy']}" src="images/accuracy_dark.svg">${(pos.accuracy * config.factor_m).toFixed()} ${config.unit_m}${provider}<br>` : ''}
|
|
||||||
</div>${stats}</div>
|
|
||||||
<div id="pfooter">${uUtils.sprintf(lang.strings['pointof'], id + 1, count)}</div>
|
|
||||||
</div>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update track summary
|
|
||||||
* @param {number} timestamp
|
|
||||||
* @param {number=} totalDistance Total distance (m)
|
|
||||||
* @param {number=} totalTime Total time (s)
|
|
||||||
*/
|
|
||||||
updateSummary(timestamp, totalDistance, totalTime) {
|
|
||||||
if (config.showLatest) {
|
|
||||||
const today = new Date();
|
|
||||||
const date = new Date(timestamp * 1000);
|
|
||||||
let dateString = '';
|
|
||||||
if (date.toDateString() !== today.toDateString()) {
|
|
||||||
dateString = `${date.getFullYear()}-${(`0${date.getMonth() + 1}`).slice(-2)}-${(`0${date.getDate()}`).slice(-2)}<br>`;
|
|
||||||
}
|
|
||||||
let timeString = date.toTimeString();
|
|
||||||
let offset;
|
|
||||||
if ((offset = timeString.indexOf(' ')) >= 0) {
|
|
||||||
timeString = `${timeString.substr(0, offset)} <span style="font-weight:normal">${timeString.substr(offset + 1)}</span>`;
|
|
||||||
}
|
|
||||||
this.summary.innerHTML = `
|
|
||||||
<div class="menu-title">${lang.strings['latest']}:</div>
|
|
||||||
${dateString}
|
|
||||||
${timeString}`;
|
|
||||||
} else {
|
|
||||||
this.summary.innerHTML = `
|
|
||||||
<div class="menu-title">${lang.strings['summary']}</div>
|
|
||||||
<div><img class="icon" alt="${lang.strings['tdistance']}" title="${lang.strings['tdistance']}" src="images/distance.svg"> ${(totalDistance.toKm() * config.factor_km).toFixed(2)} ${config.unit_km}</div>
|
|
||||||
<div><img class="icon" alt="${lang.strings['ttime']}" title="${lang.strings['ttime']}" src="images/time.svg"> ${totalTime.toHMS()}</div>`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clear map canvas
|
|
||||||
*/
|
|
||||||
clearMapCanvas() {
|
|
||||||
this.map.innerHTML = '';
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Toggle user menu visibility
|
|
||||||
*/
|
|
||||||
showUserMenu() {
|
|
||||||
if (this.userDropdown.style.display === 'block') {
|
|
||||||
this.userDropdown.style.display = 'none';
|
|
||||||
} else {
|
|
||||||
this.userDropdown.style.display = 'block';
|
|
||||||
window.addEventListener('click', this.hideUserMenu, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Click listener callback to hide user menu
|
|
||||||
* @param {MouseEvent} e
|
|
||||||
*/
|
|
||||||
hideUserMenu(e) {
|
|
||||||
const parent = e.target.parentElement;
|
|
||||||
this.userDropdown.style.display = 'none';
|
|
||||||
window.removeEventListener('click', this.hideUserMenu, true);
|
|
||||||
if (!parent.classList.contains('dropdown')) {
|
|
||||||
e.stopPropagation();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove HTML element
|
|
||||||
* @param {string} id Element ID
|
|
||||||
*/
|
|
||||||
static removeElementById(id) {
|
|
||||||
const tag = document.getElementById(id);
|
|
||||||
if (tag && tag.parentNode) {
|
|
||||||
tag.parentNode.removeChild(tag);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param {(Event|uEvent)} event
|
|
||||||
* @param {*=} args
|
|
||||||
*/
|
|
||||||
handleEvent(event, args) {
|
|
||||||
if (event.type === uEvent.CHART_READY) {
|
|
||||||
// toggle chart link
|
|
||||||
const hasPoints = args > 0;
|
|
||||||
if (hasPoints) {
|
|
||||||
this.chartLink.style.visibility = 'visible';
|
|
||||||
} 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set language
|
|
||||||
* @param {string} languageCode Language code
|
|
||||||
*/
|
|
||||||
static setLang(languageCode) {
|
|
||||||
uUtils.setCookie('lang', languageCode, 30);
|
|
||||||
uUI.reload();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set units
|
|
||||||
* @param {string} unitCode New units
|
|
||||||
*/
|
|
||||||
static setUnits(unitCode) {
|
|
||||||
uUtils.setCookie('units', unitCode, 30);
|
|
||||||
uUI.reload();
|
|
||||||
}
|
|
||||||
|
|
||||||
static reload() {
|
|
||||||
window.location.reload();
|
|
||||||
}
|
|
||||||
|
|
||||||
static toggleLatest() {
|
|
||||||
config.showLatest = !config.showLatest;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get SVG marker path
|
|
||||||
* @param {boolean} isLarge Large marker with hole if true
|
|
||||||
* @return {string}
|
|
||||||
*/
|
|
||||||
static getMarkerPath(isLarge) {
|
|
||||||
const markerHole = 'M15,34.911c0,0,0.359-3.922,1.807-8.588c0.414-1.337,1.011-2.587,2.495-4.159' +
|
|
||||||
'c1.152-1.223,3.073-2.393,3.909-4.447c1.681-6.306-3.676-9.258-8.211-9.258c-4.536,0-9.893,2.952-8.211,9.258' +
|
|
||||||
'c0.836,2.055,2.756,3.225,3.91,4.447c1.484,1.572,2.08,2.822,2.495,4.159C14.64,30.989,15,34.911,15,34.911z M18,15.922' +
|
|
||||||
'c0,1.705-1.342,3.087-2.999,3.087c-1.657,0-3-1.382-3-3.087c0-1.704,1.343-3.086,3-3.086C16.658,12.836,18,14.218,18,15.922z';
|
|
||||||
const marker = 'M14.999,34.911c0,0,0.232-1.275,1.162-4.848c0.268-1.023,0.652-1.98,1.605-3.184' +
|
|
||||||
'c0.742-0.937,1.975-1.832,2.514-3.404c1.082-4.828-2.363-7.088-5.281-7.088c-2.915,0-6.361,2.26-5.278,7.088' +
|
|
||||||
'c0.538,1.572,1.771,2.468,2.514,3.404c0.953,1.203,1.337,2.16,1.604,3.184C14.77,33.635,14.999,34.911,14.999,34.911z';
|
|
||||||
return isLarge ? markerHole : marker;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get marker extra mark
|
|
||||||
* @param {boolean} isLarge
|
|
||||||
* @return {string}
|
|
||||||
*/
|
|
||||||
static getMarkerExtra(isLarge) {
|
|
||||||
const offset1 = isLarge ? 'M26.074,13.517' : 'M23.328,20.715';
|
|
||||||
const offset2 = isLarge ? 'M28.232,10.942' : 'M25.486,18.141';
|
|
||||||
return `<path fill="none" stroke="red" stroke-width="2" d="${offset1}c0-3.961-3.243-7.167-7.251-7.167"/>
|
|
||||||
<path fill="none" stroke="red" stroke-width="2" d="${offset2}c-0.5-4.028-3.642-7.083-7.724-7.542"/>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get inline SVG source
|
|
||||||
* @param {string} fill
|
|
||||||
* @param {boolean=} isLarge
|
|
||||||
* @param {boolean=} isExtra
|
|
||||||
* @return {string}
|
|
||||||
*/
|
|
||||||
static getSvgSrc(fill, isLarge, isExtra) {
|
|
||||||
const svg = `<svg viewBox="0 0 30 35" width="30px" height="35px" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<g><path stroke="black" fill="${fill}" d="${uUI.getMarkerPath(isLarge)}"/>${isExtra ? uUI.getMarkerExtra(isLarge) : ''}</g></svg>`;
|
|
||||||
return `data:image/svg+xml,${encodeURIComponent(svg)}`;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,48 +0,0 @@
|
|||||||
/*
|
|
||||||
* μ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';
|
|
||||||
import uBinder from './binder.js';
|
|
||||||
import uChart from './chart.js';
|
|
||||||
import uEvent from './event.js';
|
|
||||||
import uMap from './map.js';
|
|
||||||
import uUI from './ui.js';
|
|
||||||
|
|
||||||
|
|
||||||
export const uLogger = {
|
|
||||||
/** @type {?UserList} */
|
|
||||||
userList: null,
|
|
||||||
/** @type {?TrackList} */
|
|
||||||
trackList: null
|
|
||||||
};
|
|
||||||
|
|
||||||
const binder = new uBinder();
|
|
||||||
binder.addEventListener(uEvent.PASSWORD, auth);
|
|
||||||
config.binder = binder;
|
|
||||||
|
|
||||||
new uMap(binder);
|
|
||||||
new uChart(binder);
|
|
||||||
new uUI(binder);
|
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
|
||||||
uLogger.userList = new UserList('#user', binder);
|
|
||||||
uLogger.trackList = new TrackList('#track', binder);
|
|
||||||
});
|
|
75
js/user.js
75
js/user.js
@ -1,75 +0,0 @@
|
|||||||
/*
|
|
||||||
* μ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';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @class uUser
|
|
||||||
* @extends {uData}
|
|
||||||
* @property {number} id
|
|
||||||
* @property {string} login
|
|
||||||
* @property {string} [password]
|
|
||||||
*/
|
|
||||||
export default class uUser extends uData {
|
|
||||||
/**
|
|
||||||
* @param {number} id
|
|
||||||
* @param {string} login
|
|
||||||
*/
|
|
||||||
constructor(id, login) {
|
|
||||||
super(id, login, 'id', 'login');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param {string} action
|
|
||||||
* @return {Promise<uUser>}
|
|
||||||
*/
|
|
||||||
update(action) {
|
|
||||||
const pass = this.password;
|
|
||||||
// don't store password in class property
|
|
||||||
delete this.password;
|
|
||||||
return uAjax.post('utils/handleuser.php',
|
|
||||||
{
|
|
||||||
action: action,
|
|
||||||
login: this.login,
|
|
||||||
pass: pass
|
|
||||||
}).then((xml) => {
|
|
||||||
if (action === 'add') {
|
|
||||||
this.id = uUtils.getNodeAsInt(xml, 'userid');
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string} password
|
|
||||||
* @param {string} oldPassword
|
|
||||||
* @return {Promise<void>}
|
|
||||||
*/
|
|
||||||
changePass(password, oldPassword) {
|
|
||||||
return uAjax.post('utils/changepass.php',
|
|
||||||
{
|
|
||||||
login: this.login,
|
|
||||||
pass: password,
|
|
||||||
oldpass: oldPassword
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
179
js/userdialog.js
179
js/userdialog.js
@ -1,179 +0,0 @@
|
|||||||
/*
|
|
||||||
* μ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 uModal from './modal.js';
|
|
||||||
import uUtils from './utils.js';
|
|
||||||
|
|
||||||
export default class UserDialog {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string} type: edit, add, pass
|
|
||||||
* @param {uUser=} user Update existing user if supplied
|
|
||||||
*/
|
|
||||||
constructor(type, user) {
|
|
||||||
this.type = type;
|
|
||||||
this.user = user;
|
|
||||||
this.dialog = new uModal(this.getHtml());
|
|
||||||
this.form = this.dialog.modal.querySelector('#userForm');
|
|
||||||
this.form.onsubmit = () => false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return {string}
|
|
||||||
*/
|
|
||||||
getHtml() {
|
|
||||||
let deleteButton = '';
|
|
||||||
let header = '';
|
|
||||||
let action;
|
|
||||||
let fields;
|
|
||||||
switch (this.type) {
|
|
||||||
case 'add':
|
|
||||||
action = 'add';
|
|
||||||
header = `<label><b>${lang.strings['username']}</b></label>
|
|
||||||
<input type="text" placeholder="${lang.strings['usernameenter']}" name="login" required>`;
|
|
||||||
fields = `<label><b>${lang.strings['password']}</b></label>
|
|
||||||
<input type="password" placeholder="${lang.strings['passwordenter']}" name="pass" required>
|
|
||||||
<label><b>${lang.strings['passwordrepeat']}</b></label>
|
|
||||||
<input type="password" placeholder="${lang.strings['passwordenter']}" name="pass2" required>`;
|
|
||||||
break;
|
|
||||||
case 'edit':
|
|
||||||
action = 'update';
|
|
||||||
deleteButton = `<div style="float:left">${uUtils.sprintf(lang.strings['editinguser'], `<b>${uUtils.htmlEncode(this.user.login)}</b>`)}</div>
|
|
||||||
<div class="red-button button-resolve" data-action="delete" data-confirm="${uUtils.sprintf(lang.strings['userdelwarn'], uUtils.htmlEncode(this.user.login))}"><b><a>${lang.strings['deluser']}</a></b></div>
|
|
||||||
<div style="clear: both; padding-bottom: 1em;"></div>`;
|
|
||||||
fields = `<label><b>${lang.strings['password']}</b></label>
|
|
||||||
<input type="password" placeholder="${lang.strings['passwordenter']}" name="pass" required>
|
|
||||||
<label><b>${lang.strings['passwordrepeat']}</b></label>
|
|
||||||
<input type="password" placeholder="${lang.strings['passwordenter']}" name="pass2" required>`;
|
|
||||||
break;
|
|
||||||
case 'pass':
|
|
||||||
action = 'update';
|
|
||||||
fields = `<label><b>${lang.strings['oldpassword']}</b></label>
|
|
||||||
<input type="password" placeholder="${lang.strings['passwordenter']}" name="oldpass" required>
|
|
||||||
<label><b>${lang.strings['newpassword']}</b></label>
|
|
||||||
<input type="password" placeholder="${lang.strings['passwordenter']}" name="pass" required>
|
|
||||||
<label><b>${lang.strings['newpasswordrepeat']}</b></label>
|
|
||||||
<input type="password" placeholder="${lang.strings['passwordenter']}" name="pass2" required>`;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new Error(`Unknown dialog type: ${this.type}`);
|
|
||||||
}
|
|
||||||
return `${deleteButton}
|
|
||||||
<form id="userForm">
|
|
||||||
${header}
|
|
||||||
${fields}
|
|
||||||
<div class="buttons">
|
|
||||||
<button class="button-reject" type="button">${lang.strings['cancel']}</button>
|
|
||||||
<button class="button-resolve" type="submit" data-action="${action}">${lang.strings['submit']}</button>
|
|
||||||
</div>
|
|
||||||
</form>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Show edit user dialog
|
|
||||||
* @see {uModal}
|
|
||||||
* @returns {Promise<ModalResult>}
|
|
||||||
*/
|
|
||||||
show() {
|
|
||||||
return new Promise((resolve) => {
|
|
||||||
this.resolveModal(resolve);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {ModalCallback} resolve
|
|
||||||
*/
|
|
||||||
resolveModal(resolve) {
|
|
||||||
this.dialog.show().then((result) => {
|
|
||||||
if (result.cancelled) {
|
|
||||||
return this.hide();
|
|
||||||
}
|
|
||||||
if (result.action === 'update' || result.action === 'add') {
|
|
||||||
if (!this.validate()) {
|
|
||||||
return this.resolveModal(resolve);
|
|
||||||
}
|
|
||||||
result.data = this.getData();
|
|
||||||
}
|
|
||||||
return resolve(result);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Hide dialog
|
|
||||||
*/
|
|
||||||
hide() {
|
|
||||||
this.dialog.hide();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get data from track form
|
|
||||||
* @return {boolean|{login: string, password: string, oldPassword: ?string}}
|
|
||||||
*/
|
|
||||||
getData() {
|
|
||||||
let login;
|
|
||||||
if (this.type === 'add') {
|
|
||||||
login = this.form.elements['login'].value.trim();
|
|
||||||
} else {
|
|
||||||
login = this.user.login;
|
|
||||||
}
|
|
||||||
let oldPass = null;
|
|
||||||
if (this.type === 'pass') {
|
|
||||||
oldPass = this.form.elements['oldpass'].value.trim();
|
|
||||||
}
|
|
||||||
const pass = this.form.elements['pass'].value.trim();
|
|
||||||
return { login: login, password: pass, oldPassword: oldPass };
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validate form
|
|
||||||
* @return {boolean} True if valid
|
|
||||||
*/
|
|
||||||
validate() {
|
|
||||||
if (this.type === 'add') {
|
|
||||||
const login = this.form.elements['login'].value.trim();
|
|
||||||
if (!login) {
|
|
||||||
alert(lang.strings['allrequired']);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else if (this.type === 'pass') {
|
|
||||||
const oldPass = this.form.elements['oldpass'].value.trim();
|
|
||||||
if (!oldPass) {
|
|
||||||
alert(lang.strings['allrequired']);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const pass = this.form.elements['pass'].value.trim();
|
|
||||||
const pass2 = this.form.elements['pass2'].value.trim();
|
|
||||||
if (!pass || !pass2) {
|
|
||||||
alert(lang.strings['allrequired']);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (pass !== pass2) {
|
|
||||||
alert(lang.strings['passnotmatch']);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!config.pass_regex.test(pass)) {
|
|
||||||
alert(lang.strings['passlenmin'] + '\n' + lang.strings['passrules']);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
146
js/userlist.js
146
js/userlist.js
@ -1,146 +0,0 @@
|
|||||||
/*
|
|
||||||
* μ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';
|
|
||||||
import { uLogger } from './ulogger.js';
|
|
||||||
import uUser from './user.js';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @class UserList
|
|
||||||
* @extends {uList<uUser>}
|
|
||||||
*/
|
|
||||||
export default class UserList extends uList {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string} selector
|
|
||||||
* @param {uBinder} binder
|
|
||||||
*/
|
|
||||||
constructor(selector, binder) {
|
|
||||||
super(selector, binder, uUser);
|
|
||||||
super.hasHead = true;
|
|
||||||
super.allValue = `- ${lang.strings['allusers']} -`;
|
|
||||||
super.headValue = lang.strings['suser'];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @override
|
|
||||||
*/
|
|
||||||
onChange() {
|
|
||||||
if (config.showLatest) {
|
|
||||||
if (this.isSelectedAllOption) {
|
|
||||||
uLogger.trackList.fetchLatest();
|
|
||||||
} else {
|
|
||||||
uLogger.trackList.fetch()
|
|
||||||
.then(() => uLogger.trackList.fetchLatest());
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @override
|
|
||||||
*/
|
|
||||||
onEdit() {
|
|
||||||
if (this.isSelectedAllOption) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (this.current) {
|
|
||||||
if (this.current.login === auth.user.login) {
|
|
||||||
alert(lang.strings['selfeditwarn']);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.editUser();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {UserDialog=} modal
|
|
||||||
*/
|
|
||||||
editUser(modal) {
|
|
||||||
const dialog = modal || new UserDialog('edit', this.current);
|
|
||||||
dialog.show()
|
|
||||||
.then((result) => {
|
|
||||||
switch (result.action) {
|
|
||||||
case 'update':
|
|
||||||
// currently only password
|
|
||||||
this.current.password = result.data.password;
|
|
||||||
return this.current.update('update');
|
|
||||||
case 'delete':
|
|
||||||
return this.current.update('delete').then(() => this.remove(this.current.id));
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
throw new Error();
|
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
alert(lang.strings['actionsuccess']);
|
|
||||||
dialog.hide();
|
|
||||||
})
|
|
||||||
.catch((msg) => {
|
|
||||||
alert(`${lang.strings['actionfailure']}\n${msg}`);
|
|
||||||
this.editUser(dialog);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @override
|
|
||||||
*/
|
|
||||||
onAdd() {
|
|
||||||
this.addUser();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {UserDialog=} modal
|
|
||||||
*/
|
|
||||||
addUser(modal) {
|
|
||||||
const dialog = modal || new UserDialog('add');
|
|
||||||
dialog.show()
|
|
||||||
.then((result) => {
|
|
||||||
const newUser = new uUser(0, result.data.login);
|
|
||||||
newUser.password = result.data.password;
|
|
||||||
return newUser.update('add')
|
|
||||||
})
|
|
||||||
.then((user) => {
|
|
||||||
alert(lang.strings['actionsuccess']);
|
|
||||||
this.add(user);
|
|
||||||
dialog.hide();
|
|
||||||
})
|
|
||||||
.catch((msg) => {
|
|
||||||
alert(`${lang.strings['actionfailure']}\n${msg}`);
|
|
||||||
this.addUser(dialog);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
286
js/utils.js
286
js/utils.js
@ -1,286 +0,0 @@
|
|||||||
/*
|
|
||||||
* μ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 {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set cookie
|
|
||||||
* @param {string} name
|
|
||||||
* @param {(string|number)} value
|
|
||||||
* @param {number=} days
|
|
||||||
*/
|
|
||||||
static setCookie(name, value, days) {
|
|
||||||
let expires = '';
|
|
||||||
if (days) {
|
|
||||||
const date = new Date();
|
|
||||||
date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
|
|
||||||
expires = `; expires=${date.toUTCString()}`;
|
|
||||||
}
|
|
||||||
document.cookie = `ulogger_${name}=${value}${expires}; path=/`;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* sprintf, naive approach, only %s, %d supported
|
|
||||||
* @param {string} fmt String
|
|
||||||
* @param {...(string|number)=} params Optional parameters
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
static sprintf(fmt, params) { // eslint-disable-line no-unused-vars
|
|
||||||
const args = Array.prototype.slice.call(arguments);
|
|
||||||
const format = args.shift();
|
|
||||||
let i = 0;
|
|
||||||
return format.replace(/%%|%s|%d/g, (match) => {
|
|
||||||
if (match === '%%') {
|
|
||||||
return '%';
|
|
||||||
}
|
|
||||||
return (typeof args[i] != 'undefined') ? args[i++] : match;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add script tag
|
|
||||||
* @param {string} url attribute
|
|
||||||
* @param {string} id attribute
|
|
||||||
* @param {Function=} onload
|
|
||||||
*/
|
|
||||||
static addScript(url, id, onload) {
|
|
||||||
if (id && document.getElementById(id)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const tag = document.createElement('script');
|
|
||||||
tag.type = 'text/javascript';
|
|
||||||
tag.src = url;
|
|
||||||
if (id) {
|
|
||||||
tag.id = id;
|
|
||||||
}
|
|
||||||
tag.async = true;
|
|
||||||
if (onload instanceof Function) {
|
|
||||||
tag.onload = onload;
|
|
||||||
}
|
|
||||||
|
|
||||||
document.getElementsByTagName('head')[0].appendChild(tag);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Encode string for HTML
|
|
||||||
* @param {string} s
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
static htmlEncode(s) {
|
|
||||||
return s.replace(/&/g, '&')
|
|
||||||
.replace(/"/g, '"')
|
|
||||||
.replace(/'/g, ''')
|
|
||||||
.replace(/</g, '<')
|
|
||||||
.replace(/>/g, '>');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Convert hex string and opacity to an rgba string
|
|
||||||
* @param {string} hex
|
|
||||||
* @param {number} opacity
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
static hexToRGBA(hex, opacity) {
|
|
||||||
return 'rgba(' + (hex = hex.replace('#', ''))
|
|
||||||
.match(new RegExp('(.{' + hex.length / 3 + '})', 'g'))
|
|
||||||
.map((l) => parseInt(hex.length % 2 ? l + l : l, 16))
|
|
||||||
.concat(opacity || 1).join(',') + ')';
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add link tag with type css
|
|
||||||
* @param {string} url attribute
|
|
||||||
* @param {string} id attribute
|
|
||||||
*/
|
|
||||||
static addCss(url, id) {
|
|
||||||
if (id && document.getElementById(id)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const tag = document.createElement('link');
|
|
||||||
tag.type = 'text/css';
|
|
||||||
tag.rel = 'stylesheet';
|
|
||||||
tag.href = url;
|
|
||||||
if (id) {
|
|
||||||
tag.id = id;
|
|
||||||
}
|
|
||||||
document.getElementsByTagName('head')[0].appendChild(tag);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string} html HTML representing a single element
|
|
||||||
* @return {Node}
|
|
||||||
*/
|
|
||||||
static nodeFromHtml(html) {
|
|
||||||
const template = document.createElement('template');
|
|
||||||
template.innerHTML = html;
|
|
||||||
return template.content.firstChild;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string} html HTML representing a single element
|
|
||||||
* @return {NodeList}
|
|
||||||
*/
|
|
||||||
static nodesFromHtml(html) {
|
|
||||||
const template = document.createElement('template');
|
|
||||||
template.innerHTML = html;
|
|
||||||
return template.content.childNodes;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param {NodeList} nodeList
|
|
||||||
* @param {string} selector
|
|
||||||
* @return {?Element}
|
|
||||||
*/
|
|
||||||
static querySelectorInList(nodeList, selector) {
|
|
||||||
for (const node of nodeList) {
|
|
||||||
if (node instanceof HTMLElement) {
|
|
||||||
const el = node.querySelector(selector);
|
|
||||||
if (el) {
|
|
||||||
return el;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get value of first XML child node with given name
|
|
||||||
* @param {(Element|XMLDocument)} node
|
|
||||||
* @param {string} name Node name
|
|
||||||
* @returns {?string} Node value or null if not found
|
|
||||||
*/
|
|
||||||
static getNode(node, name) {
|
|
||||||
const el = node.getElementsByTagName(name);
|
|
||||||
if (el.length) {
|
|
||||||
const children = el[0].childNodes;
|
|
||||||
if (children.length) {
|
|
||||||
return children[0].nodeValue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get value of first XML child node with given name
|
|
||||||
* @param {(Element|XMLDocument)} node
|
|
||||||
* @param {string} name Node name
|
|
||||||
* @returns {?number} Node value or null if not found
|
|
||||||
*/
|
|
||||||
static getNodeAsFloat(node, name) {
|
|
||||||
const str = uUtils.getNode(node, name);
|
|
||||||
if (str != null) {
|
|
||||||
return parseFloat(str);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get value of first XML child node with given name
|
|
||||||
* @param {(Element|XMLDocument)} node
|
|
||||||
* @param {string} name Node name
|
|
||||||
* @returns {?number} Node value or null if not found
|
|
||||||
*/
|
|
||||||
static getNodeAsInt(node, name) {
|
|
||||||
const str = uUtils.getNode(node, name);
|
|
||||||
if (str != null) {
|
|
||||||
return parseInt(str);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get value of first XML child node with given name
|
|
||||||
* @param {(Element|XMLDocument)} node
|
|
||||||
* @param {string} name Node name
|
|
||||||
* @returns {Object<string, string>} Node value or null if not found
|
|
||||||
*/
|
|
||||||
static getNodesArray(node, name) {
|
|
||||||
const el = node.getElementsByTagName(name);
|
|
||||||
if (el.length) {
|
|
||||||
const obj = {};
|
|
||||||
const children = el[0].childNodes;
|
|
||||||
for (const child of children) {
|
|
||||||
if (child.nodeType === Node.ELEMENT_NODE) {
|
|
||||||
obj[child.nodeName] = child.firstChild ? child.firstChild.nodeValue : '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return obj;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get value of first XML child node with given name
|
|
||||||
* @param {(Element|XMLDocument)} node
|
|
||||||
* @param {string} name Node name
|
|
||||||
* @returns {?number} Node value or null if not found
|
|
||||||
*/
|
|
||||||
static getAttributeAsInt(node, name) {
|
|
||||||
const str = node.getAttribute(name);
|
|
||||||
if (str != null) {
|
|
||||||
return parseInt(str);
|
|
||||||
}
|
|
||||||
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
|
|
||||||
Number.prototype.toHMS = function () {
|
|
||||||
let s = this;
|
|
||||||
const d = Math.floor(s / 86400);
|
|
||||||
const h = Math.floor((s % 86400) / 3600);
|
|
||||||
const m = Math.floor(((s % 86400) % 3600) / 60);
|
|
||||||
s = ((s % 86400) % 3600) % 60;
|
|
||||||
return ((d > 0) ? (d + ' d ') : '') + (('00' + h).slice(-2)) + ':' + (('00' + m).slice(-2)) + ':' + (('00' + s).slice(-2)) + '';
|
|
||||||
};
|
|
||||||
|
|
||||||
// meters to km
|
|
||||||
Number.prototype.toKm = function () {
|
|
||||||
return Math.round(this / 10) / 100;
|
|
||||||
};
|
|
||||||
|
|
||||||
// m/s to km/h
|
|
||||||
Number.prototype.toKmH = function () {
|
|
||||||
return Math.round(this * 3600 / 10) / 100;
|
|
||||||
};
|
|
Loading…
x
Reference in New Issue
Block a user