sidebar-resizer 추가
This commit is contained in:
@@ -34,7 +34,8 @@ 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-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,
|
||||
│ # check-files-exist, move-files
|
||||
├── preload/
|
||||
@@ -63,6 +64,7 @@ src/
|
||||
│ ├── auto-scroll.js # Drag auto-scroll helper
|
||||
│ └── ghost.js # Drag ghost UI helper
|
||||
├── file-explorer.js # Sidebar directory tree (lazy-load, expand/collapse)
|
||||
├── sidebar-resize.js # Pointer-drag resizing for the aside/main split; persisted 340–500px width
|
||||
├── selection.js # Rubber-band, Shift-click, Cmd+click, resetAnchor
|
||||
├── pinch-zoom.js # Ctrl+scroll to resize thumbnails (CSS --thumb-size, 150–400px)
|
||||
└── 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-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 |
|
||||
| `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 |
|
||||
|
||||
|
||||
@@ -68,6 +68,17 @@ function register(mainWindow) {
|
||||
cfg.previewOn = on;
|
||||
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 };
|
||||
@@ -17,6 +17,8 @@ contextBridge.exposeInMainWorld('api', {
|
||||
revealFolder: (p) => ipcRenderer.invoke('reveal-folder', p),
|
||||
getPreviewOn: () => ipcRenderer.invoke('get-preview-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),
|
||||
moveFiles: (args) => ipcRenderer.invoke('move-files', args),
|
||||
stageDeleteFiles: (paths) => ipcRenderer.invoke('stage-delete-files', paths),
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
@@ -24,6 +24,8 @@
|
||||
</section>
|
||||
</aside>
|
||||
|
||||
<div id="sidebar-resizer" aria-hidden="true"></div>
|
||||
|
||||
<main>
|
||||
<nav class="content-nav">
|
||||
<div class="sort-group">
|
||||
|
||||
@@ -17,6 +17,7 @@ import * as selection from './handlers/selection.js';
|
||||
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';
|
||||
|
||||
// ── DOM ──────────────────────────────────────────────
|
||||
const btnOpen = document.getElementById('btn-open');
|
||||
@@ -36,6 +37,8 @@ const btnPreviewToggle = document.getElementById('btn-preview-toggle');
|
||||
const btnUndo = document.getElementById('btn-undo');
|
||||
const btnRedo = document.getElementById('btn-redo');
|
||||
const fileExplorerContainer = document.querySelector('.file-explorer');
|
||||
const aside = document.querySelector('aside');
|
||||
const sidebarResizer = document.getElementById('sidebar-resizer');
|
||||
|
||||
const folderController = createFolderController({
|
||||
btnRename,
|
||||
@@ -74,15 +77,22 @@ window.api.onFullscreenChange((isFullscreen) => {
|
||||
|
||||
// ── 앱 시작 ───────────────────────────────────────
|
||||
(async () => {
|
||||
const [lastFolder, homeDir, previewOn] = await Promise.all([
|
||||
const [lastFolder, homeDir, previewOn, sidebarWidth] = await Promise.all([
|
||||
window.api.getLastFolder(),
|
||||
window.api.getHomeDir(),
|
||||
window.api.getPreviewOn(),
|
||||
window.api.getSidebarWidth(),
|
||||
]);
|
||||
folderController.setHomeDir(homeDir);
|
||||
|
||||
btnPreviewToggle.classList.toggle('active', previewOn);
|
||||
hoverPreview.setEnabled(previewOn);
|
||||
sidebarResize.init({
|
||||
aside,
|
||||
handle: sidebarResizer,
|
||||
initialWidth: sidebarWidth,
|
||||
onResizeEnd: width => window.api.setSidebarWidth(width),
|
||||
});
|
||||
|
||||
const target = lastFolder || homeDir;
|
||||
await fileExplorer.loadTree(homeDir, target);
|
||||
|
||||
+31
-2
@@ -83,14 +83,43 @@ header h1 {
|
||||
|
||||
/* ── 사이드바 ── */
|
||||
aside {
|
||||
width: 300px;
|
||||
width: 340px;
|
||||
min-width: 340px;
|
||||
max-width: 500px;
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border-right: 1px solid var(--border);
|
||||
background: var(--surface);
|
||||
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 {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
Reference in New Issue
Block a user