파일 삭제 기능 추가, undo / redo 기능에 적용

This commit is contained in:
2026-05-15 23:32:29 +09:00
parent 1e88859484
commit 999cc9f3fb
12 changed files with 289 additions and 34 deletions
+64 -19
View File
@@ -7,6 +7,7 @@ import {
} from './grid.js';
import { doRename } from './rename.js';
import { handleFolderDrop } from './move.js';
import * as deleteHandler from './delete.js';
import * as pinchZoom from './handlers/pinch-zoom.js';
import * as selection from './handlers/selection.js';
import * as dragDrop from './handlers/drag-drop.js';
@@ -39,13 +40,14 @@ history.setOnUpdate(updateHistoryButtons);
const callbacks = {
refreshCardSelection, updateSelCount, updateNumberBadges, updateNewNameLabels,
pushHistory: (affectedPaths) => history.push(state.files, affectedPaths),
pushHistory: (affectedPaths) => history.pushReorder(state.files, affectedPaths),
};
pinchZoom.init({ gridContainer, thumbSizeHint });
selection.init({ gridContainer, rubberBand, grid, state, callbacks });
dragDrop.init({ grid, gridContainer, dragGhostEl, state, callbacks, fileExplorerEl: fileExplorerContainer, onFolderDrop: handleFolderDrop });
hoverPreview.init();
deleteHandler.init(grid);
fileExplorer.init(fileExplorerContainer, loadFolder);
window.api.onFullscreenChange((isFullscreen) => {
@@ -93,8 +95,10 @@ document.addEventListener('keydown', (e) => {
refreshCardSelection();
updateSelCount();
}
if (e.code === 'KeyU') performUndo();
if (e.code === 'KeyR') performRedo();
if (e.metaKey && !e.shiftKey && e.code === 'KeyZ') { e.preventDefault(); performUndo(); }
if (e.metaKey && e.shiftKey && e.code === 'KeyZ') { e.preventDefault(); performRedo(); }
if (e.code === 'KeyP') btnPreviewToggle.click();
if (e.code === 'Backspace' || e.code === 'Delete') deleteHandler.deleteSelected();
});
// ── 폴더 열기 (다이얼로그) ───────────────────────
@@ -145,7 +149,7 @@ async function loadFolder(folder) {
updatePreview();
btnRename.disabled = false;
history.reset();
history.push(state.files);
history.pushReorder(state.files);
}
// ── 정렬 ─────────────────────────────────────────
@@ -158,7 +162,7 @@ async function sortFiles(mode) {
updateSortButtons();
await renderGrid();
setStatus(`${mode === 'name' ? '이름순' : '날짜순'}으로 정렬됨`, true);
history.push(state.files);
history.pushReorder(state.files);
}
function updateSortButtons() {
@@ -167,43 +171,84 @@ function updateSortButtons() {
}
// ── Undo / Redo ───────────────────────────────────
let _historyBusy = false;
function updateHistoryButtons() {
btnUndo.disabled = !history.canUndo();
btnRedo.disabled = !history.canRedo();
}
function performUndo() {
if (!history.canUndo()) {
showToast('더 이상 되돌릴 수 없습니다');
async function performUndo() {
if (_historyBusy || !history.canUndo()) {
if (!history.canUndo()) showToast('더 이상 되돌릴 수 없습니다');
return;
}
_applyHistorySnapshot(history.undo());
_historyBusy = true;
try {
const { entry, targetFiles } = history.undo();
if (entry.type === 'delete') {
const { restored, errors } = await window.api.restoreStagedFiles(entry.deletedEntries);
if (errors.length) showToast('일부 파일 복원 실패: ' + errors[0]);
_applyHistorySnapshot(targetFiles, new Set(restored));
if (restored.length) setStatus(`${restored.length}개 파일 복원됨`, true);
} else {
_applyHistorySnapshot(targetFiles, entry.affectedPaths);
}
} finally {
_historyBusy = false;
}
}
function performRedo() {
if (!history.canRedo()) {
showToast('마지막 작업 상태입니다');
async function performRedo() {
if (_historyBusy || !history.canRedo()) {
if (!history.canRedo()) showToast('마지막 작업 상태입니다');
return;
}
_applyHistorySnapshot(history.redo());
_historyBusy = true;
try {
const nextEntry = history.peekRedo();
if (nextEntry.type === 'delete') {
const pathsToRedo = nextEntry.deletedEntries.map(e => e.originalPath);
const { staged, errors } = await window.api.stageDeleteFiles(pathsToRedo);
if (errors.length) showToast('일부 파일 삭제 실패: ' + errors[0]);
const stagedByPath = new Map(staged.map(s => [s.originalPath, s.stagingPath]));
nextEntry.deletedEntries.forEach(e => {
if (stagedByPath.has(e.originalPath)) e.stagingPath = stagedByPath.get(e.originalPath);
});
const { targetFiles } = history.redo();
_applyHistorySnapshot(targetFiles, null);
if (staged.length) setStatus(`${staged.length}개 파일 삭제됨`, true);
} else {
const { entry, targetFiles } = history.redo();
_applyHistorySnapshot(targetFiles, entry.affectedPaths);
}
} finally {
_historyBusy = false;
}
}
function _applyHistorySnapshot({ files, affectedPaths }) {
function _applyHistorySnapshot(targetFiles, affectedPaths = null) {
state.files.length = 0;
state.files.push(...files);
state.files.push(...targetFiles);
state.selectedIdxs.clear();
if (affectedPaths) {
if (affectedPaths && affectedPaths.size > 0) {
const pathToIdx = new Map(state.files.map((f, i) => [f.path, i]));
affectedPaths.forEach(path => {
const idx = pathToIdx.get(path);
affectedPaths.forEach(p => {
const idx = pathToIdx.get(p);
if (idx !== undefined) state.selectedIdxs.add(idx);
});
}
state.sortMode = null;
updateSortButtons();
renderGrid();
if (!state.files.length) {
showEmptyGrid();
btnRename.disabled = true;
} else {
renderGrid();
btnRename.disabled = false;
}
updateHistoryButtons();
}