es6 initial changes
This commit is contained in:
parent
7f1170187c
commit
1339682b66
@ -426,7 +426,11 @@ class InternalAPITest extends UloggerAPITestCase {
|
||||
|
||||
$options = [
|
||||
"http_errors" => false,
|
||||
"form_params" => [ "userid" => $this->testUserId ],
|
||||
"form_params" => [
|
||||
"login" => $this->testUser,
|
||||
"pass" => $this->testPass,
|
||||
"oldpass" => $this->testPass
|
||||
],
|
||||
];
|
||||
$response = $this->http->post("/utils/changepass.php", $options);
|
||||
$this->assertEquals(401, $response->getStatusCode(), "Unexpected status code");
|
||||
@ -453,7 +457,7 @@ class InternalAPITest extends UloggerAPITestCase {
|
||||
$this->assertEquals((string) $xml->message, "Empty password", "Wrong error message");
|
||||
}
|
||||
|
||||
public function testChangePassNoUser() {
|
||||
public function testChangePassUserUnknown() {
|
||||
$this->assertTrue($this->authenticate(), "Authentication failed");
|
||||
|
||||
$options = [
|
||||
@ -472,12 +476,31 @@ class InternalAPITest extends UloggerAPITestCase {
|
||||
$this->assertEquals((string) $xml->message, "User unknown", "Wrong error message");
|
||||
}
|
||||
|
||||
public function testChangePassEmptyLogin() {
|
||||
$this->assertTrue($this->authenticate(), "Authentication failed");
|
||||
|
||||
$options = [
|
||||
"http_errors" => false,
|
||||
"form_params" => [
|
||||
"pass" => $this->testPass,
|
||||
],
|
||||
];
|
||||
$response = $this->http->post("/utils/changepass.php", $options);
|
||||
$this->assertEquals(200, $response->getStatusCode(), "Unexpected status code");
|
||||
|
||||
$xml = $this->getXMLfromResponse($response);
|
||||
$this->assertTrue($xml !== false, "XML object is not false");
|
||||
$this->assertEquals((int) $xml->error, 1, "Wrong error status");
|
||||
$this->assertEquals((string) $xml->message, "Empty login", "Wrong error message");
|
||||
}
|
||||
|
||||
public function testChangePassWrongOldpass() {
|
||||
$this->assertTrue($this->authenticate(), "Authentication failed");
|
||||
|
||||
$options = [
|
||||
"http_errors" => false,
|
||||
"form_params" => [
|
||||
"login" => $this->testAdminUser,
|
||||
"oldpass" => "badpass",
|
||||
"pass" => "newpass",
|
||||
],
|
||||
@ -497,6 +520,7 @@ class InternalAPITest extends UloggerAPITestCase {
|
||||
$options = [
|
||||
"http_errors" => false,
|
||||
"form_params" => [
|
||||
"login" => $this->testAdminUser,
|
||||
"pass" => "newpass",
|
||||
],
|
||||
];
|
||||
@ -517,6 +541,7 @@ class InternalAPITest extends UloggerAPITestCase {
|
||||
$options = [
|
||||
"http_errors" => false,
|
||||
"form_params" => [
|
||||
"login" => $this->testAdminUser,
|
||||
"oldpass" => $this->testAdminPass,
|
||||
"pass" => $newPass,
|
||||
],
|
||||
@ -539,6 +564,7 @@ class InternalAPITest extends UloggerAPITestCase {
|
||||
$options = [
|
||||
"http_errors" => false,
|
||||
"form_params" => [
|
||||
"login" => $this->testUser,
|
||||
"oldpass" => $this->testPass,
|
||||
"pass" => $newPass,
|
||||
],
|
||||
|
1
css/chartist.min.css
vendored
Normal file
1
css/chartist.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
@ -290,6 +290,10 @@ button {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
button > * {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
#cancel {
|
||||
margin-top: 0.5em;
|
||||
}
|
||||
|
@ -230,14 +230,15 @@
|
||||
return $positionsArr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get array of all positions
|
||||
*
|
||||
* @param int $userId Optional limit to given user id
|
||||
* @param int $trackId Optional limit to given track id
|
||||
* @return array|bool Array of uPosition positions, false on error
|
||||
*/
|
||||
public static function getAll($userId = NULL, $trackId = NULL) {
|
||||
/**
|
||||
* Get array of all positions
|
||||
*
|
||||
* @param int $userId Optional limit to given user id
|
||||
* @param int $trackId Optional limit to given track id
|
||||
* @param int $afterId Optional limit to positions with id greater then given id
|
||||
* @return array|bool Array of uPosition positions, false on error
|
||||
*/
|
||||
public static function getAll($userId = NULL, $trackId = NULL, $afterId = NULL) {
|
||||
$rules = [];
|
||||
if (!empty($userId)) {
|
||||
$rules[] = "p.user_id = " . self::db()->quote($userId);
|
||||
@ -245,6 +246,9 @@
|
||||
if (!empty($trackId)) {
|
||||
$rules[] = "p.track_id = " . self::db()->quote($trackId);
|
||||
}
|
||||
if (!empty($trackId)) {
|
||||
$rules[] = "p.id > " . self::db()->quote($afterId);
|
||||
}
|
||||
if (!empty($rules)) {
|
||||
$where = "WHERE " . implode(" AND ", $rules);
|
||||
} else {
|
||||
|
61
index.php
61
index.php
@ -44,7 +44,6 @@
|
||||
$auth->exitWithRedirect("login.php");
|
||||
}
|
||||
|
||||
|
||||
$displayUserId = NULL;
|
||||
$usersArr = [];
|
||||
if ($auth->isAdmin() || uConfig::$public_tracks) {
|
||||
@ -77,64 +76,8 @@
|
||||
<head>
|
||||
<title><?= $lang["title"] ?></title>
|
||||
<?php include("meta.php"); ?>
|
||||
<script>
|
||||
/** @namespace uLogger */
|
||||
var uLogger = window.uLogger || {};
|
||||
/** @type {number} userId */
|
||||
uLogger.userId = <?= json_encode($displayUserId ? $displayUserId : -1) ?>;
|
||||
/** @type {number} trackId */
|
||||
uLogger.trackId = <?= json_encode($displayTrackId ? $displayTrackId : -1) ?>;
|
||||
|
||||
/** @type {uLogger.config} */
|
||||
uLogger.config = {
|
||||
/** @type {number} */
|
||||
interval: <?= json_encode(uConfig::$interval) ?>,
|
||||
/** @type {string} */
|
||||
units: <?= json_encode(uConfig::$units) ?>,
|
||||
/** @type {string} */
|
||||
mapapi: <?= json_encode(uConfig::$mapapi) ?>,
|
||||
/** @type {?string} */
|
||||
gkey: <?= json_encode(uConfig::$gkey) ?>,
|
||||
/** @type {Object.<string, string>} */
|
||||
ol_layers: <?= json_encode(uConfig::$ol_layers) ?>,
|
||||
/** @type {number} */
|
||||
init_latitude: <?= json_encode(uConfig::$init_latitude) ?>,
|
||||
/** @type {number} */
|
||||
init_longitude: <?= json_encode(uConfig::$init_longitude) ?>,
|
||||
/** @type {boolean} */
|
||||
admin: <?= json_encode($auth->isAdmin()) ?>,
|
||||
/** @type {?string} */
|
||||
auth: <?= json_encode($auth->isAuthenticated() ? $auth->user->login : NULL) ?>,
|
||||
/** @type {RegExp} */
|
||||
pass_regex: <?= uConfig::passRegex() ?>,
|
||||
/** @type {number} */
|
||||
strokeWeight: <?= json_encode(uConfig::$strokeWeight) ?>,
|
||||
/** @type {string} */
|
||||
strokeColor: <?= json_encode(uConfig::$strokeColor) ?>,
|
||||
/** @type {number} */
|
||||
strokeOpacity: <?= json_encode(uConfig::$strokeOpacity) ?>
|
||||
};
|
||||
|
||||
/** @type {uLogger.lang} */
|
||||
uLogger.lang = {
|
||||
/** @type {Object.<string, string>} */
|
||||
strings: <?= json_encode($lang) ?>
|
||||
};
|
||||
</script>
|
||||
<script src="js/main.js"></script>
|
||||
<?php if ($auth->isAdmin()): ?>
|
||||
<script src="js/admin.js"></script>
|
||||
<?php endif; ?>
|
||||
<?php if ($auth->isAuthenticated()): ?>
|
||||
<script src="js/track.js"></script>
|
||||
<?php endif; ?>
|
||||
<script src="js/pass.js"></script>
|
||||
<script src="js/api_gmaps.js"></script>
|
||||
<script src="js/api_openlayers.js"></script>
|
||||
<script src="//www.google.com/jsapi"></script>
|
||||
<script>
|
||||
google.load('visualization', '1', { packages:['corechart'] });
|
||||
</script>
|
||||
<script type="module" src="js/ulogger.js"></script>
|
||||
<!-- <script src="dist/ulogger.js"></script>-->
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
141
js/admin.js
141
js/admin.js
@ -1,141 +0,0 @@
|
||||
/* μlogger
|
||||
*
|
||||
* Copyright(C) 2017 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/>.
|
||||
*/
|
||||
|
||||
/** @namespace */
|
||||
var uLogger = window.uLogger || {};
|
||||
(function (ul) {
|
||||
|
||||
/**
|
||||
* @typedef uLogger.admin
|
||||
* @memberOf uLogger
|
||||
* @type {Object}
|
||||
* @property {function} addUser
|
||||
* @property {function} editUser
|
||||
* @property {function} submitUser
|
||||
*/
|
||||
ul.admin = (function (ns) {
|
||||
|
||||
/**
|
||||
* Show add user dialog
|
||||
*/
|
||||
function addUser() {
|
||||
var form = '<form id="userForm" method="post" onsubmit="uLogger.admin.submitUser(\'add\'); return false">';
|
||||
form += '<label><b>' + ns.lang.strings['username'] + '</b></label><input type="text" placeholder="' + ns.lang.strings['usernameenter'] + '" name="login" required>';
|
||||
form += '<label><b>' + ns.lang.strings['password'] + '</b></label><input type="password" placeholder="' + ns.lang.strings['passwordenter'] + '" name="pass" required>';
|
||||
form += '<label><b>' + ns.lang.strings['passwordrepeat'] + '</b></label><input type="password" placeholder="' + ns.lang.strings['passwordenter'] + '" name="pass2" required>';
|
||||
form += '<div class="buttons"><button type="button" onclick="uLogger.ui.removeModal()">' + ns.lang.strings['cancel'] + '</button><button type="submit">' + ns.lang.strings['submit'] + '</button></div>';
|
||||
form += '</form>';
|
||||
ns.ui.showModal(form);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show edit user dialog
|
||||
*/
|
||||
function editUser() {
|
||||
var userForm = ns.ui.userSelect;
|
||||
var userLogin = (userForm) ? userForm.options[userForm.selectedIndex].text : ns.config.auth;
|
||||
if (userLogin === ns.config.auth) {
|
||||
alert(ns.lang.strings['selfeditwarn']);
|
||||
return;
|
||||
}
|
||||
var message = '<div style="float:left">' + ns.sprintf(ns.lang.strings['editinguser'], '<b>' + ns.htmlEncode(userLogin) + '</b>') + '</div>';
|
||||
message += '<div class="red-button"><b><a href="javascript:void(0);" onclick="uLogger.admin.submitUser(\'delete\'); return false">' + ns.lang.strings['deluser'] + '</a></b></div>';
|
||||
message += '<div style="clear: both; padding-bottom: 1em;"></div>';
|
||||
|
||||
var form = '<form id="userForm" method="post" onsubmit="uLogger.admin.submitUser(\'update\'); return false">';
|
||||
form += '<input type="hidden" name="login" value="' + ns.htmlEncode(userLogin) + '">';
|
||||
form += '<label><b>' + ns.lang.strings['password'] + '</b></label><input type="password" placeholder="' + ns.lang.strings['passwordenter'] + '" name="pass" required>';
|
||||
form += '<label><b>' + ns.lang.strings['passwordrepeat'] + '</b></label><input type="password" placeholder="' + ns.lang.strings['passwordenter'] + '" name="pass2" required>';
|
||||
form += '<div class="buttons"><button type="button" onclick="uLogger.ui.removeModal()">' + ns.lang.strings['cancel'] + '</button><button type="submit">' + ns.lang.strings['submit'] + '</button></div>';
|
||||
form += '</form>';
|
||||
ns.ui.showModal(message + form);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show confirmation dialog
|
||||
* @param {string} login
|
||||
* @returns {boolean} True if confirmed
|
||||
*/
|
||||
function confirmedDelete(login) {
|
||||
return confirm(ns.sprintf(ns.lang.strings['userdelwarn'], '"' + login + '"'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit user form
|
||||
* @param {string} action Add, delete, update
|
||||
*/
|
||||
function submitUser(action) {
|
||||
var form = document.getElementById('userForm');
|
||||
var login = form.elements['login'].value.trim();
|
||||
if (!login) {
|
||||
alert(ns.lang.strings['allrequired']);
|
||||
return;
|
||||
}
|
||||
var pass = null;
|
||||
var pass2 = null;
|
||||
if (action !== 'delete') {
|
||||
pass = form.elements['pass'].value;
|
||||
pass2 = form.elements['pass2'].value;
|
||||
if (!pass || !pass2) {
|
||||
alert(ns.lang.strings['allrequired']);
|
||||
return;
|
||||
}
|
||||
if (pass !== pass2) {
|
||||
alert(ns.lang.strings['passnotmatch']);
|
||||
return;
|
||||
}
|
||||
if (!ns.config.pass_regex.test(pass)) {
|
||||
alert(ns.lang.strings['passlenmin'] + '\n' + ns.lang.strings['passrules']);
|
||||
return;
|
||||
}
|
||||
} else if (!confirmedDelete(login)) {
|
||||
return;
|
||||
}
|
||||
|
||||
ns.post('utils/handleuser.php',
|
||||
{
|
||||
action: action,
|
||||
login: login,
|
||||
pass: pass
|
||||
},
|
||||
{
|
||||
success: function () {
|
||||
ns.ui.removeModal();
|
||||
alert(ns.lang.strings['actionsuccess']);
|
||||
if (action === 'delete') {
|
||||
var f = ns.ui.userSelect;
|
||||
f.remove(f.selectedIndex);
|
||||
ns.selectUser(f);
|
||||
}
|
||||
},
|
||||
fail: function (message) {
|
||||
alert(ns.lang.strings['actionfailure'] + '\n' + message);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// noinspection JSUnusedGlobalSymbols
|
||||
return {
|
||||
addUser: addUser,
|
||||
editUser: editUser,
|
||||
submitUser: submitUser
|
||||
}
|
||||
|
||||
})(ul);
|
||||
|
||||
})(uLogger);
|
98
js/ajax.js
Normal file
98
js/ajax.js
Normal file
@ -0,0 +1,98 @@
|
||||
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'
|
||||
* @param {HTMLElement} [options.loader] Optional element to animate during loading
|
||||
* @return {Promise<Document, string>}
|
||||
*/
|
||||
static ajax(url, data, options) {
|
||||
const params = [];
|
||||
data = data || {};
|
||||
options = options || {};
|
||||
let method = options.method || 'GET';
|
||||
const loader = options.loader;
|
||||
const xhr = new XMLHttpRequest();
|
||||
return new Promise((resolve, reject) => {
|
||||
xhr.onreadystatechange = function () {
|
||||
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);
|
||||
}
|
||||
if (loader) {
|
||||
// UI.removeLoader(loader);
|
||||
}
|
||||
};
|
||||
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);
|
||||
if (loader) {
|
||||
// UI.setLoader(loader);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
296
js/api_gmaps.js
296
js/api_gmaps.js
@ -1,296 +0,0 @@
|
||||
/* μlogger
|
||||
*
|
||||
* Copyright(C) 2017 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/>.
|
||||
*/
|
||||
|
||||
// google maps
|
||||
/** @namespace */
|
||||
var uLogger = uLogger || {};
|
||||
/** @namespace */
|
||||
uLogger.mapAPI = uLogger.mapAPI || {};
|
||||
/** @namespace */
|
||||
uLogger.mapAPI.gmaps = (function(ns) {
|
||||
|
||||
/** @type {google.maps.Map} */
|
||||
var map;
|
||||
/** @type {google.maps.Polyline[]} */
|
||||
var polies = [];
|
||||
/** @type {google.maps.Marker[]} */
|
||||
var markers = [];
|
||||
/** @type {google.maps.InfoWindow[]} */
|
||||
var popups = [];
|
||||
/** @type {google.maps.InfoWindow} */
|
||||
var popup;
|
||||
/** @type {google.maps.PolylineOptions} */
|
||||
var polyOptions;
|
||||
/** @type {google.maps.MapOptions} */
|
||||
var mapOptions;
|
||||
/** @type {number} */
|
||||
var timeoutHandle;
|
||||
var name = 'gmaps';
|
||||
var isLoaded = false;
|
||||
var authError = false;
|
||||
|
||||
/**
|
||||
* Initialize map
|
||||
*/
|
||||
function init() {
|
||||
var url = '//maps.googleapis.com/maps/api/js?' + ((ns.config.gkey != null) ? ('key=' + ns.config.gkey + '&') : '') + 'callback=uLogger.mapAPI.gmaps.setLoaded';
|
||||
ns.addScript(url, 'mapapi_gmaps');
|
||||
if (!isLoaded) {
|
||||
throw new Error("Google Maps API not ready");
|
||||
}
|
||||
start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Start map engine when loaded
|
||||
*/
|
||||
function start() {
|
||||
if (authError) {
|
||||
gm_authFailure();
|
||||
return;
|
||||
}
|
||||
google.maps.visualRefresh = true;
|
||||
// noinspection JSValidateTypes
|
||||
polyOptions = {
|
||||
strokeColor: ns.config.strokeColor,
|
||||
strokeOpacity: ns.config.strokeOpacity,
|
||||
strokeWeight: ns.config.strokeWeight
|
||||
};
|
||||
// noinspection JSValidateTypes
|
||||
mapOptions = {
|
||||
center: new google.maps.LatLng(ns.config.init_latitude, ns.config.init_longitude),
|
||||
zoom: 8,
|
||||
mapTypeId: google.maps.MapTypeId.ROADMAP,
|
||||
scaleControl: true
|
||||
};
|
||||
map = new google.maps.Map(ns.ui.map, mapOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up API
|
||||
*/
|
||||
function cleanup() {
|
||||
polies = [];
|
||||
markers = [];
|
||||
popups = [];
|
||||
map = null;
|
||||
polyOptions = null;
|
||||
mapOptions = null;
|
||||
popup = null;
|
||||
ns.clearMapCanvas();
|
||||
}
|
||||
|
||||
/**
|
||||
* Display track
|
||||
* @param {HTMLCollection} positions XML element
|
||||
* @param {boolean} update Should fit bounds if true
|
||||
*/
|
||||
function displayTrack(positions, update) {
|
||||
var totalMeters = 0;
|
||||
var totalSeconds = 0;
|
||||
// init polyline
|
||||
var poly = new google.maps.Polyline(polyOptions);
|
||||
poly.setMap(map);
|
||||
var path = poly.getPath();
|
||||
var latlngbounds = new google.maps.LatLngBounds();
|
||||
var posLen = positions.length;
|
||||
for (var i = 0; i < posLen; i++) {
|
||||
var p = ns.parsePosition(positions[i], i);
|
||||
totalMeters += p.distance;
|
||||
totalSeconds += p.seconds;
|
||||
p.totalMeters = totalMeters;
|
||||
p.totalSeconds = totalSeconds;
|
||||
// set marker
|
||||
setMarker(p, i, posLen);
|
||||
// update polyline
|
||||
var coordinates = new google.maps.LatLng(p.latitude, p.longitude);
|
||||
path.push(coordinates);
|
||||
latlngbounds.extend(coordinates);
|
||||
}
|
||||
if (update) {
|
||||
map.fitBounds(latlngbounds);
|
||||
if (i === 1) {
|
||||
// only one point, zoom out
|
||||
var zListener =
|
||||
google.maps.event.addListenerOnce(map, 'bounds_changed', function () {
|
||||
if (this.getZoom()) {
|
||||
this.setZoom(15);
|
||||
}
|
||||
});
|
||||
setTimeout(function () { google.maps.event.removeListener(zListener) }, 2000);
|
||||
}
|
||||
}
|
||||
polies.push(poly);
|
||||
|
||||
ns.updateSummary(p.timestamp, totalMeters, totalSeconds);
|
||||
if (p.tid !== ns.config.trackid) {
|
||||
ns.config.trackid = p.tid;
|
||||
ns.setTrack(ns.config.trackid);
|
||||
}
|
||||
ns.updateChart();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear map
|
||||
*/
|
||||
function clearMap() {
|
||||
if (polies) {
|
||||
for (var i = 0; i < polies.length; i++) {
|
||||
polies[i].setMap(null);
|
||||
}
|
||||
}
|
||||
if (markers) {
|
||||
for (var j = 0; j < markers.length; j++) {
|
||||
google.maps.event.removeListener(popups[j].listener);
|
||||
popups[j].setMap(null);
|
||||
markers[j].setMap(null);
|
||||
}
|
||||
}
|
||||
markers.length = 0;
|
||||
polies.length = 0;
|
||||
popups.lentgth = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set marker
|
||||
* @param {uLogger.Position} pos
|
||||
* @param {number} id
|
||||
* @param {number} posLen
|
||||
*/
|
||||
function setMarker(pos, id, posLen) {
|
||||
// marker
|
||||
// noinspection JSCheckFunctionSignatures
|
||||
var marker = new google.maps.Marker({
|
||||
position: new google.maps.LatLng(pos.latitude, pos.longitude),
|
||||
title: (new Date(pos.timestamp * 1000)).toLocaleString(),
|
||||
map: map
|
||||
});
|
||||
if (ns.isLatest()) {
|
||||
marker.setIcon('images/marker-red.png');
|
||||
} else if (id === 0) {
|
||||
marker.setIcon('images/marker-green.png');
|
||||
} else if (id === posLen - 1) {
|
||||
marker.setIcon('images/marker-red.png');
|
||||
} else {
|
||||
marker.setIcon('images/marker-white.png');
|
||||
}
|
||||
// popup
|
||||
var content = ns.getPopupHtml(pos, id, posLen);
|
||||
popup = new google.maps.InfoWindow();
|
||||
// noinspection JSUndefinedPropertyAssignment
|
||||
popup.listener = google.maps.event.addListener(marker, 'click', (function (_marker, _content) {
|
||||
return function () {
|
||||
popup.setContent(_content);
|
||||
popup.open(map, _marker);
|
||||
ns.chartShowPosition(id);
|
||||
}
|
||||
})(marker, content));
|
||||
markers.push(marker);
|
||||
popups.push(popup);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add listener on chart to show position on map
|
||||
* @param {google.visualization.LineChart} chart
|
||||
* @param {google.visualization.DataTable} data
|
||||
*/
|
||||
function addChartEvent(chart, data) {
|
||||
google.visualization.events.addListener(chart, 'select', function () {
|
||||
if (popup) { popup.close(); clearTimeout(timeoutHandle); }
|
||||
var selection = chart.getSelection()[0];
|
||||
if (selection) {
|
||||
var id = data.getValue(selection.row, 0) - 1;
|
||||
var icon = markers[id].getIcon();
|
||||
markers[id].setIcon('images/marker-gold.png');
|
||||
timeoutHandle = setTimeout(function () { markers[id].setIcon(icon); }, 2000);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get map bounds
|
||||
* eg. ((52.20105108685229, 20.789387865580238), (52.292069558807135, 21.172192736185707))
|
||||
* @returns {number[]} Bounds
|
||||
*/
|
||||
function getBounds() {
|
||||
var bounds = map.getBounds();
|
||||
var lat_sw = bounds.getSouthWest().lat();
|
||||
var lon_sw = bounds.getSouthWest().lng();
|
||||
var lat_ne = bounds.getNorthEast().lat();
|
||||
var lon_ne = bounds.getNorthEast().lng();
|
||||
return [lon_sw, lat_sw, lon_ne, lat_ne];
|
||||
}
|
||||
|
||||
/**
|
||||
* Zoom to track extent
|
||||
*/
|
||||
function zoomToExtent() {
|
||||
var latlngbounds = new google.maps.LatLngBounds();
|
||||
for (var i = 0; i < markers.length; i++) {
|
||||
var coordinates = new google.maps.LatLng(markers[i].position.lat(), markers[i].position.lng());
|
||||
latlngbounds.extend(coordinates);
|
||||
}
|
||||
map.fitBounds(latlngbounds);
|
||||
}
|
||||
|
||||
/**
|
||||
* Zoom to bounds
|
||||
* @param {number[]} bounds
|
||||
*/
|
||||
function zoomToBounds(bounds) {
|
||||
var sw = new google.maps.LatLng(bounds[1], bounds[0]);
|
||||
var ne = new google.maps.LatLng(bounds[3], bounds[2]);
|
||||
var latLngBounds = new google.maps.LatLngBounds(sw, ne);
|
||||
map.fitBounds(latLngBounds);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update size
|
||||
*/
|
||||
function updateSize() {
|
||||
// ignore for google API
|
||||
}
|
||||
|
||||
return {
|
||||
name: name,
|
||||
init: init,
|
||||
setLoaded: function () { isLoaded = true; },
|
||||
cleanup: cleanup,
|
||||
displayTrack: displayTrack,
|
||||
clearMap: clearMap,
|
||||
setMarker: setMarker,
|
||||
addChartEvent: addChartEvent,
|
||||
getBounds: getBounds,
|
||||
zoomToExtent: zoomToExtent,
|
||||
zoomToBounds: zoomToBounds,
|
||||
updateSize: updateSize
|
||||
}
|
||||
|
||||
})(uLogger);
|
||||
|
||||
/**
|
||||
* Callback for Google Maps API
|
||||
* It will be called when authentication fails
|
||||
*/
|
||||
function gm_authFailure() {
|
||||
uLogger.mapAPI.gmaps.authError = true;
|
||||
var message = uLogger.sprintf(uLogger.lang.strings['apifailure'], 'Google Maps');
|
||||
message += '<br><br>' + uLogger.lang.strings['gmauthfailure'];
|
||||
message += '<br><br>' + uLogger.lang.strings['gmapilink'];
|
||||
uLogger.ui.showModal(message);
|
||||
}
|
@ -1,494 +0,0 @@
|
||||
/* μlogger
|
||||
*
|
||||
* Copyright(C) 2017 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/>.
|
||||
*/
|
||||
|
||||
|
||||
// openlayers 3+
|
||||
/** @namespace */
|
||||
var uLogger = uLogger || {};
|
||||
/** @namespace */
|
||||
uLogger.mapAPI = uLogger.mapAPI || {};
|
||||
/** @namespace */
|
||||
uLogger.mapAPI.ol = (function(ns) {
|
||||
|
||||
/** @type {ol.Map} */
|
||||
var map;
|
||||
/** @type {ol.layer.Vector} */
|
||||
var layerTrack;
|
||||
/** @type {ol.layer.Vector} */
|
||||
var layerMarkers;
|
||||
/** @type {ol.layer.Base} */
|
||||
var selectedLayer;
|
||||
/** @type {ol.style.Style|{}} */
|
||||
var olStyles;
|
||||
var name = 'openlayers';
|
||||
|
||||
/**
|
||||
* Initialize map
|
||||
*/
|
||||
function init() {
|
||||
var urls = [];
|
||||
urls.push('//cdn.polyfill.io/v2/polyfill.min.js?features=requestAnimationFrame,Element.prototype.classList');
|
||||
urls.push('js/ol.js');
|
||||
for (var i = 0; i < urls.length; i++) {
|
||||
ns.addScript(urls[i], 'mapapi_openlayers' + '_' + i);
|
||||
}
|
||||
|
||||
ns.addCss('css/ol.css', 'ol_css');
|
||||
|
||||
var controls = [
|
||||
new ol.control.Zoom(),
|
||||
new ol.control.Rotate(),
|
||||
new ol.control.ScaleLine(),
|
||||
new ol.control.ZoomToExtent({label: getExtentImg()})
|
||||
];
|
||||
|
||||
var view = new ol.View({
|
||||
center: ol.proj.fromLonLat([ns.config.init_longitude, ns.config.init_latitude]),
|
||||
zoom: 8
|
||||
});
|
||||
|
||||
map = new ol.Map({
|
||||
target: 'map-canvas',
|
||||
controls: controls,
|
||||
view: view
|
||||
});
|
||||
|
||||
// default layer: OpenStreetMap
|
||||
var osm = new ol.layer.Tile({
|
||||
name: 'OpenStreetMap',
|
||||
visible: true,
|
||||
source: new ol.source.OSM()
|
||||
});
|
||||
map.addLayer(osm);
|
||||
selectedLayer = osm;
|
||||
|
||||
// add extra layers
|
||||
for (var layerName in ns.config.ol_layers) {
|
||||
if (ns.config.ol_layers.hasOwnProperty(layerName)) {
|
||||
var layerUrl = ns.config.ol_layers[layerName];
|
||||
var ol_layer = new ol.layer.Tile({
|
||||
name: layerName,
|
||||
visible: false,
|
||||
source: new ol.source.XYZ({
|
||||
url: layerUrl
|
||||
})
|
||||
});
|
||||
map.addLayer(ol_layer);
|
||||
}
|
||||
}
|
||||
|
||||
// init layers
|
||||
var lineStyle = new ol.style.Style({
|
||||
stroke: new ol.style.Stroke({
|
||||
color: ns.hexToRGBA(ns.config.strokeColor, ns.config.strokeOpacity),
|
||||
width: ns.config.strokeWeight
|
||||
})
|
||||
});
|
||||
layerTrack = new ol.layer.Vector({
|
||||
name: 'Track',
|
||||
type: 'data',
|
||||
source: new ol.source.Vector(),
|
||||
style: lineStyle
|
||||
});
|
||||
layerMarkers = new ol.layer.Vector({
|
||||
name: 'Markers',
|
||||
type: 'data',
|
||||
source: new ol.source.Vector()
|
||||
});
|
||||
map.addLayer(layerTrack);
|
||||
map.addLayer(layerMarkers);
|
||||
|
||||
// styles
|
||||
olStyles = {};
|
||||
var iconRed = new ol.style.Icon({
|
||||
anchor: [0.5, 1],
|
||||
src: 'images/marker-red.png'
|
||||
});
|
||||
var iconGreen = new ol.style.Icon({
|
||||
anchor: [0.5, 1],
|
||||
src: 'images/marker-green.png'
|
||||
});
|
||||
var iconWhite = new ol.style.Icon({
|
||||
anchor: [0.5, 1],
|
||||
opacity: 0.7,
|
||||
src: 'images/marker-white.png'
|
||||
});
|
||||
var iconGold = new ol.style.Icon({
|
||||
anchor: [0.5, 1],
|
||||
src: 'images/marker-gold.png'
|
||||
});
|
||||
olStyles['red'] = new ol.style.Style({
|
||||
image: iconRed
|
||||
});
|
||||
olStyles['green'] = new ol.style.Style({
|
||||
image: iconGreen
|
||||
});
|
||||
olStyles['white'] = new ol.style.Style({
|
||||
image: iconWhite
|
||||
});
|
||||
olStyles['gold'] = new ol.style.Style({
|
||||
image: iconGold
|
||||
});
|
||||
|
||||
// popups
|
||||
var popupContainer = document.createElement('div');
|
||||
popupContainer.id = 'popup';
|
||||
popupContainer.className = 'ol-popup';
|
||||
document.body.appendChild(popupContainer);
|
||||
var popupCloser = document.createElement('a');
|
||||
popupCloser.id = 'popup-closer';
|
||||
popupCloser.className = 'ol-popup-closer';
|
||||
popupCloser.href = '#';
|
||||
popupContainer.appendChild(popupCloser);
|
||||
var popupContent = document.createElement('div');
|
||||
popupContent.id = 'popup-content';
|
||||
popupContainer.appendChild(popupContent);
|
||||
|
||||
var popup = new ol.Overlay({
|
||||
element: popupContainer,
|
||||
autoPan: true,
|
||||
autoPanAnimation: {
|
||||
duration: 250
|
||||
}
|
||||
});
|
||||
|
||||
popupCloser.onclick = function () {
|
||||
// eslint-disable-next-line no-undefined
|
||||
popup.setPosition(undefined);
|
||||
popupCloser.blur();
|
||||
return false;
|
||||
};
|
||||
|
||||
// add click handler to map to show popup
|
||||
map.on('click', function (e) {
|
||||
var coordinate = e.coordinate;
|
||||
var feature = map.forEachFeatureAtPixel(e.pixel,
|
||||
function (_feature, _layer) {
|
||||
if (_layer.get('name') === 'Markers') {
|
||||
return _feature;
|
||||
}
|
||||
return null;
|
||||
});
|
||||
if (feature) {
|
||||
var pos = feature.get('p');
|
||||
var id = feature.getId();
|
||||
var posLen = feature.get('posLen');
|
||||
// popup show
|
||||
popup.setPosition(coordinate);
|
||||
popupContent.innerHTML = ns.getPopupHtml(pos, id, posLen);
|
||||
map.addOverlay(popup);
|
||||
ns.chartShowPosition(id);
|
||||
} else {
|
||||
// popup destroy
|
||||
// eslint-disable-next-line no-undefined
|
||||
popup.setPosition(undefined);
|
||||
}
|
||||
});
|
||||
|
||||
// change mouse cursor when over marker
|
||||
map.on('pointermove', function (e) {
|
||||
var hit = map.forEachFeatureAtPixel(e.pixel, function (feature, layer) {
|
||||
return layer.get('name') === 'Markers';
|
||||
});
|
||||
if (hit) {
|
||||
this.getTargetElement().style.cursor = 'pointer';
|
||||
} else {
|
||||
this.getTargetElement().style.cursor = '';
|
||||
}
|
||||
});
|
||||
|
||||
// layer switcher
|
||||
var switcher = document.createElement('div');
|
||||
switcher.id = 'switcher';
|
||||
switcher.className = 'ol-control';
|
||||
document.body.appendChild(switcher);
|
||||
var switcherContent = document.createElement('div');
|
||||
switcherContent.id = 'switcher-content';
|
||||
switcherContent.className = 'ol-layerswitcher';
|
||||
switcher.appendChild(switcherContent);
|
||||
|
||||
map.getLayers().forEach(function (layer) {
|
||||
var layerLabel = document.createElement('label');
|
||||
layerLabel.innerHTML = layer.get('name');
|
||||
switcherContent.appendChild(layerLabel);
|
||||
|
||||
var layerRadio = document.createElement('input');
|
||||
if (layer.get('type') === 'data') {
|
||||
layerRadio.type = 'checkbox';
|
||||
layerLabel.className = 'ol-datalayer';
|
||||
} else {
|
||||
layerRadio.type = 'radio';
|
||||
}
|
||||
layerRadio.name = 'layer';
|
||||
layerRadio.value = layer.get('name');
|
||||
layerRadio.onclick = switchLayer;
|
||||
if (layer.getVisible()) {
|
||||
layerRadio.checked = true;
|
||||
}
|
||||
layerLabel.insertBefore(layerRadio, layerLabel.childNodes[0]);
|
||||
});
|
||||
|
||||
function switchLayer() {
|
||||
var targetName = this.value;
|
||||
map.getLayers().forEach(function (layer) {
|
||||
if (layer.get('name') === targetName) {
|
||||
if (layer.get('type') === 'data') {
|
||||
if (layer.getVisible()) {
|
||||
layer.setVisible(false);
|
||||
} else {
|
||||
layer.setVisible(true);
|
||||
}
|
||||
} else {
|
||||
selectedLayer.setVisible(false);
|
||||
selectedLayer = layer;
|
||||
layer.setVisible(true);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
var switcherButton = document.createElement('button');
|
||||
var layerImg = document.createElement('img');
|
||||
layerImg.src = 'images/layers.svg';
|
||||
layerImg.style.width = '60%';
|
||||
switcherButton.appendChild(layerImg);
|
||||
|
||||
// eslint-disable-next-line func-style
|
||||
var switcherHandle = function () {
|
||||
var el = document.getElementById('switcher');
|
||||
if (el.style.display === 'block') {
|
||||
el.style.display = 'none';
|
||||
} else {
|
||||
el.style.display = 'block';
|
||||
}
|
||||
};
|
||||
|
||||
switcherButton.addEventListener('click', switcherHandle, false);
|
||||
switcherButton.addEventListener('touchstart', switcherHandle, false);
|
||||
|
||||
var element = document.createElement('div');
|
||||
element.className = 'ol-switcher-button ol-unselectable ol-control';
|
||||
element.appendChild(switcherButton);
|
||||
|
||||
var switcherControl = new ol.control.Control({
|
||||
element: element
|
||||
});
|
||||
map.addControl(switcherControl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up API
|
||||
*/
|
||||
function cleanup() {
|
||||
map = null;
|
||||
layerTrack = null;
|
||||
layerMarkers = null;
|
||||
selectedLayer = null;
|
||||
olStyles = null;
|
||||
ns.removeElementById('popup');
|
||||
ns.removeElementById('switcher');
|
||||
ns.clearMapCanvas();
|
||||
}
|
||||
|
||||
/**
|
||||
* Display track
|
||||
* @param {HTMLCollection} positions XML element
|
||||
* @param {boolean} update Should fit bounds if true
|
||||
*/
|
||||
function displayTrack(positions, update) {
|
||||
var totalMeters = 0;
|
||||
var totalSeconds = 0;
|
||||
var points = [];
|
||||
var posLen = positions.length;
|
||||
for (var i = 0; i < posLen; i++) {
|
||||
var p = ns.parsePosition(positions[i], i);
|
||||
totalMeters += p.distance;
|
||||
totalSeconds += p.seconds;
|
||||
p.totalMeters = totalMeters;
|
||||
p.totalSeconds = totalSeconds;
|
||||
// set marker
|
||||
setMarker(p, i, posLen);
|
||||
// update polyline
|
||||
var point = ol.proj.fromLonLat([p.longitude, p.latitude]);
|
||||
points.push(point);
|
||||
}
|
||||
var lineString = new ol.geom.LineString(points);
|
||||
|
||||
var lineFeature = new ol.Feature({
|
||||
geometry: lineString
|
||||
});
|
||||
|
||||
layerTrack.getSource().addFeature(lineFeature);
|
||||
|
||||
var extent = layerTrack.getSource().getExtent();
|
||||
|
||||
map.getControls().forEach(function (el) {
|
||||
if (el instanceof ol.control.ZoomToExtent) {
|
||||
map.removeControl(el);
|
||||
}
|
||||
});
|
||||
|
||||
if (update) {
|
||||
map.getView().fit(extent);
|
||||
var zoom = map.getView().getZoom();
|
||||
if (zoom > 20) {
|
||||
map.getView().setZoom(20);
|
||||
extent = map.getView().calculateExtent(map.getSize());
|
||||
}
|
||||
}
|
||||
|
||||
var zoomToExtentControl = new ol.control.ZoomToExtent({
|
||||
extent: extent,
|
||||
label: getExtentImg()
|
||||
});
|
||||
map.addControl(zoomToExtentControl);
|
||||
|
||||
ns.updateSummary(p.timestamp, totalMeters, totalSeconds);
|
||||
if (p.tid !== ns.config.trackid) {
|
||||
ns.config.trackid = p.tid;
|
||||
ns.setTrack(ns.config.trackid);
|
||||
}
|
||||
ns.updateChart();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear map
|
||||
*/
|
||||
function clearMap() {
|
||||
if (layerTrack) {
|
||||
layerTrack.getSource().clear();
|
||||
}
|
||||
if (layerMarkers) {
|
||||
layerMarkers.getSource().clear();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set marker
|
||||
* @param {uLogger.Position} pos
|
||||
* @param {number} id
|
||||
* @param {number} posLen
|
||||
*/
|
||||
function setMarker(pos, id, posLen) {
|
||||
// marker
|
||||
var marker = new ol.Feature({
|
||||
geometry: new ol.geom.Point(ol.proj.fromLonLat([pos.longitude, pos.latitude]))
|
||||
});
|
||||
|
||||
var iconStyle;
|
||||
if (ns.isLatest()) {
|
||||
iconStyle = olStyles['red'];
|
||||
} else if (id === 0) {
|
||||
iconStyle = olStyles['green'];
|
||||
} else if (id === posLen - 1) {
|
||||
iconStyle = olStyles['red'];
|
||||
} else {
|
||||
iconStyle = olStyles['white'];
|
||||
}
|
||||
marker.setStyle(iconStyle);
|
||||
marker.setId(id);
|
||||
marker.set('p', pos);
|
||||
marker.set('posLen', posLen);
|
||||
layerMarkers.getSource().addFeature(marker);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add listener on chart to show position on map
|
||||
* @param {google.visualization.LineChart} chart
|
||||
* @param {google.visualization.DataTable} data
|
||||
*/
|
||||
function addChartEvent(chart, data) {
|
||||
google.visualization.events.addListener(chart, 'select', function () {
|
||||
var selection = chart.getSelection()[0];
|
||||
if (selection) {
|
||||
var id = data.getValue(selection.row, 0) - 1;
|
||||
var marker = layerMarkers.getSource().getFeatureById(id);
|
||||
var initStyle = marker.getStyle();
|
||||
var iconStyle = olStyles['gold'];
|
||||
marker.setStyle(iconStyle);
|
||||
setTimeout(function () {
|
||||
marker.setStyle(initStyle);
|
||||
}, 2000);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get map bounds
|
||||
* eg. (20.597985430276808, 52.15547181298076, 21.363595171488573, 52.33750879522563)
|
||||
* @returns {number[]} Bounds
|
||||
*/
|
||||
function getBounds() {
|
||||
var extent = map.getView().calculateExtent(map.getSize());
|
||||
var bounds = ol.proj.transformExtent(extent, 'EPSG:900913', 'EPSG:4326');
|
||||
var lon_sw = bounds[0];
|
||||
var lat_sw = bounds[1];
|
||||
var lon_ne = bounds[2];
|
||||
var lat_ne = bounds[3];
|
||||
return [lon_sw, lat_sw, lon_ne, lat_ne];
|
||||
}
|
||||
|
||||
/**
|
||||
* Zoom to track extent
|
||||
*/
|
||||
function zoomToExtent() {
|
||||
map.getView().fit(layerMarkers.getSource().getExtent());
|
||||
}
|
||||
|
||||
/**
|
||||
* Zoom to bounds
|
||||
* @param {number[]} bounds
|
||||
*/
|
||||
function zoomToBounds(bounds) {
|
||||
var extent = ol.proj.transformExtent(bounds, 'EPSG:4326', 'EPSG:900913');
|
||||
map.getView().fit(extent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update size
|
||||
*/
|
||||
function updateSize() {
|
||||
map.updateSize();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get extent image
|
||||
* @returns {HTMLImageElement}
|
||||
*/
|
||||
function getExtentImg() {
|
||||
var extentImg = document.createElement('img');
|
||||
extentImg.src = 'images/extent.svg';
|
||||
extentImg.style.width = '60%';
|
||||
return extentImg;
|
||||
}
|
||||
|
||||
return {
|
||||
name: name,
|
||||
init: init,
|
||||
cleanup: cleanup,
|
||||
displayTrack: displayTrack,
|
||||
clearMap: clearMap,
|
||||
setMarker: setMarker,
|
||||
addChartEvent: addChartEvent,
|
||||
getBounds: getBounds,
|
||||
zoomToExtent: zoomToExtent,
|
||||
zoomToBounds: zoomToBounds,
|
||||
updateSize: updateSize
|
||||
}
|
||||
|
||||
})(uLogger);
|
77
js/auth.js
Normal file
77
js/auth.js
Normal file
@ -0,0 +1,77 @@
|
||||
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);
|
||||
});
|
||||
}
|
||||
}
|
58
js/binder.js
Normal file
58
js/binder.js
Normal file
@ -0,0 +1,58 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
110
js/chart.js
Normal file
110
js/chart.js
Normal file
@ -0,0 +1,110 @@
|
||||
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.UI_READY, this);
|
||||
binder.addEventListener(uEvent.TRACK_READY, this);
|
||||
this._binder = binder;
|
||||
this._targetEl = 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', () => {
|
||||
const points = document.querySelectorAll('.ct-chart-line .ct-point');
|
||||
for (let i = 0; i < points.length; i++) {
|
||||
((id) => {
|
||||
points[id].addEventListener('click', () => {
|
||||
/** @todo trigger marker action */
|
||||
console.log(id);
|
||||
});
|
||||
})(i);
|
||||
}
|
||||
this._binder.dispatchEvent('chart ready', points.length);
|
||||
});
|
||||
|
||||
// need to update chart first time the container becomes visible
|
||||
if (this._targetEl.parentNode.style.display !== 'block') {
|
||||
const observer = new MutationObserver(() => {
|
||||
if (this._targetEl.parentNode.style.display === 'block') {
|
||||
// eslint-disable-next-line no-underscore-dangle
|
||||
this._targetEl.__chartist__.update();
|
||||
observer.disconnect();
|
||||
}
|
||||
});
|
||||
observer.observe(this._targetEl.parentNode, { attributes: true });
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
275
js/config.js
Normal file
275
js/config.js
Normal file
@ -0,0 +1,275 @@
|
||||
import uEvent from './event.js';
|
||||
|
||||
export default class uConfig {
|
||||
|
||||
/**
|
||||
*
|
||||
* @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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} value
|
||||
*/
|
||||
set factor_kmh(value) {
|
||||
this._factor_kmh = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {string}
|
||||
*/
|
||||
get unit_kmh() {
|
||||
return this._unit_kmh;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} value
|
||||
*/
|
||||
set unit_kmh(value) {
|
||||
this._unit_kmh = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {number}
|
||||
*/
|
||||
get factor_m() {
|
||||
return this._factor_m;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} value
|
||||
*/
|
||||
set factor_m(value) {
|
||||
this._factor_m = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {string}
|
||||
*/
|
||||
get unit_m() {
|
||||
return this._unit_m;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} value
|
||||
*/
|
||||
set unit_m(value) {
|
||||
this._unit_m = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {number}
|
||||
*/
|
||||
get factor_km() {
|
||||
return this._factor_km;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} value
|
||||
*/
|
||||
set factor_km(value) {
|
||||
this._factor_km = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {string}
|
||||
*/
|
||||
get unit_km() {
|
||||
return this._unit_km;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} value
|
||||
*/
|
||||
set unit_km(value) {
|
||||
this._unit_km = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {boolean}
|
||||
*/
|
||||
get showLatest() {
|
||||
return this._showLatest;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {boolean} value
|
||||
*/
|
||||
set showLatest(value) {
|
||||
this._showLatest = value;
|
||||
this.notify('showLatest');
|
||||
}
|
||||
}
|
120
js/constants.js
Normal file
120
js/constants.js
Normal file
@ -0,0 +1,120 @@
|
||||
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');
|
||||
this.config.factor_kmh = 1;
|
||||
this.config.unit_kmh = 'km/h';
|
||||
this.config.factor_m = 1;
|
||||
this.config.unit_m = 'm';
|
||||
this.config.factor_km = 1;
|
||||
this.config.unit_km = 'km';
|
||||
if (this.config.units === 'imperial') {
|
||||
this.config.factor_kmh = 0.62; // to mph
|
||||
this.config.unit_kmh = 'mph';
|
||||
this.config.factor_m = 3.28; // to feet
|
||||
this.config.unit_m = 'ft';
|
||||
this.config.factor_km = 0.62; // to miles
|
||||
this.config.unit_km = 'mi';
|
||||
} else if (this.config.units === 'nautical') {
|
||||
this.config.factor_kmh = 0.54; // to knots
|
||||
this.config.unit_kmh = 'kt';
|
||||
this.config.factor_m = 1; // meters
|
||||
this.config.unit_m = 'm';
|
||||
this.config.factor_km = 0.54; // to nautical miles
|
||||
this.config.unit_km = 'nm';
|
||||
}
|
||||
this.config.showLatest = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
50
js/data.js
Normal file
50
js/data.js
Normal file
@ -0,0 +1,50 @@
|
||||
/**
|
||||
* @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);
|
||||
}
|
||||
}
|
55
js/event.js
Normal file
55
js/event.js
Normal file
@ -0,0 +1,55 @@
|
||||
/* 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_READY() { return 'µChartReady'; }
|
||||
static get CONFIG() { return 'µConfig'; }
|
||||
static get EDIT() { return 'µEdit'; }
|
||||
static get EXPORT() { return 'µExport'; }
|
||||
static get OPEN_URL() { return 'µOpen'; }
|
||||
static get IMPORT() { return 'µImport'; }
|
||||
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.constructor.name} => ${listener.name}`);
|
||||
await listener(this, args);
|
||||
})();
|
||||
}
|
||||
}
|
||||
}
|
2
js/lib/chartist-plugin-axistitle.min.js
vendored
Normal file
2
js/lib/chartist-plugin-axistitle.min.js
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
!function(a,b){"function"==typeof define&&define.amd?define(["chartist"],function(c){return a.returnExportsGlobal=b(c)}):"object"==typeof exports?module.exports=b(require("chartist")):a["Chartist.plugins.ctAxisTitle"]=b(Chartist)}(this,function(a){return function(a,b,c){"use strict";var d={axisTitle:"",axisClass:"ct-axis-title",offset:{x:0,y:0},textAnchor:"middle",flipTitle:!1},e={axisX:d,axisY:d},f=function(a){return a instanceof Function?a():a},g=function(a){return a instanceof Function?a():a};c.plugins=c.plugins||{},c.plugins.ctAxisTitle=function(a){return a=c.extend({},e,a),function(b){b.on("created",function(b){if(!a.axisX.axisTitle&&!a.axisY.axisTitle)throw new Error("ctAxisTitle plugin - You must provide at least one axis title");if(!b.axisX&&!b.axisY)throw new Error("ctAxisTitle plugin can only be used on charts that have at least one axis");var d,e,h,i=c.normalizePadding(b.options.chartPadding);if(a.axisX.axisTitle&&b.axisX&&(d=b.axisX.axisLength/2+b.options.axisY.offset+i.left,e=i.top,"end"===b.options.axisY.position&&(d-=b.options.axisY.offset),"end"===b.options.axisX.position&&(e+=b.axisY.axisLength),h=new c.Svg("text"),h.addClass(g(a.axisX.axisClass)),h.text(f(a.axisX.axisTitle)),h.attr({x:d+a.axisX.offset.x,y:e+a.axisX.offset.y,"text-anchor":a.axisX.textAnchor}),b.svg.append(h,!0)),a.axisY.axisTitle&&b.axisY){d=0,e=b.axisY.axisLength/2+i.top,"start"===b.options.axisX.position&&(e+=b.options.axisX.offset),"end"===b.options.axisY.position&&(d=b.axisX.axisLength);var j="rotate("+(a.axisY.flipTitle?-90:90)+", "+d+", "+e+")";h=new c.Svg("text"),h.addClass(g(a.axisY.axisClass)),h.text(f(a.axisY.axisTitle)),h.attr({x:d+a.axisY.offset.x,y:e+a.axisY.offset.y,transform:j,"text-anchor":a.axisY.textAnchor}),b.svg.append(h,!0)}})}}}(window,document,a),a.plugins.ctAxisTitle});
|
||||
//# sourceMappingURL=chartist-plugin-axistitle.min.js.map
|
1
js/lib/chartist-plugin-axistitle.min.js.map
Normal file
1
js/lib/chartist-plugin-axistitle.min.js.map
Normal file
File diff suppressed because one or more lines are too long
10
js/lib/chartist.min.js
vendored
Normal file
10
js/lib/chartist.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
js/lib/chartist.min.js.map
Normal file
1
js/lib/chartist.min.js.map
Normal file
File diff suppressed because one or more lines are too long
232
js/list.js
Normal file
232
js/list.js
Normal file
@ -0,0 +1,232 @@
|
||||
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.domElement) {
|
||||
this.domElement.addEventListener('change', this, false);
|
||||
}
|
||||
if (this.binder) {
|
||||
this.binder.addEventListener(uEvent.ADD, 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';
|
||||
}
|
||||
|
||||
/**
|
||||
* @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();
|
||||
}
|
||||
/** @todo set defaults ?? */
|
||||
// var defaultTrack = tid || getNodeAsInt(tracks[0], 'trackid');
|
||||
// var defaultUser = uid || ns.userId;
|
||||
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 {(Event|uEvent)} event
|
||||
* @param {*=} eventData
|
||||
*/
|
||||
handleEvent(event, eventData) {
|
||||
if (event.type === 'change') {
|
||||
this.selectedId = this.domElement.options[this.domElement.selectedIndex].value;
|
||||
this.onChange();
|
||||
} else if (event.type === uEvent.EDIT && this.domElement === eventData) {
|
||||
this.onEdit();
|
||||
} else if (event.type === uEvent.ADD && this.domElement === eventData) {
|
||||
this.onAdd();
|
||||
}
|
||||
}
|
||||
|
||||
// /**
|
||||
// * @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) {
|
||||
if (this.data.length) {
|
||||
this.selectedId = this.data[0].key.toString();
|
||||
} else {
|
||||
this.selectedId = '';
|
||||
}
|
||||
this.onChange();
|
||||
}
|
||||
this.render();
|
||||
}
|
||||
|
||||
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
|
||||
onAdd() {
|
||||
}
|
||||
}
|
1231
js/main.js
1231
js/main.js
File diff suppressed because it is too large
Load Diff
110
js/map.js
Normal file
110
js/map.js
Normal file
@ -0,0 +1,110 @@
|
||||
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';
|
||||
|
||||
/**
|
||||
* @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.TRACK_READY, this);
|
||||
binder.addEventListener(uEvent.UI_READY, this);
|
||||
binder.addEventListener(uEvent.API_CHANGE, this);
|
||||
this.loadTime = 0;
|
||||
this.savedBounds = null;
|
||||
this.api = null;
|
||||
this.mapElement = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.mapElement);
|
||||
} catch (e) {
|
||||
setTimeout(() => {
|
||||
this.loadTime += 50;
|
||||
this.waitAndInit();
|
||||
}, 50);
|
||||
return;
|
||||
}
|
||||
this.loadTime = 0;
|
||||
let update = 1;
|
||||
if (this.savedBounds) {
|
||||
this.api.zoomToBounds(this.savedBounds);
|
||||
update = 0;
|
||||
}
|
||||
// if (latest && isSelectedAllUsers()) {
|
||||
// loadLastPositionAllUsers();
|
||||
// } else {
|
||||
// loadTrack(ns.userId, ns.trackId, update);
|
||||
uLogger.trackList.onChange();
|
||||
// }
|
||||
// save current api as default
|
||||
uUtils.setCookie('api', config.mapapi, 30);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {uEvent} event
|
||||
* @param {*=} args
|
||||
*/
|
||||
handleEvent(event, args) {
|
||||
if (event.type === uEvent.TRACK_READY) {
|
||||
const track = args;
|
||||
this.api.clearMap();
|
||||
/** @todo use update */
|
||||
const update = 1;
|
||||
this.api.displayTrack(track, update);
|
||||
} 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);
|
||||
}
|
||||
}
|
||||
}
|
302
js/mapapi/api_gmaps.js
Normal file
302
js/mapapi/api_gmaps.js
Normal file
@ -0,0 +1,302 @@
|
||||
/* μlogger
|
||||
*
|
||||
* Copyright(C) 2017 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 { uLogger } from '../ulogger.js';
|
||||
import uUI from '../ui.js';
|
||||
import uUtils from '../utils.js';
|
||||
|
||||
// google maps
|
||||
|
||||
/** @type {google.maps.Map} */
|
||||
let map = null;
|
||||
/** @type {google.maps.Polyline[]} */
|
||||
const polies = [];
|
||||
/** @type {google.maps.Marker[]} */
|
||||
const markers = [];
|
||||
/** @type {google.maps.InfoWindow[]} */
|
||||
const popups = [];
|
||||
/** @type {google.maps.InfoWindow} */
|
||||
let popup = null;
|
||||
/** @type {google.maps.PolylineOptions} */
|
||||
let polyOptions = null;
|
||||
/** @type {google.maps.MapOptions} */
|
||||
let mapOptions = null;
|
||||
/** @type {number} */
|
||||
let timeoutHandle = 0;
|
||||
const name = 'gmaps';
|
||||
let isLoaded = false;
|
||||
let authError = false;
|
||||
|
||||
/**
|
||||
* Initialize map
|
||||
* @param {HTMLElement} el
|
||||
*/
|
||||
function init(el) {
|
||||
const url = '//maps.googleapis.com/maps/api/js?' + ((config.gkey != null) ? ('key=' + config.gkey + '&') : '') + 'callback=gm_loaded';
|
||||
uUtils.addScript(url, 'mapapi_gmaps');
|
||||
if (!isLoaded) {
|
||||
throw new Error('Google Maps API not ready');
|
||||
}
|
||||
start(el);
|
||||
}
|
||||
|
||||
/**
|
||||
* Start map engine when loaded
|
||||
* @param {HTMLElement} el
|
||||
*/
|
||||
function start(el) {
|
||||
if (authError) {
|
||||
window.gm_authFailure();
|
||||
return;
|
||||
}
|
||||
google.maps.visualRefresh = true;
|
||||
// noinspection JSValidateTypes
|
||||
polyOptions = {
|
||||
strokeColor: config.strokeColor,
|
||||
strokeOpacity: config.strokeOpacity,
|
||||
strokeWeight: config.strokeWeight
|
||||
};
|
||||
// noinspection JSValidateTypes
|
||||
mapOptions = {
|
||||
center: new google.maps.LatLng(config.init_latitude, config.init_longitude),
|
||||
zoom: 8,
|
||||
mapTypeId: google.maps.MapTypeId.ROADMAP,
|
||||
scaleControl: true
|
||||
};
|
||||
map = new google.maps.Map(el, mapOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up API
|
||||
*/
|
||||
function cleanup() {
|
||||
polies.length = 0;
|
||||
markers.length = 0;
|
||||
popups.length = 0;
|
||||
map = null;
|
||||
polyOptions = null;
|
||||
mapOptions = null;
|
||||
popup = null;
|
||||
// ui.clearMapCanvas();
|
||||
}
|
||||
|
||||
/**
|
||||
* Display track
|
||||
* @param {boolean} update Should fit bounds if true
|
||||
*/
|
||||
function displayTrack(update) {
|
||||
const track = uLogger.trackList.current;
|
||||
if (!track) {
|
||||
return;
|
||||
}
|
||||
// init polyline
|
||||
const poly = new google.maps.Polyline(polyOptions);
|
||||
poly.setMap(map);
|
||||
const path = poly.getPath();
|
||||
const latlngbounds = new google.maps.LatLngBounds();
|
||||
let i = 0;
|
||||
for (const position of track.positions) {
|
||||
// set marker
|
||||
setMarker(i++);
|
||||
// update polyline
|
||||
const coordinates = new google.maps.LatLng(position.latitude, position.longitude);
|
||||
path.push(coordinates);
|
||||
latlngbounds.extend(coordinates);
|
||||
}
|
||||
if (update) {
|
||||
map.fitBounds(latlngbounds);
|
||||
if (i === 1) {
|
||||
// only one point, zoom out
|
||||
const zListener =
|
||||
google.maps.event.addListenerOnce(map, 'bounds_changed', function () {
|
||||
if (this.getZoom()) {
|
||||
this.setZoom(15);
|
||||
}
|
||||
});
|
||||
setTimeout(function () { google.maps.event.removeListener(zListener) }, 2000);
|
||||
}
|
||||
}
|
||||
polies.push(poly);
|
||||
|
||||
/** @todo handle summary and chart in track */
|
||||
// ns.updateSummary(p.timestamp, totalDistance, totalSeconds);
|
||||
// ns.updateChart();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear map
|
||||
*/
|
||||
function clearMap() {
|
||||
if (polies) {
|
||||
for (let i = 0; i < polies.length; i++) {
|
||||
polies[i].setMap(null);
|
||||
}
|
||||
}
|
||||
if (markers) {
|
||||
for (let i = 0; i < markers.length; i++) {
|
||||
google.maps.event.removeListener(popups[i].listener);
|
||||
popups[i].setMap(null);
|
||||
markers[i].setMap(null);
|
||||
}
|
||||
}
|
||||
markers.length = 0;
|
||||
polies.length = 0;
|
||||
popups.length = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set marker
|
||||
* @param {number} id
|
||||
*/
|
||||
function setMarker(id) {
|
||||
// marker
|
||||
const position = uLogger.trackList.current.positions[id];
|
||||
const posLen = uLogger.trackList.current.length;
|
||||
// noinspection JSCheckFunctionSignatures
|
||||
const marker = new google.maps.Marker({
|
||||
position: new google.maps.LatLng(position.latitude, position.longitude),
|
||||
title: (new Date(position.timestamp * 1000)).toLocaleString(),
|
||||
map: map
|
||||
});
|
||||
if (config.showLatest) {
|
||||
marker.setIcon('images/marker-red.png');
|
||||
} else if (id === 0) {
|
||||
marker.setIcon('images/marker-green.png');
|
||||
} else if (id === posLen - 1) {
|
||||
marker.setIcon('images/marker-red.png');
|
||||
} else {
|
||||
marker.setIcon('images/marker-white.png');
|
||||
}
|
||||
// popup
|
||||
const content = uUI.getPopupHtml(id);
|
||||
popup = new google.maps.InfoWindow();
|
||||
// noinspection JSUndefinedPropertyAssignment
|
||||
popup.listener = google.maps.event.addListener(marker, 'click', (function (_marker, _content) {
|
||||
return function () {
|
||||
popup.setContent(_content);
|
||||
popup.open(map, _marker);
|
||||
/** @todo handle chart */
|
||||
// ns.chartShowPosition(id);
|
||||
}
|
||||
})(marker, content));
|
||||
markers.push(marker);
|
||||
popups.push(popup);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add listener on chart to show position on map
|
||||
* @param {google.visualization.LineChart} chart
|
||||
* @param {google.visualization.DataTable} data
|
||||
*/
|
||||
function addChartEvent(chart, data) {
|
||||
google.visualization.events.addListener(chart, 'select', function () {
|
||||
if (popup) { popup.close(); clearTimeout(timeoutHandle); }
|
||||
const selection = chart.getSelection()[0];
|
||||
if (selection) {
|
||||
const id = data.getValue(selection.row, 0) - 1;
|
||||
const icon = markers[id].getIcon();
|
||||
markers[id].setIcon('images/marker-gold.png');
|
||||
timeoutHandle = setTimeout(function () { markers[id].setIcon(icon); }, 2000);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get map bounds
|
||||
* eg. ((52.20105108685229, 20.789387865580238), (52.292069558807135, 21.172192736185707))
|
||||
* @returns {number[]} Bounds
|
||||
*/
|
||||
function getBounds() {
|
||||
const bounds = map.getBounds();
|
||||
const lat_sw = bounds.getSouthWest().lat();
|
||||
const lon_sw = bounds.getSouthWest().lng();
|
||||
const lat_ne = bounds.getNorthEast().lat();
|
||||
const lon_ne = bounds.getNorthEast().lng();
|
||||
return [ lon_sw, lat_sw, lon_ne, lat_ne ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Zoom to track extent
|
||||
*/
|
||||
function zoomToExtent() {
|
||||
const latlngbounds = new google.maps.LatLngBounds();
|
||||
for (let i = 0; i < markers.length; i++) {
|
||||
const coordinates = new google.maps.LatLng(markers[i].position.lat(), markers[i].position.lng());
|
||||
latlngbounds.extend(coordinates);
|
||||
}
|
||||
map.fitBounds(latlngbounds);
|
||||
}
|
||||
|
||||
/**
|
||||
* Zoom to bounds
|
||||
* @param {number[]} bounds
|
||||
*/
|
||||
function zoomToBounds(bounds) {
|
||||
const sw = new google.maps.LatLng(bounds[1], bounds[0]);
|
||||
const ne = new google.maps.LatLng(bounds[3], bounds[2]);
|
||||
const latLngBounds = new google.maps.LatLngBounds(sw, ne);
|
||||
map.fitBounds(latLngBounds);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update size
|
||||
*/
|
||||
function updateSize() {
|
||||
// ignore for google API
|
||||
}
|
||||
|
||||
function setAuthError() { authError = true; }
|
||||
function setLoaded() { isLoaded = true; }
|
||||
|
||||
export {
|
||||
name,
|
||||
init,
|
||||
cleanup,
|
||||
displayTrack,
|
||||
clearMap,
|
||||
setMarker,
|
||||
addChartEvent,
|
||||
getBounds,
|
||||
zoomToExtent,
|
||||
zoomToBounds,
|
||||
updateSize,
|
||||
setAuthError,
|
||||
setLoaded
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Callback for Google Maps API
|
||||
* It will be called when authentication fails
|
||||
*/
|
||||
window.gm_authFailure = function () {
|
||||
setAuthError();
|
||||
let message = uUtils.sprintf(lang.strings['apifailure'], 'Google Maps');
|
||||
message += '<br><br>' + lang.strings['gmauthfailure'];
|
||||
message += '<br><br>' + lang.strings['gmapilink'];
|
||||
uUI.resolveModal(message);
|
||||
};
|
||||
|
||||
/**
|
||||
* Callback for Google Maps API
|
||||
* It will be called when API is loaded
|
||||
*/
|
||||
window.gm_loaded = function () {
|
||||
setLoaded();
|
||||
};
|
475
js/mapapi/api_openlayers.js
Normal file
475
js/mapapi/api_openlayers.js
Normal file
@ -0,0 +1,475 @@
|
||||
/* μlogger
|
||||
*
|
||||
* Copyright(C) 2017 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 { uLogger } from '../ulogger.js';
|
||||
import uUI from '../ui.js';
|
||||
import uUtils from '../utils.js';
|
||||
|
||||
// openlayers 3+
|
||||
|
||||
/** @type {ol.Map} */
|
||||
let map = null;
|
||||
/** @type {ol.layer.Vector} */
|
||||
let layerTrack = null;
|
||||
/** @type {ol.layer.Vector} */
|
||||
let layerMarkers = null;
|
||||
/** @type {ol.layer.Base} */
|
||||
let selectedLayer = null;
|
||||
/** @type {ol.style.Style|{}} */
|
||||
let olStyles = {};
|
||||
const name = 'openlayers';
|
||||
|
||||
/**
|
||||
* Initialize map
|
||||
*/
|
||||
function init(target) {
|
||||
|
||||
uUtils.addScript('//cdn.polyfill.io/v2/polyfill.min.js?features=requestAnimationFrame,Element.prototype.classList', 'mapapi_openlayers_polyfill');
|
||||
uUtils.addScript('js/lib/ol.js', 'mapapi_openlayers');
|
||||
uUtils.addCss('css/ol.css', 'ol_css');
|
||||
|
||||
const controls = [
|
||||
new ol.control.Zoom(),
|
||||
new ol.control.Rotate(),
|
||||
new ol.control.ScaleLine(),
|
||||
new ol.control.ZoomToExtent({ label: getExtentImg() })
|
||||
];
|
||||
|
||||
const view = new ol.View({
|
||||
center: ol.proj.fromLonLat([ config.init_longitude, config.init_latitude ]),
|
||||
zoom: 8
|
||||
});
|
||||
|
||||
map = new ol.Map({
|
||||
target: target,
|
||||
controls: controls,
|
||||
view: view
|
||||
});
|
||||
|
||||
// default layer: OpenStreetMap
|
||||
const osm = new ol.layer.Tile({
|
||||
name: 'OpenStreetMap',
|
||||
visible: true,
|
||||
source: new ol.source.OSM()
|
||||
});
|
||||
map.addLayer(osm);
|
||||
selectedLayer = osm;
|
||||
|
||||
// add extra layers
|
||||
for (const layerName in config.ol_layers) {
|
||||
if (config.ol_layers.hasOwnProperty(layerName)) {
|
||||
const layerUrl = config.ol_layers[layerName];
|
||||
const ol_layer = new ol.layer.Tile({
|
||||
name: layerName,
|
||||
visible: false,
|
||||
source: new ol.source.XYZ({
|
||||
url: layerUrl
|
||||
})
|
||||
});
|
||||
map.addLayer(ol_layer);
|
||||
}
|
||||
}
|
||||
|
||||
// init layers
|
||||
const lineStyle = new ol.style.Style({
|
||||
stroke: new ol.style.Stroke({
|
||||
color: uUtils.hexToRGBA(config.strokeColor, config.strokeOpacity),
|
||||
width: config.strokeWeight
|
||||
})
|
||||
});
|
||||
layerTrack = new ol.layer.Vector({
|
||||
name: 'Track',
|
||||
type: 'data',
|
||||
source: new ol.source.Vector(),
|
||||
style: lineStyle
|
||||
});
|
||||
layerMarkers = new ol.layer.Vector({
|
||||
name: 'Markers',
|
||||
type: 'data',
|
||||
source: new ol.source.Vector()
|
||||
});
|
||||
map.addLayer(layerTrack);
|
||||
map.addLayer(layerMarkers);
|
||||
|
||||
// styles
|
||||
olStyles = {};
|
||||
const iconRed = new ol.style.Icon({
|
||||
anchor: [ 0.5, 1 ],
|
||||
src: 'images/marker-red.png'
|
||||
});
|
||||
const iconGreen = new ol.style.Icon({
|
||||
anchor: [ 0.5, 1 ],
|
||||
src: 'images/marker-green.png'
|
||||
});
|
||||
const iconWhite = new ol.style.Icon({
|
||||
anchor: [ 0.5, 1 ],
|
||||
opacity: 0.7,
|
||||
src: 'images/marker-white.png'
|
||||
});
|
||||
const iconGold = new ol.style.Icon({
|
||||
anchor: [ 0.5, 1 ],
|
||||
src: 'images/marker-gold.png'
|
||||
});
|
||||
olStyles['red'] = new ol.style.Style({
|
||||
image: iconRed
|
||||
});
|
||||
olStyles['green'] = new ol.style.Style({
|
||||
image: iconGreen
|
||||
});
|
||||
olStyles['white'] = new ol.style.Style({
|
||||
image: iconWhite
|
||||
});
|
||||
olStyles['gold'] = new ol.style.Style({
|
||||
image: iconGold
|
||||
});
|
||||
|
||||
// popups
|
||||
const popupContainer = document.createElement('div');
|
||||
popupContainer.id = 'popup';
|
||||
popupContainer.className = 'ol-popup';
|
||||
document.body.appendChild(popupContainer);
|
||||
const popupCloser = document.createElement('a');
|
||||
popupCloser.id = 'popup-closer';
|
||||
popupCloser.className = 'ol-popup-closer';
|
||||
popupCloser.href = '#';
|
||||
popupContainer.appendChild(popupCloser);
|
||||
const popupContent = document.createElement('div');
|
||||
popupContent.id = 'popup-content';
|
||||
popupContainer.appendChild(popupContent);
|
||||
|
||||
const popup = new ol.Overlay({
|
||||
element: popupContainer,
|
||||
autoPan: true,
|
||||
autoPanAnimation: {
|
||||
duration: 250
|
||||
}
|
||||
});
|
||||
|
||||
popupCloser.onclick = () => {
|
||||
// eslint-disable-next-line no-undefined
|
||||
popup.setPosition(undefined);
|
||||
popupCloser.blur();
|
||||
return false;
|
||||
};
|
||||
|
||||
// add click handler to map to show popup
|
||||
map.on('click', (e) => {
|
||||
const coordinate = e.coordinate;
|
||||
const feature = map.forEachFeatureAtPixel(e.pixel,
|
||||
(_feature, _layer) => {
|
||||
if (_layer.get('name') === 'Markers') {
|
||||
return _feature;
|
||||
}
|
||||
return null;
|
||||
});
|
||||
if (feature) {
|
||||
// popup show
|
||||
popup.setPosition(coordinate);
|
||||
popupContent.innerHTML = uUI.getPopupHtml(feature.getId());
|
||||
map.addOverlay(popup);
|
||||
// ns.chartShowPosition(id);
|
||||
} else {
|
||||
// popup destroy
|
||||
// eslint-disable-next-line no-undefined
|
||||
popup.setPosition(undefined);
|
||||
}
|
||||
});
|
||||
|
||||
// change mouse cursor when over marker
|
||||
map.on('pointermove', function({ pixel }) {
|
||||
const hit = map.forEachFeatureAtPixel(pixel, (_feature, _layer) => _layer.get('name') === 'Markers');
|
||||
if (hit) {
|
||||
this.getTargetElement().style.cursor = 'pointer';
|
||||
} else {
|
||||
this.getTargetElement().style.cursor = '';
|
||||
}
|
||||
});
|
||||
|
||||
// layer switcher
|
||||
const switcher = document.createElement('div');
|
||||
switcher.id = 'switcher';
|
||||
switcher.className = 'ol-control';
|
||||
document.body.appendChild(switcher);
|
||||
const switcherContent = document.createElement('div');
|
||||
switcherContent.id = 'switcher-content';
|
||||
switcherContent.className = 'ol-layerswitcher';
|
||||
switcher.appendChild(switcherContent);
|
||||
|
||||
map.getLayers().forEach((_layer) => {
|
||||
const layerLabel = document.createElement('label');
|
||||
layerLabel.innerHTML = _layer.get('name');
|
||||
switcherContent.appendChild(layerLabel);
|
||||
|
||||
const layerRadio = document.createElement('input');
|
||||
if (_layer.get('type') === 'data') {
|
||||
layerRadio.type = 'checkbox';
|
||||
layerLabel.className = 'ol-datalayer';
|
||||
} else {
|
||||
layerRadio.type = 'radio';
|
||||
}
|
||||
layerRadio.name = 'layer';
|
||||
layerRadio.value = _layer.get('name');
|
||||
layerRadio.onclick = switchLayer;
|
||||
if (_layer.getVisible()) {
|
||||
layerRadio.checked = true;
|
||||
}
|
||||
layerLabel.insertBefore(layerRadio, layerLabel.childNodes[0]);
|
||||
});
|
||||
|
||||
function switchLayer() {
|
||||
const targetName = this.value;
|
||||
map.getLayers().forEach((_layer) => {
|
||||
if (_layer.get('name') === targetName) {
|
||||
if (_layer.get('type') === 'data') {
|
||||
if (_layer.getVisible()) {
|
||||
_layer.setVisible(false);
|
||||
} else {
|
||||
_layer.setVisible(true);
|
||||
}
|
||||
} else {
|
||||
selectedLayer.setVisible(false);
|
||||
selectedLayer = _layer;
|
||||
_layer.setVisible(true);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const switcherButton = document.createElement('button');
|
||||
const layerImg = document.createElement('img');
|
||||
layerImg.src = 'images/layers.svg';
|
||||
layerImg.style.width = '60%';
|
||||
switcherButton.appendChild(layerImg);
|
||||
|
||||
const switcherHandle = () => {
|
||||
const el = document.getElementById('switcher');
|
||||
if (el.style.display === 'block') {
|
||||
el.style.display = 'none';
|
||||
} else {
|
||||
el.style.display = 'block';
|
||||
}
|
||||
};
|
||||
|
||||
switcherButton.addEventListener('click', switcherHandle, false);
|
||||
switcherButton.addEventListener('touchstart', switcherHandle, false);
|
||||
|
||||
const element = document.createElement('div');
|
||||
element.className = 'ol-switcher-button ol-unselectable ol-control';
|
||||
element.appendChild(switcherButton);
|
||||
|
||||
const switcherControl = new ol.control.Control({
|
||||
element: element
|
||||
});
|
||||
map.addControl(switcherControl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up API
|
||||
*/
|
||||
function cleanup() {
|
||||
map = null;
|
||||
layerTrack = null;
|
||||
layerMarkers = null;
|
||||
selectedLayer = null;
|
||||
olStyles = null;
|
||||
uUI.removeElementById('popup');
|
||||
uUI.removeElementById('switcher');
|
||||
// ui.clearMapCanvas();
|
||||
}
|
||||
|
||||
/**
|
||||
* Display track
|
||||
* @param {boolean} update Should fit bounds if true
|
||||
*/
|
||||
function displayTrack(update) {
|
||||
const track = uLogger.trackList.current;
|
||||
if (!track) {
|
||||
return;
|
||||
}
|
||||
const points = [];
|
||||
let i = 0;
|
||||
for (const position of track.positions) {
|
||||
// set marker
|
||||
setMarker(i++);
|
||||
// update polyline
|
||||
const point = ol.proj.fromLonLat([ position.longitude, position.latitude ]);
|
||||
points.push(point);
|
||||
}
|
||||
const lineString = new ol.geom.LineString(points);
|
||||
|
||||
const lineFeature = new ol.Feature({
|
||||
geometry: lineString
|
||||
});
|
||||
|
||||
layerTrack.getSource().addFeature(lineFeature);
|
||||
|
||||
let extent = layerTrack.getSource().getExtent();
|
||||
|
||||
map.getControls().forEach((el) => {
|
||||
if (el instanceof ol.control.ZoomToExtent) {
|
||||
map.removeControl(el);
|
||||
}
|
||||
});
|
||||
|
||||
if (update) {
|
||||
map.getView().fit(extent);
|
||||
const zoom = map.getView().getZoom();
|
||||
if (zoom > 20) {
|
||||
map.getView().setZoom(20);
|
||||
extent = map.getView().calculateExtent(map.getSize());
|
||||
}
|
||||
}
|
||||
|
||||
const zoomToExtentControl = new ol.control.ZoomToExtent({
|
||||
extent,
|
||||
label: getExtentImg()
|
||||
});
|
||||
map.addControl(zoomToExtentControl);
|
||||
|
||||
/** @todo handle summary and chart in track */
|
||||
|
||||
// ns.updateSummary(p.timestamp, totalDistance, totalSeconds);
|
||||
// ns.updateChart();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear map
|
||||
*/
|
||||
function clearMap() {
|
||||
if (layerTrack) {
|
||||
layerTrack.getSource().clear();
|
||||
}
|
||||
if (layerMarkers) {
|
||||
layerMarkers.getSource().clear();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set marker
|
||||
* @param {number} id
|
||||
*/
|
||||
function setMarker(id) {
|
||||
// marker
|
||||
const position = uLogger.trackList.current.positions[id];
|
||||
const posLen = uLogger.trackList.current.positions.length;
|
||||
const marker = new ol.Feature({
|
||||
geometry: new ol.geom.Point(ol.proj.fromLonLat([ position.longitude, position.latitude ]))
|
||||
});
|
||||
|
||||
let iconStyle;
|
||||
if (config.showLatest) {
|
||||
iconStyle = olStyles['red'];
|
||||
} else if (id === 0) {
|
||||
iconStyle = olStyles['green'];
|
||||
} else if (id === posLen - 1) {
|
||||
iconStyle = olStyles['red'];
|
||||
} else {
|
||||
iconStyle = olStyles['white'];
|
||||
}
|
||||
marker.setStyle(iconStyle);
|
||||
marker.setId(id);
|
||||
/** @todo why set position in marker? ID should be enough */
|
||||
marker.set('p', position);
|
||||
marker.set('posLen', posLen);
|
||||
layerMarkers.getSource().addFeature(marker);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add listener on chart to show position on map
|
||||
* @param {google.visualization.LineChart} chart
|
||||
* @param {google.visualization.DataTable} data
|
||||
*/
|
||||
function addChartEvent(chart, data) {
|
||||
google.visualization.events.addListener(chart, 'select', () => {
|
||||
const selection = chart.getSelection()[0];
|
||||
if (selection) {
|
||||
const id = data.getValue(selection.row, 0) - 1;
|
||||
const marker = layerMarkers.getSource().getFeatureById(id);
|
||||
const initStyle = marker.getStyle();
|
||||
const iconStyle = olStyles['gold'];
|
||||
marker.setStyle(iconStyle);
|
||||
setTimeout(() => {
|
||||
marker.setStyle(initStyle);
|
||||
}, 2000);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get map bounds
|
||||
* eg. (20.597985430276808, 52.15547181298076, 21.363595171488573, 52.33750879522563)
|
||||
* @returns {number[]} Bounds
|
||||
*/
|
||||
function getBounds() {
|
||||
const extent = map.getView().calculateExtent(map.getSize());
|
||||
const bounds = ol.proj.transformExtent(extent, 'EPSG:900913', 'EPSG:4326');
|
||||
const lon_sw = bounds[0];
|
||||
const lat_sw = bounds[1];
|
||||
const lon_ne = bounds[2];
|
||||
const lat_ne = bounds[3];
|
||||
return [ lon_sw, lat_sw, lon_ne, lat_ne ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Zoom to track extent
|
||||
*/
|
||||
function zoomToExtent() {
|
||||
map.getView().fit(layerMarkers.getSource().getExtent());
|
||||
}
|
||||
|
||||
/**
|
||||
* Zoom to bounds
|
||||
* @param {number[]} bounds
|
||||
*/
|
||||
function zoomToBounds(bounds) {
|
||||
const extent = ol.proj.transformExtent(bounds, 'EPSG:4326', 'EPSG:900913');
|
||||
map.getView().fit(extent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update size
|
||||
*/
|
||||
function updateSize() {
|
||||
map.updateSize();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get extent image
|
||||
* @returns {HTMLImageElement}
|
||||
*/
|
||||
function getExtentImg() {
|
||||
const extentImg = document.createElement('img');
|
||||
extentImg.src = 'images/extent.svg';
|
||||
extentImg.style.width = '60%';
|
||||
return extentImg;
|
||||
}
|
||||
|
||||
export {
|
||||
name,
|
||||
init,
|
||||
cleanup,
|
||||
displayTrack,
|
||||
clearMap,
|
||||
setMarker,
|
||||
addChartEvent,
|
||||
getBounds,
|
||||
zoomToExtent,
|
||||
zoomToBounds,
|
||||
updateSize
|
||||
}
|
126
js/modal.js
Normal file
126
js/modal.js
Normal file
@ -0,0 +1,126 @@
|
||||
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
|
||||
}
|
||||
}
|
77
js/pass.js
77
js/pass.js
@ -1,77 +0,0 @@
|
||||
/* μlogger
|
||||
*
|
||||
* Copyright(C) 2017 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/>.
|
||||
*/
|
||||
|
||||
/** @namespace */
|
||||
var uLogger = window.uLogger || {};
|
||||
(function (ns) {
|
||||
|
||||
/**
|
||||
* Show change password dialog
|
||||
*/
|
||||
function changePass() {
|
||||
var form = '<form id="passForm" method="post" onsubmit="uLogger.submitPass(); return false">';
|
||||
form += '<label><b>' + ns.lang.strings['oldpassword'] + '</b></label><input type="password" placeholder="' + ns.lang.strings['passwordenter'] + '" name="oldpass" required>';
|
||||
form += '<label><b>' + ns.lang.strings['newpassword'] + '</b></label><input type="password" placeholder="' + ns.lang.strings['passwordenter'] + '" name="pass" required>';
|
||||
form += '<label><b>' + ns.lang.strings['newpasswordrepeat'] + '</b></label><input type="password" placeholder="' + ns.lang.strings['passwordenter'] + '" name="pass2" required>';
|
||||
form += '<button type="button" onclick="uLogger.ui.removeModal()">' + ns.lang.strings['cancel'] + '</button><button type="submit">' + ns.lang.strings['submit'] + '</button>';
|
||||
form += '</form>';
|
||||
ns.ui.showModal(form);
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit password form
|
||||
*/
|
||||
function submitPass() {
|
||||
var form = document.getElementById('passForm');
|
||||
var oldpass = form.elements['oldpass'].value;
|
||||
var pass = form.elements['pass'].value;
|
||||
var pass2 = form.elements['pass2'].value;
|
||||
if (!oldpass || !pass || !pass2) {
|
||||
alert(ns.lang.strings['allrequired']);
|
||||
return;
|
||||
}
|
||||
if (pass !== pass2) {
|
||||
alert(ns.lang.strings['passnotmatch']);
|
||||
return;
|
||||
}
|
||||
if (!ns.config.pass_regex.test(pass)) {
|
||||
alert(ns.lang.strings['passlenmin'] + '\n' + ns.lang.strings['passrules']);
|
||||
return;
|
||||
}
|
||||
|
||||
ns.post('utils/changepass.php',
|
||||
{
|
||||
oldpass: oldpass,
|
||||
pass: pass
|
||||
},
|
||||
{
|
||||
success: function () {
|
||||
ns.ui.removeModal();
|
||||
alert(ns.lang.strings['actionsuccess']);
|
||||
},
|
||||
fail: function (message) {
|
||||
alert(ns.lang.strings['actionfailure'] + '\n' + message);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// exports
|
||||
ns.changePass = changePass;
|
||||
ns.submitPass = submitPass;
|
||||
|
||||
})(uLogger);
|
50
js/position.js
Normal file
50
js/position.js
Normal file
@ -0,0 +1,50 @@
|
||||
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} 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.comments = uUtils.getNode(xml, 'comments'); // 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;
|
||||
}
|
||||
}
|
261
js/track.js
261
js/track.js
@ -1,109 +1,176 @@
|
||||
/* μlogger
|
||||
*
|
||||
* Copyright(C) 2017 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 {?uPosition[]} positions
|
||||
* @property {?Array<{x: number, y: number}>} plotData
|
||||
*/
|
||||
|
||||
/** @namespace */
|
||||
var uLogger = window.uLogger || {};
|
||||
(function (ns) {
|
||||
export default class uTrack extends uData {
|
||||
|
||||
/**
|
||||
* Show edit track dialog
|
||||
*/
|
||||
function editTrack() {
|
||||
var userForm = ns.ui.userSelect;
|
||||
var trackUser = (userForm) ? userForm.options[userForm.selectedIndex].text : ns.config.auth;
|
||||
if (trackUser !== ns.config.auth && !ns.config.admin) {
|
||||
alert(ns.lang.strings['owntrackswarn']);
|
||||
return;
|
||||
}
|
||||
var trackForm = ns.ui.trackSelect;
|
||||
if (trackForm.selectedIndex < 0) {
|
||||
return;
|
||||
}
|
||||
var trackId = trackForm.options[trackForm.selectedIndex].value;
|
||||
var trackName = trackForm.options[trackForm.selectedIndex].text;
|
||||
var message = '<div style="float:left">' + ns.sprintf(ns.lang.strings['editingtrack'], '<b>' + ns.htmlEncode(trackName) + '</b>') + '</div>';
|
||||
message += '<div class="red-button"><b><a href="javascript:void(0);" onclick="uLogger.submitTrack(\'delete\'); return false">' + ns.lang.strings['deltrack'] + '</a></b></div>';
|
||||
message += '<div style="clear: both; padding-bottom: 1em;"></div>';
|
||||
|
||||
var form = '<form id="trackForm" method="post" onsubmit="uLogger.submitTrack(\'update\'); return false">';
|
||||
form += '<input type="hidden" name="trackid" value="' + trackId + '">';
|
||||
form += '<label><b>' + ns.lang.strings['trackname'] + '</b></label><input type="text" placeholder="' + ns.lang.strings['trackname'] + '" name="trackname" value="' + ns.htmlEncode(trackName) + '" required>';
|
||||
form += '<div class="buttons"><button type="button" onclick="uLogger.ui.removeModal()">' + ns.lang.strings['cancel'] + '</button><button type="submit">' + ns.lang.strings['submit'] + '</button></div>';
|
||||
form += '</form>';
|
||||
ns.ui.showModal(message + form);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show confirmation dialog
|
||||
* @param {number} id
|
||||
* @param {string} name
|
||||
* @returns {boolean} True if confirmed
|
||||
* @param {uUser} user
|
||||
*/
|
||||
function confirmedDelete(name) {
|
||||
return confirm(ns.sprintf(ns.lang.strings['trackdelwarn'], '"' + name + '"'));
|
||||
constructor(id, name, user) {
|
||||
super(id, name, 'id', 'name');
|
||||
this._user = user;
|
||||
this._positions = null;
|
||||
this._plotData = null;
|
||||
this._maxId = 0;
|
||||
this._onlyLatest = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Submit form dialog
|
||||
* @param action
|
||||
* @return {?uPosition[]}
|
||||
*/
|
||||
function submitTrack(action) {
|
||||
var form = document.getElementById('trackForm');
|
||||
var trackId = parseInt(form.elements['trackid'].value);
|
||||
var trackName = form.elements['trackname'].value.trim();
|
||||
if (isNaN(trackId)) {
|
||||
alert(ns.lang.strings['allrequired']);
|
||||
return;
|
||||
}
|
||||
if (action !== 'delete') {
|
||||
if (!trackName) {
|
||||
alert(ns.lang.strings['allrequired']);
|
||||
return;
|
||||
}
|
||||
} else if (!confirmedDelete(trackName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
ns.post('utils/handletrack.php',
|
||||
{
|
||||
action: action,
|
||||
trackid: trackId,
|
||||
trackname: trackName
|
||||
},
|
||||
{
|
||||
success: function () {
|
||||
ns.ui.removeModal();
|
||||
alert(ns.lang.strings['actionsuccess']);
|
||||
var el = ns.ui.trackSelect;
|
||||
if (action === 'delete') {
|
||||
el.remove(el.selectedIndex);
|
||||
ns.map.clearMap();
|
||||
ns.selectTrack();
|
||||
} else {
|
||||
el.options[el.selectedIndex].innerHTML = ns.htmlEncode(trackName);
|
||||
}
|
||||
},
|
||||
fail: function (message) {
|
||||
alert(ns.lang.strings['actionfailure'] + '\n' + message);
|
||||
}
|
||||
});
|
||||
get positions() {
|
||||
return this._positions;
|
||||
}
|
||||
|
||||
ns.editTrack = editTrack;
|
||||
ns.submitTrack = submitTrack;
|
||||
/**
|
||||
* @param {uUser} user
|
||||
*/
|
||||
set user(user) {
|
||||
this._user = user;
|
||||
}
|
||||
|
||||
})(uLogger);
|
||||
/**
|
||||
* @return {uUser}
|
||||
*/
|
||||
get user() {
|
||||
return this._user;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {boolean} value
|
||||
*/
|
||||
set onlyLatest(value) {
|
||||
this._onlyLatest = value;
|
||||
}
|
||||
|
||||
clear() {
|
||||
this._positions = null;
|
||||
this._plotData = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get track data from xml
|
||||
* @param {XMLDocument} xml
|
||||
* @param {boolean} isUpdate
|
||||
*/
|
||||
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, {
|
||||
// loader: ui.trackTitle
|
||||
}).then((xml) => {
|
||||
this.fromXml(xml, isUpdate);
|
||||
return this.render();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} action
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
update(action) {
|
||||
return uAjax.post('utils/handletrack.php',
|
||||
{
|
||||
action: action,
|
||||
trackid: this.id,
|
||||
trackname: this.name
|
||||
});
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
108
js/trackdialog.js
Normal file
108
js/trackdialog.js
Normal file
@ -0,0 +1,108 @@
|
||||
/* μ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;
|
||||
}
|
||||
|
||||
}
|
188
js/tracklist.js
Normal file
188
js/tracklist.js
Normal file
@ -0,0 +1,188 @@
|
||||
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.CONFIG, this);
|
||||
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 === '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}`));
|
||||
} else if (event.type === uEvent.CONFIG && data === 'showLatest') {
|
||||
if (config.showLatest) {
|
||||
this.fetchLatest().catch((msg) => alert(`${lang.strings['actionfailure']}\n${msg}`));
|
||||
} else {
|
||||
this.fetchTrack();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {HTMLFormElement} form
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
import(form) {
|
||||
return uAjax.post('utils/import.php', form,
|
||||
{
|
||||
// loader: ui.importTitle
|
||||
})
|
||||
.then((xml) => {
|
||||
const root = xml.getElementsByTagName('root');
|
||||
const trackCnt = uUtils.getNodeAsInt(root[0], 'trackcnt');
|
||||
if (trackCnt > 1) {
|
||||
alert(uUtils.sprintf(lang.strings['imultiple'], trackCnt));
|
||||
}
|
||||
const trackId = uUtils.getNodeAsInt(root[0], 'trackid');
|
||||
return this.fetch().then(() => this.select(trackId));
|
||||
}).catch((msg) => alert(`${lang.strings['actionfailure']}\n${msg}`));
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch tracks for current user
|
||||
* @return {Promise<Document, string>}
|
||||
*/
|
||||
fetch() {
|
||||
return uAjax.get('utils/gettracks.php',
|
||||
{
|
||||
userid: uLogger.userList.current.id
|
||||
},
|
||||
{
|
||||
// loader: ui.trackTitle
|
||||
}).then((xml) => {
|
||||
this.clear();
|
||||
return this.fromXml(xml.getElementsByTagName('track'), 'trackid', 'trackname');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch track with latest position for current user
|
||||
* @throws
|
||||
* @return {Promise<Document, string>}
|
||||
*/
|
||||
fetchLatest() {
|
||||
return uAjax.get('utils/getpositions.php', {
|
||||
userid: uLogger.userList.current.id,
|
||||
last: 1
|
||||
}, {
|
||||
// loader: ui.trackTitle
|
||||
}).then((xml) => {
|
||||
const xmlPos = xml.getElementsByTagName('position');
|
||||
if (xmlPos.length === 1) {
|
||||
const position = uPosition.fromXml(xmlPos[0]);
|
||||
if (this.has(position.trackid)) {
|
||||
this.select(position.trackid, true);
|
||||
this.current.fromXml(xml, false);
|
||||
this.current.onlyLatest = true;
|
||||
return this.current.render();
|
||||
}
|
||||
// tracklist needs update
|
||||
return this.fetch().fetchLatest();
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
onChange() {
|
||||
this.fetchTrack();
|
||||
}
|
||||
|
||||
fetchTrack() {
|
||||
if (this.current) {
|
||||
this.current.fetch()
|
||||
.catch((msg) => 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() {
|
||||
}
|
||||
}
|
@ -1,60 +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/>.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef uLogger.config
|
||||
* @memberOf uLogger
|
||||
* @type {Object}
|
||||
* @property {number} interval
|
||||
* @property {string} units
|
||||
* @property {string} mapapi
|
||||
* @property {?string} gkey
|
||||
* @property {Object.<string, string>} ol_layers
|
||||
* @property {number} init_latitude
|
||||
* @property {number} init_longitude
|
||||
* @property {boolean} admin
|
||||
* @property {?string} auth
|
||||
* @property {RegExp} pass_regex
|
||||
* @property {number} strokeWeight
|
||||
* @property {string} strokeColor
|
||||
* @property {number} strokeOpacity
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef uLogger.lang
|
||||
* @memberOf uLogger
|
||||
* @type {Object}
|
||||
* @property {Object.<string, string>} strings
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} uLogger.mapAPI.api
|
||||
* @memberOf uLogger
|
||||
* @type {Object}
|
||||
* @property {string} name
|
||||
* @property {function} init
|
||||
* @property {function} cleanup
|
||||
* @property {function(HTMLCollection, boolean)} displayTrack
|
||||
* @property {function} clearMap
|
||||
* @property {function(uLogger.Position, number, number)} setMarker
|
||||
* @property {function} addChartEvent
|
||||
* @property {function} getBounds
|
||||
* @property {function} zoomToExtent
|
||||
* @property {function} zoomToBounds
|
||||
* @property {function} updateSize
|
||||
*/
|
449
js/ui.js
Normal file
449
js/ui.js
Normal file
@ -0,0 +1,449 @@
|
||||
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);
|
||||
document.addEventListener('DOMContentLoaded', () => { this.initUI(); });
|
||||
this.isLiveOn = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize uUI elements
|
||||
*/
|
||||
initUI() {
|
||||
/** @type {HTMLElement} */
|
||||
this.menu = document.getElementById('menu');
|
||||
/** @type {?HTMLElement} */
|
||||
this.menuHead = document.getElementById('menu_head');
|
||||
/** @type {?HTMLElement} */
|
||||
this.userDropdown = document.getElementById('user_dropdown');
|
||||
/** @type {?HTMLElement} */
|
||||
this.menuPass = document.getElementById('menu_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.api = document.getElementsByName('api')[0];
|
||||
// noinspection JSValidateTypes
|
||||
/** @type {HTMLSelectElement} */
|
||||
this.lang = document.getElementsByName('lang')[0];
|
||||
// noinspection JSValidateTypes
|
||||
/** @type {HTMLSelectElement} */
|
||||
this.units = 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 = this.track ? this.track.getElementsByClassName('menutitle')[0] : null;
|
||||
/** @type {HTMLElement} */
|
||||
this.import = document.getElementById('import');
|
||||
/** @type {HTMLElement} */
|
||||
this.importTitle = this.import ? this.import.getElementsByClassName('menutitle')[0] : 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.auto = document.getElementById('auto');
|
||||
/** @type {HTMLElement} */
|
||||
this.setTime = document.getElementById('set_time');
|
||||
/** @type {HTMLElement} */
|
||||
this.exportKml = document.getElementById('export_kml');
|
||||
/** @type {HTMLElement} */
|
||||
this.exportGpx = document.getElementById('export_gpx');
|
||||
/** @type {?HTMLElement} */
|
||||
this.inputFile = document.getElementById('inputFile');
|
||||
/** @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.menuHead) {
|
||||
this.menuHead.onclick = () => this.showUserMenu();
|
||||
}
|
||||
if (this.menuPass) {
|
||||
this.menuPass.onclick = () => {
|
||||
this.emit(uEvent.PASSWORD);
|
||||
}
|
||||
}
|
||||
this.hideUserMenu = this.hideUserMenu.bind(this);
|
||||
this.latest.onchange = () => uUI.toggleLatest();
|
||||
this.autoReload.onchange = () => this.toggleAutoReload();
|
||||
this.setTime.onclick = () => this.setAutoReloadTime();
|
||||
this.forceReload.onclick = () => this.trackReload();
|
||||
this.chartLink.onclick = () => this.toggleChart();
|
||||
this.api.onchange = () => {
|
||||
const api = this.api.options[this.api.selectedIndex].value;
|
||||
this.emit(uEvent.API_CHANGE, api);
|
||||
};
|
||||
this.lang.onchange = () => {
|
||||
uUI.setLang(this.lang.options[this.lang.selectedIndex].value);
|
||||
};
|
||||
this.units.onchange = () => {
|
||||
uUI.setUnits(this.units.options[this.units.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.auto.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 d = new Date(pos.timestamp * 1000);
|
||||
date = `${d.getFullYear()}-${(`0${d.getMonth() + 1}`).slice(-2)}-${(`0${d.getDate()}`).slice(-2)}`;
|
||||
time = d.toTimeString();
|
||||
let offset;
|
||||
if ((offset = time.indexOf(' ')) >= 0) {
|
||||
time = `${time.substr(0, offset)} <span class="smaller">${time.substr(offset + 1)}</span>`;
|
||||
}
|
||||
}
|
||||
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.comment != null) ? `<div id="pcomments">${uUtils.htmlEncode(pos.comment)}</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>`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear map canvas
|
||||
*/
|
||||
clearMapCanvas() {
|
||||
this.map.innerHTML = '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle user menu visibility
|
||||
*/
|
||||
showUserMenu() {
|
||||
if (this.userDropdown.classList.contains('show')) {
|
||||
this.userDropdown.classList.remove('show');
|
||||
} else {
|
||||
this.userDropdown.classList.add('show');
|
||||
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.classList.remove('show');
|
||||
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.OPEN_URL) {
|
||||
window.location.assign(args);
|
||||
} else if (event.type === uEvent.CONFIG) {
|
||||
if (args === 'showLatest') {
|
||||
this.latest.checked = config.showLatest;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
29
js/ulogger.js
Normal file
29
js/ulogger.js
Normal file
@ -0,0 +1,29 @@
|
||||
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);
|
||||
});
|
56
js/user.js
Normal file
56
js/user.js
Normal file
@ -0,0 +1,56 @@
|
||||
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
|
||||
});
|
||||
}
|
||||
}
|
178
js/userdialog.js
Normal file
178
js/userdialog.js
Normal file
@ -0,0 +1,178 @@
|
||||
/* μ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;
|
||||
}
|
||||
|
||||
}
|
114
js/userlist.js
Normal file
114
js/userlist.js
Normal file
@ -0,0 +1,114 @@
|
||||
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 (this.isSelectedAllOption) {
|
||||
// clearOptions(ui.trackSelect);
|
||||
// loadLastPositionAllUsers();
|
||||
} else if (config.showLatest) {
|
||||
uLogger.trackList.fetchLatest()
|
||||
.catch((msg) => alert(`${lang.strings['actionfailure']}\n${msg}`));
|
||||
} else {
|
||||
uLogger.trackList.fetch()
|
||||
.catch((msg) => alert(`${lang.strings['actionfailure']}\n${msg}`));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @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);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
244
js/utils.js
Normal file
244
js/utils.js
Normal file
@ -0,0 +1,244 @@
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
};
|
26
login.php
26
login.php
@ -27,7 +27,7 @@
|
||||
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<html lang="<?= uConfig::$lang ?>">
|
||||
<head>
|
||||
<title><?= $lang["title"] ?></title>
|
||||
<?php include("meta.php"); ?>
|
||||
@ -42,18 +42,20 @@
|
||||
<div id="title"><?= $lang["title"] ?></div>
|
||||
<div id="subtitle"><?= $lang["private"] ?></div>
|
||||
<form action="<?= BASE_URL ?>" method="post">
|
||||
<?= $lang["username"] ?>:<br>
|
||||
<input type="text" name="user"><br>
|
||||
<?= $lang["password"] ?>:<br>
|
||||
<input type="password" name="pass"><br>
|
||||
<br>
|
||||
<input type="submit" value="<?= $lang["login"] ?>">
|
||||
<input type="hidden" name="action" value="auth">
|
||||
<?php if (!uConfig::$require_authentication): ?>
|
||||
<div id="cancel"><a href="<?= BASE_URL ?>"><?= $lang["cancel"] ?></a></div>
|
||||
<?php endif; ?>
|
||||
<label for="login-user"><?= $lang["username"] ?></label><br>
|
||||
<input id="login-user" type="text" name="user" required><br>
|
||||
<label for="login-pass"><?= $lang["password"] ?></label><br>
|
||||
<input id="login-pass" type="password" name="pass" required><br>
|
||||
<br>
|
||||
<input type="submit" value="<?= $lang["login"] ?>">
|
||||
<input type="hidden" name="action" value="auth">
|
||||
<?php if (!uConfig::$require_authentication): ?>
|
||||
<div id="cancel"><a href="<?= BASE_URL ?>"><?= $lang["cancel"] ?></a></div>
|
||||
<?php endif; ?>
|
||||
</form>
|
||||
<div id="error"><?= (($auth_error) ? $lang["authfail"] : "") ?></div>
|
||||
<?php if ($auth_error): ?>
|
||||
<div id="error"><?= $lang["authfail"] ?></div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
7252
package-lock.json
generated
Normal file
7252
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -33,20 +33,23 @@
|
||||
if (empty($pass)) {
|
||||
uUtils::exitWithError("Empty password");
|
||||
}
|
||||
if ($auth->isAdmin() && !empty($login)) {
|
||||
// different user, only admin
|
||||
$passUser = new uUser($login);
|
||||
if (!$passUser->isValid) {
|
||||
uUtils::exitWithError("User unknown");
|
||||
}
|
||||
} else if (!empty($login)) {
|
||||
uUtils::exitWithError("Unauthorized");
|
||||
} else {
|
||||
if (empty($login)) {
|
||||
uUtils::exitWithError("Empty login");
|
||||
}
|
||||
if ($auth->user->login === $login) {
|
||||
// current user
|
||||
$passUser = $auth->user;
|
||||
if (!$passUser->validPassword($oldpass)) {
|
||||
uUtils::exitWithError("Wrong old password");
|
||||
}
|
||||
} else if ($auth->isAdmin()) {
|
||||
// different user, only admin
|
||||
$passUser = new uUser($login);
|
||||
if (!$passUser->isValid) {
|
||||
uUtils::exitWithError("User unknown");
|
||||
}
|
||||
} else {
|
||||
uUtils::exitWithError("Unauthorized");
|
||||
}
|
||||
if ($passUser->setPass($pass) === false) {
|
||||
uUtils::exitWithError("Server error");
|
||||
|
75
utils/getconstants.php
Normal file
75
utils/getconstants.php
Normal file
@ -0,0 +1,75 @@
|
||||
<?php
|
||||
/* μlogger
|
||||
*
|
||||
* Copyright(C) 2017 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/>.
|
||||
*/
|
||||
|
||||
require_once(dirname(__DIR__) . "/helpers/auth.php");
|
||||
require_once(ROOT_DIR . "/helpers/config.php");
|
||||
require_once(ROOT_DIR . "/helpers/lang.php");
|
||||
|
||||
$auth = new uAuth();
|
||||
$langStrings = (new uLang(uConfig::$lang))->getStrings();
|
||||
|
||||
header("Content-type: text/xml");
|
||||
$xml = new XMLWriter();
|
||||
$xml->openURI("php://output");
|
||||
$xml->startDocument("1.0");
|
||||
$xml->setIndent(true);
|
||||
$xml->startElement('root');
|
||||
|
||||
|
||||
$xml->startElement("auth");
|
||||
$xml->writeElement("isAdmin", $auth->isAdmin());
|
||||
$xml->writeElement("isAuthenticated", $auth->isAuthenticated());
|
||||
if ($auth->isAuthenticated()) {
|
||||
$xml->writeElement("userId", $auth->user->id);
|
||||
$xml->writeElement("userLogin", $auth->user->login);
|
||||
}
|
||||
$xml->endElement();
|
||||
|
||||
$xml->startElement("config");
|
||||
$xml->writeElement("interval", uConfig::$interval);
|
||||
$xml->writeElement("units", uConfig::$units);
|
||||
$xml->writeElement("mapapi", uConfig::$mapapi);
|
||||
$xml->writeElement("gkey", uConfig::$gkey);
|
||||
$xml->startElement("ol_layers");
|
||||
foreach (uConfig::$ol_layers as $key => $val) {
|
||||
$xml->writeElement($key, $val);
|
||||
}
|
||||
$xml->endElement();
|
||||
$xml->writeElement("init_latitude", uConfig::$init_latitude);
|
||||
$xml->writeElement("init_longitude", uConfig::$init_longitude);
|
||||
$xml->writeElement("pass_regex", uConfig::passRegex());
|
||||
$xml->writeElement("strokeWeight", uConfig::$strokeWeight);
|
||||
$xml->writeElement("strokeColor", uConfig::$strokeColor);
|
||||
$xml->writeElement("strokeOpacity", uConfig::$strokeOpacity);
|
||||
$xml->endElement();
|
||||
|
||||
$xml->startElement("lang");
|
||||
$xml->startElement("strings");
|
||||
foreach ($langStrings as $key => $val) {
|
||||
$xml->writeElement($key, $val);
|
||||
}
|
||||
$xml->endElement();
|
||||
$xml->endElement();
|
||||
|
||||
|
||||
$xml->endElement();
|
||||
$xml->endDocument();
|
||||
$xml->flush();
|
||||
|
||||
?>
|
@ -25,6 +25,7 @@ $auth = new uAuth();
|
||||
|
||||
$userId = uUtils::getInt('userid');
|
||||
$trackId = uUtils::getInt('trackid');
|
||||
$afterId = uUtils::getInt('afterid');
|
||||
$last = uUtils::getInt('last');
|
||||
|
||||
$positionsArr = [];
|
||||
@ -33,7 +34,7 @@ if ($userId) {
|
||||
($auth->isAuthenticated() && ($auth->isAdmin() || $auth->user->id === $userId))) {
|
||||
if ($trackId) {
|
||||
// get all track data
|
||||
$positionsArr = uPosition::getAll($userId, $trackId);
|
||||
$positionsArr = uPosition::getAll($userId, $trackId, $afterId);
|
||||
} else if ($last) {
|
||||
// get data only for latest point
|
||||
$position = uPosition::getLast($userId);
|
||||
|
@ -35,14 +35,17 @@
|
||||
}
|
||||
|
||||
$aUser = new uUser($login);
|
||||
$data = NULL;
|
||||
|
||||
switch ($action) {
|
||||
case 'add':
|
||||
if ($aUser->isValid) {
|
||||
uUtils::exitWithError($lang["userexists"]);
|
||||
}
|
||||
if (empty($pass) || uUser::add($login, $pass) === false) {
|
||||
if (empty($pass) || ($userId = uUser::add($login, $pass)) === false) {
|
||||
uUtils::exitWithError($lang["servererror"]);
|
||||
} else {
|
||||
$data = [ 'userid' => $userId ];
|
||||
}
|
||||
break;
|
||||
|
||||
@ -64,6 +67,6 @@
|
||||
break;
|
||||
}
|
||||
|
||||
uUtils::exitWithSuccess();
|
||||
uUtils::exitWithSuccess($data);
|
||||
|
||||
?>
|
Loading…
x
Reference in New Issue
Block a user