35c61c2496
- Apps tab: new AppEntry type (source: 'app'), apps.json storage, IPC handlers (apps:add/remove/update), square tile layout in grid, blue APP badge - Light/dark theme: CSS variables in :root/.light, ThemeToggle (Sun/Moon), persisted in localStorage, smooth 0.2s transition - Visual overhaul: gradient logo, pill badges, glassmorphism header, gradient play button, modern AdminPanel as slide-in drawer from right - Animations: 3D tilt + glow on hover, bounce on favorite star, ripple on play, slide indicator in CategoryFilter, per-category grid re-animation - AdminPanel: ESC to close, slide-in/out animation, Game/App toggle in add form, separate sections for games and apps Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
106 lines
3.2 KiB
TypeScript
106 lines
3.2 KiB
TypeScript
import { app } from 'electron'
|
|
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs'
|
|
import { join, sep } from 'path'
|
|
import { v4 as uuidv4 } from 'uuid'
|
|
|
|
export interface CustomGame {
|
|
id: string
|
|
name: string
|
|
exe: string
|
|
args: string[]
|
|
image: string
|
|
category: string
|
|
source: 'custom'
|
|
}
|
|
|
|
export interface AppEntry {
|
|
id: string
|
|
name: string
|
|
exe: string
|
|
args: string[]
|
|
image: string
|
|
category: string
|
|
source: 'app'
|
|
}
|
|
|
|
function getUserDataPath(filename: string): string {
|
|
const dir = app.getPath('userData')
|
|
if (!existsSync(dir)) mkdirSync(dir, { recursive: true })
|
|
return join(dir, filename)
|
|
}
|
|
|
|
function readJsonList<T>(filename: string): T[] {
|
|
const path = getUserDataPath(filename)
|
|
if (!existsSync(path)) return []
|
|
try {
|
|
const data = JSON.parse(readFileSync(path, 'utf-8'))
|
|
return Array.isArray(data?.items) ? data.items : []
|
|
} catch {
|
|
return []
|
|
}
|
|
}
|
|
|
|
function writeJsonList<T>(filename: string, items: T[]): void {
|
|
writeFileSync(getUserDataPath(filename), JSON.stringify({ items }, null, 2), 'utf-8')
|
|
}
|
|
|
|
// ── Custom games ─────────────────────────────────────────────────────────
|
|
|
|
export function readCustomGames(): CustomGame[] {
|
|
// Support both old format (games.json with { games: [] }) and new format
|
|
const path = getUserDataPath('games.json')
|
|
if (!existsSync(path)) return []
|
|
try {
|
|
const data = JSON.parse(readFileSync(path, 'utf-8'))
|
|
if (Array.isArray(data?.games)) return data.games // legacy
|
|
if (Array.isArray(data?.items)) return data.items // new
|
|
return []
|
|
} catch {
|
|
return []
|
|
}
|
|
}
|
|
|
|
export function writeCustomGames(games: CustomGame[]): void {
|
|
writeFileSync(getUserDataPath('games.json'), JSON.stringify({ items: games }, null, 2), 'utf-8')
|
|
}
|
|
|
|
export function addCustomGame(data: Omit<CustomGame, 'id' | 'source'>): CustomGame {
|
|
const games = readCustomGames()
|
|
const newGame: CustomGame = { ...data, id: uuidv4(), source: 'custom' }
|
|
writeCustomGames([...games, newGame])
|
|
return newGame
|
|
}
|
|
|
|
export function removeCustomGame(id: string): void {
|
|
writeCustomGames(readCustomGames().filter((g) => g.id !== id))
|
|
}
|
|
|
|
export function updateCustomGame(updated: CustomGame): void {
|
|
writeCustomGames(readCustomGames().map((g) => (g.id === updated.id ? { ...g, ...updated } : g)))
|
|
}
|
|
|
|
// ── App entries ──────────────────────────────────────────────────────────
|
|
|
|
export function readApps(): AppEntry[] {
|
|
return readJsonList<AppEntry>('apps.json')
|
|
}
|
|
|
|
export function writeApps(apps: AppEntry[]): void {
|
|
writeJsonList('apps.json', apps)
|
|
}
|
|
|
|
export function addApp(data: Omit<AppEntry, 'id' | 'source'>): AppEntry {
|
|
const apps = readApps()
|
|
const newApp: AppEntry = { ...data, id: uuidv4(), source: 'app' }
|
|
writeApps([...apps, newApp])
|
|
return newApp
|
|
}
|
|
|
|
export function removeApp(id: string): void {
|
|
writeApps(readApps().filter((a) => a.id !== id))
|
|
}
|
|
|
|
export function updateApp(updated: AppEntry): void {
|
|
writeApps(readApps().map((a) => (a.id === updated.id ? { ...a, ...updated } : a)))
|
|
}
|