diff --git a/src/components/Timeline.vue b/src/components/Timeline.vue index 532d9488..3924dd32 100644 --- a/src/components/Timeline.vue +++ b/src/components/Timeline.vue @@ -227,6 +227,9 @@ export default defineComponent({ routeIsFolders(): boolean { return this.$route.name === 'folders'; }, + routeHasNative(): boolean { + return this.routeIsBase && nativex.has(); + }, isMonthView(): boolean { if (this.$route.query.sort === 'timeline') return false; @@ -705,6 +708,10 @@ export default defineComponent({ if (!noCache) { try { if ((cache = await utils.getCachedData(cacheUrl))) { + if (this.routeHasNative) { + await nativex.extendDaysWithLocal(cache); + } + await this.processDays(cache); this.loading--; } @@ -723,6 +730,11 @@ export default defineComponent({ // Put back into cache utils.cacheData(cacheUrl, data); + // Extend with native days + if (this.routeHasNative) { + await nativex.extendDaysWithLocal(data); + } + // Make sure we're still on the same page if (this.state !== startState) return; await this.processDays(data); @@ -862,7 +874,15 @@ export default defineComponent({ const cacheUrl = this.getDayUrl(dayId); try { const cache = await utils.getCachedData(cacheUrl); - if (cache) this.processDay(dayId, cache); + if (cache) { + // Cache only contains remote images; update from local too + if (this.routeHasNative) { + await nativex.extendDayWithLocal(dayId, cache); + } + + // Process the cache + this.processDay(dayId, cache); + } } catch { console.warn(`Failed to process day cache: ${cacheUrl}`); } @@ -887,8 +907,13 @@ export default defineComponent({ async fetchDayExpire() { if (this.fetchDayQueue.length === 0) return; + // Map of dayId to photos + const dayIds = this.fetchDayQueue; + const dayMap = new Map(); + for (const dayId of dayIds) dayMap.set(dayId, []); + // Construct URL - const dayStr = this.fetchDayQueue.join(','); + const dayStr = dayIds.join(','); const url = this.getDayUrl(dayStr); this.fetchDayQueue = []; @@ -905,20 +930,8 @@ export default defineComponent({ // Bin the data into separate days // It is already sorted in dayid DESC - const dayMap = new Map(); for (const photo of data) { - if (!dayMap.has(photo.dayid)) dayMap.set(photo.dayid, []); - dayMap.get(photo.dayid)!.push(photo); - } - - // Get local images if we are running in native environment. - // Get them all together for each day here. - if (nativex.has()) { - const promises: Promise[] = []; - for (const [dayId, photos] of dayMap) { - promises.push(nativex.extendDayWithLocal(dayId, photos)); - } - await Promise.all(promises); + dayMap.get(photo.dayid)?.push(photo); } // Store cache asynchronously @@ -928,9 +941,20 @@ export default defineComponent({ // These loops cannot be combined because processDay // creates circular references which cannot be stringified for (const [dayId, photos] of dayMap) { + if (photos.length === 0) continue; utils.cacheData(this.getDayUrl(dayId), photos); } + // Get local images if we are running in native environment. + // Get them all together for each day here. + if (this.routeHasNative) { + await Promise.all( + Array.from(dayMap.entries()).map(([dayId, photos]) => { + return nativex.extendDayWithLocal(dayId, photos); + }) + ); + } + // Process each day as needed for (const [dayId, photos] of dayMap) { // Check if the response has any delta diff --git a/src/native.ts b/src/native.ts index ea30a918..8ebdf803 100644 --- a/src/native.ts +++ b/src/native.ts @@ -1,4 +1,4 @@ -import type { IPhoto } from './types'; +import type { IDay, IPhoto } from './types'; /** * Type of a native promise (this will be the exact type in Java). @@ -10,6 +10,7 @@ type NativePromise = (call: string, arg: T) => void; */ export type NativeX = { isNative: () => boolean; + getLocalDays: NativePromise; getLocalByDayId: NativePromise; getJpeg: NativePromise; }; @@ -79,6 +80,13 @@ globalThis.nativexr = (call: string, resolve?: string, reject?: string) => { */ export const has = () => !!nativex; +/** + * Gets the local days array. + * + * @returns List of local days (JSON string) + */ +const getLocalDays = nativePromisify(nativex?.getLocalDays.bind(nativex)); + /** * Gets the local photos for a day with a dayId. * @@ -95,6 +103,32 @@ const getLocalByDayId = nativePromisify(nativex?.getLocalByDayId */ const getJpeg = nativePromisify(nativex?.getJpeg.bind(nativex), true); +/** + * Extend a list of days with local days. + * Fetches the local days from the native interface. + */ +export async function extendDaysWithLocal(days: IDay[]) { + if (!has()) return; + + // Query native part + const local: IDay[] = JSON.parse(await getLocalDays(0)); + const remoteMap = new Map(days.map((d) => [d.dayid, d])); + + // Merge local days into remote days + for (const day of local) { + const remote = remoteMap.get(day.dayid); + if (remote) { + remote.count = Math.max(remote.count, day.count); + } else { + days.push(day); + } + } + + // TODO: sort depends on view + // (but we show it for only timeline anyway for now) + days.sort((a, b) => b.dayid - a.dayid); +} + /** * Extend a list of photos with local photos. * Fetches the local photos from the native interface and filters out duplicates.