// Rôle : affiche les pages toolbox, le panneau latéral et leurs contrôles. import React, { useEffect, useRef, useState } from "react"; 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 { usePointerReorder } from "../../hooks/usePointerReorder.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, normalizeChecklistData, normalizeCountersData, normalizeImageAnnotationData, normalizeLinksData, normalizeTaskPlannerData, normalizeTimerData, normalizeUrl, uid } from "./storage/toolboxStorage.js"; const DRAWER_WIDTH_SETTING = "drawerWidth"; 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 { draggingId: draggingToolboxId, dropTarget, startDrag: startToolboxDrag } = usePointerReorder({ targetSelector: ".toolbox-card", getTargetId: (target) => target.dataset.toolboxId, getPlacement: (event, target) => { const rect = target.getBoundingClientRect(); return event.clientY > rect.top + rect.height / 2 || event.clientX > rect.left + rect.width / 2 ? "after" : "before"; }, onMove: (draggingId, targetId, placement) => { const nextIds = toolboxes.map((toolbox) => toolbox.id).filter((id) => id !== draggingId); const targetIndex = nextIds.indexOf(targetId); nextIds.splice(placement === "after" ? targetIndex + 1 : targetIndex, 0, draggingId); actions.updateToolboxOrder(nextIds); } }); return (

{content.eyebrow}

{content.title}

{content.summary}

{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 ToolboxView({ siteContent, toolbox, toolboxGame, embedded, actions, storageUsage, getModuleData, updateToolbox, updateModuleData, addImageFiles }) { const moduleColumns = Number(toolbox.moduleColumns) === 1 ? 1 : 2; const toolboxGameCover = getGameCardCover(toolboxGame); const moduleText = siteContent.toolboxes.modules; const moduleContext = { getModuleData, moduleText, normalizeChecklistData, normalizeLinksData, normalizeCountersData, normalizeCalculatorData, normalizeImageAnnotationData, normalizeTimerData, normalizeTaskPlannerData, normalizeUrl, hostnameFromUrl, copyText, notify: actions.notify, compressImageFile: compressImage, clampQty, uid, setModuleData: updateModuleData, addImageFiles, createImageAnnotationModule, setImage: actions.setImage, refresh: () => {} }; function addModule(type) { if (!type) return; updateToolbox({ ...toolbox, modules: [...toolbox.modules, { id: uid("mod"), type }] }); } function createImageAnnotationModule(dataUrl) { if (!dataUrl) return; const moduleId = uid("mod"); updateToolbox({ ...toolbox, modules: [...toolbox.modules, { id: moduleId, type: "imageAnnotation" }] }); updateModuleData(toolbox.id, moduleId, { image: dataUrl, markers: [] }, "imageAnnotation"); } function moveModule(fromModuleId, toModuleId, placement = "before") { if (!fromModuleId || !toModuleId || fromModuleId === toModuleId) return; const modules = [...toolbox.modules]; const fromIndex = modules.findIndex((module) => module.id === fromModuleId); const toIndex = modules.findIndex((module) => module.id === toModuleId); if (fromIndex < 0 || toIndex < 0) return; const [moved] = modules.splice(fromIndex, 1); const targetIndex = modules.findIndex((module) => module.id === toModuleId); modules.splice(placement === "after" ? targetIndex + 1 : targetIndex, 0, moved); updateToolbox({ ...toolbox, modules }); } return (

Toolbox

{!embedded && !toolboxGame && updateToolbox({ ...toolbox, 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 && (
)} 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) })} 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); updateToolbox({ ...toolbox, modules: toolbox.modules.filter((module) => module.id !== moduleId) }); } })} onMove={moveModule} /> {!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" }) { const ref = useRef(null); useEffect(() => { if (ref.current && document.activeElement !== ref.current) ref.current.textContent = value; }, [value]); return (

{ event.currentTarget.dataset.previousTitle = event.currentTarget.textContent.trim(); }} onBlur={(event) => onSave(event.currentTarget.textContent.trim() || fallback)} onKeyDown={(event) => { if (event.key === "Enter") { event.preventDefault(); event.currentTarget.blur(); } if (event.key === "Escape") { event.preventDefault(); event.currentTarget.textContent = event.currentTarget.dataset.previousTitle || value; event.currentTarget.blur(); } }} >{value}

); }