사이드바 너비, 카드 크기 정보 저장, 앱 실행시 적용 + 개발 모드 실행시 로그 출력
This commit is contained in:
@@ -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) {
|
||||
|
||||
@@ -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 });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user