3e73967c7b
UI overhaul to a darktable tone-and-manner and a set of features adapted from
darktable's proven patterns (reimplemented in our Electron/TS stack; no GPL code).
Design reskin:
- Dark neutral-gray palette + amber accent, flat/squared corners, no card shadows,
compact darktable-style top bar (logo + pipe-separated view tabs), denser 15px base
- Done via design tokens (Tailwind slate/brand/radius/shadow remap) — minimal churn
Metadata & collections (Phase A/B):
- exifr now captures GPS + camera; asset table ALTER-migrated (gpsLat/gpsLon/camera,
metaVersion backfill on re-index)
- Collection facet bar (year timeline / camera / color-label) filters the grid
Map & relation finder (Phase C):
- Leaflet + online OSM map tab; geotagged photos as markers
- relationService: related photos by place (GPS<1km) + time (+/-2d) + CLIP similarity
Easy mode (Phase D):
- easyMode setting (menu / onboarding); scales the whole UI (rem) + bigger thumbnails
+ large icon nav with plain labels (4050 accessibility)
Library usability:
- Video thumbnails (representative frame capture in the inference worker)
- Media filter (All / Photos / Videos) to separate them
- Clearer culling labels ("Good shots" / "To cull") + explanation tooltip
- Multi-select tiles -> Export selected to a folder (copy, best-cut extraction) and
Delete to Recycle Bin (shell.trashItem) behind a confirm dialog
- ONNX Runtime wasm bundled locally (offline) via copy-ort-wasm + asarUnpack
Docs: DARKTABLE_REVIEW (feasibility + roadmap A->D). All typecheck/tests/build green;
boot smoke verified each phase.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
53 lines
1.6 KiB
TypeScript
53 lines
1.6 KiB
TypeScript
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, QUALITY_THRESHOLDS } from '@shared/constants'
|
|
import { DEFAULT_LANG } from '@shared/i18n'
|
|
|
|
const DEFAULTS: Settings = {
|
|
language: DEFAULT_LANG, // 기본 한국어
|
|
theme: 'dark', // 기본 다크모드
|
|
onboarded: false,
|
|
qualityThresholds: { ...QUALITY_THRESHOLDS },
|
|
easyMode: 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()
|