사이드바 너비, 카드 크기 정보 저장, 앱 실행시 적용 + 개발 모드 실행시 로그 출력
This commit is contained in:
@@ -8,8 +8,12 @@
|
||||
node_modules/
|
||||
dist/
|
||||
out/
|
||||
debug.log
|
||||
*.log
|
||||
*.dmg
|
||||
*.pkg
|
||||
.env.production
|
||||
.env.product
|
||||
|
||||
### macOS
|
||||
# Finder metadata
|
||||
|
||||
@@ -35,7 +35,7 @@ src/
|
||||
│ ├── file-utils.js # Shared main-process file helpers: image listing/data URLs,
|
||||
│ │ # exists check, EXDEV-safe move/copy fallback
|
||||
│ ├── folder.js # select-folder, list-directories, get/set-last-folder,
|
||||
│ │ # get/set-preview-on, get/set-sidebar-width,
|
||||
│ │ # get/set-preview-on, get/set-sidebar-width, get/set-thumb-size,
|
||||
│ │ # get-home-dir, reveal-folder
|
||||
│ └── files.js # list-images, read-image, rename-files, stage/restore-delete,
|
||||
│ # check-files-exist, move-files
|
||||
@@ -90,6 +90,7 @@ Defined in `src/main/ipc/`, bridged via `src/preload/index.js` as `window.api`:
|
||||
| `get-last-folder` / `set-last-folder` | invoke | Persists last opened folder in userData config.json |
|
||||
| `get-preview-on` / `set-preview-on` | invoke | Persists hover preview toggle state in userData config.json |
|
||||
| `get-sidebar-width` / `set-sidebar-width` | invoke | Persists sidebar width in userData config.json |
|
||||
| `get-thumb-size` / `set-thumb-size` | invoke | Persists thumbnail/card size in userData config.json |
|
||||
| `reveal-folder` | invoke | Opens folder in Finder via `shell.openPath` |
|
||||
| `fullscreen-change` | push (main→renderer) | Sent on `enter-full-screen` / `leave-full-screen` window events |
|
||||
|
||||
@@ -184,10 +185,12 @@ Global shortcuts are registered in `keyboard.js`. They do not fire while any `.m
|
||||
|
||||
**Sidebar tree** (`file-explorer.js`): Lazy-load 자식 폴더. 접근 불가 폴더는 `null` 반환 → "접근 불가" 표시. 행 클릭 시 항상 펼침, 화살표 클릭 시 토글.
|
||||
|
||||
**Thumbnail size** (`pinch-zoom.js`): Ctrl+scroll changes CSS `--thumb-size` in the 150–400px range. The latest size is persisted as `thumbSize` in config.json and restored on startup without showing the size hint.
|
||||
|
||||
**Styling:** CSS는 `src/renderer/style.css`에 집중. Dark theme, CSS variables in `:root`. macOS traffic-light은 `header`의 `-webkit-app-region: drag`. Avoid inline styles in `index.html`; use small semantic classes such as `.format-arrow`, `.empty-state-hint`, and modal title variants.
|
||||
|
||||
**App icon:** 소스는 `assets/icon.png` (2048×2048). `assets/icon.icns`는 10개 해상도(16px~1024px@2x)를 포함하며 electron-builder가 사용. `app.setName('Photo Renamer')`로 개발 모드 메뉴바 이름도 지정.
|
||||
|
||||
**Build output:** `electron-builder` targets DMG for arm64 and x64.
|
||||
|
||||
**Config persistence:** `app.getPath('userData')/config.json`에 `lastFolder`, `previewOn`, `sidebarWidth`, `windowState` 저장. `windowState` includes fullscreen status and normal window bounds (`x`, `y`, `width`, `height`) and is restored on startup if the saved bounds overlap an available display.
|
||||
**Config persistence:** `app.getPath('userData')/config.json`에 `lastFolder`, `previewOn`, `sidebarWidth`, `thumbSize`, `windowState` 저장. `windowState` includes fullscreen status and normal window bounds (`x`, `y`, `width`, `height`) and is restored on startup if the saved bounds overlap an available display.
|
||||
|
||||
+4
-3
@@ -4,9 +4,10 @@
|
||||
"description": "사진 파일 이름 정리 앱",
|
||||
"main": "src/main/index.js",
|
||||
"scripts": {
|
||||
"start": "electron .",
|
||||
"build": "electron-builder --mac --dir",
|
||||
"dist": "electron-builder --mac"
|
||||
"start": "NODE_ENV=development electron .",
|
||||
"debug": "NODE_ENV=development electron --inspect=9229 --remote-debugging-port=9222 . 2>&1 | tee debug.log",
|
||||
"build": "NODE_ENV=production electron-builder --mac --dir",
|
||||
"dist": "NODE_ENV=production electron-builder --mac"
|
||||
},
|
||||
"build": {
|
||||
"appId": "com.photorenamer.app",
|
||||
|
||||
+5
-3
@@ -2,11 +2,13 @@ const { app } = require('electron');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
|
||||
const configPath = path.join(app.getPath('userData'), 'config.json');
|
||||
function getConfigPath() {
|
||||
return path.join(app.getPath('userData'), 'config.json');
|
||||
}
|
||||
|
||||
function readConfig() {
|
||||
try {
|
||||
return JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
||||
return JSON.parse(fs.readFileSync(getConfigPath(), 'utf8'));
|
||||
} catch {
|
||||
return {};
|
||||
}
|
||||
@@ -14,7 +16,7 @@ function readConfig() {
|
||||
|
||||
function writeConfig(data) {
|
||||
try {
|
||||
fs.writeFileSync(configPath, JSON.stringify(data, null, 2));
|
||||
fs.writeFileSync(getConfigPath(), JSON.stringify(data, null, 2));
|
||||
} catch {}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
const nodeEnv = normalizeNodeEnv(process.env.NODE_ENV || 'production');
|
||||
const isDevelopment = nodeEnv === 'development';
|
||||
const isProduction = nodeEnv === 'production';
|
||||
const logEnabled = isDevelopment && parseBoolean(process.env.PHOTO_RENAMER_LOGS, true);
|
||||
|
||||
module.exports = {
|
||||
isDevelopment,
|
||||
isProduction,
|
||||
logEnabled,
|
||||
nodeEnv,
|
||||
};
|
||||
|
||||
function normalizeNodeEnv(value) {
|
||||
return value === 'product' ? 'production' : value;
|
||||
}
|
||||
|
||||
function parseBoolean(value, fallback) {
|
||||
if (value === undefined) return fallback;
|
||||
return ['1', 'true', 'yes', 'on'].includes(String(value).toLowerCase());
|
||||
}
|
||||
@@ -2,6 +2,7 @@ const { app, BrowserWindow, screen } = require('electron');
|
||||
const path = require('path');
|
||||
const { registerAll } = require('./ipc');
|
||||
const { readConfig, updateConfig } = require('./config');
|
||||
const logger = require('./logger');
|
||||
|
||||
app.setName('Photo Renamer');
|
||||
|
||||
@@ -9,6 +10,7 @@ let mainWindow;
|
||||
|
||||
function createWindow() {
|
||||
const windowState = getSavedWindowState();
|
||||
logger.log('main', 'createWindow', windowState);
|
||||
mainWindow = new BrowserWindow({
|
||||
x: windowState.bounds?.x,
|
||||
y: windowState.bounds?.y,
|
||||
@@ -30,6 +32,7 @@ function createWindow() {
|
||||
mainWindow.loadFile(path.join(__dirname, '../renderer/index.html'));
|
||||
|
||||
mainWindow.once('ready-to-show', () => {
|
||||
logger.log('main', 'ready-to-show');
|
||||
mainWindow.show();
|
||||
if (windowState.isFullScreen) mainWindow.setFullScreen(true);
|
||||
});
|
||||
@@ -79,8 +82,10 @@ function saveWindowState() {
|
||||
}
|
||||
|
||||
app.whenReady().then(() => {
|
||||
logger.log('main', 'app ready');
|
||||
createWindow();
|
||||
registerAll(mainWindow);
|
||||
logger.log('main', 'ipc registered');
|
||||
});
|
||||
|
||||
app.on('window-all-closed', () => {
|
||||
|
||||
@@ -8,12 +8,17 @@ const {
|
||||
moveFileWithCopyFallback,
|
||||
readImageAsDataUrl,
|
||||
} = require('./file-utils');
|
||||
const logger = require('../logger');
|
||||
|
||||
function register() {
|
||||
ipcMain.handle('list-images', async (_event, folderPath) => {
|
||||
logger.log('ipc:files', 'list-images requested', { folderPath });
|
||||
try {
|
||||
return listImages(folderPath);
|
||||
const files = listImages(folderPath);
|
||||
logger.log('ipc:files', 'list-images result', { folderPath, count: files.length });
|
||||
return files;
|
||||
} catch (e) {
|
||||
logger.error('ipc:files', `list-images failed: ${folderPath}`, e);
|
||||
return [];
|
||||
}
|
||||
});
|
||||
|
||||
+24
-1
@@ -3,13 +3,19 @@ const path = require('path');
|
||||
const fs = require('fs');
|
||||
const os = require('os');
|
||||
const { readConfig, updateConfig } = require('../config');
|
||||
const logger = require('../logger');
|
||||
|
||||
function register(mainWindow) {
|
||||
ipcMain.handle('select-folder', async () => {
|
||||
logger.log('ipc:folder', 'select-folder requested');
|
||||
const result = await dialog.showOpenDialog(mainWindow, {
|
||||
properties: ['openDirectory'],
|
||||
title: '이미지 폴더 선택',
|
||||
});
|
||||
logger.log('ipc:folder', 'select-folder result', {
|
||||
canceled: result.canceled,
|
||||
filePaths: result.filePaths,
|
||||
});
|
||||
if (result.canceled) return null;
|
||||
return result.filePaths[0];
|
||||
});
|
||||
@@ -18,13 +24,21 @@ function register(mainWindow) {
|
||||
|
||||
ipcMain.handle('get-last-folder', () => {
|
||||
const cfg = readConfig();
|
||||
logger.log('ipc:folder', 'get-last-folder config', { lastFolder: cfg.lastFolder || null });
|
||||
if (cfg.lastFolder) {
|
||||
try { fs.accessSync(cfg.lastFolder); return cfg.lastFolder; } catch {}
|
||||
try {
|
||||
fs.accessSync(cfg.lastFolder);
|
||||
logger.log('ipc:folder', 'get-last-folder accessible', { lastFolder: cfg.lastFolder });
|
||||
return cfg.lastFolder;
|
||||
} catch (e) {
|
||||
logger.error('ipc:folder', `get-last-folder inaccessible: ${cfg.lastFolder}`, e);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
||||
ipcMain.handle('set-last-folder', (_e, folderPath) => {
|
||||
logger.log('ipc:folder', 'set-last-folder', { folderPath });
|
||||
updateConfig(cfg => { cfg.lastFolder = folderPath; });
|
||||
});
|
||||
|
||||
@@ -65,6 +79,15 @@ function register(mainWindow) {
|
||||
ipcMain.handle('set-sidebar-width', (_e, width) => {
|
||||
updateConfig(cfg => { cfg.sidebarWidth = width; });
|
||||
});
|
||||
|
||||
ipcMain.handle('get-thumb-size', () => {
|
||||
const cfg = readConfig();
|
||||
return Number.isFinite(cfg.thumbSize) ? cfg.thumbSize : null;
|
||||
});
|
||||
|
||||
ipcMain.handle('set-thumb-size', (_e, size) => {
|
||||
updateConfig(cfg => { cfg.thumbSize = size; });
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = { register };
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
const { logEnabled } = require('./env');
|
||||
|
||||
function log(scope, message, data) {
|
||||
if (!logEnabled) return;
|
||||
const suffix = data === undefined ? '' : ` ${format(data)}`;
|
||||
console.log(`[PhotoRenamer:${scope}] ${message}${suffix}`);
|
||||
}
|
||||
|
||||
function error(scope, message, err) {
|
||||
if (!logEnabled) return;
|
||||
const detail = err && err.stack ? err.stack : err;
|
||||
console.error(`[PhotoRenamer:${scope}] ${message}`, detail);
|
||||
}
|
||||
|
||||
function format(data) {
|
||||
try {
|
||||
return JSON.stringify(data);
|
||||
} catch {
|
||||
return String(data);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { error, log };
|
||||
@@ -1,6 +1,14 @@
|
||||
const { contextBridge, ipcRenderer } = require('electron');
|
||||
|
||||
const env = getPreloadEnv();
|
||||
|
||||
contextBridge.exposeInMainWorld('api', {
|
||||
env: Object.freeze({
|
||||
isDevelopment: env.isDevelopment,
|
||||
isProduction: env.isProduction,
|
||||
logEnabled: env.logEnabled,
|
||||
nodeEnv: env.nodeEnv,
|
||||
}),
|
||||
selectFolder: () => ipcRenderer.invoke('select-folder'),
|
||||
listImages: (folder) => ipcRenderer.invoke('list-images', folder),
|
||||
readImage: (path) => ipcRenderer.invoke('read-image', path),
|
||||
@@ -19,8 +27,28 @@ contextBridge.exposeInMainWorld('api', {
|
||||
setPreviewOn: (on) => ipcRenderer.invoke('set-preview-on', on),
|
||||
getSidebarWidth: () => ipcRenderer.invoke('get-sidebar-width'),
|
||||
setSidebarWidth: (width) => ipcRenderer.invoke('set-sidebar-width', width),
|
||||
getThumbSize: () => ipcRenderer.invoke('get-thumb-size'),
|
||||
setThumbSize: (size) => ipcRenderer.invoke('set-thumb-size', size),
|
||||
checkFilesExist: (folder, names) => ipcRenderer.invoke('check-files-exist', folder, names),
|
||||
moveFiles: (args) => ipcRenderer.invoke('move-files', args),
|
||||
stageDeleteFiles: (paths) => ipcRenderer.invoke('stage-delete-files', paths),
|
||||
restoreStagedFiles: (entries) => ipcRenderer.invoke('restore-staged-files', entries),
|
||||
});
|
||||
|
||||
function getPreloadEnv() {
|
||||
const nodeEnv = normalizeNodeEnv(process.env.NODE_ENV || 'production');
|
||||
const isDevelopment = nodeEnv === 'development';
|
||||
const isProduction = nodeEnv === 'production';
|
||||
const logEnabled = isDevelopment && parseBoolean(process.env.PHOTO_RENAMER_LOGS, true);
|
||||
|
||||
return { isDevelopment, isProduction, logEnabled, nodeEnv };
|
||||
}
|
||||
|
||||
function normalizeNodeEnv(value) {
|
||||
return value === 'product' ? 'production' : value;
|
||||
}
|
||||
|
||||
function parseBoolean(value, fallback) {
|
||||
if (value === undefined) return fallback;
|
||||
return ['1', 'true', 'yes', 'on'].includes(String(value).toLowerCase());
|
||||
}
|
||||
|
||||
@@ -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