그리드에 썸네일 '생성 중' 스켈레톤 표시
썸네일 미준비 셀이 빈 칸으로 보이던 것을 개선. 스냅샷 flags에 비트1(생성 실패, thumb_state=2)을 추가해 대기/실패를 구분한다. - 대기(생성 전): 은은한 pulse 스켈레톤(bg-hover) - 실패: 정적 아이콘(🖼) — 무한 pulse 방지 - 준비됨: 썸네일 이미지 검증: 619개 대기 상태에서 하단 스크롤 시 pulse 스켈레톤 50개 → 생성 완료되며 이미지로 즉시 전환 확인(bg #33363d, animation pulse). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -307,7 +307,13 @@ fn pack_snapshot(rows: Vec<(i64, Option<u32>, Option<u32>, Option<u32>, i64, i64
|
|||||||
buf.extend_from_slice(&(w.min(65535) as u16).to_le_bytes());
|
buf.extend_from_slice(&(w.min(65535) as u16).to_le_bytes());
|
||||||
buf.extend_from_slice(&(h.min(65535) as u16).to_le_bytes());
|
buf.extend_from_slice(&(h.min(65535) as u16).to_le_bytes());
|
||||||
buf.push(kind as u8);
|
buf.push(kind as u8);
|
||||||
let flags = if thumb_state == 1 { 1u8 } else { 0u8 };
|
// 비트0 = 썸네일 준비됨(state 1), 비트1 = 생성 시도했으나 실패(state 2).
|
||||||
|
// 둘 다 0이면 생성 대기(state 0) — 프론트에서 스켈레톤/실패표시 구분용.
|
||||||
|
let flags = match thumb_state {
|
||||||
|
1 => 1u8,
|
||||||
|
2 => 2u8,
|
||||||
|
_ => 0u8,
|
||||||
|
};
|
||||||
buf.push(flags);
|
buf.push(flags);
|
||||||
}
|
}
|
||||||
buf
|
buf
|
||||||
|
|||||||
@@ -387,6 +387,11 @@ export const JustifiedGrid: Component = () => {
|
|||||||
const s = snapshot();
|
const s = snapshot();
|
||||||
return !!s && (s.hasThumb(i) || version() > 0);
|
return !!s && (s.hasThumb(i) || version() > 0);
|
||||||
};
|
};
|
||||||
|
// 썸네일 미준비 상태: 실패(state 2)면 정적 표시, 그 외엔 '생성 중' 스켈레톤.
|
||||||
|
const failed = () => {
|
||||||
|
const s = snapshot();
|
||||||
|
return !!s && s.thumbFailed(i);
|
||||||
|
};
|
||||||
const isVideo = () => {
|
const isVideo = () => {
|
||||||
const s = snapshot();
|
const s = snapshot();
|
||||||
return !!s && s.kind(i) === 1;
|
return !!s && s.kind(i) === 1;
|
||||||
@@ -417,7 +422,22 @@ export const JustifiedGrid: Component = () => {
|
|||||||
if (s) onCellDragStart(e, s, i);
|
if (s) onCellDragStart(e, s, i);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Show when={showImg() && info()}>
|
<Show
|
||||||
|
when={showImg() && info()}
|
||||||
|
fallback={
|
||||||
|
// 준비 전: 실패면 정적 아이콘, 대기면 은은한 스켈레톤(생성 중)
|
||||||
|
<Show
|
||||||
|
when={!failed()}
|
||||||
|
fallback={
|
||||||
|
<div class="grid h-full w-full place-items-center text-[var(--text-muted)]">
|
||||||
|
<span class="text-base opacity-60">🖼</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div class="h-full w-full animate-pulse bg-[var(--bg-hover)]" />
|
||||||
|
</Show>
|
||||||
|
}
|
||||||
|
>
|
||||||
<img
|
<img
|
||||||
src={`${thumbUrl(info()!, id())}&v=${version()}`}
|
src={`${thumbUrl(info()!, id())}&v=${version()}`}
|
||||||
class="h-full w-full object-cover"
|
class="h-full w-full object-cover"
|
||||||
|
|||||||
@@ -98,6 +98,10 @@ export class Snapshot {
|
|||||||
hasThumb(i: number): boolean {
|
hasThumb(i: number): boolean {
|
||||||
return (this.view.getUint8(i * ITEM_BYTES + 9) & 1) !== 0;
|
return (this.view.getUint8(i * ITEM_BYTES + 9) & 1) !== 0;
|
||||||
}
|
}
|
||||||
|
/** 썸네일 생성을 시도했으나 실패함(state 2) — 대기와 구분해 스켈레톤을 멈추기 위함 */
|
||||||
|
thumbFailed(i: number): boolean {
|
||||||
|
return (this.view.getUint8(i * ITEM_BYTES + 9) & 2) !== 0;
|
||||||
|
}
|
||||||
/** 레이아웃 워커 입력용 종횡비 배열 (미지 치수는 1:1) */
|
/** 레이아웃 워커 입력용 종횡비 배열 (미지 치수는 1:1) */
|
||||||
aspectRatios(): Float32Array {
|
aspectRatios(): Float32Array {
|
||||||
const out = new Float32Array(this.count);
|
const out = new Float32Array(this.count);
|
||||||
|
|||||||
Reference in New Issue
Block a user