// 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.eyebrow}

{content.title}

{content.summary}

{content.libraryLink}

{content.preferencesLabel}

{content.toastPositionLabel}
{toolboxes.length ? toolboxes.map((toolbox) => ( )) : (

{content.emptyTitle}

{content.emptyText}

)}
); } function StorageHelpCard({ help }) { return (

{help.text}

); } export function ToolboxPage(props) { const { toolboxId, toolboxes, getToolboxGame } = props; const toolbox = toolboxes.find((item) => item.id === toolboxId); if (!toolbox) return

Toolbox introuvable

Retour
; 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 (

Toolbox

{!embedded && !toolboxGame && updateToolbox({ ...toolbox, icon })} tutorialTarget="icon" />} {!embedded && toolboxGameCover && } {embedded ?

{toolbox.name}

: ( name !== toolbox.name && updateToolbox({ ...toolbox, name })} /> )}
{toolboxGame && ( actions.setDrawerGameId("") : undefined} > {embedded ? "Vers la toolbox complète" : "Vers la page de jeu"} )}
{!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 ( ); } function EditableTitle({ value, fallback, onSave, className = "module-title", tutorialTarget = "" }) { const inlineEdit = useInlineEdit({ value, onCommit: (title) => onSave(title || fallback), blurOnEscape: true }); return (

{value}

); }