From ca8b107f61332b1179102fa7919ddf45764a7b7f Mon Sep 17 00:00:00 2001 From: MasterAcnolo <68693319+MasterAcnolo@users.noreply.github.com> Date: Sat, 11 Apr 2026 22:16:05 +0200 Subject: [PATCH] Fix: Display custom these with a basic order --- app/themeManager.js | 209 +++++++++++++++++++++----------------------- 1 file changed, 100 insertions(+), 109 deletions(-) diff --git a/app/themeManager.js b/app/themeManager.js index 4d2b4c6..c1502a9 100644 --- a/app/themeManager.js +++ b/app/themeManager.js @@ -7,6 +7,8 @@ const MAX_IMAGE_SIZE = 10 * 1024 * 1024; const ALLOWED_IMAGE_EXTENSIONS = [".jpg", ".jpeg", ".png", ".webp", ".gif", ".avif"]; const ALLOWED_IMAGE_NAMES = ["cover", "background"]; +const THEME_ORDER = ["dark", "light", "neon", "fnatic", "chirac", "drift", "nf", "songbird", "spicyfire"]; + const REQUIRED_KEYS = [ ["meta", "name"], ["meta", "author"], @@ -31,6 +33,97 @@ function validateThemeJson(json) { return { valid: true }; } +function buildThemeObject(themeId, themeJson, imageData) { + return { + id: themeId.toLowerCase(), + name: themeJson.meta.name || themeId, + author: themeJson.meta.author || "Unknown", + version: themeJson.meta.version, + subtitle: themeJson.meta.subtitle || "", + style: themeJson.style, + image: imageData, + }; +} + +function extractImage(buffer, filename) { + if (buffer.length > MAX_IMAGE_SIZE) { + logger.warn(`Image too large, ignoring`); + return null; + } + const ext = path.extname(filename).toLowerCase().replace(".", ""); + const mime = ext === "jpg" || ext === "jpeg" ? "jpeg" : ext; + return `data:image/${mime};base64,${buffer.toString("base64")}`; +} + +async function loadThemeFromZip(zipPath, themeId) { + try { + const zipBuffer = fs.readFileSync(zipPath); + const zip = await JSZip.loadAsync(zipBuffer); + + const jsonFile = Object.keys(zip.files).find(f => f.endsWith(".theme.json")); + if (!jsonFile) { logger.warn(`Theme ${themeId}: no .theme.json, skipping`); return null; } + + let themeJson; + try { themeJson = JSON.parse(await zip.files[jsonFile].async("string")); } + catch { logger.warn(`Theme ${themeId}: invalid JSON, skipping`); return null; } + + const validation = validateThemeJson(themeJson); + if (!validation.valid) { logger.warn(`Theme ${themeId}: ${validation.reason}, skipping`); return null; } + + let imageData = null; + const imageFile = Object.keys(zip.files).find(f => { + const ext = path.extname(f).toLowerCase(); + const name = path.basename(f, ext).toLowerCase(); + return ALLOWED_IMAGE_NAMES.includes(name) && ALLOWED_IMAGE_EXTENSIONS.includes(ext); + }); + + if (imageFile) { + const buf = await zip.files[imageFile].async("nodebuffer"); + imageData = extractImage(buf, imageFile); + } + + logger.info(`Theme loaded (zip): ${themeId}`); + return buildThemeObject(themeId, themeJson, imageData); + + } catch (err) { + logger.warn(`Theme ${themeId}: failed to load zip — ${err.message}`); + return null; + } +} + +async function loadThemeFromFolder(folderPath, themeId) { + try { + const jsonFile = fs.readdirSync(folderPath).find(f => f.endsWith(".theme.json")); + if (!jsonFile) { logger.warn(`Theme ${themeId}: no .theme.json, skipping`); return null; } + + let themeJson; + try { themeJson = JSON.parse(fs.readFileSync(path.join(folderPath, jsonFile), "utf-8")); } + catch { logger.warn(`Theme ${themeId}: invalid JSON, skipping`); return null; } + + const validation = validateThemeJson(themeJson); + if (!validation.valid) { logger.warn(`Theme ${themeId}: ${validation.reason}, skipping`); return null; } + + let imageData = null; + const imageFile = fs.readdirSync(folderPath).find(f => { + const ext = path.extname(f).toLowerCase(); + const name = path.basename(f, ext).toLowerCase(); + return ALLOWED_IMAGE_NAMES.includes(name) && ALLOWED_IMAGE_EXTENSIONS.includes(ext); + }); + + if (imageFile) { + const buf = fs.readFileSync(path.join(folderPath, imageFile)); + imageData = extractImage(buf, imageFile); + } + + logger.info(`Theme loaded (folder): ${themeId}`); + return buildThemeObject(themeId, themeJson, imageData); + + } catch (err) { + logger.warn(`Theme ${themeId}: failed to load folder — ${err.message}`); + return null; + } +} + let cachedThemes = null; let themeFolderPath = null; @@ -52,14 +145,12 @@ async function loadThemesFromFolder() { const filePath = path.join(themeFolderPath, file); const themeId = path.basename(file, path.extname(file)); - // ZIP if (file.endsWith(".zip")) { const theme = await loadThemeFromZip(filePath, themeId); if (theme) themes.push(theme); continue; } - // Dossier décompressé if (fs.statSync(filePath).isDirectory()) { const theme = await loadThemeFromFolder(filePath, themeId); if (theme) themes.push(theme); @@ -70,117 +161,17 @@ async function loadThemesFromFolder() { return themes; } -async function loadThemeFromZip(zipPath, themeId) { - try { - const zipBuffer = fs.readFileSync(zipPath); - const zip = await JSZip.loadAsync(zipBuffer); - - const jsonFile = Object.keys(zip.files).find(f => f.endsWith(".theme.json")); - if (!jsonFile) { - logger.warn(`Theme ${themeId}: no .theme.json found, skipping`); - return null; - } - - const jsonContent = await zip.files[jsonFile].async("string"); - let themeJson; - try { themeJson = JSON.parse(jsonContent); } - catch { logger.warn(`Theme ${themeId}: invalid JSON, skipping`); return null; } - - const validation = validateThemeJson(themeJson); - if (!validation.valid) { - logger.warn(`Theme ${themeId}: ${validation.reason}, skipping`); - return null; - } - - let imageData = null; - const imageFile = Object.keys(zip.files).find(f => { - const ext = path.extname(f).toLowerCase(); - const name = path.basename(f, ext).toLowerCase(); - return ALLOWED_IMAGE_NAMES.includes(name) && ALLOWED_IMAGE_EXTENSIONS.includes(ext); - }); - - if (imageFile) { - const imageBuffer = await zip.files[imageFile].async("nodebuffer"); - if (imageBuffer.length > MAX_IMAGE_SIZE) { - logger.warn(`Theme ${themeId}: image too large, ignoring image`); - } else { - const ext = path.extname(imageFile).toLowerCase().replace(".", ""); - const mime = ext === "jpg" || ext === "jpeg" ? "jpeg" : ext; - imageData = `data:image/${mime};base64,${imageBuffer.toString("base64")}`; - } - } - - logger.info(`Theme loaded (zip): ${themeId}`); - return buildThemeObject(themeId, themeJson, imageData); - - } catch (err) { - logger.warn(`Theme ${themeId}: failed to load zip — ${err.message}`); - return null; - } -} - -async function loadThemeFromFolder(folderPath, themeId) { - try { - const jsonFile = fs.readdirSync(folderPath).find(f => f.endsWith(".theme.json")); - if (!jsonFile) { - logger.warn(`Theme ${themeId}: no .theme.json found, skipping`); - return null; - } - - const jsonContent = fs.readFileSync(path.join(folderPath, jsonFile), "utf-8"); - let themeJson; - try { themeJson = JSON.parse(jsonContent); } - catch { logger.warn(`Theme ${themeId}: invalid JSON, skipping`); return null; } - - const validation = validateThemeJson(themeJson); - if (!validation.valid) { - logger.warn(`Theme ${themeId}: ${validation.reason}, skipping`); - return null; - } - - let imageData = null; - const imageFile = fs.readdirSync(folderPath).find(f => { - const ext = path.extname(f).toLowerCase(); - const name = path.basename(f, ext).toLowerCase(); - return ALLOWED_IMAGE_NAMES.includes(name) && ALLOWED_IMAGE_EXTENSIONS.includes(ext); - }); - - if (imageFile) { - const imageBuffer = fs.readFileSync(path.join(folderPath, imageFile)); - if (imageBuffer.length > MAX_IMAGE_SIZE) { - logger.warn(`Theme ${themeId}: image too large, ignoring image`); - } else { - const ext = path.extname(imageFile).toLowerCase().replace(".", ""); - const mime = ext === "jpg" || ext === "jpeg" ? "jpeg" : ext; - imageData = `data:image/${mime};base64,${imageBuffer.toString("base64")}`; - } - } - - logger.info(`Theme loaded (folder): ${themeId}`); - return buildThemeObject(themeId, themeJson, imageData); - - } catch (err) { - logger.warn(`Theme ${themeId}: failed to load folder — ${err.message}`); - return null; - } -} - -function buildThemeObject(themeId, themeJson, imageData) { - return { - id: themeId, - name: themeJson.meta.name || themeId, - author: themeJson.meta.author || "Unknown", - version: themeJson.meta.version, - subtitle: themeJson.meta.subtitle || "", - style: themeJson.style, - image: imageData, - }; +function getThemeOrder(id) { + const index = THEME_ORDER.indexOf(id); + return index === -1 ? Infinity : index; } async function initThemes(folderPath) { setThemeFolderPath(folderPath); - cachedThemes = await loadThemesFromFolder(); - logger.info(`${cachedThemes.length} theme(s) loaded`); + const themes = await loadThemesFromFolder(); + logger.info(`Themes before sort: ${themes.map(t => t.id).join(", ")}`); + cachedThemes = themes.sort((a, b) => getThemeOrder(a.id) - getThemeOrder(b.id)); + logger.info(`Themes after sort: ${cachedThemes.map(t => t.id).join(", ")}`); } function getThemes() {