structure refacto & file documentation
All checks were successful
Deploy Sokko G / deploy (push) Successful in 7s
All checks were successful
Deploy Sokko G / deploy (push) Successful in 7s
This commit is contained in:
parent
d7871736c8
commit
895fec2b40
84 changed files with 2629 additions and 2313 deletions
234
website/src/features/toolboxes/useToolboxActions.js
Normal file
234
website/src/features/toolboxes/useToolboxActions.js
Normal 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é.");
|
||||
}
|
||||
};
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue