import { useLayoutEffect, useMemo, useRef, useState } from "react"; import { Icon } from "../../../components/Icon.jsx"; function calculateExpression(expression) { const normalized = String(expression || "").replaceAll(",", ".").trim(); if (!normalized || !/^[\d\s+\-*/().]+$/.test(normalized)) return null; try { const value = Function(`"use strict"; return (${normalized})`)(); return Number.isFinite(value) ? value : null; } catch { return null; } } function formatResult(value) { if (!Number.isFinite(value)) return ""; return Number.parseFloat(value.toFixed(6)).toString(); } function getChildren(entries, parentId) { return entries.filter((entry) => (entry.parentId || "") === parentId); } function getDescendantIds(entries, parentId) { const children = getChildren(entries, parentId); return children.flatMap((child) => [child.id, ...getDescendantIds(entries, child.id)]); } function getEntriesInTreeOrder(entries, parentId = "") { return getChildren(entries, parentId).flatMap((entry) => [entry, ...getEntriesInTreeOrder(entries, entry.id)]); } export function CalculatorModule({ toolboxId, moduleId, context }) { const data = context.normalizeCalculatorData(context.getModuleData(toolboxId, moduleId, { entries: [] })); const textContent = context.moduleText?.calculator || {}; const [expression, setExpression] = useState(""); const [label, setLabel] = useState(""); const [activeParentId, setActiveParentId] = useState(""); const [calculatorHeight, setCalculatorHeight] = useState(0); const [copied, setCopied] = useState(false); const calculatorCardRef = useRef(null); const result = useMemo(() => calculateExpression(expression), [expression]); const activeParent = data.entries.find((entry) => entry.id === activeParentId); useLayoutEffect(() => { const element = calculatorCardRef.current; if (!element) return undefined; function updateHeight() { setCalculatorHeight(Math.ceil(element.getBoundingClientRect().height)); } updateHeight(); const observer = new ResizeObserver(updateHeight); observer.observe(element); return () => observer.disconnect(); }, []); function save(entries) { context.setModuleData(toolboxId, moduleId, { ...data, entries }); } function setScrollResults(scrollResults) { context.setModuleData(toolboxId, moduleId, { ...data, scrollResults }); } function saveResult(event) { event.preventDefault(); if (result == null) return; const cleanLabel = label.trim(); save([ ...data.entries, { id: context.uid("calc"), parentId: activeParentId, label: cleanLabel, value: result } ]); setExpression(""); setLabel(""); } function useEntry(entry) { setExpression(formatResult(entry.value)); setActiveParentId(entry.id); } function deleteEntry(entryId) { const deletedIds = new Set([entryId, ...getDescendantIds(data.entries, entryId)]); if (deletedIds.has(activeParentId)) setActiveParentId(""); save(data.entries.filter((entry) => !deletedIds.has(entry.id))); } function renameEntry(entryId, label) { const cleanLabel = label.trim(); save(data.entries.map((entry) => entry.id === entryId ? { ...entry, label: cleanLabel } : entry)); } function resetCalculator() { setExpression(""); setLabel(""); setActiveParentId(""); } async function copyChecklistImport() { const text = getEntriesInTreeOrder(data.entries) .map((entry) => `${entry.label || formatResult(entry.value)}:${formatResult(entry.value)}`) .join("\n"); if (!text || !await context.copyText(text)) return; setCopied(true); window.setTimeout(() => setCopied(false), 1400); } return (
Résultat {result == null ? "-" : formatResult(result)}
{activeParent && ( )}