사이드바 너비, 카드 크기 정보 저장, 앱 실행시 적용 + 개발 모드 실행시 로그 출력

This commit is contained in:
2026-05-18 11:14:10 +09:00
parent d74c2e055d
commit 0ef8a28d54
16 changed files with 234 additions and 44 deletions
+47 -24
View File
@@ -3,6 +3,7 @@ import { showLoading, hideLoading, setStatus } from './ui.js';
import { renderGrid, showEmptyGrid } from './grid.js';
import * as history from './history.js';
import * as fileExplorer from './handlers/file-explorer.js';
import { error, log } from './logger.js';
export function createFolderController({
btnRename,
@@ -15,10 +16,19 @@ export function createFolderController({
let homeDir = null;
async function openFolder() {
const folder = await window.api.selectFolder();
if (!folder) return;
log('folder', 'openFolder start', { homeDir });
let folder = null;
try {
folder = await window.api.selectFolder();
} catch (e) {
error('folder', 'selectFolder failed', e);
setStatus(`폴더 열기 실패: ${e.message}`, false);
return;
}
if (folder.startsWith(homeDir)) {
log('folder', 'openFolder selected', { folder });
if (!folder) return;
if (homeDir && folder.startsWith(homeDir)) {
await fileExplorer.loadTree(homeDir, folder);
} else {
await fileExplorer.loadTree(folder, folder);
@@ -27,6 +37,7 @@ export function createFolderController({
}
async function loadFolder(folder) {
log('folder', 'loadFolder start', { folder });
if (!folder) return;
state.currentFolder = folder;
dirPath.textContent = folder;
@@ -34,33 +45,45 @@ export function createFolderController({
await fileExplorer.setSelectedPath(folder);
showLoading('이미지 목록 불러오는 중...');
const list = await window.api.listImages(folder);
hideLoading();
let list = [];
try {
list = await window.api.listImages(folder);
hideLoading();
log('folder', 'loadFolder listImages complete', { folder, count: list.length });
if (!list.length) {
state.files.length = 0;
state.selectedIdxs.clear();
showEmptyGrid();
btnRename.disabled = true;
setStatus('이미지 파일 없음', false);
log('folder', 'loadFolder empty', { folder });
return;
}
if (!list.length) {
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);
log('folder', 'loadFolder done', { folder, count: state.files.length });
} catch (e) {
hideLoading();
error('folder', `loadFolder failed: ${folder}`, e);
showEmptyGrid();
btnRename.disabled = true;
setStatus('이미지 파일 없음', false);
return;
setStatus(`폴더 로드 실패: ${e.message}`, false);
}
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) {
+3 -3
View File
@@ -94,12 +94,12 @@ function nodeIcon(nodeEl) {
return nodeEl.dataset.isRoot === 'true' ? ICON_HOME : ICON_OPEN;
}
function selectNode(nodeEl) {
function selectNode(nodeEl, { notify = true } = {}) {
const path = nodeEl.dataset.path;
document.querySelectorAll('.tree-row.active').forEach(r => r.classList.remove('active'));
nodeEl.querySelector(':scope > .tree-row').classList.add('active');
_selectedPath = path;
if (_onSelect) _onSelect(path);
if (notify && _onSelect) _onSelect(path);
}
function collapseNode(nodeEl) {
@@ -149,7 +149,7 @@ async function expandNode(nodeEl, autoExpandPath) {
// Reached the target — select it
if (autoExpandPath === nodeEl.dataset.path) {
selectNode(nodeEl);
selectNode(nodeEl, { notify: false });
}
}
+14 -3
View File
@@ -2,11 +2,15 @@ const THUMB_MIN = 150;
const THUMB_MAX = 400;
let _thumbSizeHint;
let _onChange = null;
let thumbSize = 250;
let thumbHintTimer = null;
let saveTimer = null;
export function init({ gridContainer, thumbSizeHint }) {
export function init({ gridContainer, thumbSizeHint, initialSize, onChange }) {
_thumbSizeHint = thumbSizeHint;
_onChange = onChange;
if (initialSize) setThumbSize(initialSize, { showHint: false, notify: false });
gridContainer.addEventListener('wheel', (e) => {
if (!e.ctrlKey) return;
@@ -15,10 +19,11 @@ export function init({ gridContainer, thumbSizeHint }) {
}, { passive: false });
}
export function setThumbSize(size) {
export function setThumbSize(size, { showHint = true, notify = true } = {}) {
thumbSize = Math.round(Math.max(THUMB_MIN, Math.min(THUMB_MAX, size)));
document.documentElement.style.setProperty('--thumb-size', thumbSize + 'px');
_showHint(thumbSize);
if (showHint) _showHint(thumbSize);
if (notify) _scheduleSave(thumbSize);
}
function _showHint(size) {
@@ -27,3 +32,9 @@ function _showHint(size) {
clearTimeout(thumbHintTimer);
thumbHintTimer = setTimeout(() => _thumbSizeHint.classList.remove('show'), 1000);
}
function _scheduleSave(size) {
if (!_onChange) return;
clearTimeout(saveTimer);
saveTimer = setTimeout(() => _onChange(size), 250);
}
+6
View File
@@ -1,3 +1,5 @@
import { log } from './logger.js';
export function initKeyboardShortcuts({
btnOpen,
btnRename,
@@ -15,6 +17,10 @@ export function initKeyboardShortcuts({
if (e.metaKey && e.code === 'KeyO') {
e.preventDefault();
log('keyboard', 'Cmd+O detected', {
target: e.target?.tagName || null,
btnOpenDisabled: btnOpen.disabled,
});
btnOpen.click();
} else if (e.metaKey && e.code === 'KeyS') {
e.preventDefault();
+22
View File
@@ -0,0 +1,22 @@
export function log(scope, message, data) {
if (!isDevelopment()) return;
const suffix = data === undefined ? '' : ` ${format(data)}`;
console.log(`[PhotoRenamer:${scope}] ${message}${suffix}`);
}
export function error(scope, message, err) {
if (!isDevelopment()) return;
console.error(`[PhotoRenamer:${scope}] ${message}`, err);
}
function isDevelopment() {
return window.api?.env?.logEnabled === true;
}
function format(data) {
try {
return JSON.stringify(data);
} catch {
return String(data);
}
}
+17 -3
View File
@@ -18,6 +18,7 @@ import * as dragDrop from './handlers/drag-drop.js';
import * as hoverPreview from './handlers/image-preview.js';
import * as fileExplorer from './handlers/file-explorer.js';
import * as sidebarResize from './handlers/sidebar-resize.js';
import { log } from './logger.js';
// ── DOM ──────────────────────────────────────────────
const btnOpen = document.getElementById('btn-open');
@@ -64,7 +65,6 @@ const callbacks = {
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();
@@ -77,12 +77,15 @@ window.api.onFullscreenChange((isFullscreen) => {
// ── 앱 시작 ───────────────────────────────────────
(async () => {
const [lastFolder, homeDir, previewOn, sidebarWidth] = await Promise.all([
log('startup', 'init start');
const [lastFolder, homeDir, previewOn, sidebarWidth, thumbSize] = await Promise.all([
window.api.getLastFolder(),
window.api.getHomeDir(),
window.api.getPreviewOn(),
window.api.getSidebarWidth(),
window.api.getThumbSize(),
]);
log('startup', 'settings loaded', { lastFolder, homeDir, previewOn, sidebarWidth, thumbSize });
folderController.setHomeDir(homeDir);
btnPreviewToggle.classList.toggle('active', previewOn);
@@ -93,16 +96,27 @@ window.api.onFullscreenChange((isFullscreen) => {
initialWidth: sidebarWidth,
onResizeEnd: width => window.api.setSidebarWidth(width),
});
pinchZoom.init({
gridContainer,
thumbSizeHint,
initialSize: thumbSize,
onChange: size => window.api.setThumbSize(size),
});
const target = lastFolder || homeDir;
log('startup', 'initial folder target', { target, fromLastFolder: Boolean(lastFolder) });
await fileExplorer.loadTree(homeDir, target);
await folderController.loadFolder(target);
log('startup', 'init done');
})();
// ── 버튼 / 키보드 이벤트 ─────────────────────────
inputPrefix.addEventListener('input', updatePreview);
inputDigits.addEventListener('input', updatePreview);
btnOpen.addEventListener('click', folderController.openFolder);
btnOpen.addEventListener('click', () => {
log('ui', 'open button clicked');
folderController.openFolder();
});
btnSortName.addEventListener('click', () => folderController.sortFiles('name'));
btnSortDate.addEventListener('click', () => folderController.sortFiles('date'));
btnRename.addEventListener('click', doRename);