Files
Krunker-Civilian-Client-Test/src/main/css-themes.ts
T

134 lines
4.1 KiB
TypeScript

// ── CSS theme & loading screen background management ──
// Scans swap directory for user CSS themes and loading screen backgrounds.
import { readdirSync, readFileSync } from 'fs';
import { join, extname, basename } from 'path';
export interface ThemeEntry {
id: string;
label: string;
}
export interface LoadingThemeEntry {
id: string;
label: string;
}
export function listThemes(swapDir: string): ThemeEntry[] {
const entries: ThemeEntry[] = [{ id: 'disabled', label: 'Disabled' }];
const themesDir = join(swapDir, 'themes');
try {
const files = readdirSync(themesDir);
for (const file of files) {
if (extname(file).toLowerCase() === '.css') {
entries.push({ id: `user:${file}`, label: basename(file, '.css') });
}
}
} catch { /* themes dir doesn't exist yet — that's fine */ }
return entries;
}
export function getThemeCSS(themeId: string, swapDir: string): string {
if (themeId === 'disabled' || !themeId) return '';
const prefix = 'user:';
if (!themeId.startsWith(prefix)) return '';
const filename = basename(themeId.slice(prefix.length));
if (!filename) return '';
try {
return readFileSync(join(swapDir, 'themes', filename), 'utf-8');
} catch { return ''; }
}
const IMAGE_EXTS = new Set(['.png', '.jpg', '.jpeg', '.gif', '.webp']);
export function listLoadingThemes(swapDir: string): LoadingThemeEntry[] {
const entries: LoadingThemeEntry[] = [
{ id: 'disabled', label: 'Disabled (Default)' },
{ id: 'swap:random', label: 'Random (from backgrounds/)' },
];
const bgDir = join(swapDir, 'backgrounds');
try {
const files = readdirSync(bgDir);
for (const file of files) {
if (IMAGE_EXTS.has(extname(file).toLowerCase())) {
entries.push({ id: `swap:${file}`, label: file });
}
}
} catch { /* backgrounds dir doesn't exist yet */ }
return entries;
}
function mimeFromExt(ext: string): string {
switch (ext.toLowerCase()) {
case '.jpg':
case '.jpeg':
return 'image/jpeg';
case '.gif':
return 'image/gif';
case '.webp':
return 'image/webp';
default:
return 'image/png';
}
}
function getBackgroundFiles(swapDir: string): string[] {
const bgDir = join(swapDir, 'backgrounds');
try {
return readdirSync(bgDir).filter(f => IMAGE_EXTS.has(extname(f).toLowerCase()));
} catch { return []; }
}
function fileToDataUri(filePath: string): string {
const data = readFileSync(filePath);
const mime = mimeFromExt(extname(filePath));
return `data:${mime};base64,${data.toString('base64')}`;
}
export function getLoadingScreenCSS(loadingTheme: string, backgroundUrl: string, swapDir: string): string {
let imageUrl = '';
// Explicit URL takes priority
if (backgroundUrl) {
try {
new URL(backgroundUrl);
imageUrl = `url(${backgroundUrl})`;
} catch { /* invalid URL — ignore */ }
}
if (!imageUrl && loadingTheme && loadingTheme !== 'disabled') {
const bgDir = join(swapDir, 'backgrounds');
if (loadingTheme === 'swap:random') {
const files = getBackgroundFiles(swapDir);
if (files.length > 0) {
const pick = files[Math.floor(Math.random() * files.length)];
try {
imageUrl = `url(${fileToDataUri(join(bgDir, pick))})`;
} catch { /* read failed */ }
}
} else if (loadingTheme.startsWith('swap:')) {
const filename = basename(loadingTheme.slice(5));
if (!filename) return '';
try {
imageUrl = `url(${fileToDataUri(join(bgDir, filename))})`;
} catch { /* read failed */ }
}
}
if (!imageUrl) return '';
return `
#instructionHolder[style^="display: block"] {
background-image: initial !important;
}
#instructionHolder {
background-image: ${imageUrl} !important;
background-size: cover !important;
background-position: center !important;
}
#instructions {
display: block;
visibility: hidden;
}`;
}