structure refacto & file documentation
All checks were successful
Deploy Sokko G / deploy (push) Successful in 7s

This commit is contained in:
Shinuwa 2026-07-25 17:51:53 +02:00
parent d7871736c8
commit 895fec2b40
84 changed files with 2629 additions and 2313 deletions

View file

@ -0,0 +1,234 @@
// Rôle : regroupe les actions métier de création, import/export, lien et édition toolbox.
import {
compactModuleDataForStorage,
createGlobalExportPayload,
createToolboxExportPayload,
globalModuleKey,
labelFromFileName,
normalizeToolbox,
uid
} from "./storage/toolboxStorage.js";
import { compressImage } from "../../utils/imageCompression.js";
function downloadJson(payload, filename) {
const blob = new Blob([JSON.stringify(payload, null, 2)], { type: "application/json" });
const link = document.createElement("a");
link.href = URL.createObjectURL(blob);
link.download = filename;
link.click();
URL.revokeObjectURL(link.href);
}
export function useToolboxActions({ store, notify, setConfirmModal, setCreateModal, setLinkModalGameId, setDrawerGameId, setImage }) {
function createToolbox(name, gameId = "") {
const now = new Date().toISOString();
const toolbox = {
id: uid("tbx"),
name: name.trim() || "Nouvelle toolbox",
updatedAt: now,
modules: [
{ id: uid("mod"), type: "notepad", title: "Notes rapides" },
{ id: uid("mod"), type: "checklist" }
]
};
if (!store.setToolboxes([toolbox, ...store.toolboxes])) return null;
if (gameId) store.setLinks({ ...store.links, [gameId]: toolbox.id });
return toolbox;
}
function updateToolbox(nextToolbox) {
store.setToolboxes(store.toolboxes.map((toolbox) => toolbox.id === nextToolbox.id
? { ...nextToolbox, updatedAt: new Date().toISOString() }
: toolbox));
}
function deleteToolbox(id) {
const toolbox = store.toolboxes.find((item) => item.id === id);
store.removeToolboxModuleData(toolbox);
store.setToolboxes(store.toolboxes.filter((item) => item.id !== id));
const nextLinks = { ...store.links };
Object.entries(nextLinks).forEach(([gameId, toolboxId]) => {
if (toolboxId === id) delete nextLinks[gameId];
});
store.setLinks(nextLinks);
}
function linkToolboxToGame(gameId, toolboxId) {
const previousToolboxId = store.links[gameId] || "";
const nextLinks = { ...store.links };
if (toolboxId) nextLinks[gameId] = toolboxId;
else delete nextLinks[gameId];
store.setLinks(nextLinks);
const touched = new Set([previousToolboxId, toolboxId].filter(Boolean));
store.setToolboxes(store.toolboxes.map((toolbox) => touched.has(toolbox.id)
? { ...toolbox, updatedAt: new Date().toISOString() }
: toolbox));
}
async function addImageFiles(toolboxId, moduleId, files) {
const imageFiles = [...files].filter((file) => file?.type?.startsWith("image/"));
if (!imageFiles.length) return false;
const data = store.getModuleData(toolboxId, moduleId, { images: [] });
for (const file of imageFiles) {
const image = { id: uid("image"), dataUrl: await compressImage(file) };
const label = labelFromFileName(file.name);
if (label) image.label = label;
data.images.unshift(image);
}
return store.updateModuleData(toolboxId, moduleId, data);
}
function createChecklistFromList(gameId, list) {
const toolboxId = store.links[gameId] || "";
const toolbox = store.toolboxes.find((item) => item.id === toolboxId);
if (!toolbox || !list) return false;
const moduleId = uid("mod");
const sections = (list.categories || []).map((category) => ({
id: uid("section"),
title: category.title || "",
items: (category.items || []).map((item) => ({
id: uid("item"),
label: item.name,
qtyTarget: Math.max(1, Number.parseInt(item.quantity, 10) || 1),
qtyCurrent: 0
})).filter((item) => item.label)
})).filter((section) => section.items.length);
if (!sections.length) return false;
setConfirmModal({
title: "Créer une checklist",
message: `Créer la checklist "${list.title || "Checklist"}" dans la toolbox "${toolbox.name}" ?`,
confirmLabel: "Créer",
onResolve: (confirmed) => {
if (!confirmed) return;
store.setToolboxes(store.toolboxes.map((item) => item.id === toolboxId
? { ...item, modules: [...item.modules, { id: moduleId, type: "checklist", title: list.title || "Checklist" }], updatedAt: new Date().toISOString() }
: item));
store.updateModuleData(toolboxId, moduleId, { sections }, "checklist");
notify("Checklist créée dans la toolbox associée.");
}
});
return true;
}
async function importToolboxPayload(file, gameId = "") {
const payload = JSON.parse(await file.text());
if (!payload.toolbox?.modules) throw new Error("Format d'outils invalide");
const imported = normalizeToolbox({ ...payload.toolbox, id: uid("tbx"), updatedAt: new Date().toISOString() });
const moduleIdMap = new Map();
imported.modules = imported.modules.map((module) => {
const nextId = uid("mod");
moduleIdMap.set(module.id, nextId);
return { ...module, id: nextId };
});
if (!store.setToolboxes([imported, ...store.toolboxes])) return null;
Object.entries(payload.modules || {}).forEach(([oldId, data]) => {
const nextId = moduleIdMap.get(oldId);
const module = imported.modules.find((item) => item.id === nextId);
if (nextId) store.updateModuleData(imported.id, nextId, data, module?.type);
});
if (gameId) linkToolboxToGame(gameId, imported.id);
return imported;
}
async function importAllToolboxesPayload(file) {
const payload = JSON.parse(await file.text());
if (!Array.isArray(payload.toolboxes) || !payload.modules || typeof payload.modules !== "object") {
throw new Error("Format d'import global invalide");
}
const toolboxIdMap = new Map();
const moduleIdMap = new Map();
const importedToolboxes = payload.toolboxes.map((toolbox) => {
const nextToolboxId = uid("tbx");
toolboxIdMap.set(toolbox.id, nextToolboxId);
const modules = (toolbox.modules || []).map((module) => {
const nextModuleId = uid("mod");
moduleIdMap.set(globalModuleKey(toolbox.id, module.id), nextModuleId);
return { ...module, id: nextModuleId };
});
return normalizeToolbox({ ...toolbox, id: nextToolboxId, name: `${toolbox.name || "Toolbox"} (import)`, modules, updatedAt: new Date().toISOString() });
});
const nextLinks = { ...store.links };
Object.entries(payload.links || {}).forEach(([gameId, oldToolboxId]) => {
const nextToolboxId = toolboxIdMap.get(oldToolboxId);
if (nextToolboxId) nextLinks[gameId] = nextToolboxId;
});
Object.entries(payload.modules).forEach(([key, data]) => {
const [oldToolboxId] = key.split(":");
const nextToolboxId = toolboxIdMap.get(oldToolboxId);
const nextModuleId = moduleIdMap.get(key);
const toolbox = importedToolboxes.find((item) => item.id === nextToolboxId);
const module = toolbox?.modules.find((item) => item.id === nextModuleId);
const compact = compactModuleDataForStorage(module?.type, data);
if (nextToolboxId && nextModuleId && compact) store.updateModuleData(nextToolboxId, nextModuleId, compact, module?.type);
});
store.setToolboxes([...importedToolboxes, ...store.toolboxes]);
store.setLinks(nextLinks);
store.refreshQuota();
}
return {
createToolbox,
updateToolbox,
deleteToolbox,
linkToolboxToGame,
setCreateModal,
setConfirmModal,
setLinkModalGameId,
setDrawerGameId,
setImage,
createChecklistFromList,
addImageFiles,
notify,
removeModuleData: store.removeModuleData,
updateModuleData: store.updateModuleData,
updateToolboxOrder: (orderedIds) => {
const order = new Map(orderedIds.map((id, index) => [id, index]));
store.setToolboxes([...store.toolboxes].sort((a, b) => (order.get(a.id) ?? 9999) - (order.get(b.id) ?? 9999)));
},
importToolbox: async (file, gameId = "") => {
try {
const toolbox = await importToolboxPayload(file, gameId);
if (toolbox) notify("Toolbox importée.");
return toolbox;
} catch (error) {
setConfirmModal({
title: "Import impossible",
message: error.message,
confirmLabel: "Compris",
cancelLabel: "Fermer",
danger: true
});
return null;
}
},
importAllToolboxes: async (file) => {
try {
await importAllToolboxesPayload(file);
notify("Import global terminé.");
return true;
} catch (error) {
setConfirmModal({
title: "Import global impossible",
message: error.message,
confirmLabel: "Compris",
cancelLabel: "Fermer",
danger: true
});
return false;
}
},
exportToolbox: (id) => {
const toolbox = normalizeToolbox(store.toolboxes.find((item) => item.id === id));
if (!toolbox) return;
downloadJson(createToolboxExportPayload(toolbox, store.moduleData), `${toolbox.name.toLowerCase().replace(/[^a-z0-9]+/gi, "-")}.sokkog.json`);
notify("Export de la toolbox lancé.");
},
exportAllToolboxes: () => {
downloadJson(createGlobalExportPayload(store.toolboxes, store.links, store.moduleData), `sokkog-global-${new Date().toISOString().slice(0, 10)}.json`);
notify("Export global lancé.");
}
};
}