Add photo frame idle mode and switch to Skylight-style theme
- After 5 min of no touch/motion, dashboard hides behind a fullscreen photo slideshow with centered time and date overlay - Photos loaded from PHOTOS_PATH env var (defaults to ~/Pictures/dashboard) via IPC + file:// URLs; traversal-guarded, recursive up to 2 levels - Motion or touch exits idle back to dashboard - Theme repainted warm cream / sage / stone ink with Nunito font and rounded cards; dark tokens kept so component classes still resolve - Adds PHOTO_FRAME.md with Samba cifs mount + systemd env instructions
This commit is contained in:
53
electron/services/PhotoManager.ts
Normal file
53
electron/services/PhotoManager.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user