devices in progress, UI half done...

This commit is contained in:
Julien Veyssier 2019-04-17 02:44:08 +02:00
Родитель b90f8ff5bb
Коммит fc2eac072b
7 изменённых файлов: 367 добавлений и 61 удалений

Просмотреть файл

@ -72,12 +72,14 @@ return [
'requirements' => ['path' => '.+']
],
['name' => 'devices_api#getDevices', 'url' => '/api/{apiversion}/devices', 'verb' => 'GET'],
['name' => 'devices_api#getDevicePoints', 'url' => '/api/{apiversion}/devices/{id}', 'verb' => 'GET'],
['name' => 'devices_api#addDevicePoint', 'url' => '/api/{apiversion}/devices', 'verb' => 'POST'],
['name' => 'devices_api#editDevice', 'url' => '/api/{apiversion}/devices/{id}', 'verb' => 'PUT'],
['name' => 'devices_api#deleteDevice', 'url' => '/api/{apiversion}/devices/{id}', 'verb' => 'DELETE'],
// devices
['name' => 'devices#getDevices', 'url' => '/devices', 'verb' => 'GET'],
['name' => 'devices#getDevicePoints', 'url' => '/devices/{id}', 'verb' => 'GET'],
['name' => 'devices#addDevicePoint', 'url' => '/devices', 'verb' => 'POST'],
['name' => 'devices#editDevice', 'url' => '/devices/{id}', 'verb' => 'PUT'],
['name' => 'devices#deleteDevice', 'url' => '/devices/{id}', 'verb' => 'DELETE'],

Просмотреть файл

