import * as fs from 'fs'; import * as path from 'path'; const IMAGE_EXTS = new Set(['.jpg', '.jpeg', '.png', '.webp', '.gif', '.heic', '.heif']); /** * Lists image files from the configured photos directory. * Recurses one level deep so users can organise photos into subfolders. */ export class PhotoManager { constructor(private photosDir: string) {} setDir(dir: string): void { this.photosDir = dir; } getDir(): string { return this.photosDir; } list(): string[] { if (!this.photosDir || !fs.existsSync(this.photosDir)) return []; const files: string[] = []; try { this.walk(this.photosDir, files, 0); } catch (err) { console.error('PhotoManager: failed to list photos', err); } // Return paths relative to photosDir so the renderer can build photo:// URLs return files.map((f) => path.relative(this.photosDir, f)); } private walk(dir: string, out: string[], depth: number): void { if (depth > 2) return; for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { if (entry.name.startsWith('.')) continue; const full = path.join(dir, entry.name); if (entry.isDirectory()) { this.walk(full, out, depth + 1); } else if (entry.isFile() && IMAGE_EXTS.has(path.extname(entry.name).toLowerCase())) { out.push(full); } } } resolve(relative: string): string | null { if (!this.photosDir) return null; const safe = path.normalize(relative).replace(/^(\.\.[\/\\])+/, ''); const full = path.join(this.photosDir, safe); if (!full.startsWith(this.photosDir)) return null; return full; } }