Add video sorting, reference thumbnails, theme/i18n, menu, DnD/paste, presets

Feature work on top of the initial organizer:

- Videos: .mp4/.mov/.avi/.mkv/.webm/.m4v sorted into output/Movie/YYYY/MM
- Profiles: reference-image thumbnail cards via a secure photoai-media:// protocol
  (serves only registered reference images); per-thumbnail delete; add via
  click, drag & drop, or paste (Ctrl+V) using webUtils.getPathForFile + a
  byte-based addReferenceData path for clipboard images
- Presets: local person library (max 5) saved to userData; one-click apply into
  active profiles; reusing stored descriptors (no recompute)
- Theme: dark/light with dark default (Tailwind class strategy)
- i18n: ko/en table-based localization; first-run onboarding to pick
  language + theme; English-neutral "Unsorted" folder (was [미정])
- App menu: File/Edit/View/Window/Help, localized; Help opens a detailed,
  theme-aware user guide window
- UI: History block scrolls internally (no whole-window scroll);
  threshold/concurrency tooltips; generic example name
- Settings persisted to userData/settings.json; menu + renderer kept in sync
- Docs: NextGen Photo AI feasibility review + Phase 0/1 roadmap

All typecheck/tests (12) /build green; boot smoke verified.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-01 15:40:44 +09:00
parent 8a8c10248c
commit 6dce580846
38 changed files with 1916 additions and 212 deletions
+50
View File
@@ -0,0 +1,50 @@
import { app } from 'electron'
import { readFile, writeFile, mkdir } from 'node:fs/promises'
import { join } from 'node:path'
import type { Settings } from '@shared/types'
import { SETTINGS_FILE } from '@shared/constants'
import { DEFAULT_LANG } from '@shared/i18n'
const DEFAULTS: Settings = {
language: DEFAULT_LANG, // 기본 한국어
theme: 'dark', // 기본 다크모드
onboarded: false
}
/** 앱 설정(언어/테마/온보딩) 영속화. userData/settings.json */
class SettingsStore {
private settings: Settings = { ...DEFAULTS }
private loaded = false
private filePath(): string {
return join(app.getPath('userData'), SETTINGS_FILE)
}
async load(): Promise<Settings> {
if (this.loaded) return this.settings
try {
const raw = await readFile(this.filePath(), 'utf-8')
const parsed = JSON.parse(raw) as Partial<Settings>
this.settings = { ...DEFAULTS, ...parsed }
} catch {
this.settings = { ...DEFAULTS }
}
this.loaded = true
return this.settings
}
/** 동기 접근(load 이후). 메뉴 빌드 등에서 사용 */
current(): Settings {
return this.settings
}
async set(patch: Partial<Settings>): Promise<Settings> {
await this.load()
this.settings = { ...this.settings, ...patch }
await mkdir(app.getPath('userData'), { recursive: true })
await writeFile(this.filePath(), JSON.stringify(this.settings, null, 2), 'utf-8')
return this.settings
}
}
export const settingsStore = new SettingsStore()