fix(move): close the dialog when a move starts so header progress shows
The move dialog held its full-screen overlay open for the whole operation, hiding exactly the header reindex/status pill the user waits on. Snapshot the draft state, closeMove() up front, and run the move in the background with a toast.loading→success/error — mirrors the archive flow. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -120,19 +120,38 @@
|
|||||||
// (nothing picked) so a falsy check doesn't wrongly block root.
|
// (nothing picked) so a falsy check doesn't wrongly block root.
|
||||||
if (!s || pickedPath === null || submitting) return;
|
if (!s || pickedPath === null || submitting) return;
|
||||||
submitting = true;
|
submitting = true;
|
||||||
|
|
||||||
|
// Snapshot the draft before closing — closeMove() nulls the subject,
|
||||||
|
// which the reset effect uses to wipe pickedPath/mode/subfolder.
|
||||||
|
const dest = pickedPath;
|
||||||
|
const opMode = mode;
|
||||||
|
const sub = subfolder.trim() || null;
|
||||||
|
const delHeap = mode === 'move' && deleteHeap;
|
||||||
|
const labelName = folderName;
|
||||||
|
|
||||||
|
// Close the dialog immediately and run the move in the background. The
|
||||||
|
// move can be slow (a folder/heap with many files triggers a real
|
||||||
|
// disk move + reindex) and its progress surfaces in the header pill;
|
||||||
|
// keeping the modal + overlay up would hide exactly the feedback the
|
||||||
|
// user is waiting on. Mirrors the archive flow (toast + header pill).
|
||||||
|
closeMove();
|
||||||
|
|
||||||
|
const verbing = opMode === 'copy' ? 'Copying' : 'Moving';
|
||||||
|
const tid = toast.loading(`${verbing}…`);
|
||||||
try {
|
try {
|
||||||
if (s.kind === 'heap') {
|
if (s.kind === 'heap') {
|
||||||
const r = await convertHeap(s.heap.UID, {
|
const r = await convertHeap(s.heap.UID, {
|
||||||
targetFolder: toOriginalsPath(pickedPath),
|
targetFolder: toOriginalsPath(dest),
|
||||||
mode,
|
mode: opMode,
|
||||||
subfolder: subfolder.trim() || null,
|
subfolder: sub,
|
||||||
deleteHeap: mode === 'move' && deleteHeap
|
deleteHeap: delHeap
|
||||||
});
|
});
|
||||||
qc.invalidateQueries({ queryKey: ['photos'] });
|
qc.invalidateQueries({ queryKey: ['photos'] });
|
||||||
qc.invalidateQueries({ queryKey: ['folders'] });
|
qc.invalidateQueries({ queryKey: ['folders'] });
|
||||||
qc.invalidateQueries({ queryKey: ['heaps'] });
|
qc.invalidateQueries({ queryKey: ['heaps'] });
|
||||||
toast.success(
|
toast.success(
|
||||||
moveSummary(mode === 'copy' ? 'Copied' : 'Moved', mode === 'copy' ? r.copied : r.moved, r.errors.length)
|
moveSummary(opMode === 'copy' ? 'Copied' : 'Moved', opMode === 'copy' ? r.copied : r.moved, r.errors.length),
|
||||||
|
{ id: tid }
|
||||||
);
|
);
|
||||||
if (r.heap_deleted && filters.section === 'heap' && filters.heapUid === s.heap.UID) {
|
if (r.heap_deleted && filters.section === 'heap' && filters.heapUid === s.heap.UID) {
|
||||||
setSection('all-photos');
|
setSection('all-photos');
|
||||||
@@ -141,32 +160,30 @@
|
|||||||
} else if (s.kind === 'photos') {
|
} else if (s.kind === 'photos') {
|
||||||
const r = await movePhotosToFolder({
|
const r = await movePhotosToFolder({
|
||||||
uids: s.uids,
|
uids: s.uids,
|
||||||
targetFolder: toOriginalsPath(pickedPath),
|
targetFolder: toOriginalsPath(dest),
|
||||||
mode,
|
mode: opMode,
|
||||||
subfolder: subfolder.trim() || null
|
subfolder: sub
|
||||||
});
|
});
|
||||||
qc.invalidateQueries({ queryKey: ['photos'] });
|
qc.invalidateQueries({ queryKey: ['photos'] });
|
||||||
qc.invalidateQueries({ queryKey: ['folders'] });
|
qc.invalidateQueries({ queryKey: ['folders'] });
|
||||||
toast.success(
|
toast.success(
|
||||||
moveSummary(mode === 'copy' ? 'Copied' : 'Moved', mode === 'copy' ? r.copied : r.moved, r.errors.length)
|
moveSummary(opMode === 'copy' ? 'Copied' : 'Moved', opMode === 'copy' ? r.copied : r.moved, r.errors.length),
|
||||||
|
{ id: tid }
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
// Folder reparent (move only). Translate both the folder's own
|
// Folder reparent (move only). Translate both the folder's own
|
||||||
// path and the destination parent to originals-relative for the
|
// path and the destination parent to originals-relative for the
|
||||||
// sidecar, which moves real directories on disk.
|
// sidecar, which moves real directories on disk.
|
||||||
await moveFolder(toOriginalsPath(s.path), toOriginalsPath(pickedPath));
|
await moveFolder(toOriginalsPath(s.path), toOriginalsPath(dest));
|
||||||
qc.invalidateQueries({ queryKey: ['photos'] });
|
qc.invalidateQueries({ queryKey: ['photos'] });
|
||||||
qc.invalidateQueries({ queryKey: ['folders'] });
|
qc.invalidateQueries({ queryKey: ['folders'] });
|
||||||
const newUiPath = pickedPath === '' ? folderName : `${pickedPath}/${folderName}`;
|
const newUiPath = dest === '' ? labelName : `${dest}/${labelName}`;
|
||||||
toast.success(`Moved ${folderName} → ${pickedPath === '' ? '/' : pickedPath}`);
|
toast.success(`Moved ${labelName} → ${dest === '' ? '/' : dest}`, { id: tid });
|
||||||
// If we just moved the folder the timeline is showing, follow it.
|
// If we just moved the folder the timeline is showing, follow it.
|
||||||
if (filters.folderPath === s.path) setFolderPath(newUiPath);
|
if (filters.folderPath === s.path) setFolderPath(newUiPath);
|
||||||
}
|
}
|
||||||
closeMove();
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
toast.error(err instanceof Error ? err.message : 'Move failed');
|
toast.error(err instanceof Error ? err.message : 'Move failed', { id: tid });
|
||||||
} finally {
|
|
||||||
submitting = false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
Reference in New Issue
Block a user