리펙토링: 전체 구조 검토, 개선
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
const { ipcMain } = require('electron');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const crypto = require('crypto');
|
||||
|
||||
const EXTS = new Set(['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.tiff', '.tif', '.webp', '.heic', '.heif']);
|
||||
|
||||
@@ -12,7 +13,7 @@ function register() {
|
||||
.map(f => {
|
||||
const fullPath = path.join(folderPath, f);
|
||||
const stat = fs.statSync(fullPath);
|
||||
return { name: f, path: fullPath, mtime: stat.mtimeMs, size: stat.size };
|
||||
return { name: f, path: fullPath, mtime: stat.mtimeMs };
|
||||
})
|
||||
.sort((a, b) => a.name.localeCompare(b.name, 'ko'));
|
||||
return files;
|
||||
@@ -78,7 +79,7 @@ function register() {
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
const src = files[i].path;
|
||||
const ext = path.extname(files[i].name).toLowerCase();
|
||||
const tmp = path.join(folderPath, `__tmp_rename_${i}_${Date.now()}${ext}`);
|
||||
const tmp = path.join(folderPath, `__tmp_${crypto.randomUUID()}${ext}`);
|
||||
try {
|
||||
fs.renameSync(src, tmp);
|
||||
tmpMap.push({ tmp, ext, idx: i });
|
||||
|
||||
@@ -53,7 +53,7 @@ function register(mainWindow) {
|
||||
return { name: e.name, path: fullPath, hasChildren };
|
||||
})
|
||||
.sort((a, b) => a.name.localeCompare(b.name));
|
||||
} catch { return []; }
|
||||
} catch { return null; }
|
||||
});
|
||||
|
||||
ipcMain.handle('reveal-folder', (_e, folderPath) => shell.openPath(folderPath));
|
||||
|
||||
@@ -5,7 +5,11 @@ contextBridge.exposeInMainWorld('api', {
|
||||
listImages: (folder) => ipcRenderer.invoke('list-images', folder),
|
||||
readImage: (path) => ipcRenderer.invoke('read-image', path),
|
||||
renameFiles: (args) => ipcRenderer.invoke('rename-files', args),
|
||||
onFullscreenChange: (callback) => ipcRenderer.on('fullscreen-change', (_e, v) => callback(v)),
|
||||
onFullscreenChange: (() => {
|
||||
let _cb = null;
|
||||
ipcRenderer.on('fullscreen-change', (_e, v) => { if (_cb) _cb(v); });
|
||||
return (callback) => { _cb = callback; };
|
||||
})(),
|
||||
getHomeDir: () => ipcRenderer.invoke('get-home-dir'),
|
||||
getLastFolder: () => ipcRenderer.invoke('get-last-folder'),
|
||||
setLastFolder: (p) => ipcRenderer.invoke('set-last-folder', p),
|
||||
|
||||
@@ -31,13 +31,14 @@ export function renderGrid() {
|
||||
const img = entry.target.querySelector('img.card-thumb');
|
||||
if (!img || img.src) return;
|
||||
const dataUrl = await window.api.readImage(entry.target.dataset.filepath);
|
||||
if (dataUrl) img.src = dataUrl;
|
||||
if (dataUrl && entry.target.isConnected) img.src = dataUrl;
|
||||
});
|
||||
}, { root: gridContainer, rootMargin: '400px 0px', threshold: 0 });
|
||||
|
||||
Array.from(grid.children).forEach(card => observer.observe(card));
|
||||
_cardObserver = observer;
|
||||
|
||||
selection.resetAnchor();
|
||||
refreshCardSelection();
|
||||
updateNumberBadges();
|
||||
updateNewNameLabels();
|
||||
|
||||
@@ -125,7 +125,14 @@ async function expandNode(nodeEl, autoExpandPath) {
|
||||
if (!childrenEl.dataset.loaded) {
|
||||
childrenEl.dataset.loaded = 'true';
|
||||
const dirs = await window.api.listDirectories(nodeEl.dataset.path);
|
||||
dirs.forEach(dir => childrenEl.appendChild(createNode(dir)));
|
||||
if (dirs === null) {
|
||||
const err = document.createElement('div');
|
||||
err.className = 'tree-access-error';
|
||||
err.textContent = '접근 불가';
|
||||
childrenEl.appendChild(err);
|
||||
} else {
|
||||
dirs.forEach(dir => childrenEl.appendChild(createNode(dir)));
|
||||
}
|
||||
}
|
||||
|
||||
// Auto-expand toward the target path
|
||||
|
||||
@@ -4,9 +4,15 @@ let _state, _cb;
|
||||
let rbActive = false;
|
||||
let rbStartX = 0;
|
||||
let rbStartY = 0;
|
||||
let _rafPending = false;
|
||||
let _lastMoveEvent = null;
|
||||
|
||||
let lastSelectedIdx = null;
|
||||
|
||||
export function resetAnchor() {
|
||||
lastSelectedIdx = null;
|
||||
}
|
||||
|
||||
export function init({ gridContainer, rubberBand, grid, state, callbacks }) {
|
||||
_rubberBand = rubberBand;
|
||||
_grid = grid;
|
||||
@@ -98,6 +104,17 @@ function _onStart(e) {
|
||||
|
||||
function _onMove(e) {
|
||||
if (!rbActive) return;
|
||||
_lastMoveEvent = e;
|
||||
if (!_rafPending) {
|
||||
_rafPending = true;
|
||||
requestAnimationFrame(_processMove);
|
||||
}
|
||||
}
|
||||
|
||||
function _processMove() {
|
||||
_rafPending = false;
|
||||
const e = _lastMoveEvent;
|
||||
if (!rbActive || !e) return;
|
||||
|
||||
const left = Math.min(rbStartX, e.clientX);
|
||||
const top = Math.min(rbStartY, e.clientY);
|
||||
@@ -110,11 +127,11 @@ function _onMove(e) {
|
||||
});
|
||||
|
||||
const sel = { left, top, right: left + width, bottom: top + height };
|
||||
const preserve = e.shiftKey || e.ctrlKey || e.metaKey;
|
||||
Array.from(_grid.children).forEach((card, i) => {
|
||||
const r = card.getBoundingClientRect();
|
||||
const overlaps = !(r.right < sel.left || r.left > sel.right ||
|
||||
r.bottom < sel.top || r.top > sel.bottom);
|
||||
const preserve = e.shiftKey || e.ctrlKey || e.metaKey;
|
||||
if (overlaps) {
|
||||
_state.selectedIdxs.add(i);
|
||||
} else if (!preserve) {
|
||||
|
||||
@@ -35,7 +35,11 @@ export async function doRename() {
|
||||
});
|
||||
const conflicting = newNames.filter(n => unselectedNames.has(n));
|
||||
if (conflicting.length) {
|
||||
renameConflictList.innerHTML = conflicting.map(n => `<li>${n}</li>`).join('');
|
||||
renameConflictList.replaceChildren(...conflicting.map(n => {
|
||||
const li = document.createElement('li');
|
||||
li.textContent = n;
|
||||
return li;
|
||||
}));
|
||||
renameConflictModal.style.display = 'flex';
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -12,6 +12,8 @@ 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');
|
||||
@@ -48,6 +50,7 @@ window.api.onFullscreenChange((isFullscreen) => {
|
||||
window.api.getHomeDir(),
|
||||
window.api.getPreviewOn(),
|
||||
]);
|
||||
_homeDir = homeDir;
|
||||
|
||||
btnPreviewToggle.classList.toggle('active', previewOn);
|
||||
hoverPreview.setEnabled(previewOn);
|
||||
@@ -85,7 +88,7 @@ document.addEventListener('keydown', (e) => {
|
||||
async function openFolder() {
|
||||
const folder = await window.api.selectFolder();
|
||||
if (!folder) return;
|
||||
const homeDir = await window.api.getHomeDir();
|
||||
const homeDir = _homeDir;
|
||||
if (folder.startsWith(homeDir)) {
|
||||
await fileExplorer.loadTree(homeDir, folder);
|
||||
} else {
|
||||
|
||||
@@ -474,6 +474,10 @@ footer span { font-size: 11px; color: var(--muted); }
|
||||
border-left: 1px solid rgba(255,255,255,.05);
|
||||
margin-left: 15px;
|
||||
}
|
||||
.tree-access-error {
|
||||
font-size: 11px; color: var(--muted); opacity: .5;
|
||||
padding: 2px 8px; font-style: italic;
|
||||
}
|
||||
|
||||
/* ── 폴더 드롭 타겟 하이라이트 ── */
|
||||
.tree-row.drop-target {
|
||||
|
||||
Reference in New Issue
Block a user