sidebar-resizer 추가

This commit is contained in:
2026-05-17 23:30:31 +09:00
parent 2fc48f75ff
commit 3d0943b6fe
7 changed files with 116 additions and 5 deletions
+4 -1
View File
@@ -34,7 +34,8 @@ src/
│ ├── file-utils.js # Shared main-process file helpers: image listing/data URLs, │ ├── file-utils.js # Shared main-process file helpers: image listing/data URLs,
│ │ # exists check, EXDEV-safe move/copy fallback │ │ # exists check, EXDEV-safe move/copy fallback
│ ├── folder.js # select-folder, list-directories, get/set-last-folder, │ ├── folder.js # select-folder, list-directories, get/set-last-folder,
│ │ # get/set-preview-on, get-home-dir, reveal-folder │ │ # get/set-preview-on, get/set-sidebar-width,
│ │ # get-home-dir, reveal-folder
│ └── files.js # list-images, read-image, rename-files, stage/restore-delete, │ └── files.js # list-images, read-image, rename-files, stage/restore-delete,
│ # check-files-exist, move-files │ # check-files-exist, move-files
├── preload/ ├── preload/
@@ -63,6 +64,7 @@ src/
│ ├── auto-scroll.js # Drag auto-scroll helper │ ├── auto-scroll.js # Drag auto-scroll helper
│ └── ghost.js # Drag ghost UI helper │ └── ghost.js # Drag ghost UI helper
├── file-explorer.js # Sidebar directory tree (lazy-load, expand/collapse) ├── file-explorer.js # Sidebar directory tree (lazy-load, expand/collapse)
├── sidebar-resize.js # Pointer-drag resizing for the aside/main split; persisted 340500px width
├── selection.js # Rubber-band, Shift-click, Cmd+click, resetAnchor ├── selection.js # Rubber-band, Shift-click, Cmd+click, resetAnchor
├── pinch-zoom.js # Ctrl+scroll to resize thumbnails (CSS --thumb-size, 150400px) ├── pinch-zoom.js # Ctrl+scroll to resize thumbnails (CSS --thumb-size, 150400px)
└── image-preview.js # Hover preview overlay with circular loading indicator └── image-preview.js # Hover preview overlay with circular loading indicator
@@ -86,6 +88,7 @@ Defined in `src/main/ipc/`, bridged via `src/preload/index.js` as `window.api`:
| `get-home-dir` | invoke | Returns `os.homedir()` | | `get-home-dir` | invoke | Returns `os.homedir()` |
| `get-last-folder` / `set-last-folder` | invoke | Persists last opened folder in userData config.json | | `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-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 |
| `reveal-folder` | invoke | Opens folder in Finder via `shell.openPath` | | `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 | | `fullscreen-change` | push (main→renderer) | Sent on `enter-full-screen` / `leave-full-screen` window events |
+11
View File
@@ -68,6 +68,17 @@ function register(mainWindow) {
cfg.previewOn = on; cfg.previewOn = on;
writeConfig(cfg); writeConfig(cfg);
}); });
ipcMain.handle('get-sidebar-width', () => {
const cfg = readConfig();
return Number.isFinite(cfg.sidebarWidth) ? cfg.sidebarWidth : null;
});
ipcMain.handle('set-sidebar-width', (_e, width) => {
const cfg = readConfig();
cfg.sidebarWidth = width;
writeConfig(cfg);
});
} }
module.exports = { register }; module.exports = { register };
+2
View File
@@ -17,6 +17,8 @@ contextBridge.exposeInMainWorld('api', {
revealFolder: (p) => ipcRenderer.invoke('reveal-folder', p), revealFolder: (p) => ipcRenderer.invoke('reveal-folder', p),
getPreviewOn: () => ipcRenderer.invoke('get-preview-on'), getPreviewOn: () => ipcRenderer.invoke('get-preview-on'),
setPreviewOn: (on) => ipcRenderer.invoke('set-preview-on', on), setPreviewOn: (on) => ipcRenderer.invoke('set-preview-on', on),
getSidebarWidth: () => ipcRenderer.invoke('get-sidebar-width'),
setSidebarWidth: (width) => ipcRenderer.invoke('set-sidebar-width', width),
checkFilesExist: (folder, names) => ipcRenderer.invoke('check-files-exist', folder, names), checkFilesExist: (folder, names) => ipcRenderer.invoke('check-files-exist', folder, names),
moveFiles: (args) => ipcRenderer.invoke('move-files', args), moveFiles: (args) => ipcRenderer.invoke('move-files', args),
stageDeleteFiles: (paths) => ipcRenderer.invoke('stage-delete-files', paths), stageDeleteFiles: (paths) => ipcRenderer.invoke('stage-delete-files', paths),
+54
View File
@@ -0,0 +1,54 @@
const MIN_WIDTH = 340;
const MAX_WIDTH = 500;
let _aside = null;
let _handle = null;
let _onResizeEnd = null;
export function init({ aside, handle, initialWidth = MIN_WIDTH, onResizeEnd }) {
_aside = aside;
_handle = handle;
_onResizeEnd = onResizeEnd;
setWidth(initialWidth);
_handle.addEventListener('pointerdown', onPointerDown);
}
function onPointerDown(e) {
e.preventDefault();
_handle.setPointerCapture(e.pointerId);
document.body.classList.add('resizing-sidebar');
_handle.addEventListener('pointermove', onPointerMove);
_handle.addEventListener('pointerup', onPointerUp);
_handle.addEventListener('pointercancel', onPointerUp);
}
function onPointerMove(e) {
const left = _aside.getBoundingClientRect().left;
setWidth(e.clientX - left);
}
function onPointerUp(e) {
if (_handle.hasPointerCapture(e.pointerId)) {
_handle.releasePointerCapture(e.pointerId);
}
document.body.classList.remove('resizing-sidebar');
_handle.removeEventListener('pointermove', onPointerMove);
_handle.removeEventListener('pointerup', onPointerUp);
_handle.removeEventListener('pointercancel', onPointerUp);
_onResizeEnd?.(getWidth());
}
function setWidth(width) {
_aside.style.width = `${clampWidth(width)}px`;
}
function getWidth() {
return Math.round(_aside.getBoundingClientRect().width);
}
function clampWidth(width) {
return Math.max(MIN_WIDTH, Math.min(MAX_WIDTH, Number(width) || MIN_WIDTH));
}
+2
View File
@@ -24,6 +24,8 @@
</section> </section>
</aside> </aside>
<div id="sidebar-resizer" aria-hidden="true"></div>
<main> <main>
<nav class="content-nav"> <nav class="content-nav">
<div class="sort-group"> <div class="sort-group">
+11 -1
View File
@@ -17,6 +17,7 @@ import * as selection from './handlers/selection.js';
import * as dragDrop from './handlers/drag-drop.js'; import * as dragDrop from './handlers/drag-drop.js';
import * as hoverPreview from './handlers/image-preview.js'; import * as hoverPreview from './handlers/image-preview.js';
import * as fileExplorer from './handlers/file-explorer.js'; import * as fileExplorer from './handlers/file-explorer.js';
import * as sidebarResize from './handlers/sidebar-resize.js';
// ── DOM ────────────────────────────────────────────── // ── DOM ──────────────────────────────────────────────
const btnOpen = document.getElementById('btn-open'); const btnOpen = document.getElementById('btn-open');
@@ -36,6 +37,8 @@ const btnPreviewToggle = document.getElementById('btn-preview-toggle');
const btnUndo = document.getElementById('btn-undo'); const btnUndo = document.getElementById('btn-undo');
const btnRedo = document.getElementById('btn-redo'); const btnRedo = document.getElementById('btn-redo');
const fileExplorerContainer = document.querySelector('.file-explorer'); const fileExplorerContainer = document.querySelector('.file-explorer');
const aside = document.querySelector('aside');
const sidebarResizer = document.getElementById('sidebar-resizer');
const folderController = createFolderController({ const folderController = createFolderController({
btnRename, btnRename,
@@ -74,15 +77,22 @@ window.api.onFullscreenChange((isFullscreen) => {
// ── 앱 시작 ─────────────────────────────────────── // ── 앱 시작 ───────────────────────────────────────
(async () => { (async () => {
const [lastFolder, homeDir, previewOn] = await Promise.all([ const [lastFolder, homeDir, previewOn, sidebarWidth] = await Promise.all([
window.api.getLastFolder(), window.api.getLastFolder(),
window.api.getHomeDir(), window.api.getHomeDir(),
window.api.getPreviewOn(), window.api.getPreviewOn(),
window.api.getSidebarWidth(),
]); ]);
folderController.setHomeDir(homeDir); folderController.setHomeDir(homeDir);
btnPreviewToggle.classList.toggle('active', previewOn); btnPreviewToggle.classList.toggle('active', previewOn);
hoverPreview.setEnabled(previewOn); hoverPreview.setEnabled(previewOn);
sidebarResize.init({
aside,
handle: sidebarResizer,
initialWidth: sidebarWidth,
onResizeEnd: width => window.api.setSidebarWidth(width),
});
const target = lastFolder || homeDir; const target = lastFolder || homeDir;
await fileExplorer.loadTree(homeDir, target); await fileExplorer.loadTree(homeDir, target);
+31 -2
View File
@@ -83,14 +83,43 @@ header h1 {
/* ── 사이드바 ── */ /* ── 사이드바 ── */
aside { aside {
width: 300px; width: 340px;
min-width: 340px;
max-width: 500px;
flex-shrink: 0; flex-shrink: 0;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
border-right: 1px solid var(--border);
background: var(--surface); background: var(--surface);
overflow: hidden; overflow: hidden;
} }
#sidebar-resizer {
width: 7px;
flex: 0 0 7px;
margin-left: -3px;
margin-right: -4px;
cursor: col-resize;
position: relative;
z-index: 30;
}
#sidebar-resizer::before {
content: "";
position: absolute;
top: 0;
bottom: 0;
left: 3px;
width: 1px;
background: var(--border);
}
#sidebar-resizer:hover::before,
body.resizing-sidebar #sidebar-resizer::before {
left: 2px;
width: 3px;
background: var(--accent);
}
body.resizing-sidebar {
cursor: col-resize;
user-select: none;
}
.sidebar-nav { .sidebar-nav {
display: flex; display: flex;
flex-direction: row; flex-direction: row;