@ -6,7 +6,7 @@ function DevicesController(optionsController, timeFilterController) {
// indexed by device id
// those actually added to map, those which get toggled
this.mapDeviceLayers = {};
// layers which actually contain lines/waypoints, those which get filtered
// layers which actually contain lines, those which get filtered
this.deviceLayers = {};
this.deviceColors = {};
this.deviceDivIcon = {};
@ -44,6 +44,23 @@ DevicesController.prototype = {
var id = $(this).parent().parent().parent().attr('device');
that.toggleDevice(id, true);
});
// toggle devices
$('body').on('click', '#toggleDevicesButton', function(e) {
that.toggleDevices();
that.optionsController.saveOptionValues({devicesEnabled: that.map.hasLayer(that.mainLayer)});
that.updateMyFirstLastDates();
});
// expand track list
$('body').on('click', '#navigation-devices > a', function(e) {
that.toggleDeviceList();
that.optionsController.saveOptionValues({deviceListShow: $('#navigation-devices').hasClass('open')});
});
$('body').on('click', '#navigation-devices', function(e) {
if (e.target.tagName === 'LI' && $(e.target).attr('id') === 'navigation-devices') {
that.toggleDeviceList();
that.optionsController.saveOptionValues({deviceListShow: $('#navigation-devices').hasClass('open')});
}
});
// send my position on page load
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(function (position) {
@ -55,6 +72,261 @@ DevicesController.prototype = {
}
},
// expand or fold device list in sidebar
toggleDeviceList: function() {
$('#navigation-devices').toggleClass('open');
},
// toggle tracks general layer on map and save state in user options
toggleDevices: function() {
if (this.map.hasLayer(this.mainLayer)) {
this.map.removeLayer(this.mainLayer);
// color of the eye
$('#toggleDevicesButton button').addClass('icon-toggle').attr('style', '');
}
else {
if (!this.deviceListLoaded) {
this.getDevices();
}
this.map.addLayer(this.mainLayer);
// color of the eye
var color = OCA.Theming.color.replace('#', '');
var imgurl = OC.generateUrl('/svg/core/actions/toggle?color='+color);
$('#toggleDevicesButton button').removeClass('icon-toggle').css('background-image', 'url('+imgurl+')');
}
},
getDevices: function() {
var that = this;
$('#navigation-devices').addClass('icon-loading-small');
var req = {};
var url = OC.generateUrl('/apps/maps/devices');
$.ajax({
type: 'GET',
url: url,
data: req,
async: true
}).done(function (response) {
var i, device;
for (i=0; i < response.length; i++) {
device = response[i];
that.addDeviceMap(device, false, true);
}
that.deviceListLoaded = true;
}).always(function (response) {
$('#navigation-devices').removeClass('icon-loading-small');
}).fail(function() {
OC.Notification.showTemporary(t('maps', 'Failed to load device list'));
});
},
addDeviceMap: function(device, show=false, pageLoad=false) {
// color
var color = device.color || OCA.Theming.color;
this.deviceDivIcon[device.id] = L.divIcon({
iconAnchor: [12, 25],
className: 'trackWaypoint trackWaypoint-'+device.id,
html: ''
});
this.devices[device.id] = device;
this.mapDeviceLayers[device.id] = L.featureGroup();
this.deviceLayers[device.id] = L.featureGroup();
this.deviceLayers[device.id].loaded = false;
this.mapDeviceLayers[device.id].addLayer(this.deviceLayers[device.id]);
var name = device.user_agent;
// side menu entry
var imgurl = OC.generateUrl('/svg/core/clients/phone?color='+color.replace('#', ''));
var li = '<li class="device-line" id="'+name+'-device" device="'+device.id+'" name="'+name+'">' +
' <a href="#" class="device-name" id="'+name+'-device-name" style="background-image: url('+imgurl+')">'+name+'</a>' +
' <div class="app-navigation-entry-utils">' +
' <ul>' +
' <li class="app-navigation-entry-utils-menu-button toggleDeviceButton" title="'+t('maps', 'Toggle device')+'">' +
' <button class="icon-toggle"></button>' +
' </li>' +
' <li class="app-navigation-entry-utils-menu-button deviceMenuButton">' +
' <button></button>' +
' </li>' +
' </ul>' +
' </div>' +
' <div class="app-navigation-entry-menu">' +
' <ul>' +
' <li>' +
' <a href="#" class="changeDeviceColor">' +
' <span class="icon-rename"></span>' +
' <span>'+t('maps', 'Change device color')+'</span>' +
' </a>' +
' </li>' +
' <li>' +
' <a href="#" class="deleteDevice">' +
' <span class="icon-delete"></span>' +
' <span>'+t('maps', 'Delete')+'</span>' +
' </a>' +
' </li>' +
' </ul>' +
' </div>' +
' <div class="app-navigation-entry-deleted">' +
' <div class="app-navigation-entry-deleted-description">'+t('maps', 'Device deleted')+'</div>' +
' <button class="app-navigation-entry-deleted-button icon-history undoDeleteDevice" title="Undo"></button>' +
' </div>' +
'</li>';
var beforeThis = null;
var nameLower = name.toLowerCase();
var deviceName;
$('#device-list > li').each(function() {
deviceName = $(this).attr('name');
if (nameLower.localeCompare(deviceName) < 0) {
beforeThis = $(this);
return false;
}
});
if (beforeThis !== null) {
$(li).insertBefore(beforeThis);
}
else {
$('#device-list').append(li);
}
// enable if in saved options or if it should be enabled for another reason
if (show || this.optionsController.enabledDevices.indexOf(device.id) !== -1) {
this.toggleDevice(device.id, false, pageLoad);
}
},
saveEnabledDevices: function(additionalIds=[]) {
var deviceList = [];
var layer;
for (var id in this.mapDeviceLayers) {
layer = this.mapDeviceLayers[id];
if (this.mainLayer.hasLayer(layer)) {
deviceList.push(id);
}
}
for (var i=0; i < additionalIds.length; i++) {
deviceList.push(additionalIds[i]);
}
var deviceStringList = deviceList.join('|');
this.optionsController.saveOptionValues({enabledDevices: deviceStringList});
// this is used when tracks are loaded again
this.optionsController.enabledDevices = deviceList;
},
restoreDevicesState: function(enabledDeviceList) {
var id;
for (var i=0; i < enabledDeviceList.length; i++) {
id = enabledDeviceList[i];
if (this.mapDeviceLayers.hasOwnProperty(id)) {
this.toggleDevice(id, false, true);
}
}
},
toggleDevice: function(id, save=false, pageLoad=false) {
var deviceLayer = this.deviceLayers[id];
if (!deviceLayer.loaded) {
this.loadDevicePoints(id, save, pageLoad);
}
this.toggleMapDeviceLayer(id);
if (save) {
this.saveEnabledDevices();
this.updateMyFirstLastDates();
}
},
toggleMapDeviceLayer: function(id) {
var mapDeviceLayer = this.mapDeviceLayers[id];
var eyeButton = $('#device-list > li[device="'+id+'"] .toggleDeviceButton button');
// hide device
if (this.mainLayer.hasLayer(mapDeviceLayer)) {
this.mainLayer.removeLayer(mapDeviceLayer);
// color of the eye
eyeButton.addClass('icon-toggle').attr('style', '');
}
// show track
else {
this.mainLayer.addLayer(mapDeviceLayer);
// color of the eye
var color = OCA.Theming.color.replace('#', '');
var imgurl = OC.generateUrl('/svg/core/actions/toggle?color='+color);
eyeButton.removeClass('icon-toggle').css('background-image', 'url('+imgurl+')');
}
},
loadDevicePoints: function(id, save=false, pageLoad=false) {
var that = this;
$('#device-list > li[device="'+id+'"]').addClass('icon-loading-small');
var req = {};
var url = OC.generateUrl('/apps/maps/devices/'+id);
$.ajax({
type: 'GET',
url: url,
data: req,
async: true
}).done(function (response) {
that.addPoints(id, response);
that.deviceLayers[id].loaded = true;
that.updateMyFirstLastDates(pageLoad);
}).always(function (response) {
$('#device-list > li[device="'+id+'"]').removeClass('icon-loading-small');
}).fail(function() {
OC.Notification.showTemporary(t('maps', 'Failed to load device points'));
});
},
addPoints: function(id, points) {
console.log('add points for device '+id);
console.log(points);
},
updateMyFirstLastDates: function(pageLoad=false) {
if (!this.map.hasLayer(this.mainLayer)) {
this.firstDate = null;
this.lastDate = null;
return;
}
var id;
// we update dates only if nothing is currently loading
for (id in this.mapDeviceLayers) {
if (this.mainLayer.hasLayer(this.mapDeviceLayers[id]) && !this.deviceLayers[id].loaded) {
return;
}
}
var initMinDate = Math.floor(Date.now() / 1000) + 1000000
var initMaxDate = 0;
var first = initMinDate;
var last = initMaxDate;
for (id in this.mapDeviceLayers) {
if (this.mainLayer.hasLayer(this.mapDeviceLayers[id]) && this.deviceLayers[id].loaded && this.devices[id].firstDate) {
if (this.devices[id].firstDate < first) {
first = this.devices[id].firstDate;
}
if (this.devices[id].lastDate > last) {
last = this.device[id].lastDate;
}
}
}
if (first !== initMinDate
&& last !== initMaxDate) {
this.firstDate = first;
this.lastDate = last;
}
else {
this.firstDate = null;
this.lastDate = null;
}
if (pageLoad) {
this.timeFilterController.updateSliderRangeFromController();
this.timeFilterController.setSliderToMaxInterval();
}
},
sendMyPosition: function(lat, lng, acc) {
var that = this;
var ts = Math.floor(Date.now() / 1000);

Просмотреть файл

@ -99,6 +99,7 @@
optionValues: {},
enabledFavoriteCategories: [],
enabledTracks: [],
enabledDevices: [],
saveOptionValues: function (optionValues) {
var req = {
options: optionValues
@ -180,6 +181,23 @@
if (!optionsValues.hasOwnProperty('tracksEnabled') || optionsValues.tracksEnabled === 'true') {
tracksController.toggleTracks();
}
if (!optionsValues.hasOwnProperty('deviceListShow') || optionsValues.deviceListShow === 'true') {
devicesController.toggleDeviceList();
}
if (optionsValues.hasOwnProperty('enabledDevices')
&& optionsValues.enabledDevices
&& optionsValues.enabledDevices !== '')
{
that.enabledDevices = optionsValues.enabledDevices.split('|').map(function (x) {
return parseInt(x);
});
if (devicesController.deviceListLoaded) {
devicesController.restoreDevicesState(that.enabledDevices);
}
}
if (!optionsValues.hasOwnProperty('devicesEnabled') || optionsValues.devicesEnabled === 'true') {
devicesController.toggleDevices();
}
// save tile layer when changed
// do it after restore, otherwise restoring triggers save

Просмотреть файл

@ -142,7 +142,7 @@ TracksController.prototype = {
});
},
// expand or fold categories in sidebar
// expand or fold track list in sidebar
toggleTrackList: function() {
$('#navigation-tracks').toggleClass('open');
},
@ -465,6 +465,7 @@ TracksController.prototype = {
var beforeThis = null;
var nameLower = name.toLowerCase();
var trackName;
$('#track-list > li').each(function() {
trackName = $(this).attr('name');
if (nameLower.localeCompare(trackName) < 0) {

Просмотреть файл

@ -82,10 +82,10 @@ class DevicesApiController extends ApiController {
* @NoCSRFRequired
* @CORS
*/
public function getDevices($apiversion, $pruneBefore=0) {
public function getDevices($apiversion) {
$now = new \DateTime();
$devices = $this->devicesService->getDevicesFromDB($this->userId, $pruneBefore);
$devices = $this->devicesService->getDevicesFromDB($this->userId);
$etag = md5(json_encode($devices));
if ($this->request->getHeader('If-None-Match') === '"'.$etag.'"') {
@ -96,6 +96,16 @@ class DevicesApiController extends ApiController {
->setETag($etag);
}
/**
* @NoAdminRequired
* @NoCSRFRequired
* @CORS
*/
public function getDevicePoints($id, $pruneBefore=0) {
$points = $this->devicesService->getDevicePointsFromDB($this->userId, $id, $pruneBefore);
return new DataResponse($points);
}
/**
* @NoAdminRequired
* @NoCSRFRequired
@ -113,7 +123,10 @@ class DevicesApiController extends ApiController {
}
$deviceId = $this->devicesService->getOrCreateDeviceFromDB($this->userId, $ua);
$pointId = $this->devicesService->addPointToDB($deviceId, $lat, $lng, $ts, $altitude, $battery, $accuracy);
return new DataResponse($pointId);
return new DataResponse([
'deviceId'=>$deviceId,
'pointId'=>$pointId
]);
}
else {
return new DataResponse('invalid values', 400);

Просмотреть файл

@ -82,11 +82,19 @@ class DevicesController extends Controller {
/**
* @NoAdminRequired
*/
public function getDevices($pruneBefore=0) {
$devices = $this->devicesService->getDevicesFromDB($this->userId, $pruneBefore);
public function getDevices() {
$devices = $this->devicesService->getDevicesFromDB($this->userId);
return new DataResponse($devices);
}
/**
* @NoAdminRequired
*/
public function getDevicePoints($id, $pruneBefore=0) {
$points = $this->devicesService->getDevicePointsFromDB($this->userId, $id, $pruneBefore);
return new DataResponse($points);
}
/**
* @NoAdminRequired
*/
@ -102,7 +110,10 @@ class DevicesController extends Controller {
}
$deviceId = $this->devicesService->getOrCreateDeviceFromDB($this->userId, $ua);
$pointId = $this->devicesService->addPointToDB($deviceId, $lat, $lng, $ts, $altitude, $battery, $accuracy);
return new DataResponse($pointId);
return new DataResponse([
'deviceId'=>$deviceId,
'pointId'=>$pointId
]);
}
else {
return new DataResponse('invalid values', 400);

Просмотреть файл

@ -33,56 +33,7 @@ class DevicesService {
* @param int $pruneBefore
* @return array with devices
*/
public function getDevicesFromDB($userId, $pruneBefore=0) {
$deviceIds = [];
$qb = $this->qb;
$qb->select('id')
->from('maps_devices', 'd')
->where(
$qb->expr()->eq('user_id', $qb->createNamedParameter($userId, IQueryBuilder::PARAM_STR))
);
$req = $qb->execute();
while ($row = $req->fetch()) {
array_push($deviceIds, intval($row['id']));
}
$req->closeCursor();
$qb = $qb->resetQueryParts();
// get coordinates
$pointsByDevice = [];
foreach ($deviceIds as $deviceId) {
$qb->select('id', 'lat', 'lng', 'timestamp', 'altitude', 'accuracy', 'battery')
->from('maps_device_points', 'p')
->where(
$qb->expr()->eq('device_id', $qb->createNamedParameter($deviceId, IQueryBuilder::PARAM_INT))
);
if (intval($pruneBefore) > 0) {
$qb->andWhere(
$qb->expr()->gt('timestamp', $qb->createNamedParameter($pruneBefore, IQueryBuilder::PARAM_INT))
);
}
$qb->orderBy('timestamp', 'ASC');
$req = $qb->execute();
$points = [];
while ($row = $req->fetch()) {
array_push($points, [
'id' => intval($row['id']),
'lat' => floatval($row['lat']),
'lng' => floatval($row['lng']),
'timestamp' => intval($row['timestamp']),
'altitude' => floatval($row['altitude']),
'accuracy' => floatval($row['accuracy']),
'battery' => floatval($row['battery'])
]);
}
$pointsByDevice[$deviceId] = $points;
}
$req->closeCursor();
$qb = $qb->resetQueryParts();
// build device list
public function getDevicesFromDB($userId) {
$devices = [];
$qb = $this->qb;
$qb->select('id', 'user_agent', 'color')
@ -96,8 +47,7 @@ class DevicesService {
array_push($devices, [
'id' => intval($row['id']),
'user_agent' => $row['user_agent'],
'color' => $row['color'],
'points' => $pointsByDevice[intval($row['id'])]
'color' => $row['color']
]);
}
$req->closeCursor();
@ -105,6 +55,45 @@ class DevicesService {
return $devices;
}
public function getDevicePointsFromDB($userId, $deviceId, $pruneBefore=0) {
$qb = $this->qb;
// get coordinates
$qb->select('p.id', 'lat', 'lng', 'timestamp', 'altitude', 'accuracy', 'battery')
->from('maps_device_points', 'p')
->innerJoin('p', 'maps_devices', 'd', $qb->expr()->eq('d.id', 'p.device_id'))
->where(
$qb->expr()->eq('p.device_id', $qb->createNamedParameter($deviceId, IQueryBuilder::PARAM_INT))
)
->andWhere(
$qb->expr()->eq('d.user_id', $qb->createNamedParameter($userId, IQueryBuilder::PARAM_STR))
);
if (intval($pruneBefore) > 0) {
$qb->andWhere(
$qb->expr()->gt('timestamp', $qb->createNamedParameter($pruneBefore, IQueryBuilder::PARAM_INT))
);
}
$qb->orderBy('timestamp', 'ASC');
error_log($qb->getSQL());
$req = $qb->execute();
$points = [];
while ($row = $req->fetch()) {
array_push($points, [
'id' => intval($row['id']),
'lat' => floatval($row['lat']),
'lng' => floatval($row['lng']),
'timestamp' => intval($row['timestamp']),
'altitude' => floatval($row['altitude']),
'accuracy' => floatval($row['accuracy']),
'battery' => floatval($row['battery'])
]);
}
$req->closeCursor();
$qb = $qb->resetQueryParts();
return $points;
}
public function getOrCreateDeviceFromDB($userId, $userAgent) {
$deviceId = null;
$qb = $this->qb;
@ -216,7 +205,7 @@ class DevicesService {
$qb = $this->dbconnection->getQueryBuilder();
$qb->select($qb->createFunction('COUNT(*)'))
->from('maps_devices', 'd')
->innerJoin('bo', 'maps_device_points', 'p', $qb->expr()->eq('d.id', 'p.device_id'))
->innerJoin('d', 'maps_device_points', 'p', $qb->expr()->eq('d.id', 'p.device_id'))
->where(
$qb->expr()->eq('d.user_id', $qb->createNamedParameter($userId, IQueryBuilder::PARAM_INT))
);