add multi-page notepad and memos toolbox tool
All checks were successful
Deploy Sokko G / deploy (push) Successful in 16s
All checks were successful
Deploy Sokko G / deploy (push) Successful in 16s
This commit is contained in:
parent
fcabbe8029
commit
6125336338
33 changed files with 3787 additions and 138 deletions
|
|
@ -23,6 +23,7 @@ import {
|
|||
normalizeGoalTreeData,
|
||||
normalizeImageAnnotationData,
|
||||
normalizeLinksData,
|
||||
normalizeMemosData,
|
||||
normalizeModuleOrder,
|
||||
normalizeNotepadData,
|
||||
normalizeTableData,
|
||||
|
|
@ -191,6 +192,7 @@ function ToolboxView({ siteContent, toolbox, toolboxGame, embedded, actions, sto
|
|||
normalizeChecklistData,
|
||||
normalizeCombosData,
|
||||
normalizeLinksData,
|
||||
normalizeMemosData,
|
||||
normalizeCountersData,
|
||||
normalizeEquipmentPlannerData,
|
||||
normalizeGoalTreeData,
|
||||
|
|
|
|||
192
website/src/features/toolboxes/modules/MemosModule.jsx
Normal file
192
website/src/features/toolboxes/modules/MemosModule.jsx
Normal file
|
|
@ -0,0 +1,192 @@
|
|||
// Rôle : fournit l'outil de mémos courts avec post-it, bande déroulante et tri manuel.
|
||||
import { CompactDropdown } from "../../../components/CompactDropdown.jsx";
|
||||
import { Icon } from "../../../components/Icon.jsx";
|
||||
import { moveItem, useGroupedReorder } from "../../../hooks/useGroupedReorder.js";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { TextExchangeActions } from "./TextExchangeActions.jsx";
|
||||
import { TEXT_COLORS } from "./notepadUtils.js";
|
||||
|
||||
export function MemosModule({ toolboxId, moduleId, context, editing, textExchangeOpen }) {
|
||||
const storedData = context.getModuleData(toolboxId, moduleId, { items: [] });
|
||||
const data = useMemo(() => context.normalizeMemosData(storedData), [context, storedData]);
|
||||
const textContent = context.moduleText?.memos || {};
|
||||
const tickerItems = data.tickerEnabled ? data.items : [];
|
||||
const [drafts, setDrafts] = useState({});
|
||||
const {
|
||||
itemReorder,
|
||||
getItemProps,
|
||||
isItemDragging,
|
||||
isItemDropTarget,
|
||||
getDropPlacement
|
||||
} = useGroupedReorder({
|
||||
namespace: `memos-${moduleId}`,
|
||||
items: data.items,
|
||||
getItemId: (memo) => memo.id,
|
||||
getParentId: () => moduleId,
|
||||
orientation: "horizontal",
|
||||
onItemMove: (operation) => saveItems(moveItem(data.items, operation.sourceId, operation.targetId, operation.placement)),
|
||||
hierarchy: { enabled: true, stickyParents: true }
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
setDrafts(Object.fromEntries(data.items.map((item) => [item.id, item.text])));
|
||||
}, [data.items]);
|
||||
|
||||
function save(nextData) {
|
||||
context.setModuleData(toolboxId, moduleId, nextData, "memos");
|
||||
}
|
||||
|
||||
function saveItems(items) {
|
||||
save({ ...data, items });
|
||||
}
|
||||
|
||||
function addMemo() {
|
||||
const now = new Date().toISOString();
|
||||
save({
|
||||
...data,
|
||||
items: [
|
||||
{ id: context.uid("memo"), text: textContent.defaultText || "Nouveau mémo", color: "#f6c453", updatedAt: now },
|
||||
...data.items
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
function updateMemo(memoId, updater) {
|
||||
const now = new Date().toISOString();
|
||||
save({
|
||||
...data,
|
||||
items: data.items.map((item) => item.id === memoId ? { ...updater(item), updatedAt: now } : item)
|
||||
});
|
||||
}
|
||||
|
||||
function deleteMemo(memoId) {
|
||||
save({ ...data, items: data.items.filter((item) => item.id !== memoId) });
|
||||
}
|
||||
|
||||
function commitMemoText(memoId) {
|
||||
const nextText = String(drafts[memoId] || "").trim();
|
||||
if (!nextText) {
|
||||
deleteMemo(memoId);
|
||||
return;
|
||||
}
|
||||
updateMemo(memoId, (item) => ({ ...item, text: nextText }));
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="memos-module">
|
||||
{textExchangeOpen && (
|
||||
<div className="module-text-exchange-panel">
|
||||
<TextExchangeActions type="memos" data={data} context={context} textContent={textContent} onImport={(nextData) => save(nextData)} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{editing && (
|
||||
<div className="module-add-panel">
|
||||
<button className="primary memos-add-button" type="button" onClick={addMemo}>
|
||||
<Icon name="add" />
|
||||
<span>{textContent.addButton || "Ajouter"}</span>
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="memos-toolbar">
|
||||
<button
|
||||
className={`memos-ticker-toggle ${data.tickerEnabled ? "active" : ""}`}
|
||||
type="button"
|
||||
onClick={() => save({ ...data, tickerEnabled: !data.tickerEnabled })}
|
||||
aria-pressed={data.tickerEnabled}
|
||||
title={data.tickerEnabled ? textContent.hideTickerTitle || "Masquer la bande" : textContent.showTickerTitle || "Afficher la bande"}
|
||||
>
|
||||
<Icon name={data.tickerEnabled ? "eye-open" : "eye-closed"} />
|
||||
</button>
|
||||
{tickerItems.length > 0 && (
|
||||
<div className="memos-ticker" aria-label={textContent.tickerLabel || "Mémos"}>
|
||||
<div className="memos-ticker-track">
|
||||
{[...tickerItems, ...tickerItems].map((memo, index) => (
|
||||
<span className="memos-ticker-item" key={`${memo.id}-${index}`} style={{ "--memo-color": memo.color }}>
|
||||
{memo.text}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="memos-list">
|
||||
{data.items.length ? data.items.map((memo) => {
|
||||
const className = [
|
||||
"memo-card",
|
||||
isItemDragging(memo.id) ? "is-dragging" : "",
|
||||
isItemDropTarget(memo.id) ? "is-drop-target" : "",
|
||||
getDropPlacement("item", memo.id) === "after" ? "drop-after" : ""
|
||||
].filter(Boolean).join(" ");
|
||||
|
||||
return (
|
||||
<article className={className} key={memo.id} style={{ "--memo-color": memo.color }} {...getItemProps({ itemId: memo.id, parentId: moduleId })}>
|
||||
<button
|
||||
className="memo-drag-handle"
|
||||
type="button"
|
||||
onPointerDown={(event) => itemReorder.startDrag(event, memo.id)}
|
||||
aria-label={`${textContent.reorderTitle || "Déplacer"} ${memo.text}`}
|
||||
title={textContent.reorderTitle || "Déplacer"}
|
||||
>
|
||||
<Icon name="drag" />
|
||||
</button>
|
||||
<textarea
|
||||
value={drafts[memo.id] ?? memo.text}
|
||||
rows={3}
|
||||
placeholder={textContent.placeholder || "Mémo court..."}
|
||||
onChange={(event) => setDrafts((current) => ({ ...current, [memo.id]: event.target.value }))}
|
||||
onBlur={() => commitMemoText(memo.id)}
|
||||
aria-label={textContent.editLabel || "Modifier le mémo"}
|
||||
/>
|
||||
<footer>
|
||||
<CompactDropdown
|
||||
className="memo-color-dropdown notepad-color-dropdown"
|
||||
menuClassName="memo-color-menu notepad-color-menu"
|
||||
label={textContent.colorLabel || "Couleur du mémo"}
|
||||
renderTrigger={({ toggle }) => (
|
||||
<button
|
||||
className="notepad-color-toggle"
|
||||
type="button"
|
||||
style={{ "--swatch-color": memo.color }}
|
||||
onClick={toggle}
|
||||
title={textContent.colorLabel || "Couleur du mémo"}
|
||||
aria-label={textContent.colorLabel || "Couleur du mémo"}
|
||||
/>
|
||||
)}
|
||||
>
|
||||
{({ close }) => (
|
||||
<>
|
||||
{TEXT_COLORS.map((color) => (
|
||||
<button
|
||||
key={color}
|
||||
className={`notepad-swatch ${memo.color === color ? "active" : ""}`}
|
||||
type="button"
|
||||
style={{ "--swatch-color": color }}
|
||||
onClick={() => {
|
||||
updateMemo(memo.id, (item) => ({ ...item, color }));
|
||||
close();
|
||||
}}
|
||||
aria-label={`${textContent.colorLabel || "Couleur"} ${color}`}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</CompactDropdown>
|
||||
<button className="memo-delete-button danger" type="button" onClick={() => deleteMemo(memo.id)} aria-label={textContent.deleteTitle || "Supprimer le mémo"} title={textContent.deleteTitle || "Supprimer"}>
|
||||
<Icon name="trash" />
|
||||
</button>
|
||||
</footer>
|
||||
</article>
|
||||
);
|
||||
}) : (
|
||||
<button className="memos-empty" type="button" onClick={addMemo}>
|
||||
<Icon name="add" />
|
||||
<span>{textContent.emptyLabel || "Ajouter un premier mémo"}</span>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,5 +1,7 @@
|
|||
// Rôle : fournit l'outil bloc-notes riche avec annotations dessinées.
|
||||
// Rôle : fournit l'outil Bloc Note multi-pages avec annotations dessinées.
|
||||
import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
|
||||
import { CompactDropdown } from "../../../components/CompactDropdown.jsx";
|
||||
import { Icon } from "../../../components/Icon.jsx";
|
||||
import { DrawingControls } from "./DrawingControls.jsx";
|
||||
import { DrawingOverlay } from "./DrawingOverlay.jsx";
|
||||
import { NotepadToolbar } from "./NotepadToolbar.jsx";
|
||||
|
|
@ -21,21 +23,24 @@ export function NotepadModule({ toolboxId, moduleId, context, textExchangeOpen }
|
|||
const storedData = context.getModuleData(toolboxId, moduleId, { text: "" });
|
||||
const normalizedData = useMemo(() => context.normalizeNotepadData(storedData), [context, storedData]);
|
||||
const textContent = context.moduleText?.notepad || {};
|
||||
const activePage = normalizedData.pages.find((page) => page.id === normalizedData.activePageId) || normalizedData.pages[0];
|
||||
const editorRef = useRef(null);
|
||||
const surfaceRef = useRef(null);
|
||||
const selectionOffsetRef = useRef(null);
|
||||
const localSaveUpdatedAtRef = useRef("");
|
||||
const moduleKeyRef = useRef(`${toolboxId}:${moduleId}`);
|
||||
const [html, setHtml] = useState(normalizedData.html);
|
||||
const [text, setText] = useState(normalizedData.text);
|
||||
const [updatedAt, setUpdatedAt] = useState(normalizedData.updatedAt);
|
||||
const [drawingMode, setDrawingMode] = useState(normalizedData.drawingMode);
|
||||
const pageKeyRef = useRef(`${toolboxId}:${moduleId}:${activePage.id}`);
|
||||
const [html, setHtml] = useState(activePage.html);
|
||||
const [text, setText] = useState(activePage.text);
|
||||
const [updatedAt, setUpdatedAt] = useState(activePage.updatedAt);
|
||||
const [drawingMode, setDrawingMode] = useState(activePage.drawingMode);
|
||||
const [drawingActive, setDrawingActive] = useState(false);
|
||||
const [eraseActive, setEraseActive] = useState(false);
|
||||
const [drawingColor, setDrawingColor] = useState("#f6c453");
|
||||
const [drawingWidth, setDrawingWidth] = useState(4);
|
||||
const [textColor, setTextColor] = useState(TEXT_COLORS[0]);
|
||||
const [highlightColor, setHighlightColor] = useState(HIGHLIGHT_COLORS[0]);
|
||||
const [editingPageId, setEditingPageId] = useState("");
|
||||
const [pageTitleDrafts, setPageTitleDrafts] = useState({});
|
||||
const [activeFormats, setActiveFormats] = useState({
|
||||
bold: false,
|
||||
italic: false,
|
||||
|
|
@ -45,30 +50,34 @@ export function NotepadModule({ toolboxId, moduleId, context, textExchangeOpen }
|
|||
bulletList: false,
|
||||
numberList: false
|
||||
});
|
||||
const sessionKey = `sokkog:notepad-drawings:${toolboxId}:${moduleId}`;
|
||||
const sessionKey = `sokkog:notepad-drawings:${toolboxId}:${moduleId}:${activePage.id}`;
|
||||
const [temporaryStrokes, setTemporaryStrokes] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
const moduleKey = `${toolboxId}:${moduleId}`;
|
||||
if (moduleKeyRef.current !== moduleKey) {
|
||||
moduleKeyRef.current = moduleKey;
|
||||
setPageTitleDrafts(Object.fromEntries(normalizedData.pages.map((page) => [page.id, page.title])));
|
||||
}, [normalizedData.pages]);
|
||||
|
||||
useEffect(() => {
|
||||
const pageKey = `${toolboxId}:${moduleId}:${activePage.id}`;
|
||||
if (pageKeyRef.current !== pageKey) {
|
||||
pageKeyRef.current = pageKey;
|
||||
localSaveUpdatedAtRef.current = "";
|
||||
}
|
||||
if (normalizedData.updatedAt && normalizedData.updatedAt === localSaveUpdatedAtRef.current) return;
|
||||
if (activePage.updatedAt && activePage.updatedAt === localSaveUpdatedAtRef.current) return;
|
||||
const editor = editorRef.current;
|
||||
if (document.activeElement === editor && getPlainText(editor) === normalizedData.text) {
|
||||
setText(normalizedData.text);
|
||||
setUpdatedAt(normalizedData.updatedAt);
|
||||
setDrawingMode(normalizedData.drawingMode);
|
||||
if (document.activeElement === editor && getPlainText(editor) === activePage.text) {
|
||||
setText(activePage.text);
|
||||
setUpdatedAt(activePage.updatedAt);
|
||||
setDrawingMode(activePage.drawingMode);
|
||||
return;
|
||||
}
|
||||
setHtml(normalizedData.html);
|
||||
setText(normalizedData.text);
|
||||
setUpdatedAt(normalizedData.updatedAt);
|
||||
setDrawingMode(normalizedData.drawingMode);
|
||||
setHtml(activePage.html);
|
||||
setText(activePage.text);
|
||||
setUpdatedAt(activePage.updatedAt);
|
||||
setDrawingMode(activePage.drawingMode);
|
||||
setDrawingActive(false);
|
||||
setEraseActive(false);
|
||||
}, [toolboxId, moduleId, normalizedData.html, normalizedData.text, normalizedData.updatedAt, normalizedData.drawingMode]);
|
||||
}, [toolboxId, moduleId, activePage.id, activePage.html, activePage.text, activePage.updatedAt, activePage.drawingMode]);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (!editorRef.current || editorRef.current.innerHTML === html) return;
|
||||
|
|
@ -121,48 +130,57 @@ export function NotepadModule({ toolboxId, moduleId, context, textExchangeOpen }
|
|||
}
|
||||
}, [context, drawingMode, sessionKey, temporaryStrokes, textContent.sessionStorageError]);
|
||||
|
||||
const activeStrokes = drawingMode === "permanent" ? normalizedData.drawings.strokes : temporaryStrokes;
|
||||
const activeStrokes = drawingMode === "permanent" ? activePage.drawings.strokes : temporaryStrokes;
|
||||
const formattedUpdatedAt = formatUpdatedAt(updatedAt);
|
||||
|
||||
function saveNotepad(nextData) {
|
||||
context.setModuleData(toolboxId, moduleId, nextData, "notepad");
|
||||
}
|
||||
|
||||
function updateActivePage(updater, nextActivePageId = normalizedData.activePageId) {
|
||||
const pages = normalizedData.pages.map((page) => page.id === activePage.id ? updater(page) : page);
|
||||
saveNotepad({ ...normalizedData, activePageId: nextActivePageId, pages });
|
||||
}
|
||||
|
||||
function saveContent(nextHtml, nextText) {
|
||||
const nextUpdatedAt = new Date().toISOString();
|
||||
localSaveUpdatedAtRef.current = nextUpdatedAt;
|
||||
setHtml(nextHtml);
|
||||
setText(nextText);
|
||||
setUpdatedAt(nextUpdatedAt);
|
||||
context.setModuleData(toolboxId, moduleId, {
|
||||
...normalizedData,
|
||||
updateActivePage((page) => ({
|
||||
...page,
|
||||
html: nextHtml,
|
||||
text: nextText,
|
||||
updatedAt: nextUpdatedAt,
|
||||
drawingMode,
|
||||
drawings: drawingMode === "permanent" ? { strokes: activeStrokes } : { strokes: [] }
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
function saveDrawingMode(nextMode, nextStrokes) {
|
||||
setDrawingMode(nextMode);
|
||||
if (nextMode === "temporary") setTemporaryStrokes(nextStrokes);
|
||||
context.setModuleData(toolboxId, moduleId, {
|
||||
...normalizedData,
|
||||
updateActivePage((page) => ({
|
||||
...page,
|
||||
html,
|
||||
text,
|
||||
updatedAt,
|
||||
drawingMode: nextMode,
|
||||
drawings: nextMode === "permanent" ? { strokes: nextStrokes } : { strokes: [] }
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
function saveStrokes(nextStrokes) {
|
||||
if (drawingMode === "permanent") {
|
||||
context.setModuleData(toolboxId, moduleId, {
|
||||
...normalizedData,
|
||||
updateActivePage((page) => ({
|
||||
...page,
|
||||
html,
|
||||
text,
|
||||
updatedAt,
|
||||
drawingMode,
|
||||
drawings: { strokes: nextStrokes }
|
||||
});
|
||||
}));
|
||||
} else {
|
||||
setTemporaryStrokes(nextStrokes);
|
||||
}
|
||||
|
|
@ -211,9 +229,10 @@ export function NotepadModule({ toolboxId, moduleId, context, textExchangeOpen }
|
|||
|
||||
const getSurfaceSize = useCallback(() => {
|
||||
const element = surfaceRef.current;
|
||||
const editor = editorRef.current;
|
||||
return {
|
||||
width: element?.clientWidth || 0,
|
||||
height: Math.max(element?.scrollHeight || 0, element?.clientHeight || 0)
|
||||
height: Math.max(editor?.scrollHeight || 0, element?.clientHeight || 0)
|
||||
};
|
||||
}, []);
|
||||
|
||||
|
|
@ -230,6 +249,39 @@ export function NotepadModule({ toolboxId, moduleId, context, textExchangeOpen }
|
|||
saveStrokes([]);
|
||||
}
|
||||
|
||||
function selectPage(pageId) {
|
||||
if (pageId === normalizedData.activePageId) return;
|
||||
saveNotepad({ ...normalizedData, activePageId: pageId });
|
||||
}
|
||||
|
||||
function addPage(close) {
|
||||
const nextPage = context.normalizeNotepadData({ pages: [{ id: context.uid("page"), title: `Page ${normalizedData.pages.length + 1}` }] }).pages[0];
|
||||
saveNotepad({
|
||||
...normalizedData,
|
||||
activePageId: nextPage.id,
|
||||
pages: [...normalizedData.pages, nextPage]
|
||||
});
|
||||
close();
|
||||
}
|
||||
|
||||
function renamePage(pageId) {
|
||||
const nextTitle = String(pageTitleDrafts[pageId] || "").trim();
|
||||
if (!nextTitle) return;
|
||||
saveNotepad({
|
||||
...normalizedData,
|
||||
pages: normalizedData.pages.map((page) => page.id === pageId ? { ...page, title: nextTitle } : page)
|
||||
});
|
||||
setEditingPageId("");
|
||||
}
|
||||
|
||||
function deletePage(pageId, close) {
|
||||
if (normalizedData.pages.length <= 1) return;
|
||||
const pages = normalizedData.pages.filter((page) => page.id !== pageId);
|
||||
const activePageId = pageId === normalizedData.activePageId ? pages[0].id : normalizedData.activePageId;
|
||||
saveNotepad({ ...normalizedData, activePageId, pages });
|
||||
if (pageId === normalizedData.activePageId) close();
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="notepad-module">
|
||||
{textExchangeOpen && (
|
||||
|
|
@ -237,6 +289,88 @@ export function NotepadModule({ toolboxId, moduleId, context, textExchangeOpen }
|
|||
<TextExchangeActions type="notepad" data={normalizedData} context={context} textContent={textContent} onImport={(nextData) => context.setModuleData(toolboxId, moduleId, nextData, "notepad")} />
|
||||
</div>
|
||||
)}
|
||||
<CompactDropdown
|
||||
className="notepad-pages-dropdown"
|
||||
menuClassName="notepad-pages-menu"
|
||||
label={textContent.pagesMenuLabel || "Pages du bloc note"}
|
||||
renderTrigger={({ open, toggle }) => (
|
||||
<button
|
||||
className={`notepad-pages-toggle ${open ? "active" : ""}`}
|
||||
type="button"
|
||||
onClick={toggle}
|
||||
aria-label={textContent.pagesToggleLabel || "Afficher les pages du bloc note"}
|
||||
aria-expanded={open}
|
||||
title={textContent.pagesToggleLabel || "Afficher les pages du bloc note"}
|
||||
>
|
||||
<Icon name="pages" />
|
||||
</button>
|
||||
)}
|
||||
>
|
||||
{({ close }) => (
|
||||
<>
|
||||
<div className="notepad-pages-list">
|
||||
{normalizedData.pages.map((page) => (
|
||||
<div className={`notepad-page-row ${page.id === normalizedData.activePageId ? "active" : ""}`} key={page.id}>
|
||||
<div className="notepad-page-main">
|
||||
{editingPageId === page.id ? (
|
||||
<input
|
||||
className="notepad-page-title-input"
|
||||
value={pageTitleDrafts[page.id] ?? page.title}
|
||||
aria-label={`${textContent.renamePageLabel || "Renommer"} ${page.title}`}
|
||||
autoFocus
|
||||
onChange={(event) => setPageTitleDrafts((current) => ({ ...current, [page.id]: event.target.value }))}
|
||||
onBlur={() => renamePage(page.id)}
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === "Enter") {
|
||||
event.preventDefault();
|
||||
renamePage(page.id);
|
||||
}
|
||||
if (event.key === "Escape") {
|
||||
setPageTitleDrafts((current) => ({ ...current, [page.id]: page.title }));
|
||||
setEditingPageId("");
|
||||
}
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<button type="button" className="notepad-page-select" onClick={() => { selectPage(page.id); close(); }}>
|
||||
{page.title}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
<div className="notepad-page-actions">
|
||||
<button
|
||||
className="notepad-page-edit"
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setPageTitleDrafts((current) => ({ ...current, [page.id]: page.title }));
|
||||
setEditingPageId(page.id);
|
||||
}}
|
||||
aria-label={`${textContent.renamePageLabel || "Renommer"} ${page.title}`}
|
||||
title={textContent.renamePageLabel || "Renommer"}
|
||||
>
|
||||
<Icon name="edit" />
|
||||
</button>
|
||||
<button
|
||||
className="notepad-page-delete danger"
|
||||
type="button"
|
||||
onClick={() => deletePage(page.id, close)}
|
||||
disabled={normalizedData.pages.length <= 1}
|
||||
aria-label={`${textContent.deletePageLabel || "Supprimer"} ${page.title}`}
|
||||
title={textContent.deletePageLabel || "Supprimer la page"}
|
||||
>
|
||||
<Icon name="trash" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<button className="notepad-page-add" type="button" onClick={() => addPage(close)}>
|
||||
<Icon name="add" />
|
||||
<span>{textContent.addPageLabel || "Ajouter une page"}</span>
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
</CompactDropdown>
|
||||
<NotepadToolbar
|
||||
textContent={textContent}
|
||||
activeFormats={activeFormats}
|
||||
|
|
@ -258,7 +392,7 @@ export function NotepadModule({ toolboxId, moduleId, context, textExchangeOpen }
|
|||
suppressContentEditableWarning
|
||||
role="textbox"
|
||||
aria-multiline="true"
|
||||
data-placeholder={textContent.placeholder || "Notes rapides..."}
|
||||
data-placeholder={textContent.placeholder || "Bloc Note..."}
|
||||
onInput={handleEditorInput}
|
||||
onPaste={handlePaste}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ export function NotepadToolbar({
|
|||
onHighlightColorApply
|
||||
}) {
|
||||
return (
|
||||
<div className="notepad-toolbar" aria-label={textContent.toolbarLabel || "Mise en page du bloc notes"}>
|
||||
<div className="notepad-toolbar" aria-label={textContent.toolbarLabel || "Mise en page du bloc note"}>
|
||||
<div className="notepad-format-controls">
|
||||
<div className="notepad-toolbar-group" role="group" aria-label={textContent.textFormatLabel || "Style du texte"}>
|
||||
<ToolbarButton active={activeFormats.bold} title={textContent.boldTitle || "Gras"} onClick={() => onCommand("bold")}>B</ToolbarButton>
|
||||
|
|
|
|||
|
|
@ -199,7 +199,7 @@ function ModuleShell({ toolbox, module, context, reorder, itemGroup = "", onRena
|
|||
<span className="module-icon" aria-hidden="true">
|
||||
<span className={`module-icon-svg module-icon-${definition.icon || "notepad"}`} />
|
||||
</span>
|
||||
<EditableModuleTitle value={module.title || label} fallback={label} tutorialTarget={module.type === "notepad" ? "moduleTitle" : ""} onSave={(title) => onRename(module.id, title)} />
|
||||
<EditableModuleTitle value={module.title || label} fallback={label} tutorialTarget={module.type === "memos" ? "moduleTitle" : ""} onSave={(title) => onRename(module.id, title)} />
|
||||
</div>
|
||||
<div>
|
||||
{definition.scrollable && (
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// Rôle : regroupe les constantes et helpers DOM du bloc-notes riche.
|
||||
// Rôle : regroupe les constantes et helpers DOM du Bloc Note riche.
|
||||
export const TEXT_COLORS = ["#f5f7ff", "#b4bdd3", "#f6c453", "#22d3ee", "#8b5cf6", "#d946ef"];
|
||||
export const HIGHLIGHT_COLORS = ["#f6c453", "#22d3ee", "#8b5cf6", "#d946ef", "#202745"];
|
||||
export const DRAWING_WIDTHS = [2, 4, 8, 12];
|
||||
|
|
|
|||
|
|
@ -4,12 +4,14 @@ import {
|
|||
exportChecklistText,
|
||||
exportCountersText,
|
||||
exportLinksText,
|
||||
exportMemosText,
|
||||
exportNotepadText,
|
||||
exportTableTsv,
|
||||
importCalculatorText,
|
||||
importChecklistText,
|
||||
importCountersText,
|
||||
importLinksText,
|
||||
importMemosText,
|
||||
importNotepadText,
|
||||
importTableTsv
|
||||
} from "./textImport/basic.js";
|
||||
|
|
@ -24,10 +26,15 @@ export { parseColonImportLines };
|
|||
|
||||
const TEXT_EXCHANGE_HANDLERS = {
|
||||
notepad: {
|
||||
hasContent: (data) => Boolean(data?.text || data?.html || data?.drawings?.strokes?.length),
|
||||
hasContent: (data) => Boolean(data?.pages?.some((page) => page.text || page.html || page.drawings?.strokes?.length) || data?.text || data?.html || data?.drawings?.strokes?.length),
|
||||
exportText: exportNotepadText,
|
||||
importText: importNotepadText
|
||||
},
|
||||
memos: {
|
||||
hasContent: (data) => Boolean(data?.items?.length),
|
||||
exportText: exportMemosText,
|
||||
importText: importMemosText
|
||||
},
|
||||
checklist: {
|
||||
hasContent: (data) => Boolean(data?.sections?.some((section) => section.items?.length)),
|
||||
exportText: exportChecklistText,
|
||||
|
|
|
|||
|
|
@ -3,10 +3,15 @@ import { cellAddress, columnIndexToName } from "../tableFormulaEngine.js";
|
|||
import { cloneDrawingData, lineError, parseBoolean, parseColonImportLines, parseInteger, parseMetaLine, parseNumber, splitLines, textToNotepadExchangeHtml } from "./shared.js";
|
||||
|
||||
export function exportNotepadText(data) {
|
||||
const html = String(data?.html || "").trim() || textToNotepadExchangeHtml(data?.text || "");
|
||||
const lines = ["@notepad", "@html", html, "@endhtml"];
|
||||
if (data?.drawingMode === "permanent") lines.push("@drawingMode: permanent");
|
||||
if (data?.drawings?.strokes?.length) lines.push(`@drawings: ${JSON.stringify(cloneDrawingData(data.drawings))}`);
|
||||
const pages = Array.isArray(data?.pages) && data.pages.length ? data.pages : [data];
|
||||
const lines = ["@notepad"];
|
||||
pages.forEach((page, index) => {
|
||||
const html = String(page?.html || "").trim() || textToNotepadExchangeHtml(page?.text || "");
|
||||
lines.push(`@page: ${page?.title || `Page ${index + 1}`}`);
|
||||
lines.push("@html", html, "@endhtml");
|
||||
if (page?.drawingMode === "permanent") lines.push("@drawingMode: permanent");
|
||||
if (page?.drawings?.strokes?.length) lines.push(`@drawings: ${JSON.stringify(cloneDrawingData(page.drawings))}`);
|
||||
});
|
||||
return lines.join("\n");
|
||||
}
|
||||
|
||||
|
|
@ -38,16 +43,34 @@ export function importNotepadText(text, context) {
|
|||
|
||||
let readingHtml = false;
|
||||
let htmlWasOpened = false;
|
||||
let htmlWasClosed = false;
|
||||
const htmlLines = [];
|
||||
let drawingMode = "temporary";
|
||||
let drawings = { strokes: [] };
|
||||
const pages = [];
|
||||
let currentPage = {
|
||||
title: "Page 1",
|
||||
drawingMode: "temporary",
|
||||
drawings: { strokes: [] }
|
||||
};
|
||||
|
||||
function flushPage() {
|
||||
if (!htmlWasOpened && !currentPage.drawings.strokes.length && currentPage.drawingMode !== "permanent") return;
|
||||
pages.push({
|
||||
...currentPage,
|
||||
html: htmlLines.join("\n").trim()
|
||||
});
|
||||
htmlLines.length = 0;
|
||||
htmlWasOpened = false;
|
||||
currentPage = {
|
||||
title: `Page ${pages.length + 1}`,
|
||||
drawingMode: "temporary",
|
||||
drawings: { strokes: [] }
|
||||
};
|
||||
}
|
||||
|
||||
lines.forEach((line, index) => {
|
||||
const cleanLine = line.trim();
|
||||
if (readingHtml) {
|
||||
if (cleanLine === "@endhtml") {
|
||||
readingHtml = false;
|
||||
htmlWasClosed = true;
|
||||
return;
|
||||
}
|
||||
htmlLines.push(line);
|
||||
|
|
@ -61,24 +84,55 @@ export function importNotepadText(text, context) {
|
|||
return;
|
||||
}
|
||||
const meta = parseMetaLine(cleanLine);
|
||||
if (meta?.key === "page") {
|
||||
flushPage();
|
||||
currentPage.title = meta.value || `Page ${pages.length + 1}`;
|
||||
return;
|
||||
}
|
||||
if (meta?.key === "drawingMode") {
|
||||
if (!["temporary", "permanent"].includes(meta.value)) throw lineError(index, "mode de dessin invalide.");
|
||||
drawingMode = meta.value;
|
||||
currentPage.drawingMode = meta.value;
|
||||
return;
|
||||
}
|
||||
if (meta?.key === "drawings") {
|
||||
try {
|
||||
drawings = cloneImportedNotepadDrawings(JSON.parse(meta.value), context);
|
||||
currentPage.drawings = cloneImportedNotepadDrawings(JSON.parse(meta.value), context);
|
||||
} catch (error) {
|
||||
throw lineError(index, error?.message || "dessins invalides.");
|
||||
}
|
||||
if (drawings.strokes.length) drawingMode = "permanent";
|
||||
if (currentPage.drawings.strokes.length) currentPage.drawingMode = "permanent";
|
||||
return;
|
||||
}
|
||||
throw lineError(index, `ligne bloc notes invalide: ${cleanLine}`);
|
||||
throw lineError(index, `ligne bloc note invalide: ${cleanLine}`);
|
||||
});
|
||||
if (readingHtml || (htmlWasOpened && !htmlWasClosed)) throw new Error("Bloc HTML non terminé.");
|
||||
return context.normalizeNotepadData({ html: htmlLines.join("\n").trim(), drawingMode, drawings });
|
||||
if (readingHtml) throw new Error("Bloc HTML non terminé.");
|
||||
flushPage();
|
||||
return context.normalizeNotepadData({ pages });
|
||||
}
|
||||
|
||||
|
||||
export function exportMemosText(data) {
|
||||
return (data.items || []).map((memo) => `- ${memo.text} | color=${memo.color || "#f6c453"}`).join("\n");
|
||||
}
|
||||
|
||||
|
||||
export function importMemosText(text, context) {
|
||||
const items = [];
|
||||
splitLines(text).forEach((line) => {
|
||||
const cleanLine = line.trim();
|
||||
if (!cleanLine) return;
|
||||
const memoLine = cleanLine.replace(/^[-*]\s*/, "").trim();
|
||||
const colorMatch = memoLine.match(/\s+\|\s*color=(#[0-9a-f]{6})\s*$/i);
|
||||
const label = (colorMatch ? memoLine.slice(0, colorMatch.index) : memoLine).trim();
|
||||
if (!label) return;
|
||||
items.push({
|
||||
id: context.uid("memo"),
|
||||
text: label,
|
||||
color: colorMatch?.[1] || "#f6c453",
|
||||
updatedAt: new Date().toISOString()
|
||||
});
|
||||
});
|
||||
return context.normalizeMemosData({ items });
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import { CountersModule } from "./CountersModule.jsx";
|
|||
import { ImageAnnotationModule } from "./ImageAnnotationModule.jsx";
|
||||
import { ImagesModule } from "./ImagesModule.jsx";
|
||||
import { LinksModule } from "./LinksModule.jsx";
|
||||
import { MemosModule } from "./MemosModule.jsx";
|
||||
import { NotepadModule } from "./NotepadModule.jsx";
|
||||
import { TableModule } from "./TableModule.jsx";
|
||||
import { TaskPlannerModule } from "./TaskPlannerModule.jsx";
|
||||
|
|
@ -20,6 +21,7 @@ const EquipmentPlannerModule = lazy(() => import("./EquipmentPlannerModule.jsx")
|
|||
|
||||
export const MODULE_COMPONENTS = {
|
||||
notepad: { ...TOOLBOX_MODULE_DEFINITIONS.notepad, Component: NotepadModule },
|
||||
memos: { ...TOOLBOX_MODULE_DEFINITIONS.memos, Component: MemosModule },
|
||||
checklist: { ...TOOLBOX_MODULE_DEFINITIONS.checklist, Component: ChecklistModule },
|
||||
images: { ...TOOLBOX_MODULE_DEFINITIONS.images, Component: ImagesModule },
|
||||
links: { ...TOOLBOX_MODULE_DEFINITIONS.links, Component: LinksModule },
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import { compactEquipmentPlannerDataForStorage } from "./equipmentPlanner.js";
|
|||
import { compactGoalTreeDataForStorage } from "./goalTree.js";
|
||||
import { compactImageAnnotationDataForStorage, compactImagesDataForStorage } from "./imageAnnotation.js";
|
||||
import { compactLinksDataForStorage } from "./links.js";
|
||||
import { compactMemosDataForStorage } from "./memos.js";
|
||||
import { compactNotepadDataForStorage } from "./notepad.js";
|
||||
import { compactTableDataForStorage } from "./table.js";
|
||||
import { compactTaskPlannerDataForStorage } from "./taskPlanner.js";
|
||||
|
|
@ -15,6 +16,7 @@ import { compactTimerDataForStorage } from "./timer.js";
|
|||
|
||||
export const MODULE_DATA_COMPACTORS = {
|
||||
notepad: compactNotepadDataForStorage,
|
||||
memos: compactMemosDataForStorage,
|
||||
checklist: compactChecklistDataForStorage,
|
||||
images: compactImagesDataForStorage,
|
||||
imageAnnotation: compactImageAnnotationDataForStorage,
|
||||
|
|
|
|||
43
website/src/features/toolboxes/storage/modules/memos.js
Normal file
43
website/src/features/toolboxes/storage/modules/memos.js
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
// Rôle : normalise et compacte les mémos courts des toolboxes.
|
||||
import { NOTEPAD_ALLOWED_COLORS } from "./drawing.js";
|
||||
import { normalizeIsoDate, uid } from "./shared.js";
|
||||
|
||||
const DEFAULT_MEMO_COLOR = "#f6c453";
|
||||
|
||||
function normalizeMemoItem(item) {
|
||||
const text = String(item?.text || "").trim();
|
||||
if (!text) return null;
|
||||
const color = NOTEPAD_ALLOWED_COLORS.has(String(item?.color || "").toLowerCase()) ? String(item.color).toLowerCase() : DEFAULT_MEMO_COLOR;
|
||||
return {
|
||||
id: item?.id || uid("memo"),
|
||||
text,
|
||||
color,
|
||||
updatedAt: normalizeIsoDate(item?.updatedAt)
|
||||
};
|
||||
}
|
||||
|
||||
export function normalizeMemosData(data) {
|
||||
const source = data && typeof data === "object" ? data : {};
|
||||
return {
|
||||
tickerEnabled: source.tickerEnabled !== false,
|
||||
items: (Array.isArray(source.items) ? source.items : []).map(normalizeMemoItem).filter(Boolean)
|
||||
};
|
||||
}
|
||||
|
||||
export function compactMemosDataForStorage(value) {
|
||||
const normalized = normalizeMemosData(value);
|
||||
const compact = {};
|
||||
if (normalized.tickerEnabled === false) compact.tickerEnabled = false;
|
||||
if (normalized.items.length) {
|
||||
compact.items = normalized.items.map((item) => {
|
||||
const compactItem = {
|
||||
id: item.id,
|
||||
text: item.text,
|
||||
color: item.color
|
||||
};
|
||||
if (item.updatedAt) compactItem.updatedAt = item.updatedAt;
|
||||
return compactItem;
|
||||
});
|
||||
}
|
||||
return Object.keys(compact).length ? compact : null;
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
// Rôle : normalise, nettoie et compacte les données du bloc-notes riche.
|
||||
// Rôle : normalise, nettoie et compacte les pages du bloc-note riche.
|
||||
import { NOTEPAD_ALLOWED_COLORS, normalizeDrawingData } from "./drawing.js";
|
||||
import { normalizeIsoDate } from "./shared.js";
|
||||
import { normalizeIsoDate, uid } from "./shared.js";
|
||||
|
||||
const NOTEPAD_ALLOWED_TAGS = new Set(["p", "div", "br", "strong", "b", "em", "i", "u", "s", "strike", "ul", "ol", "li", "h3", "h4", "span", "mark"]);
|
||||
const NOTEPAD_RGB_COLOR_MAP = {
|
||||
|
|
@ -106,24 +106,66 @@ function notepadHtmlToText(value) {
|
|||
.trim();
|
||||
}
|
||||
|
||||
export function normalizeNotepadData(data) {
|
||||
const source = data && typeof data === "object" ? data : {};
|
||||
function normalizeNotepadPage(page, index = 0) {
|
||||
const source = page && typeof page === "object" ? page : {};
|
||||
const legacyText = String(source.text || "");
|
||||
const html = sanitizeNotepadHtml(source.html ? source.html : textToNotepadHtml(legacyText));
|
||||
const text = notepadHtmlToText(html) || legacyText.trim();
|
||||
const updatedAt = normalizeIsoDate(source.updatedAt);
|
||||
const drawingMode = source.drawingMode === "permanent" ? "permanent" : "temporary";
|
||||
const drawings = normalizeDrawingData(source.drawings);
|
||||
return { html, text, updatedAt, drawingMode, drawings };
|
||||
return {
|
||||
id: source.id || uid("page"),
|
||||
title: String(source.title || `Page ${index + 1}`).trim() || `Page ${index + 1}`,
|
||||
html,
|
||||
text,
|
||||
updatedAt,
|
||||
drawingMode,
|
||||
drawings
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeLegacyNotepadData(data) {
|
||||
const source = data && typeof data === "object" ? data : {};
|
||||
return normalizeNotepadPage({ ...source, id: source.id || uid("page"), title: source.title || "Page 1" }, 0);
|
||||
}
|
||||
|
||||
export function normalizeNotepadData(data) {
|
||||
const source = data && typeof data === "object" ? data : {};
|
||||
const pages = (Array.isArray(source.pages) ? source.pages : [normalizeLegacyNotepadData(source)])
|
||||
.map((page, index) => normalizeNotepadPage(page, index))
|
||||
.filter(Boolean);
|
||||
const normalizedPages = pages.length ? pages : [normalizeNotepadPage({ title: "Page 1" }, 0)];
|
||||
const activePageId = normalizedPages.some((page) => page.id === source.activePageId) ? source.activePageId : normalizedPages[0].id;
|
||||
const activePage = normalizedPages.find((page) => page.id === activePageId) || normalizedPages[0];
|
||||
return {
|
||||
activePageId,
|
||||
pages: normalizedPages,
|
||||
html: activePage.html,
|
||||
text: activePage.text,
|
||||
updatedAt: activePage.updatedAt,
|
||||
drawingMode: activePage.drawingMode,
|
||||
drawings: activePage.drawings
|
||||
};
|
||||
}
|
||||
|
||||
export function compactNotepadDataForStorage(value) {
|
||||
const normalized = normalizeNotepadData(value);
|
||||
const compact = {};
|
||||
if (normalized.html && normalized.text) compact.html = normalized.html;
|
||||
if (normalized.text) compact.text = normalized.text;
|
||||
if (normalized.updatedAt) compact.updatedAt = normalized.updatedAt;
|
||||
if (normalized.drawingMode === "permanent") compact.drawingMode = "permanent";
|
||||
if (normalized.drawingMode === "permanent" && normalized.drawings.strokes.length) compact.drawings = normalized.drawings;
|
||||
return Object.keys(compact).length ? compact : null;
|
||||
const hasMultiplePages = normalized.pages.length > 1;
|
||||
const pages = normalized.pages.map((page, index) => {
|
||||
const compact = {
|
||||
id: page.id,
|
||||
title: page.title || `Page ${index + 1}`
|
||||
};
|
||||
if (page.html && page.text) compact.html = page.html;
|
||||
if (page.text) compact.text = page.text;
|
||||
if (page.updatedAt) compact.updatedAt = page.updatedAt;
|
||||
if (page.drawingMode === "permanent") compact.drawingMode = "permanent";
|
||||
if (page.drawingMode === "permanent" && page.drawings.strokes.length) compact.drawings = page.drawings;
|
||||
return compact;
|
||||
}).filter((page) => hasMultiplePages || page.text || page.html || page.drawings?.strokes?.length || page.title !== "Page 1");
|
||||
if (!pages.length) return null;
|
||||
const compact = { pages };
|
||||
if (pages.some((page) => page.id === normalized.activePageId) && normalized.activePageId !== pages[0].id) compact.activePageId = normalized.activePageId;
|
||||
return compact;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,8 @@ export const ID_PREFIXES = {
|
|||
goal: "g",
|
||||
relation: "e",
|
||||
stroke: "d",
|
||||
page: "p",
|
||||
memo: "w",
|
||||
equipmentType: "y",
|
||||
equipment: "q",
|
||||
trait: "v",
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ export { normalizeEquipmentPlannerData, summarizeEquipmentPlannerData } from "./
|
|||
export { normalizeGoalTreeData } from "./modules/goalTree.js";
|
||||
export { labelFromFileName, normalizeImageAnnotationData } from "./modules/imageAnnotation.js";
|
||||
export { hostnameFromUrl, normalizeLinksData, normalizeUrl } from "./modules/links.js";
|
||||
export { normalizeMemosData } from "./modules/memos.js";
|
||||
export { normalizeNotepadData, sanitizeNotepadHtml } from "./modules/notepad.js";
|
||||
export { uid } from "./modules/shared.js";
|
||||
export { normalizeTableData } from "./modules/table.js";
|
||||
|
|
@ -215,6 +216,28 @@ function remapModuleDataForExport(type, data, nextId) {
|
|||
return { counters: compact.counters.map((counter) => ({ ...counter, id: nextId("counter") })) };
|
||||
}
|
||||
|
||||
if (type === "memos") {
|
||||
const remapped = {};
|
||||
if (compact.tickerEnabled === false) remapped.tickerEnabled = false;
|
||||
if (compact.items) remapped.items = compact.items.map((memo) => ({ ...memo, id: nextId("memo") }));
|
||||
return remapped;
|
||||
}
|
||||
|
||||
if (type === "notepad") {
|
||||
const remapped = {
|
||||
pages: compact.pages.map((page) => ({
|
||||
...page,
|
||||
id: nextId("page"),
|
||||
drawings: page.drawings?.strokes?.length
|
||||
? { strokes: page.drawings.strokes.map((stroke) => ({ ...stroke, id: nextId("stroke") })) }
|
||||
: page.drawings
|
||||
}))
|
||||
};
|
||||
const activeIndex = compact.pages.findIndex((page) => page.id === compact.activePageId);
|
||||
if (activeIndex > 0) remapped.activePageId = remapped.pages[activeIndex].id;
|
||||
return remapped;
|
||||
}
|
||||
|
||||
if (type === "combos") {
|
||||
const remapped = {};
|
||||
if (compact.device) remapped.device = compact.device;
|
||||
|
|
|
|||
|
|
@ -1,14 +1,24 @@
|
|||
// Rôle : centralise les métadonnées légères des outils toolbox sans importer leurs composants.
|
||||
export const TOOLBOX_MODULE_DEFINITIONS = {
|
||||
notepad: {
|
||||
label: "Bloc notes",
|
||||
defaultTitle: "Bloc notes",
|
||||
label: "Bloc Note",
|
||||
defaultTitle: "Bloc Note",
|
||||
mode: "Texte riche",
|
||||
icon: "notepad",
|
||||
editable: true,
|
||||
scrollable: true,
|
||||
addable: false,
|
||||
libraryCategory: "notesTracking"
|
||||
},
|
||||
memos: {
|
||||
label: "Memos",
|
||||
defaultTitle: "Memos",
|
||||
mode: "Post-it courts",
|
||||
icon: "memos",
|
||||
editable: true,
|
||||
scrollable: true,
|
||||
libraryCategory: "notesTracking"
|
||||
},
|
||||
checklist: {
|
||||
label: "Checklist",
|
||||
defaultTitle: "Checklist",
|
||||
|
|
@ -134,7 +144,7 @@ export const TOOLBOX_MODULES = Object.fromEntries(Object.entries(TOOLBOX_MODULE_
|
|||
]));
|
||||
|
||||
export const LIBRARY_CATEGORY_DEFINITIONS = [
|
||||
{ key: "notesTracking", types: ["notepad", "checklist", "counters"] },
|
||||
{ key: "notesTracking", types: ["memos", "notepad", "checklist", "counters"] },
|
||||
{ key: "references", types: ["images", "imageAnnotation", "links"] },
|
||||
{ key: "calculationData", types: ["calculator", "table", "goalTree"] },
|
||||
{ key: "timeRoutines", types: ["timer", "calendar", "taskPlanner"] },
|
||||
|
|
|
|||
|
|
@ -28,10 +28,9 @@ function getLibraryUrl() {
|
|||
export function useToolboxActions({ store, notify, creationTemplate, setConfirmModal, setCreateModal, setLinkModalGameId, setDrawerGameId, setImage }) {
|
||||
function createToolbox(name, gameId = "", options = {}) {
|
||||
const now = new Date().toISOString();
|
||||
const notepadModuleId = uid("mod");
|
||||
const memosModuleId = uid("mod");
|
||||
const linksModuleId = uid("mod");
|
||||
const welcomeText = creationTemplate?.notepad?.text || "Profite bien de ta nouvelle toolbox.";
|
||||
const welcomeHtml = creationTemplate?.notepad?.html || `<p>${welcomeText}</p>`;
|
||||
const welcomeText = creationTemplate?.memos?.text || creationTemplate?.notepad?.text || "Profite bien de ta nouvelle toolbox.";
|
||||
const libraryLink = {
|
||||
id: uid("link"),
|
||||
title: creationTemplate?.links?.title || "Librairie des outils",
|
||||
|
|
@ -42,13 +41,13 @@ export function useToolboxActions({ store, notify, creationTemplate, setConfirmM
|
|||
name: name.trim() || "Nouvelle toolbox",
|
||||
updatedAt: now,
|
||||
modules: [
|
||||
{ id: notepadModuleId, type: "notepad", title: creationTemplate?.notepad?.title || "Bienvenue" },
|
||||
{ id: memosModuleId, type: "memos", title: creationTemplate?.memos?.title || "Memos" },
|
||||
{ id: linksModuleId, type: "links", title: creationTemplate?.links?.moduleTitle || "Liens utiles" }
|
||||
]
|
||||
};
|
||||
if (options.tutorial === true) toolbox.tutorial = true;
|
||||
if (!store.setToolboxes([toolbox, ...store.toolboxes])) return null;
|
||||
store.updateModuleData(toolbox.id, notepadModuleId, { html: welcomeHtml, text: welcomeText, updatedAt: now }, "notepad");
|
||||
store.updateModuleData(toolbox.id, memosModuleId, { items: [{ id: uid("memo"), text: welcomeText, color: "#f6c453", updatedAt: now }] }, "memos");
|
||||
store.updateModuleData(toolbox.id, linksModuleId, { links: [libraryLink] }, "links");
|
||||
if (gameId) store.setLinks({ ...store.links, [gameId]: toolbox.id });
|
||||
return toolbox;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue