From 69b859e47b5ca77715eab1c582adaace0decda55 Mon Sep 17 00:00:00 2001 From: Julien Veyssier Date: Sat, 6 Apr 2019 14:31:28 +0200 Subject: [PATCH 01/13] add track related menu entry --- css/style.css | 13 ++++++++++++- templates/navigation/index.php | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/css/style.css b/css/style.css index 997639ffb..b7b4aaa96 100644 --- a/css/style.css +++ b/css/style.css @@ -332,6 +332,7 @@ table.editFavorite input[type=text] { #togglePhotosButton button, #toggleContactsButton button, #toggleFavoritesButton button, +#toggleTracksButton button, .toggleCategoryButton button { opacity: 1 !important; } @@ -339,6 +340,7 @@ table.editFavorite input[type=text] { #navigation-favorites > a, #navigation-photos > a, #navigation-contacts > a, +#navigation-tracks > a, .category-line > a { opacity: 1 !important; } @@ -349,7 +351,16 @@ table.editFavorite input[type=text] { font-weight: 900; text-decoration: inherit; position: absolute; - left: 18px; + left: 16px; +} +#navigation-tracks > a::before { + content: "\f201"; + font-family: 'Font Awesome\ 5 Free'; + font-style: normal; + font-weight: 900; + text-decoration: inherit; + position: absolute; + left: 16px; } .ui-autocomplete { z-index: 10000 !important; diff --git a/templates/navigation/index.php b/templates/navigation/index.php index f2342f7ab..7d753c0a7 100644 --- a/templates/navigation/index.php +++ b/templates/navigation/index.php @@ -127,4 +127,38 @@ + From c2ebf0ff65be9c3a9a1b0a2b7b2fc16e92aa8a53 Mon Sep 17 00:00:00 2001 From: Julien Veyssier Date: Sat, 6 Apr 2019 21:00:14 +0200 Subject: [PATCH 02/13] beginning to implement tracks management, UI part --- js/script.js | 17 +++ js/tracksController.js | 184 +++++++++++++++++++++++++++++++++ templates/index.php | 1 + templates/navigation/index.php | 2 +- 4 files changed, 203 insertions(+), 1 deletion(-) create mode 100644 js/tracksController.js diff --git a/js/script.js b/js/script.js index 26841b138..1d1dbaced 100644 --- a/js/script.js +++ b/js/script.js @@ -7,6 +7,7 @@ photosController.initLayer(mapController.map); mapController.map.photosController = photosController; contactsController.initLayer(mapController.map); + tracksController.initController(mapController.map); // once controllers have been set/initialized, we can restore option values from server optionsController.restoreOptions(); @@ -130,6 +131,21 @@ if (optionsValues.hasOwnProperty('routingEnabled') && optionsValues.routingEnabled === 'true') { routingController.toggleRouting(); } + if (!optionsValues.hasOwnProperty('tracksEnabled') || optionsValues.tracksEnabled === 'true') { + tracksController.toggleTracks(); + } + if (!optionsValues.hasOwnProperty('trackListShow') || optionsValues.trackListShow === 'true') { + tracksController.toggleTrackList(); + } + if (optionsValues.hasOwnProperty('enabledTracks') + && optionsValues.enabledTracks + && optionsValues.enabledTracks !== '') + { + that.enabledTracks = optionsValues.enabledTracks.split('|'); + if (tracksController.tracksLoaded) { + tracksController.restoreTracksState(that.enabledTracks); + } + } // save tile layer when changed // do it after restore, otherwise restoring triggers save @@ -597,6 +613,7 @@ var photosController = new PhotosController(optionsController, timeFilterController); var contactsController = new ContactsController(optionsController, timeFilterController); var favoritesController = new FavoritesController(optionsController, timeFilterController); + var tracksController = new TracksController(optionsController, timeFilterController); timeFilterController.connect(); diff --git a/js/tracksController.js b/js/tracksController.js new file mode 100644 index 000000000..cf774c1b7 --- /dev/null +++ b/js/tracksController.js @@ -0,0 +1,184 @@ +function TracksController(optionsController, timeFilterController) { + this.optionsController = optionsController; + this.timeFilterController = timeFilterController; + + this.mainLayer = null; + // indexed by track file id + this.trackLayers = {}; + + this.firstDate = null; + this.lastDate = null; + + // used by optionsController to know if tracks loading + // was done before or after option restoration + this.tracksLoaded = false; +} + +TracksController.prototype = { + + // set up favorites-related UI stuff + initController : function(map) { + this.map = map; + this.mainLayer = L.featureGroup(); + var that = this; + // UI events + // click on menu buttons + $('body').on('click', '.tracksMenuButton, .trackMenuButton', function(e) { + var wasOpen = $(this).parent().parent().parent().find('>.app-navigation-entry-menu').hasClass('open'); + $('.app-navigation-entry-menu.open').removeClass('open'); + if (!wasOpen) { + $(this).parent().parent().parent().find('>.app-navigation-entry-menu').addClass('open'); + } + }); + // click on a track name : zoom to bounds + $('body').on('click', '.track-line .track-name', function(e) { + var track = $(this).text(); + that.zoomOnTrack(track); + }); + // toggle a track + $('body').on('click', '.toggleTrackButton', function(e) { + var track = $(this).parent().parent().parent().attr('track'); + that.toggleTrack(track); + that.saveEnabledTracks(); + that.updateMyFirstLastDates(); + }); + // show/hide all tracks + $('body').on('click', '#select-all-tracks', function(e) { + that.showAllTracks(); + that.saveEnabledTracks(); + that.optionsController.saveOptionValues({tracksEnabled: that.map.hasLayer(that.mainLayer)}); + }); + $('body').on('click', '#select-no-tracks', function(e) { + that.hideAllTracks(); + that.saveEnabledTracks(); + that.optionsController.saveOptionValues({tracksEnabled: that.map.hasLayer(that.mainLayer)}); + }); + // click on + button + $('body').on('click', '#addTrackButton', function(e) { + // TODO + }); + // toggle tracks + $('body').on('click', '#toggleTracksButton', function(e) { + that.toggleTracks(); + that.optionsController.saveOptionValues({tracksEnabled: that.map.hasLayer(that.mainLayer)}); + that.updateMyFirstLastDates(); + }); + // expand track list + $('body').on('click', '#navigation-tracks > a', function(e) { + that.toggleTrackList(); + that.optionsController.saveOptionValues({trackListShow: $('#navigation-tracks').hasClass('open')}); + }); + $('body').on('click', '#navigation-tracks', function(e) { + if (e.target.tagName === 'LI' && $(e.target).attr('id') === 'navigation-tracks') { + that.toggleTrackList(); + that.optionsController.saveOptionValues({trackListShow: $('#navigation-tracks').hasClass('open')}); + } + }); + }, + + // expand or fold categories in sidebar + toggleTrackList: function() { + $('#navigation-tracks').toggleClass('open'); + }, + + // toggle tracks general layer on map and save state in user options + toggleTracks: function() { + if (this.map.hasLayer(this.mainLayer)) { + this.map.removeLayer(this.mainLayer); + // color of the eye + $('#toggleTracksButton button').addClass('icon-toggle').attr('style', ''); + } + else { + 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); + $('#toggleTracksButton button').removeClass('icon-toggle').css('background-image', 'url('+imgurl+')'); + } + }, + + updateMyFirstLastDates: function() { + if (!this.map.hasLayer(this.mainLayer)) { + this.firstDate = null; + this.lastDate = null; + return; + } + + var id; + + var initMinDate = Math.floor(Date.now() / 1000) + 1000000 + var initMaxDate = 0; + + var first = initMinDate; + var last = initMaxDate; + for (id in this.trackLayers) { + if (this.mainLayer.hasLayer(this.trackLayers[id])) { + if (this.trackLayers[id].date < first) { + first = this.trackLayers[id].date; + } + if (this.trackLayers[id].date > last) { + last = this.trackLayers[id].date; + } + } + } + if (first !== initMinDate + && last !== initMaxDate) { + this.firstDate = first; + this.lastDate = last; + } + else { + this.firstDate = null; + this.lastDate = null; + } + }, + + saveEnabledTracks: function() { + var trackList = []; + var layer; + for (var id in this.trackLayers) { + layer = this.trackLayers[id]; + if (this.mainLayer.hasLayer(layer)) { + trackList.push(id); + } + } + var trackStringList = trackList.join('|'); + this.optionsController.saveOptionValues({enabledTracks: trackStringList}); + // this is used when tracks are loaded again + this.optionsController.enabledTracks = trackStringList; + }, + + restoreTracksState: function(enabledTrackList) { + var id; + for (var i=0; i < enabledTrackList.length; i++) { + id = enabledTrackList[i]; + if (this.trackLayers.hasOwnProperty(id)) { + this.toggleTrack(id); + } + } + this.updateTimeFilterRange(); + this.timeFilterController.setSliderToMaxInterval(); + }, + + showAllTracks: function() { + if (!this.map.hasLayer(this.mainLayer)) { + this.toggleTracks(); + } + for (var id in this.trackLayers) { + if (!this.mainLayer.hasLayer(this.trackLayers[id])) { + this.toggleTrack(id); + } + } + this.updateMyFirstLastDates(); + }, + + hideAllTracks: function() { + for (var id in this.trackLayers) { + if (this.mainLayer.hasLayer(this.trackLayers[id])) { + this.toggleTrack(id); + } + } + this.updateMyFirstLastDates(); + }, + + +} diff --git a/templates/index.php b/templates/index.php index 544693d1e..36b3e898f 100644 --- a/templates/index.php +++ b/templates/index.php @@ -5,6 +5,7 @@ script('maps', 'photosController'); script('maps', 'contactsController'); script('maps', 'favoritesController'); +script('maps', 'tracksController'); script('maps', 'script'); ?> diff --git a/templates/navigation/index.php b/templates/navigation/index.php index 7d753c0a7..a920de095 100644 --- a/templates/navigation/index.php +++ b/templates/navigation/index.php @@ -145,7 +145,7 @@
  • - + t('Show all tracks')); ?> From badd462b07a8376d7c77e9d019b4cc32da81dd37 Mon Sep 17 00:00:00 2001 From: Julien Veyssier Date: Sun, 7 Apr 2019 03:06:43 +0200 Subject: [PATCH 03/13] backend part for tracks --- appinfo/application.php | 23 ++++ appinfo/database.xml | 31 +++++ js/tracksController.js | 10 +- js/utils.js | 4 + lib/Controller/TracksController.php | 169 ++++++++++++++++++++++++++++ lib/Service/TracksService.php | 134 ++++++++++++++++++++++ 6 files changed, 370 insertions(+), 1 deletion(-) create mode 100644 lib/Controller/TracksController.php create mode 100644 lib/Service/TracksService.php diff --git a/appinfo/application.php b/appinfo/application.php index 0b2daa102..65fd172b3 100644 --- a/appinfo/application.php +++ b/appinfo/application.php @@ -19,6 +19,7 @@ use OCA\Maps\Controller\FavoritesController; use OCA\Maps\Controller\FavoritesApiController; use OCA\Maps\Controller\RoutingController; +use OCA\Maps\Controller\TracksController; use OCA\Maps\Hook\FileHooks; use OCA\Maps\Service\PhotofilesService; use OCA\Maps\Service\FavoritesService; @@ -103,6 +104,28 @@ public function __construct (array $urlParams=array()) { } ); + $container->registerService( + 'TracksController', function ($c) { + return new TracksController( + $c->query('AppName'), + $c->query('Request'), + $c->query('UserId'), + $c->query('ServerContainer')->getUserFolder($c->query('UserId')), + $c->query('ServerContainer')->getConfig(), + $c->getServer()->getShareManager(), + $c->getServer()->getAppManager(), + $c->getServer()->getUserManager(), + $c->getServer()->getGroupManager(), + $c->query('ServerContainer')->getL10N($c->query('AppName')), + $c->query('ServerContainer')->getLogger(), + new TracksService( + $c->query('ServerContainer')->getLogger(), + $c->query('ServerContainer')->getL10N($c->query('AppName')) + ) + ); + } + ); + $container->registerService( 'UtilsController', function ($c) { return new UtilsController( diff --git a/appinfo/database.xml b/appinfo/database.xml index 32f055bb4..08d128e9d 100644 --- a/appinfo/database.xml +++ b/appinfo/database.xml @@ -141,5 +141,36 @@ + + *dbprefix*maps_tracks + + + id + integer + 0 + true + 1 + 41 + + + user_id + text + true + 64 + + + file_id + integer + true + 10 + + + file_path + text + true + 500 + + +
    diff --git a/js/tracksController.js b/js/tracksController.js index cf774c1b7..53fab542b 100644 --- a/js/tracksController.js +++ b/js/tracksController.js @@ -55,7 +55,15 @@ TracksController.prototype = { }); // click on + button $('body').on('click', '#addTrackButton', function(e) { - // TODO + OC.dialogs.filepicker( + t('maps', 'Load gpx file'), + function(targetPath) { + that.addTrack(targetPath); + }, + false, + 'application/gpx+xml', + true + ); }); // toggle tracks $('body').on('click', '#toggleTracksButton', function(e) { diff --git a/js/utils.js b/js/utils.js index 1d5552925..c189e38c7 100644 --- a/js/utils.js +++ b/js/utils.js @@ -3,6 +3,10 @@ function basename(str) { return base; } +function dirname(path) { + return path.replace(/\\/g,'/').replace(/\/[^\/]*$/, '');; +} + function Timer(callback, mydelay) { var timerId, start, remaining = mydelay; diff --git a/lib/Controller/TracksController.php b/lib/Controller/TracksController.php new file mode 100644 index 000000000..49aea7d39 --- /dev/null +++ b/lib/Controller/TracksController.php @@ -0,0 +1,169 @@ + + * @copyright Julien Veyssier 2019 + */ + +namespace OCA\Maps\Controller; + +use OCP\App\IAppManager; + +use OCP\IURLGenerator; +use OCP\IConfig; +use \OCP\IL10N; + +use OCP\AppFramework\Http; +use OCP\AppFramework\Http\RedirectResponse; + +use OCP\AppFramework\Http\ContentSecurityPolicy; + +use OCP\IRequest; +use OCP\AppFramework\Http\TemplateResponse; +use OCP\AppFramework\Http\DataResponse; +use OCP\AppFramework\Controller; +use OCP\AppFramework\ApiController; +use OCP\Constants; +use OCP\Share; + +use OCA\Maps\Service\TracksService; + +function endswith($string, $test) { + $strlen = strlen($string); + $testlen = strlen($test); + if ($testlen > $strlen) return false; + return substr_compare($string, $test, $strlen - $testlen, $testlen) === 0; +} + +class TracksController extends Controller { + + private $userId; + private $userfolder; + private $config; + private $appVersion; + private $shareManager; + private $userManager; + private $groupManager; + private $dbconnection; + private $dbtype; + private $dbdblquotes; + private $trans; + private $logger; + private $tracksService; + protected $appName; + + public function __construct($AppName, IRequest $request, $UserId, + $userfolder, $config, $shareManager, + IAppManager $appManager, $userManager, + $groupManager, IL10N $trans, $logger, TracksService $tracksService){ + parent::__construct($AppName, $request); + $this->tracksService = $tracksService; + $this->logger = $logger; + $this->appName = $AppName; + $this->appVersion = $config->getAppValue('maps', 'installed_version'); + $this->userId = $UserId; + $this->userManager = $userManager; + $this->groupManager = $groupManager; + $this->trans = $trans; + $this->dbtype = $config->getSystemValue('dbtype'); + $this->config = $config; + $this->dbconnection = \OC::$server->getDatabaseConnection(); + if ($UserId !== '' and $userfolder !== null){ + $this->userfolder = $userfolder; + } + $this->shareManager = $shareManager; + } + + /** + * @NoAdminRequired + */ + public function getTracks() { + $tracks = $this->tracksService->getTracksFromDB($this->userId); + $existingTracks = []; + foreach ($tracks as $track) { + if ($this->userfolder->nodeExists($track['file_path']) + and $this->userfolder->get($track['file_path'])->getType() === \OCP\Files\FileInfo::TYPE_FILE + ) { + array_push($existingTracks, $track), + } + else { + $this->deleteTrack($track['id']); + } + } + return new DataResponse($existingTracks); + } + + /** + * @NoAdminRequired + */ + public function getTrackFileContent($id) { + $track = $this->tracksService->getTrackFromDB($trackId); + if ($this->userfolder->nodeExists($track['file_path'])) { + $trackFile = $this->userfolder->get($track['file_path']); + if ($trackFile->getType() === \OCP\Files\FileInfo::TYPE_FILE) { + $trackContent = $trackFile->getContent(); + return new DataResponse($trackContent); + } + else { + return new DataResponse('bad file type', 400); + } + } + else { + return new DataResponse('file not found', 400); + } + } + + /** + * @NoAdminRequired + */ + public function addTrack($path) { + if ($path && strlen($path) > 0) { + $cleanpath = str_replace(array('../', '..\\'), '', $path); + if ($this->userfolder->nodeExists($cleanpath)) { + $trackFile = $this->userfolder->get($cleanpath); + if ($trackFile->getType() === \OCP\Files\FileInfo::TYPE_FILE) { + $trackFileId = $trackFile->getId(); + $trackId = $this->tracksService->addTrackToDB($this->userId, $cleanpath, $trackFileId); + $track = $this->tracksService->getTrackFromDB($trackId); + return new DataResponse($track); + } + else { + return new DataResponse('bad file type', 400); + } + } + else { + return new DataResponse('file not found', 400); + } + } + else { + return new DataResponse('invalid value', 400); + } + } + + /** + * @NoAdminRequired + */ + public function deleteTrack($id) { + $track = $this->tracksService->getTrackFromDB($id, $this->userId); + if ($track !== null) { + $this->tracksService->deleteTrackFromDB($id); + return new DataResponse('DELETED'); + } + else { + return new DataResponse('no such track', 400); + } + } + + /** + * @NoAdminRequired + */ + public function deleteTracks($ids) { + $this->tracksService->deleteTracksFromDB($ids, $this->userId); + return new DataResponse('DELETED'); + } + +} diff --git a/lib/Service/TracksService.php b/lib/Service/TracksService.php new file mode 100644 index 000000000..27eb340c2 --- /dev/null +++ b/lib/Service/TracksService.php @@ -0,0 +1,134 @@ +l10n = $l10n; + $this->logger = $logger; + $this->qb = \OC::$server->getDatabaseConnection()->getQueryBuilder(); + } + + /** + * @param string $userId + */ + public function getFavoritesFromDB($userId) { + $tracks = []; + $qb = $this->qb; + $qb->select('id', 'file_id', 'file_path') + ->from('maps_tracks', 't') + ->where( + $qb->expr()->eq('user_id', $qb->createNamedParameter($userId, IQueryBuilder::PARAM_STR)) + ); + $req = $qb->execute(); + + while ($row = $req->fetch()) { + $id = intval($row['id']); + $file_id = intval($row['file_id']); + $file_path = $row['file_path']; + array_push($tracks, [ + 'id' => $id, + 'file_id' => $file_id, + 'file_path' => $file_path + ]); + } + $req->closeCursor(); + $qb = $qb->resetQueryParts(); + return $tracks; + } + + public function geTrackFromDB($id, $userId=null) { + $track = null; + $qb = $this->qb; + $qb->select('id', 'file_id', 'file_path') + ->from('maps_tracks', 't') + ->where( + $qb->expr()->eq('id', $qb->createNamedParameter($id, IQueryBuilder::PARAM_INT)) + ); + if ($userId !== null) { + $qb->andWhere( + $qb->expr()->eq('user_id', $qb->createNamedParameter($userId, IQueryBuilder::PARAM_STR)) + ); + } + $req = $qb->execute(); + + while ($row = $req->fetch()) { + $id = intval($row['id']); + $file_id = intval($row['file_id']); + $file_path = $row['file_path']; + $track = [ + 'id' => $id, + 'file_id' => $file_id, + 'file_path' => $file_path + ]; + break; + } + $req->closeCursor(); + $qb = $qb->resetQueryParts(); + return $track; + } + + public function addTrackToDB($userId, $path, $fileId) { + $qb = $this->qb; + $qb->insert('maps_tracks') + ->values([ + 'user_id' => $qb->createNamedParameter($userId, IQueryBuilder::PARAM_STR), + 'file_path' => $qb->createNamedParameter($path, IQueryBuilder::PARAM_STR), + 'file_id' => $qb->createNamedParameter($fileId, IQueryBuilder::PARAM_INT) + ]); + $req = $qb->execute(); + $trackId = $qb->getLastInsertId(); + $qb = $qb->resetQueryParts(); + return $trackId; + } + + public function deleteTrackFromDB($id) { + $qb = $this->qb; + $qb->delete('maps_tracks') + ->where( + $qb->expr()->eq('id', $qb->createNamedParameter($id, IQueryBuilder::PARAM_INT)) + ); + $req = $qb->execute(); + $qb = $qb->resetQueryParts(); + } + + public function deleteTracksFromDB($ids, $userId) { + $qb = $this->qb; + $qb->delete('maps_tracks') + ->where( + $qb->expr()->eq('user_id', $qb->createNamedParameter($userId, IQueryBuilder::PARAM_STR)) + ); + if (count($ids) > 0) { + $or = $qb->expr()->orx(); + foreach ($ids as $id) { + $or->add($qb->expr()->eq('id', $qb->createNamedParameter($id, IQueryBuilder::PARAM_INT))); + } + $qb->andWhere($or); + } + else { + return; + } + $req = $qb->execute(); + $qb = $qb->resetQueryParts(); + } + +} From cf259c58294dd497faf6e283729423c6f39169e3 Mon Sep 17 00:00:00 2001 From: Julien Veyssier Date: Sun, 7 Apr 2019 05:19:47 +0200 Subject: [PATCH 04/13] partial frontend part for tracks --- appinfo/application.php | 1 + appinfo/database.xml | 6 + appinfo/info.xml | 2 +- appinfo/routes.php | 8 ++ css/style.css | 2 + js/script.js | 4 +- js/tracksController.js | 184 ++++++++++++++++++++++++++-- lib/Controller/TracksController.php | 4 +- lib/Service/TracksService.php | 28 ++--- templates/navigation/index.php | 2 +- 10 files changed, 211 insertions(+), 30 deletions(-) diff --git a/appinfo/application.php b/appinfo/application.php index 65fd172b3..e30ca99f3 100644 --- a/appinfo/application.php +++ b/appinfo/application.php @@ -23,6 +23,7 @@ use OCA\Maps\Hook\FileHooks; use OCA\Maps\Service\PhotofilesService; use OCA\Maps\Service\FavoritesService; +use OCA\Maps\Service\TracksService; class Application extends App { diff --git a/appinfo/database.xml b/appinfo/database.xml index 08d128e9d..c0022f6a8 100644 --- a/appinfo/database.xml +++ b/appinfo/database.xml @@ -170,6 +170,12 @@ true 500 + + color + text + false + 7 + diff --git a/appinfo/info.xml b/appinfo/info.xml index f790f387f..fcfb45140 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -7,7 +7,7 @@ agpl Vinzenz Rosenkranz - 0.0.4 + 0.0.5 Maps multimedia https://github.com/nextcloud/maps/issues diff --git a/appinfo/routes.php b/appinfo/routes.php index bb10edfff..bd60af4d0 100644 --- a/appinfo/routes.php +++ b/appinfo/routes.php @@ -49,5 +49,13 @@ ['name' => 'favorites#exportAllFavorites', 'url' => '/export/favorites', 'verb' => 'GET'], ['name' => 'favorites#exportFavorites', 'url' => '/export/favorites', 'verb' => 'POST'], ['name' => 'favorites#importFavorites', 'url' => '/import/favorites', 'verb' => 'POST'], + + // tracks + ['name' => 'tracks#getTracks', 'url' => '/tracks', 'verb' => 'GET'], + ['name' => 'tracks#getTrackFileContent', 'url' => '/tracks/{id}', 'verb' => 'GET'], + ['name' => 'tracks#addTrack', 'url' => '/tracks', 'verb' => 'POST'], + ['name' => 'tracks#editTrack', 'url' => '/tracks/{id}', 'verb' => 'PUT'], + ['name' => 'tracks#deleteTrack', 'url' => '/tracks/{id}', 'verb' => 'DELETE'], + ['name' => 'tracks#deleteTracks', 'url' => '/tracks', 'verb' => 'DELETE'], ] ]; diff --git a/css/style.css b/css/style.css index b7b4aaa96..a92e1d776 100644 --- a/css/style.css +++ b/css/style.css @@ -333,6 +333,7 @@ table.editFavorite input[type=text] { #toggleContactsButton button, #toggleFavoritesButton button, #toggleTracksButton button, +.toggleTrackButton button, .toggleCategoryButton button { opacity: 1 !important; } @@ -341,6 +342,7 @@ table.editFavorite input[type=text] { #navigation-photos > a, #navigation-contacts > a, #navigation-tracks > a, +.track-line > a, .category-line > a { opacity: 1 !important; } diff --git a/js/script.js b/js/script.js index 1d1dbaced..766032bf2 100644 --- a/js/script.js +++ b/js/script.js @@ -8,6 +8,7 @@ mapController.map.photosController = photosController; contactsController.initLayer(mapController.map); tracksController.initController(mapController.map); + tracksController.getTracks(); // once controllers have been set/initialized, we can restore option values from server optionsController.restoreOptions(); @@ -67,6 +68,7 @@ var optionsController = { optionValues: {}, enabledFavoriteCategories: [], + enabledTracks: [], saveOptionValues: function (optionValues) { var req = { options: optionValues @@ -142,7 +144,7 @@ && optionsValues.enabledTracks !== '') { that.enabledTracks = optionsValues.enabledTracks.split('|'); - if (tracksController.tracksLoaded) { + if (tracksController.trackListLoaded) { tracksController.restoreTracksState(that.enabledTracks); } } diff --git a/js/tracksController.js b/js/tracksController.js index 53fab542b..fc6c5d2ed 100644 --- a/js/tracksController.js +++ b/js/tracksController.js @@ -3,15 +3,16 @@ function TracksController(optionsController, timeFilterController) { this.timeFilterController = timeFilterController; this.mainLayer = null; - // indexed by track file id + // indexed by track id this.trackLayers = {}; + this.track = {}; this.firstDate = null; this.lastDate = null; // used by optionsController to know if tracks loading // was done before or after option restoration - this.tracksLoaded = false; + this.trackListLoaded = false; } TracksController.prototype = { @@ -37,20 +38,22 @@ TracksController.prototype = { }); // toggle a track $('body').on('click', '.toggleTrackButton', function(e) { - var track = $(this).parent().parent().parent().attr('track'); - that.toggleTrack(track); - that.saveEnabledTracks(); - that.updateMyFirstLastDates(); + var id = $(this).parent().parent().parent().attr('track'); + that.toggleTrack(id, true); }); // show/hide all tracks $('body').on('click', '#select-all-tracks', function(e) { that.showAllTracks(); - that.saveEnabledTracks(); + var trackStringList = Object.keys(that.trackLayers).join('|'); + that.optionsController.saveOptionValues({enabledTracks: trackStringList}); + that.optionsController.enabledTracks = trackStringList; that.optionsController.saveOptionValues({tracksEnabled: that.map.hasLayer(that.mainLayer)}); }); $('body').on('click', '#select-no-tracks', function(e) { that.hideAllTracks(); - that.saveEnabledTracks(); + var trackStringList = ''; + that.optionsController.saveOptionValues({enabledTracks: trackStringList}); + that.optionsController.enabledTracks = trackStringList; that.optionsController.saveOptionValues({tracksEnabled: that.map.hasLayer(that.mainLayer)}); }); // click on + button @@ -58,7 +61,7 @@ TracksController.prototype = { OC.dialogs.filepicker( t('maps', 'Load gpx file'), function(targetPath) { - that.addTrack(targetPath); + that.addTrackDB(targetPath); }, false, 'application/gpx+xml', @@ -105,6 +108,11 @@ TracksController.prototype = { } }, + updateTimeFilterRange: function() { + this.updateMyFirstLastDates(); + this.timeFilterController.updateSliderRangeFromController(); + }, + updateMyFirstLastDates: function() { if (!this.map.hasLayer(this.mainLayer)) { this.firstDate = null; @@ -188,5 +196,163 @@ TracksController.prototype = { this.updateMyFirstLastDates(); }, + addTrackDB: function(path) { + var that = this; + $('#navigation-tracks').addClass('icon-loading-small'); + var req = { + path: path + }; + var url = OC.generateUrl('/apps/maps/tracks'); + $.ajax({ + type: 'POST', + url: url, + data: req, + async: true + }).done(function (response) { + that.addTrackMap(response, true); + that.updateTimeFilterRange(); + }).always(function (response) { + $('#navigation-tracks').removeClass('icon-loading-small'); + }).fail(function() { + OC.Notification.showTemporary(t('maps', 'Failed to add track')); + }); + }, + + addTrackMap: function(track, show=false) { + // color + var color = track.color || OCA.Theming.color; + + this.trackLayers[track.id] = L.featureGroup(); + this.trackLayers[track.id].loaded = false; + + var name = basename(track.file_path); + + // side menu entry + var imgurl = OC.generateUrl('/svg/core/actions/address?color='+color.replace('#', '')); + var li = '
  • ' + + ' '+name+'' + + '
    ' + + '
      ' + + '
    • ' + + ' ' + + '
    • ' + + '
    • ' + + ' ' + + '
    • ' + + '
    ' + + '
    ' + + '
    ' + + ' ' + + '
    ' + + '
  • '; + + var beforeThis = null; + var nameLower = name.toLowerCase(); + $('#track-list > li').each(function() { + trackName = $(this).attr('name'); + if (nameLower.localeCompare(trackName) < 0) { + beforeThis = $(this); + return false; + } + }); + if (beforeThis !== null) { + $(li).insertBefore(beforeThis); + } + else { + $('#track-list').append(li); + } + + // enable if in saved options or if it should be enabled for another reason + if (show || this.optionsController.enabledTracks.indexOf(track.id) !== -1) { + // save if state was not restored + this.toggleTrack(track.id, show); + } + }, + + getTracks: function() { + var that = this; + $('#navigation-tracks').addClass('icon-loading-small'); + var req = {}; + var url = OC.generateUrl('/apps/maps/tracks'); + $.ajax({ + type: 'GET', + url: url, + data: req, + async: true + }).done(function (response) { + var i, track; + for (i=0; i < response.length; i++) { + track = response[i]; + that.addTrackMap(track); + } + that.trackListLoaded = true; + that.updateTimeFilterRange(); + that.timeFilterController.setSliderToMaxInterval(); + }).always(function (response) { + $('#navigation-tracks').removeClass('icon-loading-small'); + }).fail(function() { + OC.Notification.showTemporary(t('maps', 'Failed to load tracks')); + }); + }, + + toggleTrack: function(id, save=false) { + var trackLayer = this.trackLayers[id]; + if (!trackLayer.loaded) { + this.loadTrack(id, save); + } + else { + this.toggleTrackLayer(id); + if (save) { + this.saveEnabledTracks(); + this.updateMyFirstLastDates(); + } + } + }, + + toggleTrackLayer: function(id) { + var trackLayer = this.trackLayers[id]; + var eyeButton = $('#track-list > li[track="'+id+'"] .toggleTrackButton button'); + // hide track + if (this.mainLayer.hasLayer(trackLayer)) { + this.mainLayer.removeLayer(trackLayer); + // color of the eye + eyeButton.addClass('icon-toggle').attr('style', ''); + } + // show track + else { + this.mainLayer.addLayer(trackLayer); + // 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+')'); + } + }, + + loadTrack: function(id, save=false) { + var that = this; + $('#track-list > li[track="'+id+'"] .toggleTrackButton button').addClass('icon-loading-small'); + var req = {}; + var url = OC.generateUrl('/apps/maps/tracks/'+id); + $.ajax({ + type: 'GET', + url: url, + data: req, + async: true + }).done(function (response) { + that.trackLayers[id].loaded = true; + that.toggleTrack(id, save); + }).always(function (response) { + $('#track-list > li[track="'+id+'"] .toggleTrackButton button').removeClass('icon-loading-small'); + }).fail(function() { + OC.Notification.showTemporary(t('maps', 'Failed to load track content')); + }); + }, } diff --git a/lib/Controller/TracksController.php b/lib/Controller/TracksController.php index 49aea7d39..f349bda6e 100644 --- a/lib/Controller/TracksController.php +++ b/lib/Controller/TracksController.php @@ -88,7 +88,7 @@ public function getTracks() { if ($this->userfolder->nodeExists($track['file_path']) and $this->userfolder->get($track['file_path'])->getType() === \OCP\Files\FileInfo::TYPE_FILE ) { - array_push($existingTracks, $track), + array_push($existingTracks, $track); } else { $this->deleteTrack($track['id']); @@ -101,7 +101,7 @@ public function getTracks() { * @NoAdminRequired */ public function getTrackFileContent($id) { - $track = $this->tracksService->getTrackFromDB($trackId); + $track = $this->tracksService->getTrackFromDB($id); if ($this->userfolder->nodeExists($track['file_path'])) { $trackFile = $this->userfolder->get($track['file_path']); if ($trackFile->getType() === \OCP\Files\FileInfo::TYPE_FILE) { diff --git a/lib/Service/TracksService.php b/lib/Service/TracksService.php index 27eb340c2..c9329050a 100644 --- a/lib/Service/TracksService.php +++ b/lib/Service/TracksService.php @@ -31,10 +31,10 @@ public function __construct (ILogger $logger, IL10N $l10n) { /** * @param string $userId */ - public function getFavoritesFromDB($userId) { + public function getTracksFromDB($userId) { $tracks = []; $qb = $this->qb; - $qb->select('id', 'file_id', 'file_path') + $qb->select('id', 'file_id', 'file_path', 'color') ->from('maps_tracks', 't') ->where( $qb->expr()->eq('user_id', $qb->createNamedParameter($userId, IQueryBuilder::PARAM_STR)) @@ -42,13 +42,11 @@ public function getFavoritesFromDB($userId) { $req = $qb->execute(); while ($row = $req->fetch()) { - $id = intval($row['id']); - $file_id = intval($row['file_id']); - $file_path = $row['file_path']; array_push($tracks, [ - 'id' => $id, - 'file_id' => $file_id, - 'file_path' => $file_path + 'id' => intval($row['id']), + 'file_id' => intval($row['file_id']), + 'file_path' => $row['file_path'], + 'color' => $row['color'] ]); } $req->closeCursor(); @@ -56,10 +54,10 @@ public function getFavoritesFromDB($userId) { return $tracks; } - public function geTrackFromDB($id, $userId=null) { + public function getTrackFromDB($id, $userId=null) { $track = null; $qb = $this->qb; - $qb->select('id', 'file_id', 'file_path') + $qb->select('id', 'file_id', 'file_path', 'color') ->from('maps_tracks', 't') ->where( $qb->expr()->eq('id', $qb->createNamedParameter($id, IQueryBuilder::PARAM_INT)) @@ -72,13 +70,11 @@ public function geTrackFromDB($id, $userId=null) { $req = $qb->execute(); while ($row = $req->fetch()) { - $id = intval($row['id']); - $file_id = intval($row['file_id']); - $file_path = $row['file_path']; $track = [ - 'id' => $id, - 'file_id' => $file_id, - 'file_path' => $file_path + 'id' => intval($row['id']), + 'file_id' => intval($row['file_id']), + 'file_path' => $row['file_path'], + 'color' => $row['color'] ]; break; } diff --git a/templates/navigation/index.php b/templates/navigation/index.php index a920de095..6a3ecf8d4 100644 --- a/templates/navigation/index.php +++ b/templates/navigation/index.php @@ -158,7 +158,7 @@
- From d1bb7c1e7386194784c953856e28b4dcdfbf724f Mon Sep 17 00:00:00 2001 From: Julien Veyssier Date: Sun, 7 Apr 2019 17:47:55 +0200 Subject: [PATCH 05/13] add/remove tracks management --- appinfo/routes.php | 2 +- js/script.js | 4 +- js/tracksController.js | 96 +++++++++++++++++++++++++---- lib/Controller/TracksController.php | 37 +++++------ templates/navigation/index.php | 6 ++ 5 files changed, 110 insertions(+), 35 deletions(-) diff --git a/appinfo/routes.php b/appinfo/routes.php index bd60af4d0..153f4353c 100644 --- a/appinfo/routes.php +++ b/appinfo/routes.php @@ -53,7 +53,7 @@ // tracks ['name' => 'tracks#getTracks', 'url' => '/tracks', 'verb' => 'GET'], ['name' => 'tracks#getTrackFileContent', 'url' => '/tracks/{id}', 'verb' => 'GET'], - ['name' => 'tracks#addTrack', 'url' => '/tracks', 'verb' => 'POST'], + ['name' => 'tracks#addTracks', 'url' => '/tracks', 'verb' => 'POST'], ['name' => 'tracks#editTrack', 'url' => '/tracks/{id}', 'verb' => 'PUT'], ['name' => 'tracks#deleteTrack', 'url' => '/tracks/{id}', 'verb' => 'DELETE'], ['name' => 'tracks#deleteTracks', 'url' => '/tracks', 'verb' => 'DELETE'], diff --git a/js/script.js b/js/script.js index 766032bf2..b50b21f87 100644 --- a/js/script.js +++ b/js/script.js @@ -143,7 +143,9 @@ && optionsValues.enabledTracks && optionsValues.enabledTracks !== '') { - that.enabledTracks = optionsValues.enabledTracks.split('|'); + that.enabledTracks = optionsValues.enabledTracks.split('|').map(function (x) { + return parseInt(x); + }); if (tracksController.trackListLoaded) { tracksController.restoreTracksState(that.enabledTracks); } diff --git a/js/tracksController.js b/js/tracksController.js index fc6c5d2ed..f4147c75b 100644 --- a/js/tracksController.js +++ b/js/tracksController.js @@ -41,6 +41,15 @@ TracksController.prototype = { var id = $(this).parent().parent().parent().attr('track'); that.toggleTrack(id, true); }); + // remove a track + $('body').on('click', '.removeTrack', function(e) { + var id = parseInt($(this).parent().parent().parent().parent().attr('track')); + that.removeTrackDB(id); + }); + // remove all tracks + $('body').on('click', '#remove-all-tracks', function(e) { + that.removeAllTracksDB(); + }); // show/hide all tracks $('body').on('click', '#select-all-tracks', function(e) { that.showAllTracks(); @@ -61,9 +70,9 @@ TracksController.prototype = { OC.dialogs.filepicker( t('maps', 'Load gpx file'), function(targetPath) { - that.addTrackDB(targetPath); + that.addTracksDB(targetPath); }, - false, + true, 'application/gpx+xml', true ); @@ -148,7 +157,7 @@ TracksController.prototype = { } }, - saveEnabledTracks: function() { + saveEnabledTracks: function(additionalIds=[]) { var trackList = []; var layer; for (var id in this.trackLayers) { @@ -157,10 +166,13 @@ TracksController.prototype = { trackList.push(id); } } + for (var i=0; i < additionalIds.length; i++) { + trackList.push(additionalIds[i]); + } var trackStringList = trackList.join('|'); this.optionsController.saveOptionValues({enabledTracks: trackStringList}); // this is used when tracks are loaded again - this.optionsController.enabledTracks = trackStringList; + this.optionsController.enabledTracks = trackList; }, restoreTracksState: function(enabledTrackList) { @@ -196,11 +208,65 @@ TracksController.prototype = { this.updateMyFirstLastDates(); }, - addTrackDB: function(path) { + removeTrackDB: function(id) { + var that = this; + $('#track-list > li[track="'+id+'"]').addClass('icon-loading-small'); + var req = {}; + var url = OC.generateUrl('/apps/maps/tracks/'+id); + $.ajax({ + type: 'DELETE', + url: url, + data: req, + async: true + }).done(function (response) { + that.removeTrackMap(id); + that.saveEnabledTracks(); + }).always(function (response) { + $('#track-list > li[track="'+id+'"]').removeClass('icon-loading-small'); + }).fail(function() { + OC.Notification.showTemporary(t('maps', 'Failed to remove track')); + }); + }, + + removeAllTracksDB: function() { var that = this; $('#navigation-tracks').addClass('icon-loading-small'); var req = { - path: path + ids: Object.keys(this.trackLayers) + }; + var url = OC.generateUrl('/apps/maps/tracks'); + $.ajax({ + type: 'DELETE', + url: url, + data: req, + async: true + }).done(function (response) { + for (var id in that.trackLayers) { + that.removeTrackMap(id); + } + that.saveEnabledTracks(); + }).always(function (response) { + $('#navigation-tracks').removeClass('icon-loading-small'); + }).fail(function() { + OC.Notification.showTemporary(t('maps', 'Failed to remove track')); + }); + }, + + removeTrackMap: function(id) { + this.mainLayer.removeLayer(this.trackLayers[id]); + delete this.trackLayers[id]; + delete this.track[id]; + + $('#track-list > li[track="'+id+'"]').fadeOut('slow', function() { + $(this).remove(); + }); + }, + + addTracksDB: function(pathList) { + var that = this; + $('#navigation-tracks').addClass('icon-loading-small'); + var req = { + pathList: pathList }; var url = OC.generateUrl('/apps/maps/tracks'); $.ajax({ @@ -209,12 +275,17 @@ TracksController.prototype = { data: req, async: true }).done(function (response) { - that.addTrackMap(response, true); - that.updateTimeFilterRange(); + var ids = []; + for (var i=0; i < response.length; i++) { + that.addTrackMap(response[i], true); + ids.push(response[i].id); + } + that.saveEnabledTracks(ids); + that.optionsController.saveOptionValues({tracksEnabled: true}); }).always(function (response) { $('#navigation-tracks').removeClass('icon-loading-small'); }).fail(function() { - OC.Notification.showTemporary(t('maps', 'Failed to add track')); + OC.Notification.showTemporary(t('maps', 'Failed to add tracks')); }); }, @@ -271,8 +342,7 @@ TracksController.prototype = { // enable if in saved options or if it should be enabled for another reason if (show || this.optionsController.enabledTracks.indexOf(track.id) !== -1) { - // save if state was not restored - this.toggleTrack(track.id, show); + this.toggleTrack(track.id); } }, @@ -337,7 +407,7 @@ TracksController.prototype = { loadTrack: function(id, save=false) { var that = this; - $('#track-list > li[track="'+id+'"] .toggleTrackButton button').addClass('icon-loading-small'); + $('#track-list > li[track="'+id+'"]').addClass('icon-loading-small'); var req = {}; var url = OC.generateUrl('/apps/maps/tracks/'+id); $.ajax({ @@ -349,7 +419,7 @@ TracksController.prototype = { that.trackLayers[id].loaded = true; that.toggleTrack(id, save); }).always(function (response) { - $('#track-list > li[track="'+id+'"] .toggleTrackButton button').removeClass('icon-loading-small'); + $('#track-list > li[track="'+id+'"]').removeClass('icon-loading-small'); }).fail(function() { OC.Notification.showTemporary(t('maps', 'Failed to load track content')); }); diff --git a/lib/Controller/TracksController.php b/lib/Controller/TracksController.php index f349bda6e..c55638a87 100644 --- a/lib/Controller/TracksController.php +++ b/lib/Controller/TracksController.php @@ -120,28 +120,23 @@ public function getTrackFileContent($id) { /** * @NoAdminRequired */ - public function addTrack($path) { - if ($path && strlen($path) > 0) { - $cleanpath = str_replace(array('../', '..\\'), '', $path); - if ($this->userfolder->nodeExists($cleanpath)) { - $trackFile = $this->userfolder->get($cleanpath); - if ($trackFile->getType() === \OCP\Files\FileInfo::TYPE_FILE) { - $trackFileId = $trackFile->getId(); - $trackId = $this->tracksService->addTrackToDB($this->userId, $cleanpath, $trackFileId); - $track = $this->tracksService->getTrackFromDB($trackId); - return new DataResponse($track); + public function addTracks($pathList) { + $tracks = []; + foreach ($pathList as $path) { + if ($path && strlen($path) > 0) { + $cleanpath = str_replace(array('../', '..\\'), '', $path); + if ($this->userfolder->nodeExists($cleanpath)) { + $trackFile = $this->userfolder->get($cleanpath); + if ($trackFile->getType() === \OCP\Files\FileInfo::TYPE_FILE) { + $trackFileId = $trackFile->getId(); + $trackId = $this->tracksService->addTrackToDB($this->userId, $cleanpath, $trackFileId); + $track = $this->tracksService->getTrackFromDB($trackId); + array_push($tracks, $track); + } } - else { - return new DataResponse('bad file type', 400); - } - } - else { - return new DataResponse('file not found', 400); } } - else { - return new DataResponse('invalid value', 400); - } + return new DataResponse($tracks); } /** @@ -162,7 +157,9 @@ public function deleteTrack($id) { * @NoAdminRequired */ public function deleteTracks($ids) { - $this->tracksService->deleteTracksFromDB($ids, $this->userId); + if (is_array($ids) && count($ids) > 0) { + $this->tracksService->deleteTracksFromDB($ids, $this->userId); + } return new DataResponse('DELETED'); } diff --git a/templates/navigation/index.php b/templates/navigation/index.php index 6a3ecf8d4..432b3fc18 100644 --- a/templates/navigation/index.php +++ b/templates/navigation/index.php @@ -156,6 +156,12 @@ t('Hide all tracks')); ?> +
  • + + + t('Remove all tracks')); ?> + +
  • + + + From f4906e1628ee29bcfe117497162d73630c07cb92 Mon Sep 17 00:00:00 2001 From: Julien Veyssier Date: Tue, 9 Apr 2019 01:38:01 +0200 Subject: [PATCH 13/13] adapt tooltip text color to background darkness --- js/tracksController.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/js/tracksController.js b/js/tracksController.js index 141397f52..1d1cebf30 100644 --- a/js/tracksController.js +++ b/js/tracksController.js @@ -782,10 +782,14 @@ TracksController.prototype = { $('style[track='+id+']').remove(); var rgbc = hexToRgb(color); + var textcolor = 'black'; + if (rgbc.r + rgbc.g + rgbc.b < 3 * 80) { + textcolor = 'white'; + } $('