sidebar-resizer 추가
This commit is contained in:
@@ -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 340–500px 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, 150–400px)
|
├── pinch-zoom.js # Ctrl+scroll to resize thumbnails (CSS --thumb-size, 150–400px)
|
||||||
└── 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 |
|
||||||
|
|
||||||
|
|||||||
@@ -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 };
|
||||||
@@ -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),
|
||||||
|
|||||||
@@ -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>
|
</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">
|
||||||
|
|||||||
@@ -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
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user