From c25a6595ad754c322b9931d78e44f89c16f44be5 Mon Sep 17 00:00:00 2001 From: Varun Patil Date: Sun, 23 Jun 2024 17:55:55 -0700 Subject: [PATCH] feat: filter to view all files without location data in places (fix #1124) Signed-off-by: Varun Patil --- CHANGELOG.md | 1 + lib/ClustersBackend/PlacesBackend.php | 14 +++- src/components/Timeline.vue | 10 ++- .../top-matter/ClusterTopMatter.vue | 4 +- .../top-matter/PlacesDynamicTopMatter.vue | 9 ++- src/components/top-matter/PlacesTopMatter.vue | 79 +++++++++++++++++++ src/components/top-matter/TopMatter.vue | 4 +- src/router.ts | 5 ++ src/services/utils/const.ts | 1 + 9 files changed, 114 insertions(+), 13 deletions(-) create mode 100644 src/components/top-matter/PlacesTopMatter.vue diff --git a/CHANGELOG.md b/CHANGELOG.md index 15c5d691c..61fbf6146 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ All notable changes to this project will be documented in this file. ## [Unreleased] +- **Feature**: Add filter to view all files without location data in places ([#1124](https://github.com/pulsejet/memories/issues/1124)) - **Fix**: Broken indexing of large video files. If you have improperly indexed large files, run `occ memories:index -f` ([#1195](https://github.com/pulsejet/memories/issues/1195)) ## [v7.3.1] - 2024-05-03 diff --git a/lib/ClustersBackend/PlacesBackend.php b/lib/ClustersBackend/PlacesBackend.php index 213ff802c..1483b8cac 100644 --- a/lib/ClustersBackend/PlacesBackend.php +++ b/lib/ClustersBackend/PlacesBackend.php @@ -54,11 +54,21 @@ public function isEnabled(): bool public function transformDayQuery(IQueryBuilder &$query, bool $aggregate): void { - $locationId = (int) $this->request->getParam('places'); + $locId = $this->request->getParam('places'); + + // Files that have no GPS coordinates set + if ('NULL' === $locId) { + $query->andWhere($query->expr()->orX( + $query->expr()->isNull('m.lat'), + $query->expr()->isNull('m.lon'), + )); + + return; + } $query->innerJoin('m', 'memories_places', 'mp', $query->expr()->andX( $query->expr()->eq('mp.fileid', 'm.fileid'), - $query->expr()->eq('mp.osm_id', $query->createNamedParameter($locationId)), + $query->expr()->eq('mp.osm_id', $query->createNamedParameter((int) $locId)), )); } diff --git a/src/components/Timeline.vue b/src/components/Timeline.vue index 38b9ef884..0fcf9134e 100644 --- a/src/components/Timeline.vue +++ b/src/components/Timeline.vue @@ -661,12 +661,14 @@ export default defineComponent({ // Places if (this.routeIsPlaces) { - if (!name || !name.includes('-')) { + if (name?.includes('-')) { + const id = name.split('-', 1)[0]; + set(DaysFilterType.PLACE, id); + } else if (name === this.c.PLACES_NULL) { + set(DaysFilterType.PLACE, this.c.PLACES_NULL); + } else { throw new Error('Invalid place route'); } - - const id = name.split('-', 1)[0]; - set(DaysFilterType.PLACE, id); } // Tags diff --git a/src/components/top-matter/ClusterTopMatter.vue b/src/components/top-matter/ClusterTopMatter.vue index 164b60c54..0a53e6707 100644 --- a/src/components/top-matter/ClusterTopMatter.vue +++ b/src/components/top-matter/ClusterTopMatter.vue @@ -21,7 +21,7 @@ import * as strings from '@services/strings'; import BackIcon from 'vue-material-design-icons/ArrowLeft.vue'; export default defineComponent({ - name: 'TagTopMatter', + name: 'ClusterTopMatter', components: { NcActions, NcActionButton, @@ -37,8 +37,6 @@ export default defineComponent({ switch (this.$route.name) { case _m.routes.Tags.name: return this.t('recognize', this.$route.params.name); - case _m.routes.Places.name: - return this.$route.params.name?.split('-').slice(1).join('-'); default: return null; } diff --git a/src/components/top-matter/PlacesDynamicTopMatter.vue b/src/components/top-matter/PlacesDynamicTopMatter.vue index cc53cb0d7..b826f4b3c 100644 --- a/src/components/top-matter/PlacesDynamicTopMatter.vue +++ b/src/components/top-matter/PlacesDynamicTopMatter.vue @@ -29,14 +29,17 @@ export default defineComponent({ methods: { async refresh(): Promise { - // Clear folders + // Clear subplaces this.places = []; + // Skip if unidentified location view + if (this.routeIsPlacesUnassigned) return false; + // Get ID of place from URL const placeId = Number(this.$route.params.name?.split('-')[0]) || -1; const url = API.Q(API.PLACE_LIST(), { inside: placeId }); - // Make API call to get subfolders + // Make API call to get subplaces try { this.places = (await axios.get(url)).data; } catch (e) { @@ -49,7 +52,7 @@ export default defineComponent({ route(place: ICluster) { return { - name: 'places', + name: _m.routes.Places.name, params: { name: place.cluster_id + '-' + place.name, }, diff --git a/src/components/top-matter/PlacesTopMatter.vue b/src/components/top-matter/PlacesTopMatter.vue new file mode 100644 index 000000000..5b168bf69 --- /dev/null +++ b/src/components/top-matter/PlacesTopMatter.vue @@ -0,0 +1,79 @@ + + + diff --git a/src/components/top-matter/TopMatter.vue b/src/components/top-matter/TopMatter.vue index 48087f4dc..e3ecde649 100644 --- a/src/components/top-matter/TopMatter.vue +++ b/src/components/top-matter/TopMatter.vue @@ -17,6 +17,7 @@ import FolderTopMatter from './FolderTopMatter.vue'; import ClusterTopMatter from './ClusterTopMatter.vue'; import FaceTopMatter from './FaceTopMatter.vue'; import AlbumTopMatter from './AlbumTopMatter.vue'; +import PlacesTopMatter from './PlacesTopMatter.vue'; import * as utils from '@services/utils'; @@ -50,8 +51,9 @@ export default defineComponent({ return this.initstate.shareType === 'folder' ? FolderTopMatter : null; case _m.routes.Albums.name: return AlbumTopMatter; - case _m.routes.Tags.name: case _m.routes.Places.name: + return PlacesTopMatter; + case _m.routes.Tags.name: return ClusterTopMatter; case _m.routes.Recognize.name: case _m.routes.FaceRecognition.name: diff --git a/src/router.ts b/src/router.ts index 4625cd4bb..02e90a00c 100644 --- a/src/router.ts +++ b/src/router.ts @@ -165,6 +165,7 @@ export type GlobalRouteCheckers = { routeIsPublic: boolean; routeIsPeople: boolean; routeIsRecognizeUnassigned: boolean; + routeIsPlacesUnassigned: boolean; routeIsCluster: boolean; }; @@ -191,6 +192,10 @@ defineRouteChecker( 'routeIsRecognizeUnassigned', (route) => route?.name === routes.Recognize.name && route!.params.name === c.FACE_NULL, ); +defineRouteChecker( + 'routeIsPlacesUnassigned', + (route) => route?.name === routes.Places.name && route!.params.name === c.PLACES_NULL, +); defineRouteChecker('routeIsCluster', (route) => [ routes.Albums.name, diff --git a/src/services/utils/const.ts b/src/services/utils/const.ts index 0b33aeb67..887a09e11 100644 --- a/src/services/utils/const.ts +++ b/src/services/utils/const.ts @@ -12,6 +12,7 @@ export const constants = Object.freeze({ FLAG_IS_LOCAL: 1 << 6, FACE_NULL: 'NULL', + PLACES_NULL: 'NULL', MIME_RAW: 'image/x-dcraw', FORBIDDEN_EDIT_MIMES: ['image/bmp', 'image/x-dcraw', 'video/MP2T'], // Exif.php