fix(review): #90 photo strip — blob: URL for Primary photo, checkerboard thumbs
Bug #1: the first/Primary (calibration) photo rendered broken because its in-memory capture Blob was assigned to <img src> directly (String(Blob)= "object Blob"). resolvePhotoUrl() now mints a blob: object URL (revoked on unload) and falls back to the stored photo endpoint when there is no blob. Bug #2: strip thumbnails used a flat dark bg; now carry the gray checkerboard from the Capture lineup (MeasurePanel) so transparent inventory PNGs read the same on both screens. Also harden missingMinimums to filter kept internally (pre-existing test 7 failed on the clean base because it passed un-kept items).
This commit is contained in:
@@ -8,7 +8,7 @@
|
||||
import type { CalibratedCapture } from '../flow';
|
||||
import MultiSelect from './MultiSelect.svelte';
|
||||
import {
|
||||
keptInOrder, moveItem, missingMinimums, roleLabel,
|
||||
keptInOrder, moveItem, missingMinimums, roleLabel, resolvePhotoUrl,
|
||||
type LineupItem,
|
||||
} from './lineup';
|
||||
|
||||
@@ -171,7 +171,15 @@
|
||||
return m;
|
||||
};
|
||||
|
||||
// Object URLs minted from in-memory blobs; revoked on unmount/reload so the
|
||||
// <img> src stays valid. A raw Blob assigned to src renders broken — it must
|
||||
// be a blob: URL (bug: the first/Primary photo was the one carrying a blob).
|
||||
let photoUrls: string[] = [];
|
||||
|
||||
async function loadPhotos() {
|
||||
// Revoke any previous object URLs before rebuilding the strip.
|
||||
for (const u of photoUrls) URL.revokeObjectURL(u);
|
||||
photoUrls = [];
|
||||
try {
|
||||
const rows: PhotoRow[] = await listPhotos(slabId);
|
||||
const byRole = inMemByRole();
|
||||
@@ -180,9 +188,11 @@
|
||||
const role = r.role ?? 'batch';
|
||||
const n = seqByRole[role]++;
|
||||
const blob = (byRole.get(role)?.[n]) ?? null;
|
||||
const url =
|
||||
blob ??
|
||||
(photoUrl(slabId, r.id, (r.has_processed ? 'processed' : 'original') as 'original' | 'processed'));
|
||||
// Prefer the in-memory blob (fresh capture), as an object URL. Otherwise
|
||||
// fall back to the stored photo endpoint (a plain string URL).
|
||||
const endpoint = photoUrl(slabId, r.id, (r.has_processed ? 'processed' : 'original') as 'original' | 'processed');
|
||||
const url = resolvePhotoUrl(blob, endpoint);
|
||||
if (blob && blob.size > 0) photoUrls.push(url);
|
||||
return {
|
||||
id: r.id,
|
||||
role,
|
||||
@@ -190,7 +200,7 @@
|
||||
kept: true,
|
||||
original: blob ?? new Blob(),
|
||||
processed: blob ?? null,
|
||||
url: String(url),
|
||||
url,
|
||||
};
|
||||
});
|
||||
photosLoaded = true;
|
||||
@@ -201,6 +211,11 @@
|
||||
}
|
||||
}
|
||||
|
||||
onDestroy(() => {
|
||||
for (const u of photoUrls) URL.revokeObjectURL(u);
|
||||
photoUrls = [];
|
||||
});
|
||||
|
||||
// Whole-card tap = keep/remove toggle. A real drag (>6px) reorders and does NOT
|
||||
// toggle keep/remove — the threshold lives in the pointer handlers below.
|
||||
function toggleKeep(id: string) {
|
||||
@@ -778,7 +793,12 @@
|
||||
.ph-hint { color: #8a8a8a; font-size: .72rem; margin: 0 0 8px; }
|
||||
.ph-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; }
|
||||
@media (max-width: 420px) { .ph-grid { grid-template-columns: repeat(2, 1fr); } }
|
||||
.pcard { position: relative; aspect-ratio: 1; border: 2px solid #2a2a2a; border-radius: 3px; overflow: hidden; cursor: grab; background: #141414; }
|
||||
.pcard { position: relative; aspect-ratio: 1; border: 2px solid #2a2a2a; border-radius: 3px; overflow: hidden; cursor: grab;
|
||||
/* Gray checkerboard, matching the Capture lineup (MeasurePanel .lineup-item.png)
|
||||
so transparent inventory PNGs read the same on both screens (bug2). */
|
||||
background-color: #eee;
|
||||
background-image: linear-gradient(45deg,#ccc 25%,transparent 25%),linear-gradient(-45deg,#ccc 25%,transparent 25%),linear-gradient(45deg,transparent 75%,#ccc 75%),linear-gradient(-45deg,transparent 75%,#ccc 75%);
|
||||
background-size: 16px 16px; background-position: 0 0,0 8px,8px -8px,-8px 0; }
|
||||
.pcard:active { cursor: grabbing; }
|
||||
.pcard:focus-visible { outline: 2px solid #00e5ff; outline-offset: 2px; }
|
||||
.pcard.kept { border-color: #444; }
|
||||
|
||||
Reference in New Issue
Block a user