폴더/뷰 전환 시 썸네일 미갱신 버그 수정

원인: JustifiedGrid 셀 본문에서 snapshot/id를 한 번만 캡처(const)했다.
<For>는 인덱스 값이 같으면(뷰 전환 시 가시범위 [0..N] 동일) DOM 행을
재사용하므로, 재사용된 셀이 이전 폴더의 id·썸네일을 그대로 유지했다.
(레이아웃 리사이즈 버그와 동일 계열)

- id()/showImg()/isVideo()/version()을 snapshot() 기반 반응형 접근자로 변경
- 클릭/컨텍스트/드래그 핸들러도 호출 시점의 snapshot()을 읽도록
- img에 onLoad 추가 — 재사용된 img가 이전 onError로 display:none에 걸려
  새 썸네일이 안 보이던 문제 방지

검증: 3개 하위폴더 픽스처에서 폴더 클릭 시 가시 썸네일 id가
folderA(1-40)/folderB(41-80)/folderC(83-140)로 즉시 전환 확인.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
강 한
2026-07-19 15:04:05 +09:00
co-authored by Claude Fable 5
parent 31a0487419
commit afc983577f
+33 -12
View File
@@ -370,47 +370,68 @@ export const JustifiedGrid: Component = () => {
</Show>
<For each={indices()}>
{(i) => {
const snap = snapshot()!;
const id = snap.id(i);
// 위치/크기는 반응형으로 — layout() 변경(리사이즈/줌) 시 For가 DOM을
// 재사용해도 style이 갱신되도록 한다.
// 모든 파생값은 반응형으로 — For가 같은 인덱스 값에 대해 DOM을 재사용해도
// 뷰(폴더) 전환 시 스냅샷이 바뀌면 id/썸네일/선택상태가 즉시 갱신되도록 한다.
const box = () => {
const l = layout();
return l
? [l.boxes[i * 4], l.boxes[i * 4 + 1], l.boxes[i * 4 + 2], l.boxes[i * 4 + 3]]
: [0, 0, 0, 0];
};
const version = () => thumbVersions[id] ?? 0;
const showImg = () => snap.hasThumb(i) || version() > 0;
const id = () => {
const s = snapshot();
return s ? s.id(i) : -1;
};
const version = () => thumbVersions[id()] ?? 0;
const showImg = () => {
const s = snapshot();
return !!s && (s.hasThumb(i) || version() > 0);
};
const isVideo = () => {
const s = snapshot();
return !!s && s.kind(i) === 1;
};
return (
<div
class="absolute cursor-pointer overflow-hidden rounded-[3px] bg-[var(--grid-cell-bg)] outline-offset-[-2px]"
classList={{
"outline outline-2 outline-[var(--accent)]": selected().has(id),
"outline outline-2 outline-[var(--accent)]": selected().has(id()),
}}
style={{
transform: `translate(${box()[0]}px, ${box()[1]}px)`,
width: `${box()[2]}px`,
height: `${box()[3]}px`,
}}
onClick={(e) => onCellClick(e, snap, i)}
onClick={(e) => {
const s = snapshot();
if (s) onCellClick(e, s, i);
}}
onDblClick={() => openLightbox(i)}
onContextMenu={(e) => onCellContextMenu(e, snap, i)}
onContextMenu={(e) => {
const s = snapshot();
if (s) onCellContextMenu(e, s, i);
}}
draggable={true}
onDragStart={(e) => onCellDragStart(e, snap, i)}
onDragStart={(e) => {
const s = snapshot();
if (s) onCellDragStart(e, s, i);
}}
>
<Show when={showImg() && info()}>
<img
src={`${thumbUrl(info()!, id)}&v=${version()}`}
src={`${thumbUrl(info()!, id())}&v=${version()}`}
class="h-full w-full object-cover"
decoding="async"
draggable={false}
onLoad={(e) => {
(e.currentTarget as HTMLImageElement).style.display = "";
}}
onError={(e) => {
(e.currentTarget as HTMLImageElement).style.display = "none";
}}
/>
</Show>
<Show when={snap.kind(i) === 1}>
<Show when={isVideo()}>
<span class="absolute bottom-1 right-1 rounded bg-black/60 px-1 text-[10px] text-white">
</span>