diff --git a/lib/Controller/DaysController.php b/lib/Controller/DaysController.php index 4ae1f161..4b3108a7 100644 --- a/lib/Controller/DaysController.php +++ b/lib/Controller/DaysController.php @@ -184,7 +184,7 @@ class DaysController extends GenericApiController { // Do not preload anything for native clients. // Since the contents of preloads are trusted, clients will not load locals. - if (Util::callerIsNative()) { + if (Util::callerIsNative() || $this->noPreload()) { return; } @@ -283,6 +283,11 @@ class DaysController extends GenericApiController return null !== $this->request->getParam('hidden'); } + private function noPreload() + { + return null !== $this->request->getParam('nopreload'); + } + private function isMonthView() { return null !== $this->request->getParam('monthView'); diff --git a/lib/Db/TimelineQueryDays.php b/lib/Db/TimelineQueryDays.php index 40e4d103..b0353c8d 100644 --- a/lib/Db/TimelineQueryDays.php +++ b/lib/Db/TimelineQueryDays.php @@ -227,6 +227,12 @@ trait TimelineQueryDays } unset($row['categoryid']); + // Get hidden field if present + if (\array_key_exists('hidden', $row) && $row['hidden']) { + $row['ishidden'] = 1; + } + unset($row['hidden']); + // All cluster transformations ClustersBackend\Manager::applyDayPostTransforms($this->request, $row); @@ -239,11 +245,6 @@ trait TimelineQueryDays // compute AUID and discard epoch and size $row['auid'] = Exif::getAUID($epoch, $size); } - - // Get hidden field if present - if (\array_key_exists('hidden', $row)) { - $row['hidden'] = (bool) $row['hidden']; - } } /** diff --git a/src/components/Timeline.vue b/src/components/Timeline.vue index eef9d593..b1fb3d9e 100644 --- a/src/components/Timeline.vue +++ b/src/components/Timeline.vue @@ -844,8 +844,16 @@ export default defineComponent({ }, /** API url for Day call */ - getDayUrl(dayId: number | string) { - return API.Q(API.DAY(dayId), this.getQuery()); + getDayUrl(dayIds: number[]) { + const query = this.getQuery(); + + // If any day in the fetch list has local images we need to fetch + // the remote hidden images for the merging to happen correctly + if (this.routeHasNative && dayIds.some((id) => this.heads[id]?.day?.haslocal)) { + query[DaysFilterType.HIDDEN] = '1'; + } + + return API.Q(API.DAY(dayIds.join(',')), query); }, /** Fetch image data for one dayId */ @@ -858,7 +866,7 @@ export default defineComponent({ this.sizedDays.add(dayId); // Look for cache - const cacheUrl = this.getDayUrl(dayId); + const cacheUrl = this.getDayUrl([dayId]); try { const cache = await utils.getCachedData(cacheUrl); if (cache) { @@ -868,6 +876,7 @@ export default defineComponent({ } // Process the cache + utils.removeHiddenPhotos(cache); this.processDay(dayId, cache); } } catch { @@ -900,8 +909,7 @@ export default defineComponent({ for (const dayId of dayIds) dayMap.set(dayId, []); // Construct URL - const dayStr = dayIds.join(','); - const url = this.getDayUrl(dayStr); + const url = this.getDayUrl(dayIds); this.fetchDayQueue = []; try { @@ -911,7 +919,7 @@ export default defineComponent({ const data = res.data; // Check if the state has changed - if (this.state !== startState || this.getDayUrl(dayStr) !== url) { + if (this.state !== startState || this.getDayUrl(dayIds) !== url) { return; } @@ -929,7 +937,7 @@ export default defineComponent({ // creates circular references which cannot be stringified for (const [dayId, photos] of dayMap) { if (photos.length === 0) continue; - utils.cacheData(this.getDayUrl(dayId), photos); + utils.cacheData(this.getDayUrl([dayId]), photos); } // Get local images if we are running in native environment. @@ -946,6 +954,9 @@ export default defineComponent({ // Process each day as needed for (const [dayId, photos] of dayMap) { + // Remove hidden photos + utils.removeHiddenPhotos(photos); + // Check if the response has any delta const head = this.heads[dayId]; if (head?.day?.detail?.length === photos.length) { diff --git a/src/services/API.ts b/src/services/API.ts index 2d42d633..1c310b2d 100644 --- a/src/services/API.ts +++ b/src/services/API.ts @@ -34,6 +34,8 @@ export enum DaysFilterType { RECURSIVE = 'recursive', MONTH_VIEW = 'monthView', REVERSE = 'reverse', + HIDDEN = 'hidden', + NO_PRELOAD = 'nopreload', } export class API { diff --git a/src/services/utils/helpers.ts b/src/services/utils/helpers.ts index 2d70e4e0..8c1fa92f 100644 --- a/src/services/utils/helpers.ts +++ b/src/services/utils/helpers.ts @@ -135,6 +135,18 @@ export function updatePhotoFromImageInfo(photo: IPhoto, imageInfo: IImageInfo) { }; } +/** + * Remove hidden photos from the list in place + * @param photos List of photos + */ +export function removeHiddenPhotos(photos: IPhoto[]) { + for (let i = photos.length - 1; i >= 0; i--) { + if (photos[i].ishidden) { + photos.splice(i, 1); + } + } +} + /** * Get the path of the folder on folders route * This function does not check if this is the folder route diff --git a/src/types.ts b/src/types.ts index 8b99c74f..1dbce981 100644 --- a/src/types.ts +++ b/src/types.ts @@ -80,6 +80,11 @@ export type IPhoto = { isfavorite?: boolean; /** Local file from native */ islocal?: boolean; + /** + * Photo is hidden from timeline; discard immediately. + * This field exists so that we can merge with locals. + */ + ishidden?: boolean; /** AUID of file (optional, NativeX) */ auid?: number;