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
This commit is contained in:
Maurice
2026-10-12 12:59:45 +02:00
parent 8acc035efa
commit 106b748551
2 changed files with 44 additions and 5 deletions
+9 -4
View File
@@ -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
}
@@ -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);
});
});
// ==============================================================================