From aa55a42988fda90deb1457c64df786675a718b88 Mon Sep 17 00:00:00 2001 From: jisangs Date: Fri, 22 May 2026 17:10:57 +0900 Subject: [PATCH] =?UTF-8?q?=EB=A6=AC=ED=8E=99=ED=86=A0=EB=A7=81=20-=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=ED=95=9C=20=EC=9E=91=EC=97=85=20=EC=B5=9C?= =?UTF-8?q?=EC=A0=81=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AGENTS.md | 12 +++++++++--- README.md | 5 ++++- src/renderer/folder-controller.js | 10 +++++++--- src/renderer/style.css | 3 --- 4 files changed, 20 insertions(+), 10 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index f5c8323..c0f84f6 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -35,7 +35,8 @@ src/ │ ├── file-utils.js # Pure helpers: listImages, readImageAsDataUrl, fileExists, │ │ # moveFileWithCopyFallback (EXDEV-safe cross-device move) │ ├── folder.js # select-folder, list-directories, get/set-last-folder, -│ │ # get/set-preview-on, get/set-sidebar-width, get/set-thumb-size, +│ │ # get/set-preview-on, get/set-sidebar-width, +│ │ # get/set-file-explorer-visible, 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 +91,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-file-explorer-visible` / `set-file-explorer-visible` | invoke | Persists file explorer visibility in userData config.json; default is visible | | `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 | @@ -123,6 +125,7 @@ Prefer narrow utility modules (`utils/file-names.js`, `utils/dom.js`) over broad - `Cmd+Z` — Undo (되돌리기) - `Cmd+Shift+Z` — Redo (다시하기) - `Space` — 미리보기 on/off 토글 +- `D` — 파일 탐색기 표시/숨김 토글 - `Backspace` / `Delete` — 선택 파일 삭제 팝업 - `ArrowLeft` / `ArrowRight` / `ArrowUp` / `ArrowDown` — 해당 방향의 카드를 키보드 타겟으로 이동하고 단독 선택 - `Home` / `End` — 첫 카드 / 마지막 카드를 키보드 타겟으로 이동하고 단독 선택 (`grid-container` 최상단 / 최하단으로 스크롤) @@ -172,6 +175,7 @@ Global shortcuts are registered in `keyboard.js`. They do not fire while any `.m - 선택된 파일만 이름 변경 (선택 없으면 전체) - 파일명 포맷은 `input-prefix` + 자리순번 + `input-postfix` + 확장자이다. 구분자 `_`는 자동 삽입하지 않고 입력값 자체에 포함한다. - `input-prefix`의 HTML 기본값은 `photo_`, `input-postfix`의 HTML 기본값은 `_`이다. 접미사를 비우면 접두사와 자리순번만으로 저장한다. +- 폴더 로드 시 이미지 파일 유무와 관계없이 폴더명 뒤에 `_`를 붙여 `input-prefix`로 설정한다. - 상단 파일명 preview, 카드별 새 이름 라벨, rename 확인 목록은 모두 `utils/file-names.js`의 `getNameFormat()`과 `buildSequentialName()`을 사용해 같은 규칙을 공유한다. - 변경될 이름이 미선택 파일과 충돌하면 충돌 목록 모달 표시 - 완료 후 history 리셋 (rename은 파일 경로가 바뀌므로 undo 대상 아님) @@ -192,7 +196,9 @@ Global shortcuts are registered in `keyboard.js`. They do not fire while any `.m **Hover preview** (`image-preview.js`): 활성화 상태에서 카드 hover 시 대기 없이 전체 크기 팝업을 표시한다. 썸네일 캐시 우선 사용. 상태는 config.json에 영속. `Space`로 preview를 켤 때 커서가 카드 위에 있으면 즉시 팝업을 표시하고, 끌 때는 열린 팝업을 즉시 닫는다. -**Sidebar tree** (`file-explorer.js`): Lazy-load 자식 폴더. 접근 불가 폴더는 `null` 반환 → "접근 불가" 표시. 행 클릭 시 항상 펼침, 화살표 클릭 시 토글. 폴더 로드 후 `folder-controller.js`가 폴더명 뒤에 `_`를 붙여 `input-prefix`로 설정한다. +**Workspace / file explorer:** `index.html` uses semantic `section.app-workspace` above the `aside` and `main`. The top `workspace-nav` contains the file explorer visibility toggle, folder open/rename actions, preview/undo/redo controls, and sort group. The file explorer visibility toggle uses the fixed text `🗂️ (D)`, persists as `fileExplorerVisible`, and hides both `aside` and `#sidebar-resizer` via `.file-explorer-hidden`. + +**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. @@ -202,4 +208,4 @@ Global shortcuts are registered in `keyboard.js`. They do not fire while any `.m **Build output:** `electron-builder` targets DMG for arm64 and x64. -**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. +**Config persistence:** `app.getPath('userData')/config.json`에 `lastFolder`, `previewOn`, `sidebarWidth`, `fileExplorerVisible`, `thumbSize`, `windowState` 저장. `previewOn` and `fileExplorerVisible` default to `true`. `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. diff --git a/README.md b/README.md index ca4d914..310ed0b 100644 --- a/README.md +++ b/README.md @@ -5,9 +5,11 @@ macOS용 Electron 데스크톱 앱입니다. 폴더 안의 이미지 파일을 ## 주요 기능 - 이미지 폴더 열기 및 사이드바 폴더 탐색 +- 파일 탐색기 표시/숨김 토글 및 상태 저장 - 썸네일 그리드 표시와 지연 로딩 - 드래그 앤 드롭으로 카드 순서 변경 - 선택 파일 또는 전체 파일의 순차 이름 변경 +- 폴더 선택 시 이미지 유무와 관계없이 폴더명을 파일명 prefix로 자동 적용 - 사이드바 폴더로 파일 이동, 파일명 충돌 시 새 이름으로 이동 - 삭제 파일 staging 및 Undo/Redo - Hover 이미지 미리보기 토글 @@ -57,6 +59,7 @@ npm run dist | `Cmd+Z` | Undo | | `Cmd+Shift+Z` | Redo | | `Space` | Hover 미리보기 on/off | +| `D` | 파일 탐색기 표시/숨김 | | `Backspace` / `Delete` | 선택 파일 삭제 | | `ArrowLeft` / `ArrowRight` / `ArrowUp` / `ArrowDown` | 해당 방향 카드로 이동 및 단독 선택 | | `Home` / `End` | 첫 카드 / 마지막 카드로 이동 및 그리드 최상단 / 최하단 스크롤 | @@ -82,6 +85,6 @@ Electron 보안 설정은 `contextIsolation: true`, `nodeIntegration: false`를 ## 개발 메모 - 테스트와 린트 스크립트는 현재 구성되어 있지 않습니다. -- 설정값은 `app.getPath('userData')/config.json`에 저장됩니다. +- 설정값은 `app.getPath('userData')/config.json`에 저장됩니다. 최근 폴더, Hover 미리보기, 사이드바 폭, 파일 탐색기 표시 상태, 썸네일 크기, 창 위치/크기를 포함합니다. - 삭제 Undo를 위해 삭제된 파일은 `userData/deleted_staging/` 아래에 임시 보관됩니다. - 자세한 코드 작업 규칙과 아키텍처 메모는 `AGENTS.md`를 참고하세요. diff --git a/src/renderer/folder-controller.js b/src/renderer/folder-controller.js index 64a43d8..5226119 100644 --- a/src/renderer/folder-controller.js +++ b/src/renderer/folder-controller.js @@ -43,6 +43,7 @@ export function createFolderController({ dirPath.textContent = folder; await window.api.setLastFolder(folder); await fileExplorer.setSelectedPath(folder); + updateFolderPrefix(folder); showLoading('이미지 목록 불러오는 중...'); let list = []; @@ -56,6 +57,7 @@ export function createFolderController({ state.selectedIdxs.clear(); showEmptyGrid(); btnRename.disabled = true; + updatePreview(); setStatus('이미지 파일 없음', false); log('folder', 'loadFolder empty', { folder }); return; @@ -68,9 +70,6 @@ export function createFolderController({ updateSortButtons(); setStatus(`${state.files.length}개 파일 로드됨`, true); - const folderName = folder.replace(/\\/g, '/').split('/').pop(); - if (folderName) inputPrefix.value = `${folderName}_`; - renderGrid(); updatePreview(); btnRename.disabled = false; @@ -104,6 +103,11 @@ export function createFolderController({ btnSortDate.classList.toggle('active', state.sortMode === 'date'); } + function updateFolderPrefix(folder) { + const folderName = folder.replace(/\\/g, '/').split('/').pop(); + if (folderName) inputPrefix.value = `${folderName}_`; + } + return { loadFolder, openFolder, diff --git a/src/renderer/style.css b/src/renderer/style.css index 667659c..8adf47e 100644 --- a/src/renderer/style.css +++ b/src/renderer/style.css @@ -175,12 +175,9 @@ main { font-family: inherit; font-size: 13px; font-weight: 600; cursor: pointer; transition: all .15s; } -.btn-primary { background: var(--accent); color: #fff; } -.btn-primary:hover { background: #9180ff; transform: translateY(-1px); } .btn-ghost { background: transparent; color: var(--text); border: 1px solid var(--border); } .btn-ghost:hover { background: var(--card); } .btn-ghost:disabled { color: var(--muted); border-color: var(--border); opacity: .4; cursor: not-allowed; } -.btn-ghost.active { border-color: var(--accent); color: var(--accent); } .btn-info { background: #0d2535; color: var(--info); border: 1px solid #1a4060; } .btn-info:hover { background: #132e42; } .btn-info:disabled { opacity: .4; cursor: not-allowed; }