diff --git a/appinfo/routes.php b/appinfo/routes.php index ea754ed7..89b1f481 100644 --- a/appinfo/routes.php +++ b/appinfo/routes.php @@ -67,7 +67,6 @@ return [ ['name' => 'Places#preview', 'url' => '/api/places/preview/{id}', 'verb' => 'GET'], ['name' => 'Map#clusters', 'url' => '/api/map/clusters', 'verb' => 'GET'], - ['name' => 'Map#clusterPreview', 'url' => '/api/map/clusters/preview/{id}', 'verb' => 'GET'], ['name' => 'Archive#archive', 'url' => '/api/archive/{id}', 'verb' => 'PATCH'], diff --git a/lib/Controller/MapController.php b/lib/Controller/MapController.php index 4853430f..8f5cafbc 100644 --- a/lib/Controller/MapController.php +++ b/lib/Controller/MapController.php @@ -57,42 +57,24 @@ class MapController extends ApiBase try { $clusters = $this->timelineQuery->getMapClusters($gridLen, $bounds, $root); + // Get previews for each cluster + $clusterIds = array_map(function ($cluster) { + return (int) $cluster['id']; + }, $clusters); + $previews = $this->timelineQuery->getMapClusterPreviews($clusterIds, $root); + + // Merge the responses + $fileMap = []; + foreach ($previews as &$preview) { + $fileMap[$preview['mapcluster']] = $preview; + } + foreach ($clusters as &$cluster) { + $cluster['preview'] = $fileMap[$cluster['id']] ?? null; + } + return new JSONResponse($clusters); } catch (\Exception $e) { return new JSONResponse(['message' => $e->getMessage()], Http::STATUS_INTERNAL_SERVER_ERROR); } } - - /** - * @NoAdminRequired - * - * @NoCSRFRequired - * - * Get preview for a cluster - */ - public function clusterPreview(int $id) - { - $user = $this->userSession->getUser(); - if (null === $user) { - return new JSONResponse([], Http::STATUS_PRECONDITION_FAILED); - } - - // If this isn't the timeline folder then things aren't going to work - $root = $this->getRequestRoot(); - if ($root->isEmpty()) { - return new JSONResponse([], Http::STATUS_NOT_FOUND); - } - - // Run actual query - $list = $this->timelineQuery->getMapClusterPreviews($id, $root); - if (null === $list || 0 === \count($list)) { - return new JSONResponse([], Http::STATUS_NOT_FOUND); - } - shuffle($list); - - // Get preview from image list - return $this->getPreviewFromImageList(array_map(static function (&$item) { - return (int) $item['fileid']; - }, $list), 256); - } } diff --git a/lib/Db/TimelineQueryMap.php b/lib/Db/TimelineQueryMap.php index eb0d129b..1981941b 100644 --- a/lib/Db/TimelineQueryMap.php +++ b/lib/Db/TimelineQueryMap.php @@ -92,30 +92,41 @@ trait TimelineQueryMap return $clusters; } - public function getMapClusterPreviews(int $clusterId, TimelineRoot &$root) + public function getMapClusterPreviews(array $clusterIds, TimelineRoot &$root) { $query = $this->connection->getQueryBuilder(); // SELECT all photos with this tag - $query->select('f.fileid', 'f.etag')->from('memories', 'm')->where( - $query->expr()->eq('m.mapcluster', $query->createNamedParameter($clusterId, IQueryBuilder::PARAM_INT)) + $fileid = $query->createFunction('MAX(m.fileid) AS fileid'); + $query->select($fileid)->from('memories', 'm')->where( + $query->expr()->in('m.mapcluster', $query->createNamedParameter($clusterIds, IQueryBuilder::PARAM_INT_ARRAY)) ); // WHERE these photos are in the user's requested folder recursively $query = $this->joinFilecache($query, $root, true, false); - // MAX 8 - $query->setMaxResults(8); + // GROUP BY the cluster + $query->groupBy('m.mapcluster'); - // FETCH tag previews + // Get the fileIds $cursor = $this->executeQueryWithCTEs($query); - $ans = $cursor->fetchAll(); + $fileIds = $cursor->fetchAll(\PDO::FETCH_COLUMN); + + // SELECT these files from the filecache + $query = $this->connection->getQueryBuilder(); + $query->select('m.fileid', 'm.dayid', 'm.mapcluster', 'f.etag') + ->from('memories', 'm') + ->innerJoin('m', 'filecache', 'f', $query->expr()->eq('m.fileid', 'f.fileid')) + ->where($query->expr()->in('m.fileid', $query->createNamedParameter($fileIds, IQueryBuilder::PARAM_INT_ARRAY))); + $files = $query->executeQuery()->fetchAll(); // Post-process - foreach ($ans as &$row) { + foreach ($files as &$row) { $row['fileid'] = (int) $row['fileid']; + $row['mapcluster'] = (int) $row['mapcluster']; + $row['dayid'] = (int) $row['dayid']; } - return $ans; + return $files; } } diff --git a/src/components/SelectionManager.vue b/src/components/SelectionManager.vue index c7488160..0818e35c 100644 --- a/src/components/SelectionManager.vue +++ b/src/components/SelectionManager.vue @@ -868,11 +868,7 @@ export default defineComponent({ /** Open viewer with given photo */ openViewer(photo: IPhoto) { - this.$router.push({ - path: this.$route.path, - query: this.$route.query, - hash: utils.getViewerHash(photo), - }); + this.$router.push(utils.getViewerRoute(photo)); }, }, }); diff --git a/src/components/top-matter/MapSplitMatter.vue b/src/components/top-matter/MapSplitMatter.vue index 0848f373..9c6bd6fd 100644 --- a/src/components/top-matter/MapSplitMatter.vue +++ b/src/components/top-matter/MapSplitMatter.vue @@ -14,7 +14,7 @@ v-for="cluster in clusters" :key="cluster.id" :lat-lng="cluster.center" - @click="zoomTo(cluster.center)" + @click="zoomTo(cluster)" >
@@ -32,9 +32,12 @@