diff --git a/src/features/dedupe/DedupePanel.tsx b/src/features/dedupe/DedupePanel.tsx index dc3a039..8ffed25 100644 --- a/src/features/dedupe/DedupePanel.tsx +++ b/src/features/dedupe/DedupePanel.tsx @@ -5,28 +5,30 @@ import { createResource, createSignal, For, + on, Show, type Component, } from "solid-js"; import { ask } from "@tauri-apps/plugin-dialog"; import { ko } from "../../i18n/ko"; import { - cancelDedupe, dismissDupePair, listDupeGroups, resolveDupeGroup, - startDedupe, thumbUrl, trashFiles, - type DedupeProgress, type DupeGroup, } from "../../ipc/commands"; import { closeDedupe, dedupeOpen, + dedupeProgress, + dedupeRunning, info, refreshSidebar, refreshSnapshot, + requestCancelDedupe, + runDedupe, toast, view, } from "../../state/store"; @@ -40,8 +42,6 @@ function fmtSize(bytes: number): string { const KIND_LABEL: Record = { 0: "완전 동일", 1: "유사 이미지", 2: "유사 영상" }; export const DedupePanel: Component = () => { - const [running, setRunning] = createSignal(false); - const [progress, setProgress] = createSignal(); const [includeVideo, setIncludeVideo] = createSignal(false); const [threshold, setThreshold] = createSignal(5); const [groupsVersion, setGroupsVersion] = createSignal(0); @@ -67,28 +67,25 @@ export const DedupePanel: Component = () => { }); const runScan = async () => { - setRunning(true); - setProgress(undefined); const scoped = useCurrentScope() ? currentScope() : null; try { - await new Promise((resolve, reject) => { - startDedupe( - { ...(scoped?.args ?? {}), imageThreshold: threshold(), includeVideo: includeVideo() }, - (p) => { - setProgress(p); - if (p.phase === "done") resolve(); - }, - ).catch(reject); - }); + await runDedupe({ ...(scoped?.args ?? {}), imageThreshold: threshold(), includeVideo: includeVideo() }); } catch (e) { toast(String(e)); - } finally { - setRunning(false); - setProgress(undefined); - setGroupsVersion((v) => v + 1); } }; + // 탐지 완료(실행중 true→false) 시 결과 목록 갱신 — 패널이 닫혀 있다 열려도 재조회됨. + createEffect( + on( + dedupeRunning, + (running, prev) => { + if (prev && !running) setGroupsVersion((v) => v + 1); + }, + { defer: true }, + ), + ); + /** keeper 외 전부 휴지통 */ const resolveGroup = async (g: DupeGroup) => { const losers = g.members.filter((m) => !m.isKeeper).map((m) => m.fileId); @@ -181,20 +178,20 @@ export const DedupePanel: Component = () => {
- + - {progress() - ? ko.dedupe.scanning(progress()!.phase, progress()!.current, progress()!.total) + {dedupeProgress() + ? ko.dedupe.scanning(dedupeProgress()!.phase, dedupeProgress()!.current, dedupeProgress()!.total) : ko.dedupe.starting} - + + + + + {/* 백그라운드 처리 진행 — 썸네일/메타 대기 수 (있을 때만) */} 0 || pending().meta > 0}> diff --git a/src/state/store.ts b/src/state/store.ts index f696412..f90001b 100644 --- a/src/state/store.ts +++ b/src/state/store.ts @@ -3,13 +3,16 @@ import { createSignal } from "solid-js"; import { createStore, produce } from "solid-js/store"; import { appInfo, + cancelDedupe, folderSnapshot, pendingCounts as fetchPendingCounts, searchSnapshot, smartSnapshot, + startDedupe, tagSnapshot, Snapshot, type AppInfo, + type DedupeProgress, type PendingCounts, type ScanProgress, } from "../ipc/commands"; @@ -196,6 +199,39 @@ export { dedupeOpen }; export const openDedupe = () => setDedupeOpen(true); export const closeDedupe = () => setDedupeOpen(false); +// ── 중복 탐지 실행(전역) — 패널을 닫아도 상태바에서 진행이 보이도록 store가 소유 ── +const [dedupeProgress, setDedupeProgress] = createSignal(); +const [dedupeRunning, setDedupeRunning] = createSignal(false); +export { dedupeProgress, dedupeRunning }; + +export interface DedupeRunArgs { + sourceId?: number; + folderId?: number; + recursive?: boolean; + imageThreshold?: number; + includeVideo?: boolean; +} + +/** 중복 탐지 실행. 진행 상태는 store에 유지되어 패널을 닫아도 상태바에서 보인다. */ +export async function runDedupe(args: DedupeRunArgs): Promise { + if (dedupeRunning()) return; + setDedupeRunning(true); + setDedupeProgress(undefined); + try { + await new Promise((resolve, reject) => { + startDedupe(args, (p) => { + setDedupeProgress(p); + if (p.phase === "done") resolve(); + }).catch(reject); + }); + } finally { + setDedupeRunning(false); + setDedupeProgress(undefined); + } +} + +export const requestCancelDedupe = () => void cancelDedupe(); + // ── 상태 메시지 (토스트) ───────────────────────────────── const [statusMessage, setStatusMessage] = createSignal(); export { statusMessage };