Files
club-launcher/electron/config.ts
T
houseassassin 35c61c2496 feat: v1.2.0 — Apps tab, light/dark theme, visual overhaul, animations
- 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>
2026-06-02 12:59:37 +00:00

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)))
}