style / tools optis & new tools calculator
All checks were successful
Deploy Sokko G / deploy (push) Successful in 6s

This commit is contained in:
Shinuwa 2026-07-23 21:06:05 +02:00
parent 6b84607b25
commit 0db35a0d2c
23 changed files with 1106 additions and 24 deletions

View file

@ -16,7 +16,7 @@ const STORAGE_KEYS = {
const APP_STORAGE_PREFIX = "sokkog:";
const APP_STORAGE_LIMIT_BYTES = 5 * 1024 * 1024;
const APP_STORAGE_WARNING_RATIO = 0.85;
const ID_PREFIXES = { tbx: "t", mod: "m", item: "i", shot: "s", link: "l", counter: "c" };
const ID_PREFIXES = { tbx: "t", mod: "m", item: "i", shot: "s", link: "l", counter: "c", calc: "r" };
const ID_ALPHABET = "abcdefghijklmnopqrstuvwxyz0123456789";
const TOOLBOX_ICON_BASE = "/static/img/toolbox-icons/";
const TOOLBOX_ICON_FILES = [
@ -95,6 +95,24 @@ const DEFAULT_SITE_CONTENT = {
text: "",
items: []
},
modules: {
notepad: { placeholder: "Notes rapides..." },
checklist: {
itemPlaceholder: "Nouvel item",
importPlaceholder: "Importer plusieurs items...\nPotion:10\nMéga potion:5"
},
links: {
titlePlaceholder: "Nom du lien",
urlPlaceholder: "https://...",
importPlaceholder: "Importer plusieurs liens...\nBuild:https://example.com/build?class=rogue\nMap:https://example.com/map"
},
counters: { labelPlaceholder: "Nom du compteur" },
calculator: {
expressionPlaceholder: "10*10",
labelPlaceholder: "Lingots de fer",
scrollableTitle: "Liste scrollable"
}
},
emptyTitle: "Aucune toolbox",
emptyText: ""
}
@ -253,7 +271,7 @@ function parsePositiveInt(value, fallback = 1) {
function clampQty(value, target) {
const parsed = Number.parseInt(value, 10);
const safeValue = Number.isFinite(parsed) ? parsed : 0;
return Math.min(Math.max(safeValue, 0), Math.max(1, target));
return Math.min(safeValue, Math.max(1, target));
}
function normalizeChecklistItem(item) {
@ -327,6 +345,26 @@ function normalizeCountersData(data) {
};
}
function normalizeCalculatorValue(value) {
const parsed = Number(value);
return Number.isFinite(parsed) ? parsed : 0;
}
function normalizeCalculatorData(data) {
const entries = (data?.entries || [])
.map((entry) => ({
id: entry?.id || uid("calc"),
parentId: String(entry?.parentId || ""),
label: String(entry?.label || "").trim(),
value: normalizeCalculatorValue(entry?.value)
}));
const entryIds = new Set(entries.map((entry) => entry.id));
return {
scrollResults: data?.scrollResults === true,
entries: entries.map((entry) => entryIds.has(entry.parentId) ? entry : { ...entry, parentId: "" })
};
}
function compactChecklistItemForStorage(item) {
const normalized = normalizeChecklistItem(item);
const compact = { id: normalized.id, label: normalized.label };
@ -369,6 +407,20 @@ function compactModuleDataForStorage(type, value) {
}));
return counters.length ? { counters } : null;
}
if (type === "calculator") {
const normalized = normalizeCalculatorData(value);
const entries = normalized.entries.map((entry) => {
const compact = {
id: entry.id,
label: entry.label,
value: entry.value
};
if (entry.parentId) compact.parentId = entry.parentId;
return compact;
});
if (!entries.length && !normalized.scrollResults) return null;
return normalized.scrollResults ? { entries, scrollResults: true } : { entries };
}
return value;
}
@ -409,6 +461,20 @@ function remapModuleDataForExport(type, data, nextId) {
return { counters: compact.counters.map((counter) => ({ ...counter, id: nextId("counter") })) };
}
if (type === "calculator") {
const entryIdMap = new Map();
compact.entries.forEach((entry) => entryIdMap.set(entry.id, nextId("calc")));
const remapped = {
entries: compact.entries.map((entry) => {
const remapped = { ...entry, id: entryIdMap.get(entry.id) };
if (entry.parentId) remapped.parentId = entryIdMap.get(entry.parentId) || "";
return remapped;
})
};
if (compact.scrollResults) remapped.scrollResults = true;
return remapped;
}
return compact;
}
@ -842,6 +908,8 @@ function App() {
{drawerGameId && (
<ToolboxDrawer
gameId={drawerGameId}
game={getGame(drawerGameId)}
siteContent={siteContent}
toolboxes={store.toolboxes}
links={store.links}
actions={actions}
@ -1207,14 +1275,17 @@ function ToolboxGameIcon({ game }) {
);
}
function ToolboxView({ toolbox, toolboxGame, embedded, actions, updateToolbox, updateModuleData, addScreenshotFiles }) {
function ToolboxView({ siteContent, toolbox, toolboxGame, embedded, actions, updateToolbox, updateModuleData, addScreenshotFiles }) {
const moduleColumns = Number(toolbox.moduleColumns) === 1 ? 1 : 2;
const toolboxGameCover = getGameCardCover(toolboxGame);
const moduleText = siteContent?.toolboxes?.modules || DEFAULT_SITE_CONTENT.toolboxes.modules;
const moduleContext = {
getModuleData,
moduleText,
normalizeChecklistData,
normalizeLinksData,
normalizeCountersData,
normalizeCalculatorData,
normalizeUrl,
hostnameFromUrl,
copyText,
@ -1255,6 +1326,16 @@ function ToolboxView({ toolbox, toolboxGame, embedded, actions, updateToolbox, u
<EditableTitle className="toolbox-title" value={toolbox.name} fallback="Nouvelle toolbox" onSave={(name) => name !== toolbox.name && updateToolbox({ ...toolbox, name })} />
)}
</div>
{toolboxGame && (
<a
className="button toolbox-hero-link"
href={embedded ? `#/toolbox/${toolbox.id}` : `#/games/${toolboxGame.id}`}
onClick={embedded ? () => actions.setDrawerGameId("") : undefined}
>
<Icon name="enter" />
<span>{embedded ? "Vers la toolbox complète" : "Vers la page de jeu"}</span>
</a>
)}
</div>
<div className="actions">
<AddToolControls onAdd={addModule} />
@ -1291,7 +1372,7 @@ function ToolboxView({ toolbox, toolboxGame, embedded, actions, updateToolbox, u
);
}
function ToolboxDrawer({ gameId, toolboxes, links, actions, updateToolbox, updateModuleData, addScreenshotFiles }) {
function ToolboxDrawer({ gameId, game, siteContent, toolboxes, links, actions, updateToolbox, updateModuleData, addScreenshotFiles }) {
const selectedId = links[gameId] || "";
const toolbox = toolboxes.find((item) => item.id === selectedId);
const [width, setWidth] = useState(() => {
@ -1343,7 +1424,7 @@ function ToolboxDrawer({ gameId, toolboxes, links, actions, updateToolbox, updat
</div>
<button className="drawer-close-button" onClick={() => actions.setDrawerGameId("")} aria-label="Fermer" title="Fermer"><Icon name="close" /></button>
</header>
{toolbox ? <ToolboxView toolbox={toolbox} embedded actions={actions} updateToolbox={updateToolbox} updateModuleData={updateModuleData} addScreenshotFiles={addScreenshotFiles} /> : (
{toolbox ? <ToolboxView siteContent={siteContent} toolbox={toolbox} toolboxGame={game} embedded actions={actions} updateToolbox={updateToolbox} updateModuleData={updateModuleData} addScreenshotFiles={addScreenshotFiles} /> : (
<div className="empty"><p>Aucune toolbox associée à cette page jeu.</p><button className="primary" onClick={() => actions.setCreateModal({ gameId })}>Créer et associer</button></div>
)}
<StorageQuota />