// Rôle : affiche les pages toolbox, le panneau latéral et leurs contrôles.
import { useEffect, useRef, useState } from "react";
import { GuidedTooltipTour } from "../../components/GuidedTooltipTour.jsx";
import { Icon } from "../../components/Icon.jsx";
import { ImportButton } from "../../components/ImportButton.jsx";
import { StorageQuota } from "../../components/StorageQuota.jsx";
import { ToastPositionSwitch } from "../../components/ToastPositionSwitch.jsx";
import { useGroupedReorder } from "../../hooks/useGroupedReorder.js";
import { useInlineEdit } from "../../hooks/useInlineEdit.js";
import { compressImage } from "../../utils/imageCompression.js";
import { getSetting as dbGetSetting, setSetting as dbSetSetting } from "../../utils/indexedDbStorage.js";
import { getGameCardCover, ToolboxCard, ToolboxGameIcon, ToolboxIconPicker } from "./ToolboxCard.jsx";
import { AddToolControls, ToolboxModules } from "./modules/index.jsx";
import {
clampQty,
hostnameFromUrl,
normalizeCalculatorData,
normalizeCalendarData,
normalizeChecklistData,
normalizeCombosData,
normalizeCountersData,
normalizeEquipmentPlannerData,
normalizeImageAnnotationData,
normalizeLinksData,
normalizeModuleOrder,
normalizeNotepadData,
normalizeTableData,
normalizeTaskPlannerData,
normalizeTimerData,
summarizeEquipmentPlannerData,
normalizeUrl,
uid
} from "./storage/toolboxStorage.js";
const DRAWER_WIDTH_SETTING = "drawerWidth";
function addModuleToOrder(moduleOrder, modules, moduleId) {
const order = normalizeModuleOrder(moduleOrder, modules);
const two = [
order.two[0].filter((id) => id !== moduleId),
order.two[1].filter((id) => id !== moduleId)
];
const targetColumnIndex = two[0].length <= two[1].length ? 0 : 1;
two[targetColumnIndex].push(moduleId);
return {
one: [...order.one.filter((id) => id !== moduleId), moduleId],
two
};
}
function removeModuleFromOrder(moduleOrder, modules, moduleId) {
const order = normalizeModuleOrder(moduleOrder, modules);
return {
one: order.one.filter((id) => id !== moduleId),
two: [
order.two[0].filter((id) => id !== moduleId),
order.two[1].filter((id) => id !== moduleId)
]
};
}
async function copyText(value) {
try {
await navigator.clipboard.writeText(value);
return true;
} catch {
return false;
}
}
export function ToolboxesPage({ siteContent, toolboxes, getToolboxGame, actions, storageUsage, toastPosition, setToastPosition }) {
const content = siteContent.toolboxes;
const {
itemReorder,
getItemProps,
isItemDragging,
isItemDropTarget,
getDropPlacement
} = useGroupedReorder({
namespace: "toolbox-cards",
items: toolboxes,
getItemId: (toolbox) => toolbox.id,
orientation: "horizontal",
getPlacement: (event, target) => {
const rect = target.getBoundingClientRect();
return event.clientY > rect.top + rect.height / 2 || event.clientX > rect.left + rect.width / 2 ? "after" : "before";
},
onItemMove: (operation) => {
const nextIds = toolboxes.map((toolbox) => toolbox.id).filter((id) => id !== operation.sourceId);
const targetIndex = nextIds.indexOf(operation.targetId);
nextIds.splice(operation.placement === "after" ? targetIndex + 1 : targetIndex, 0, operation.sourceId);
actions.updateToolboxOrder(nextIds);
}
});
return (
{content.preferencesLabel}
{content.toastPositionLabel}
{toolboxes.length ? toolboxes.map((toolbox) => (
)) : (
{content.emptyTitle}
{content.emptyText}
)}
);
}
function StorageHelpCard({ help }) {
return (
{help.text}
{help.items.map((item, index) => - {index + 1}
{item}
)}
);
}
export function ToolboxPage(props) {
const { toolboxId, toolboxes, getToolboxGame } = props;
const toolbox = toolboxes.find((item) => item.id === toolboxId);
if (!toolbox) return ;
return ;
}
function shouldShowTutorial(toolbox, pendingTutorialToolboxId) {
return toolbox?.tutorial === true || pendingTutorialToolboxId === toolbox?.id;
}
function ToolboxView({ siteContent, toolbox, toolboxGame, embedded, actions, storageUsage, getModuleData, updateToolbox, updateModuleData, addImageFiles, pendingTutorialToolboxId = "", setPendingTutorialToolboxId }) {
const toolboxId = toolbox.id;
const toolboxTutorial = toolbox.tutorial;
const moduleColumns = embedded || Number(toolbox.moduleColumns) === 1 ? 1 : 2;
const toolboxGameCover = getGameCardCover(toolboxGame);
const moduleText = siteContent.toolboxes.modules;
const [showTutorial, setShowTutorial] = useState(() => shouldShowTutorial(toolbox, pendingTutorialToolboxId));
const currentToolboxIdRef = useRef(toolboxId);
const moduleContext = {
getModuleData,
moduleText,
normalizeChecklistData,
normalizeCombosData,
normalizeLinksData,
normalizeCountersData,
normalizeEquipmentPlannerData,
normalizeCalculatorData,
normalizeCalendarData,
normalizeImageAnnotationData,
normalizeNotepadData,
normalizeTableData,
normalizeTimerData,
normalizeTaskPlannerData,
summarizeEquipmentPlannerData,
normalizeUrl,
hostnameFromUrl,
copyText,
notify: actions.notify,
compressImageFile: compressImage,
clampQty,
uid,
setModuleData: updateModuleData,
addImageFiles,
createImageAnnotationModule,
setImage: actions.setImage,
refresh: () => {}
};
useEffect(() => {
if (currentToolboxIdRef.current === toolboxId) {
if (shouldShowTutorial(toolbox, pendingTutorialToolboxId)) setShowTutorial(true);
return;
}
currentToolboxIdRef.current = toolboxId;
setShowTutorial(shouldShowTutorial(toolbox, pendingTutorialToolboxId));
}, [pendingTutorialToolboxId, toolbox, toolboxId, toolboxTutorial]);
function addModule(type, initialData = null) {
if (!type) return;
const module = { id: uid("mod"), type };
const modules = [...toolbox.modules, module];
updateToolbox({
...toolbox,
modules,
moduleOrder: addModuleToOrder(toolbox.moduleOrder, modules, module.id)
});
if (initialData) updateModuleData(toolbox.id, module.id, initialData, type);
}
function createImageAnnotationModule(dataUrl) {
if (!dataUrl) return;
const moduleId = uid("mod");
const modules = [...toolbox.modules, { id: moduleId, type: "imageAnnotation" }];
updateToolbox({
...toolbox,
modules,
moduleOrder: addModuleToOrder(toolbox.moduleOrder, modules, moduleId)
});
updateModuleData(toolbox.id, moduleId, { image: dataUrl, markers: [] }, "imageAnnotation");
}
return (
{!embedded && showTutorial && (
{
if (pendingTutorialToolboxId === toolbox.id) setPendingTutorialToolboxId?.("");
setShowTutorial(false);
}}
onStarted={() => {
if (pendingTutorialToolboxId === toolbox.id) setPendingTutorialToolboxId?.("");
if (toolbox.tutorial === true) updateToolbox({ ...toolbox, tutorial: false });
}}
/>
)}
{!embedded && (
)}
updateToolbox({ ...toolbox, modules: toolbox.modules.map((module) => module.id === moduleId ? { ...module, title } : module) })}
onUpdateModule={(moduleId, updater) => updateToolbox({ ...toolbox, modules: toolbox.modules.map((module) => module.id === moduleId ? updater(module) : module) })}
onModuleOrderChange={(moduleOrder) => updateToolbox({ ...toolbox, moduleOrder })}
onDelete={(moduleId) => actions.setConfirmModal({
title: "Retirer l'outil",
message: "Retirer cet outil de la toolbox ?",
confirmLabel: "Retirer",
danger: true,
onResolve: (confirmed) => {
if (!confirmed) return;
actions.removeModuleData(toolbox.id, moduleId);
const modules = toolbox.modules.filter((module) => module.id !== moduleId);
updateToolbox({
...toolbox,
modules,
moduleOrder: removeModuleFromOrder(toolbox.moduleOrder, modules, moduleId)
});
}
})}
/>
{!embedded && }
);
}
export function ToolboxDrawer({ gameId, game, siteContent, toolboxes, links, actions, storageUsage, getModuleData, updateToolbox, updateModuleData, addImageFiles }) {
const selectedId = links[gameId] || "";
const toolbox = toolboxes.find((item) => item.id === selectedId);
const [width, setWidth] = useState("");
const panelRef = useRef(null);
useEffect(() => {
let cancelled = false;
dbGetSetting(DRAWER_WIDTH_SETTING, "")
.then((storedWidth) => {
if (cancelled) return;
const value = Number(storedWidth);
if (Number.isFinite(value) && value > 0) setWidth(Math.min(Math.max(value, 360), Math.floor(window.innerWidth * 0.94)));
})
.catch(() => {});
return () => { cancelled = true; };
}, []);
function startResize(event) {
const panel = panelRef.current;
if (!panel) return;
event.preventDefault();
const startX = event.clientX;
const startWidth = panel.getBoundingClientRect().width;
const minWidth = 360;
const maxWidth = Math.floor(window.innerWidth * 0.94);
document.body.classList.add("is-resizing-drawer");
const onMove = (moveEvent) => {
const nextWidth = Math.min(Math.max(startWidth + startX - moveEvent.clientX, minWidth), maxWidth);
setWidth(nextWidth);
dbSetSetting(DRAWER_WIDTH_SETTING, Math.round(nextWidth)).catch(() => {});
};
const stop = () => {
document.body.classList.remove("is-resizing-drawer");
window.removeEventListener("pointermove", onMove);
window.removeEventListener("pointerup", stop);
window.removeEventListener("pointercancel", stop);
};
window.addEventListener("pointermove", onMove);
window.addEventListener("pointerup", stop);
window.addEventListener("pointercancel", stop);
}
return (