add combos tool
All checks were successful
Deploy Sokko G / deploy (push) Successful in 7s

This commit is contained in:
Shinuwa 2026-07-31 14:46:28 +02:00
parent 025cd4bf22
commit 7b4cfb7dc2
13 changed files with 1829 additions and 2 deletions

View file

@ -1,5 +1,5 @@
// Rôle : normalise, compacte et sérialise les données toolbox persistées.
const ID_PREFIXES = { tbx: "t", mod: "m", item: "i", section: "g", image: "s", link: "l", counter: "c", calc: "r", marker: "k", timer: "z", task: "a", relation: "e", stroke: "d" };
const ID_PREFIXES = { tbx: "t", mod: "m", item: "i", section: "g", image: "s", link: "l", counter: "c", combo: "o", calc: "r", marker: "k", timer: "z", task: "a", relation: "e", stroke: "d" };
const ID_ALPHABET = "abcdefghijklmnopqrstuvwxyz0123456789";
const NOTEPAD_ALLOWED_COLORS = new Set(["#f5f7ff", "#b4bdd3", "#f6c453", "#22d3ee", "#8b5cf6", "#d946ef", "#101426", "#202745"]);
const NOTEPAD_ALLOWED_TAGS = new Set(["p", "div", "br", "strong", "b", "em", "i", "u", "s", "strike", "ul", "ol", "li", "h3", "h4", "span", "mark"]);
@ -17,6 +17,8 @@ const TIMER_TABS = new Set(["stopwatch", "countdown"]);
const COUNTDOWN_TYPES = new Set(["duration", "daily_time", "time_pattern", "interval"]);
const TIMER_ALERT_MODES = new Set(["off", "visible", "site"]);
const TASK_TYPES = new Set(["unique", "daily", "weekly"]);
const COMBO_DEVICES = new Set(["playstation", "xbox", "switch", "n64", "keyboardMouse"]);
const COMBO_INPUT_KINDS = new Set(["button", "direction", "key", "mouse"]);
const DEFAULT_TASK_PLANNER_WEEKLY_RESET_DAY = 1;
const DEFAULT_TASK_PLANNER_RESET_TIME = "00:00";
const TOOLBOX_ICON_BASE = "/static/img/toolbox-icons/";
@ -47,6 +49,7 @@ const DEFAULT_MODULE_TITLES = {
images: "Images",
links: "Liens",
counters: "Compteurs",
combos: "Combos",
calculator: "Calculateur",
table: "Tableau",
timer: "Timer",
@ -356,6 +359,56 @@ export function normalizeCountersData(data) {
};
}
function normalizeComboInput(input) {
const kind = COMBO_INPUT_KINDS.has(input?.kind) ? input.kind : "";
const value = String(input?.value || "").trim().slice(0, 24);
return kind && value ? { kind, value } : null;
}
function normalizeComboSteps(inputs) {
return (Array.isArray(inputs) ? inputs : [])
.slice(0, 64)
.map((step) => (Array.isArray(step) ? step : [])
.map(normalizeComboInput)
.filter(Boolean)
.slice(0, 8))
.filter((step) => step.length);
}
function normalizeCombo(combo) {
const name = String(combo?.name || "").trim().slice(0, 80);
const device = COMBO_DEVICES.has(combo?.device) ? combo.device : "";
const inputs = normalizeComboSteps(combo?.inputs);
if (!name && !inputs.length) return null;
const normalized = {
id: combo?.id || uid("combo"),
name: name || "Combo",
inputs
};
if (device) normalized.device = device;
return normalized;
}
function normalizeComboCategory(category) {
const title = String(category?.title || "").trim().slice(0, 80);
const combos = (Array.isArray(category?.combos) ? category.combos : []).map(normalizeCombo).filter(Boolean);
if (!combos.length) return null;
return {
id: category?.id || uid("section"),
title: title || "Catégorie",
collapsed: category?.collapsed === true,
combos
};
}
export function normalizeCombosData(data) {
return {
device: COMBO_DEVICES.has(data?.device) ? data.device : "playstation",
combos: (Array.isArray(data?.combos) ? data.combos : []).map(normalizeCombo).filter(Boolean),
categories: (Array.isArray(data?.categories) ? data.categories : []).map(normalizeComboCategory).filter(Boolean)
};
}
function normalizeCalculatorValue(value) {
const parsed = Number(value);
return Number.isFinite(parsed) ? parsed : 0;
@ -697,6 +750,41 @@ export function compactModuleDataForStorage(type, value) {
}));
return counters.length ? { counters } : null;
}
if (type === "combos") {
const normalized = normalizeCombosData(value);
const compact = {
combos: normalized.combos.map((combo) => {
const compactCombo = {
id: combo.id,
name: combo.name,
inputs: combo.inputs
};
if (combo.device) compactCombo.device = combo.device;
return compactCombo;
}),
categories: normalized.categories.map((category) => {
const compactCategory = {
id: category.id,
title: category.title,
combos: category.combos.map((combo) => {
const compactCombo = {
id: combo.id,
name: combo.name,
inputs: combo.inputs
};
if (combo.device) compactCombo.device = combo.device;
return compactCombo;
})
};
if (category.collapsed) compactCategory.collapsed = true;
return compactCategory;
})
};
if (normalized.device !== "playstation") compact.device = normalized.device;
if (!compact.combos.length) delete compact.combos;
if (!compact.categories.length) delete compact.categories;
return compact.combos || compact.categories ? compact : null;
}
if (type === "calculator") {
const normalized = normalizeCalculatorData(value);
const entries = normalized.entries.map((entry) => {
@ -837,6 +925,20 @@ function remapModuleDataForExport(type, data, nextId) {
return { counters: compact.counters.map((counter) => ({ ...counter, id: nextId("counter") })) };
}
if (type === "combos") {
const remapped = {};
if (compact.device) remapped.device = compact.device;
if (compact.combos) remapped.combos = compact.combos.map((combo) => ({ ...combo, id: nextId("combo") }));
if (compact.categories) {
remapped.categories = compact.categories.map((category) => ({
...category,
id: nextId("section"),
combos: category.combos.map((combo) => ({ ...combo, id: nextId("combo") }))
}));
}
return remapped;
}
if (type === "calculator") {
const entryIdMap = new Map();
compact.entries.forEach((entry) => entryIdMap.set(entry.id, nextId("calc")));