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