diff --git a/AGENTS.md b/AGENTS.md index 7ea580a..30c0613 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -68,7 +68,7 @@ src/ ├── 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 + └── image-preview.js # Hover preview overlay; immediate show/hide when enabled/disabled ``` ### IPC surface @@ -80,7 +80,7 @@ Defined in `src/main/ipc/`, bridged via `src/preload/index.js` as `window.api`: | `select-folder` | invoke | Opens native folder picker, returns path or null | | `list-images` | invoke | Reads directory, returns `{name, path, mtime}[]` filtered to image extensions, sorted by filename | | `read-image` | invoke | Reads file, returns base64 data URL (supports jpg/png/gif/webp/tiff/bmp/heic) | -| `rename-files({folderPath, files, prefix, digits})` | invoke | Two-phase rename: temp names (crypto.randomUUID) first, then final names | +| `rename-files({folderPath, files, prefix, digits, postfix})` | invoke | Two-phase rename: temp names (crypto.randomUUID) first, then final names using `prefix + padded sequence + postfix + ext` | | `check-files-exist(folderPath, names[])` | invoke | Returns subset of names that already exist in folderPath | | `move-files({targetFolder, files[]})` | invoke | Moves files; each file may have optional `newName`; handles cross-device (EXDEV) | | `stage-delete-files(paths[])` | invoke | Moves files to userData deleted_staging for undoable deletion; handles cross-device (EXDEV) | @@ -122,7 +122,7 @@ Prefer narrow utility modules (`utils/file-names.js`, `utils/dom.js`) over broad - `A` — 전체 선택 - `Cmd+Z` — Undo (되돌리기) - `Cmd+Shift+Z` — Redo (다시하기) -- `P` — 미리보기 on/off 토글 +- `Space` — 미리보기 on/off 토글 - `Backspace` / `Delete` — 선택 파일 삭제 팝업 Global shortcuts are registered in `keyboard.js`. They do not fire while any `.modal-overlay` is open. @@ -164,6 +164,9 @@ Global shortcuts are registered in `keyboard.js`. They do not fire while any `.m **Rename** (`rename.js`): - 선택된 파일만 이름 변경 (선택 없으면 전체) +- 파일명 포맷은 `input-prefix` + 자리순번 + `input-postfix` + 확장자이다. 구분자 `_`는 자동 삽입하지 않고 입력값 자체에 포함한다. +- `input-prefix`의 HTML 기본값은 `photo_`, `input-postfix`의 HTML 기본값은 `_`이다. 접미사를 비우면 접두사와 자리순번만으로 저장한다. +- 상단 파일명 preview, 카드별 새 이름 라벨, rename 확인 목록은 모두 `utils/file-names.js`의 `getNameFormat()`과 `buildSequentialName()`을 사용해 같은 규칙을 공유한다. - 변경될 이름이 미선택 파일과 충돌하면 충돌 목록 모달 표시 - 완료 후 history 리셋 (rename은 파일 경로가 바뀌므로 undo 대상 아님) @@ -181,9 +184,9 @@ Global shortcuts are registered in `keyboard.js`. They do not fire while any `.m **Thumbnail loading:** `IntersectionObserver` with `rootMargin: '400px 0px'` — lazy load as cards enter viewport. `entry.target.isConnected` 체크로 폴더 전환 중 스탈 카드 방지. Drag reorder remaps card elements via `card.dataset.filepath` to avoid re-fetching. -**Hover preview** (`image-preview.js`): 1초 지연 후 전체 크기 팝업. 썸네일 캐시 우선 사용. 상태는 config.json에 영속. +**Hover preview** (`image-preview.js`): 활성화 상태에서 카드 hover 시 대기 없이 전체 크기 팝업을 표시한다. 썸네일 캐시 우선 사용. 상태는 config.json에 영속. `Space`로 preview를 켤 때 커서가 카드 위에 있으면 즉시 팝업을 표시하고, 끌 때는 열린 팝업을 즉시 닫는다. -**Sidebar tree** (`file-explorer.js`): Lazy-load 자식 폴더. 접근 불가 폴더는 `null` 반환 → "접근 불가" 표시. 행 클릭 시 항상 펼침, 화살표 클릭 시 토글. +**Sidebar tree** (`file-explorer.js`): Lazy-load 자식 폴더. 접근 불가 폴더는 `null` 반환 → "접근 불가" 표시. 행 클릭 시 항상 펼침, 화살표 클릭 시 토글. 폴더 로드 후 `folder-controller.js`가 폴더명 뒤에 `_`를 붙여 `input-prefix`로 설정한다. **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. diff --git a/src/renderer/renderer.js b/src/renderer/renderer.js index 9df00a3..3ddc845 100644 --- a/src/renderer/renderer.js +++ b/src/renderer/renderer.js @@ -7,7 +7,7 @@ import { import { doRename } from './rename.js'; import { handleFolderDrop } from './move.js'; import { initModalControls } from './modal.js'; -import { clampDigits, getExtension } from './utils/file-names.js'; +import { buildSequentialName, getNameFormat } from './utils/file-names.js'; import { initKeyboardShortcuts } from './keyboard.js'; import { createFolderController } from './folder-controller.js'; import { createHistoryController } from './history-controller.js'; @@ -146,10 +146,8 @@ initKeyboardShortcuts({ // ── 포맷 미리보기 ───────────────────────────────── function updatePreview() { - const prefix = inputPrefix.value.trim() || 'photo_'; - const digits = clampDigits(inputDigits.value); - const postfix = inputPostfix.value.trim(); - const ext = state.files.length ? getExtension(state.files[0].name) : 'jpg'; - previewName.textContent = `${prefix}${'1'.padStart(digits, '0')}${postfix}.${ext}`; + const format = getNameFormat(inputPrefix, inputDigits, 3, inputPostfix); + const fileName = state.files[0]?.name || 'preview.jpg'; + previewName.textContent = buildSequentialName(fileName, 0, format); updateNewNameLabels(); } diff --git a/src/renderer/style.css b/src/renderer/style.css index aab3083..4f85ba8 100644 --- a/src/renderer/style.css +++ b/src/renderer/style.css @@ -443,40 +443,6 @@ footer span { font-size: 11px; color: var(--muted); } @keyframes spin { to { transform: rotate(360deg); } } #loading p { color: var(--text); font-size: 14px; } -/* ── 카드 호버 원형 로딩바 ── */ -.card-hover-loader { - position: absolute; - top: calc(var(--thumb-size) / 2); - left: 50%; - transform: translate(-50%, -50%); - width: 52px; - height: 52px; - pointer-events: none; - z-index: 10; - overflow: visible; -} -.card-hover-loader .loader-bg { - fill: rgba(0, 0, 0, 0.55); -} -.card-hover-loader .loader-track { - fill: none; - stroke: rgba(255, 255, 255, 0.15); - stroke-width: 3; -} -.card-hover-loader .loader-progress { - fill: none; - stroke: var(--accent); - stroke-width: 3; - stroke-linecap: round; - transform-origin: 24px 24px; - transform: rotate(-90deg); - animation: hover-loader-fill 1s linear forwards; -} -@keyframes hover-loader-fill { - from { stroke-dashoffset: 125.66; } - to { stroke-dashoffset: 0; } -} - /* ── 호버 이미지 팝업 ── */ .hover-preview { position: fixed;