리펙토링 - 전체 프로젝트 코드 (Codex)
This commit is contained in:
+42
-178
@@ -1,13 +1,16 @@
|
||||
import { state } from './state.js';
|
||||
import { showLoading, hideLoading, setStatus, showToast } from './ui.js';
|
||||
import * as history from './history.js';
|
||||
import {
|
||||
renderGrid, updateSelCount, updateNumberBadges,
|
||||
updateNewNameLabels, refreshCardSelection, showEmptyGrid,
|
||||
updateSelCount, updateNumberBadges,
|
||||
updateNewNameLabels, refreshCardSelection,
|
||||
} from './grid.js';
|
||||
import { doRename } from './rename.js';
|
||||
import { handleFolderDrop } from './move.js';
|
||||
import { initModalControls } from './modal.js';
|
||||
import { clampDigits, getExtension } from './utils/file-names.js';
|
||||
import { initKeyboardShortcuts } from './keyboard.js';
|
||||
import { createFolderController } from './folder-controller.js';
|
||||
import { createHistoryController } from './history-controller.js';
|
||||
import * as deleteHandler from './delete.js';
|
||||
import * as pinchZoom from './handlers/pinch-zoom.js';
|
||||
import * as selection from './handlers/selection.js';
|
||||
@@ -15,8 +18,6 @@ import * as dragDrop from './handlers/drag-drop.js';
|
||||
import * as hoverPreview from './handlers/image-preview.js';
|
||||
import * as fileExplorer from './handlers/file-explorer.js';
|
||||
|
||||
let _homeDir = null;
|
||||
|
||||
// ── DOM ──────────────────────────────────────────────
|
||||
const btnOpen = document.getElementById('btn-open');
|
||||
const btnSortName = document.getElementById('btn-sort-name');
|
||||
@@ -36,8 +37,23 @@ const btnUndo = document.getElementById('btn-undo');
|
||||
const btnRedo = document.getElementById('btn-redo');
|
||||
const fileExplorerContainer = document.querySelector('.file-explorer');
|
||||
|
||||
const folderController = createFolderController({
|
||||
btnRename,
|
||||
btnSortName,
|
||||
btnSortDate,
|
||||
dirPath,
|
||||
inputPrefix,
|
||||
updatePreview,
|
||||
});
|
||||
const historyController = createHistoryController({
|
||||
btnRename,
|
||||
btnUndo,
|
||||
btnRedo,
|
||||
updateSortButtons: folderController.updateSortButtons,
|
||||
});
|
||||
|
||||
// ── 핸들러 초기화 ─────────────────────────────────
|
||||
history.setOnUpdate(updateHistoryButtons);
|
||||
history.setOnUpdate(historyController.updateButtons);
|
||||
initModalControls();
|
||||
|
||||
const callbacks = {
|
||||
@@ -50,7 +66,7 @@ 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);
|
||||
fileExplorer.init(fileExplorerContainer, folderController.loadFolder);
|
||||
|
||||
window.api.onFullscreenChange((isFullscreen) => {
|
||||
document.body.classList.toggle('fullscreen', isFullscreen);
|
||||
@@ -63,202 +79,50 @@ window.api.onFullscreenChange((isFullscreen) => {
|
||||
window.api.getHomeDir(),
|
||||
window.api.getPreviewOn(),
|
||||
]);
|
||||
_homeDir = homeDir;
|
||||
folderController.setHomeDir(homeDir);
|
||||
|
||||
btnPreviewToggle.classList.toggle('active', previewOn);
|
||||
hoverPreview.setEnabled(previewOn);
|
||||
|
||||
const target = lastFolder || homeDir;
|
||||
await fileExplorer.loadTree(homeDir, target);
|
||||
await loadFolder(target);
|
||||
await folderController.loadFolder(target);
|
||||
})();
|
||||
|
||||
// ── 버튼 / 키보드 이벤트 ─────────────────────────
|
||||
inputPrefix.addEventListener('input', updatePreview);
|
||||
inputDigits.addEventListener('input', updatePreview);
|
||||
btnOpen.addEventListener('click', openFolder);
|
||||
btnSortName.addEventListener('click', () => sortFiles('name'));
|
||||
btnSortDate.addEventListener('click', () => sortFiles('date'));
|
||||
btnOpen.addEventListener('click', folderController.openFolder);
|
||||
btnSortName.addEventListener('click', () => folderController.sortFiles('name'));
|
||||
btnSortDate.addEventListener('click', () => folderController.sortFiles('date'));
|
||||
btnRename.addEventListener('click', doRename);
|
||||
btnUndo.addEventListener('click', performUndo);
|
||||
btnRedo.addEventListener('click', performRedo);
|
||||
btnUndo.addEventListener('click', historyController.performUndo);
|
||||
btnRedo.addEventListener('click', historyController.performRedo);
|
||||
btnPreviewToggle.addEventListener('click', () => {
|
||||
const on = btnPreviewToggle.classList.toggle('active');
|
||||
hoverPreview.setEnabled(on);
|
||||
window.api.setPreviewOn(on);
|
||||
});
|
||||
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.target.tagName === 'INPUT') return;
|
||||
if (e.code === 'KeyO') btnOpen.click();
|
||||
if (e.code === 'KeyS' && !btnRename.disabled) btnRename.click();
|
||||
if (e.code === 'KeyA') {
|
||||
state.files.forEach((_, i) => state.selectedIdxs.add(i));
|
||||
initKeyboardShortcuts({
|
||||
btnOpen,
|
||||
btnRename,
|
||||
btnPreviewToggle,
|
||||
deleteSelected: deleteHandler.deleteSelected,
|
||||
performUndo: historyController.performUndo,
|
||||
performRedo: historyController.performRedo,
|
||||
refreshSelection: () => {
|
||||
refreshCardSelection();
|
||||
updateSelCount();
|
||||
}
|
||||
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();
|
||||
},
|
||||
selectAll: () => state.files.forEach((_, i) => state.selectedIdxs.add(i)),
|
||||
});
|
||||
|
||||
// ── 폴더 열기 (다이얼로그) ───────────────────────
|
||||
async function openFolder() {
|
||||
const folder = await window.api.selectFolder();
|
||||
if (!folder) return;
|
||||
const homeDir = _homeDir;
|
||||
if (folder.startsWith(homeDir)) {
|
||||
await fileExplorer.loadTree(homeDir, folder);
|
||||
} else {
|
||||
await fileExplorer.loadTree(folder, folder);
|
||||
}
|
||||
await loadFolder(folder);
|
||||
}
|
||||
|
||||
// ── 폴더 로드 (트리 클릭 또는 다이얼로그 공통) ──
|
||||
async function loadFolder(folder) {
|
||||
if (!folder) return;
|
||||
state.currentFolder = folder;
|
||||
dirPath.textContent = folder;
|
||||
await window.api.setLastFolder(folder);
|
||||
await fileExplorer.setSelectedPath(folder);
|
||||
|
||||
showLoading('이미지 목록 불러오는 중...');
|
||||
const list = await window.api.listImages(folder);
|
||||
hideLoading();
|
||||
|
||||
if (!list.length) {
|
||||
state.files.length = 0;
|
||||
state.selectedIdxs.clear();
|
||||
showEmptyGrid();
|
||||
btnRename.disabled = true;
|
||||
setStatus('이미지 파일 없음', false);
|
||||
return;
|
||||
}
|
||||
|
||||
state.files.length = 0;
|
||||
state.files.push(...list);
|
||||
state.selectedIdxs.clear();
|
||||
state.sortMode = 'name';
|
||||
updateSortButtons();
|
||||
setStatus(`${state.files.length}개 파일 로드됨`, true);
|
||||
|
||||
const folderName = folder.replace(/\\/g, '/').split('/').pop();
|
||||
if (folderName) inputPrefix.value = folderName;
|
||||
|
||||
renderGrid();
|
||||
updatePreview();
|
||||
btnRename.disabled = false;
|
||||
history.reset();
|
||||
history.pushReorder(state.files);
|
||||
}
|
||||
|
||||
// ── 정렬 ─────────────────────────────────────────
|
||||
async function sortFiles(mode) {
|
||||
if (!state.files.length) return;
|
||||
if (mode === 'name') state.files.sort((a, b) => a.name.localeCompare(b.name, 'ko'));
|
||||
else state.files.sort((a, b) => a.mtime - b.mtime);
|
||||
state.sortMode = mode;
|
||||
state.selectedIdxs.clear();
|
||||
updateSortButtons();
|
||||
await renderGrid();
|
||||
setStatus(`${mode === 'name' ? '이름순' : '날짜순'}으로 정렬됨`, true);
|
||||
history.pushReorder(state.files);
|
||||
}
|
||||
|
||||
function updateSortButtons() {
|
||||
btnSortName.classList.toggle('active', state.sortMode === 'name');
|
||||
btnSortDate.classList.toggle('active', state.sortMode === 'date');
|
||||
}
|
||||
|
||||
// ── Undo / Redo ───────────────────────────────────
|
||||
let _historyBusy = false;
|
||||
|
||||
function updateHistoryButtons() {
|
||||
btnUndo.disabled = !history.canUndo();
|
||||
btnRedo.disabled = !history.canRedo();
|
||||
}
|
||||
|
||||
async function performUndo() {
|
||||
if (_historyBusy || !history.canUndo()) {
|
||||
if (!history.canUndo()) showToast('더 이상 되돌릴 수 없습니다');
|
||||
return;
|
||||
}
|
||||
_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;
|
||||
}
|
||||
}
|
||||
|
||||
async function performRedo() {
|
||||
if (_historyBusy || !history.canRedo()) {
|
||||
if (!history.canRedo()) showToast('마지막 작업 상태입니다');
|
||||
return;
|
||||
}
|
||||
_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(targetFiles, affectedPaths = null) {
|
||||
state.files.length = 0;
|
||||
state.files.push(...targetFiles);
|
||||
state.selectedIdxs.clear();
|
||||
|
||||
if (affectedPaths && affectedPaths.size > 0) {
|
||||
const pathToIdx = new Map(state.files.map((f, i) => [f.path, i]));
|
||||
affectedPaths.forEach(p => {
|
||||
const idx = pathToIdx.get(p);
|
||||
if (idx !== undefined) state.selectedIdxs.add(idx);
|
||||
});
|
||||
}
|
||||
|
||||
state.sortMode = null;
|
||||
updateSortButtons();
|
||||
if (!state.files.length) {
|
||||
showEmptyGrid();
|
||||
btnRename.disabled = true;
|
||||
} else {
|
||||
renderGrid();
|
||||
btnRename.disabled = false;
|
||||
}
|
||||
updateHistoryButtons();
|
||||
}
|
||||
|
||||
// ── 포맷 미리보기 ─────────────────────────────────
|
||||
function updatePreview() {
|
||||
const prefix = inputPrefix.value.trim() || 'photo';
|
||||
const digits = Math.max(1, Math.min(6, parseInt(inputDigits.value) || 3));
|
||||
const ext = state.files.length ? state.files[0].name.split('.').pop().toLowerCase() : 'jpg';
|
||||
const digits = clampDigits(inputDigits.value);
|
||||
const ext = state.files.length ? getExtension(state.files[0].name) : 'jpg';
|
||||
previewName.textContent = `${prefix}_${'1'.padStart(digits, '0')}.${ext}`;
|
||||
updateNewNameLabels();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user