From 106b748551f88c67e32eaa1d4dd63c8a52693e08 Mon Sep 17 00:00:00 2001 From: Maurice Date: Mon, 12 Oct 2026 12:59:45 +0200 Subject: [PATCH] fix(maps): marker thumbs from cached place photos load under the same request limit A place whose image_url already is the place-photo proxy URL skipped the five slot limiter and loaded its marker picture right away. A map showing a big list, such as "All saved" in collections, asked for every picture at once. Those downloads now wait for a slot like the provider lookups do; the entry itself is still there immediately. Refs #2559 --- client/src/services/photoService.ts | 13 ++++--- .../tests/unit/services/photoService.test.ts | 36 ++++++++++++++++++- 2 files changed, 44 insertions(+), 5 deletions(-) diff --git a/client/src/services/photoService.ts b/client/src/services/photoService.ts index 860db5138..925329682 100644 --- a/client/src/services/photoService.ts +++ b/client/src/services/photoService.ts @@ -114,10 +114,15 @@ export function fetchPhoto( cache.set(cacheKey, entry) callback?.(entry) notify(cacheKey, entry) - // Generate base64 thumb in background - void urlToBase64(photoId).then(thumb => { - if (thumb) { entry.thumbDataUrl = thumb; notifyThumb(cacheKey, thumb) } - }) + // Generate base64 thumb in background, under the same limit as the API path: + // a list of a thousand saved places would otherwise ask for a thousand + // pictures at once (#2559). + void acquireRequestSlot() + .then(() => urlToBase64(photoId)) + .then(thumb => { + if (thumb) { entry.thumbDataUrl = thumb; notifyThumb(cacheKey, thumb) } + }) + .finally(releaseRequestSlot) return } diff --git a/client/tests/unit/services/photoService.test.ts b/client/tests/unit/services/photoService.test.ts index 12a43bebd..dff707cb5 100644 --- a/client/tests/unit/services/photoService.test.ts +++ b/client/tests/unit/services/photoService.test.ts @@ -385,7 +385,7 @@ describe('fetchPhoto — stable proxy URL', () => { svc.onThumbReady('k', thumbFn); svc.fetchPhoto('k', '/api/maps/place-photo/abc123', undefined, undefined, undefined, cb); - // Synchronous — no request slot, no API call. + // The entry is there synchronously, without an API call. expect(mockPlacePhoto).not.toHaveBeenCalled(); expect(cb).toHaveBeenCalledWith({ photoUrl: '/api/maps/place-photo/abc123', thumbDataUrl: null }); @@ -454,6 +454,40 @@ describe('fetchPhoto — concurrency limiter', () => { expect(mockPlacePhoto).toHaveBeenCalledTimes(6); }); + + it('FE-COMP-PHOTO-026: proxy URL thumbs share the same limit, so a big list does not load every picture at once (#2559)', async () => { + // Images that only load when the test says so. + const pending: Array<() => void> = []; + Object.defineProperty(HTMLImageElement.prototype, 'src', { + configurable: true, + set(url: string) { + (this as HTMLImageElement & { _src: string })._src = url; + pending.push(() => { if (typeof this.onload === 'function') this.onload(new Event('load')); }); + }, + get() { + return (this as HTMLImageElement & { _src: string })._src ?? ''; + }, + }); + + for (let i = 0; i < 12; i += 1) { + svc.fetchPhoto(`p${i}`, `/api/maps/place-photo/p${i}`); + } + await flush(); + + // The entries are there at once, only the picture downloads wait their turn. + expect(svc.getCached('p11')?.photoUrl).toBe('/api/maps/place-photo/p11'); + expect(pending).toHaveLength(5); + + pending.splice(0, 2).forEach((load) => load()); + await flush(); + expect(pending).toHaveLength(5); + + while (pending.length > 0) { + pending.splice(0).forEach((load) => load()); + await flush(); + } + expect(Object.keys(svc.getAllThumbs())).toHaveLength(12); + }); }); // ==============================================================================