From 6125336338ab91c766809f5266c7d0180eb1e79b Mon Sep 17 00:00:00 2001 From: Shinuwa Date: Tue, 25 Aug 2026 12:03:11 +0200 Subject: [PATCH] add multi-page notepad and memos toolbox tool --- DESIGN_SYSTEM.md | 20 +- docs/FEATURE_CHECKLIST.md | 2 + docs/STORAGE_SCHEMA.md | 74 +- tests/helpers/data-validation.mjs | 37 +- tests/static-toolboxes.test.mjs | 2 +- tests/toolbox-modules.test.mjs | 18 +- website/public/data/library.json | 2568 ++++++++++++++++- website/public/data/site.json | 131 +- website/public/static/icons/memos.svg | 24 + website/public/static/icons/pages.svg | 2 + .../src/features/toolboxes/ToolboxPages.jsx | 2 + .../toolboxes/modules/MemosModule.jsx | 192 ++ .../toolboxes/modules/NotepadModule.jsx | 198 +- .../toolboxes/modules/NotepadToolbar.jsx | 2 +- .../toolboxes/modules/ToolboxModules.jsx | 2 +- .../toolboxes/modules/notepadUtils.js | 2 +- .../features/toolboxes/modules/textImport.js | 9 +- .../toolboxes/modules/textImport/basic.js | 82 +- .../modules/toolboxModuleComponents.jsx | 2 + .../toolboxes/storage/modules/index.js | 2 + .../toolboxes/storage/modules/memos.js | 43 + .../toolboxes/storage/modules/notepad.js | 66 +- .../toolboxes/storage/modules/shared.js | 2 + .../toolboxes/storage/toolboxStorage.js | 23 + .../toolboxes/toolboxModuleRegistry.js | 16 +- .../features/toolboxes/useToolboxActions.js | 9 +- website/src/pages/LibraryPage.jsx | 5 +- .../src/styles/home/library/_previews.scss | 4 + website/src/styles/icons/_toolbox.scss | 10 + website/src/styles/toolboxes/_core.scss | 1 + website/src/styles/toolboxes/_memos.scss | 186 ++ .../src/styles/toolboxes/_module-shell.scss | 5 + website/src/styles/toolboxes/_notepad.scss | 184 +- 33 files changed, 3787 insertions(+), 138 deletions(-) create mode 100644 website/public/static/icons/memos.svg create mode 100644 website/public/static/icons/pages.svg create mode 100644 website/src/features/toolboxes/modules/MemosModule.jsx create mode 100644 website/src/features/toolboxes/storage/modules/memos.js create mode 100644 website/src/styles/toolboxes/_memos.scss diff --git a/DESIGN_SYSTEM.md b/DESIGN_SYSTEM.md index 8c0aa16..011b0a7 100644 --- a/DESIGN_SYSTEM.md +++ b/DESIGN_SYSTEM.md @@ -280,12 +280,15 @@ Regles UI : - le mode simultane doit etre volontaire, via switch ou depot d'une touche dans une etape existante ; - les categories sont regroupees automatiquement depuis le champ categorie du combo. -### Bloc Notes Riche +### Bloc Note Riche -Le bloc notes utilise une edition directe en `contentEditable`, sans mode preview separe. +Le bloc note utilise une edition directe en `contentEditable`, sans mode preview separe. Regles UI : +- pages multiples accessibles par un bouton icone `pages` pose en absolute sur la bordure gauche de l'outil ; +- le bouton ouvre toujours un menu deroulant de pages, y compris en page toolbox complete, avec deroulement de gauche vers la droite ; +- le renommage d'une page passe par un bouton edition qui active l'edition inline du titre ; - toolbar compacte en haut de l'outil ; - boutons courts pour gras, italique, souligne, barre, titre et listes ; - le paragraphe est le style par defaut et n'a pas de bouton dedie ; @@ -296,6 +299,19 @@ Regles UI : - placeholder integre dans la surface d'edition quand la note est vide ; - le HTML persiste doit rester nettoye via allowlist, jamais via HTML libre. +### Memos + +L'outil Memos sert aux informations courtes type post-it. + +Regles UI : + +- cartes compactes, non imbriquees, avec accent couleur localise ; +- edition directe du texte court ; +- palette de couleurs compacte reprenant les swatches du bloc note ; +- la creation de memo est masquee par defaut et s'ouvre via le bouton `+` du header d'outil ; +- réorganisation des cartes par poignée de drag & drop compacte ; +- bande deroulante visible uniquement quand elle est active et qu'au moins un memo existe ; elle affiche tous les memos. + ### Calque De Dessin Les annotations dessinees utilisent un canvas transparent superpose au contenu annote. diff --git a/docs/FEATURE_CHECKLIST.md b/docs/FEATURE_CHECKLIST.md index dded614..c6212c2 100644 --- a/docs/FEATURE_CHECKLIST.md +++ b/docs/FEATURE_CHECKLIST.md @@ -15,6 +15,8 @@ Checklist à suivre lors de l'ajout ou de la modification d'un outil, d'une page - Si l'outil porte un état de progression, vérifier que l'import texte repart de l'état initial attendu. - Vérifier l'affichage dans la page toolbox complète. - Vérifier l'affichage dans le panneau latéral. +- Pour le Bloc Note, vérifier la sélection, l'ajout, le renommage et la suppression de pages en page complète, panneau latéral et mobile. +- Pour Memos, vérifier l'édition courte, les couleurs, la réorganisation et la bande déroulante. - Vérifier le mode une colonne et deux colonnes. - Vérifier que l'ajout, la suppression et la réorganisation respectent `moduleOrder.one` et `moduleOrder.two`. - Vérifier le quota de stockage si l'outil manipule des données lourdes. diff --git a/docs/STORAGE_SCHEMA.md b/docs/STORAGE_SCHEMA.md index 5ec71bf..22261d6 100644 --- a/docs/STORAGE_SCHEMA.md +++ b/docs/STORAGE_SCHEMA.md @@ -83,42 +83,75 @@ Les associations entre pages jeux et toolboxes sont stockées dans `kv/links`. } ``` -## Outil Bloc Notes +## Outil Bloc Note Type : `notepad` ```json { - "html": "

Notes rapides...

", - "text": "Notes rapides...", - "updatedAt": "2026-07-29T12:00:00.000Z", - "drawingMode": "permanent", - "drawings": { - "strokes": [ - { - "id": "stroke1", - "color": "#f6c453", - "width": 4, - "points": [ - { "x": 12.5, "y": 48 }, - { "x": 24, "y": 52.5 } + "activePageId": "page1", + "pages": [ + { + "id": "page1", + "title": "Page 1", + "html": "

Notes rapides...

", + "text": "Notes rapides...", + "updatedAt": "2026-07-29T12:00:00.000Z", + "drawingMode": "permanent", + "drawings": { + "strokes": [ + { + "id": "stroke1", + "color": "#f6c453", + "width": 4, + "points": [ + { "x": 12.5, "y": 48 }, + { "x": 24, "y": 52.5 } + ] + } ] } - ] - } + } + ] } ``` Stockage compact : -- l'ancien format `{ "text": "..." }` reste accepté et est converti en HTML riche au prochain enregistrement ; +- l'ancien format mono-page `{ "html": "...", "text": "..." }` reste accepté et est converti en `pages[0]` au prochain enregistrement ; +- `activePageId` est omis si la première page stockée est active ; - `html` est nettoyé avec une allowlist de balises et de couleurs ; - `text` conserve une version texte pour les exports et les fallbacks ; - `updatedAt` est omis si vide ou invalide ; - `drawingMode` vaut `temporary` par défaut et est omis dans ce cas ; - `drawings` est stocké uniquement si `drawingMode` vaut `permanent` et qu'au moins un trait valide existe ; -- les dessins temporaires du bloc-notes sont conservés dans `sessionStorage`, pas dans IndexedDB ; -- si le texte, la date et les dessins permanents sont vides, l'entrée de module peut être supprimée. +- les dessins temporaires du bloc-note sont conservés dans `sessionStorage` par toolbox, outil et page, pas dans IndexedDB ; +- si toutes les pages sont vides et que seule la page par défaut reste présente, l'entrée de module peut être supprimée. + +## Outil Memos + +Type : `memos` + +```json +{ + "tickerEnabled": true, + "items": [ + { + "id": "memo1", + "text": "Penser à sauvegarder avant le boss", + "color": "#f6c453", + "updatedAt": "2026-08-21T12:00:00.000Z" + } + ] +} +``` + +Stockage compact : + +- `tickerEnabled` vaut `true` par défaut et est omis dans ce cas ; +- `updatedAt` est omis si vide ou invalide ; +- les mémos sans texte sont ignorés ; +- si aucun mémo n'est valide et que la bande est active par défaut, l'entrée de module peut être supprimée. ## Outil Checklist @@ -878,7 +911,8 @@ Règles communes : Formats : -- `notepad` : format structuré `@notepad`, bloc `@html` / `@endhtml` pour le HTML riche nettoyé, meta `@drawingMode: permanent` et `@drawings: {...}` pour les dessins permanents. Un collage texte simple reste accepté et devient une note sans mise en forme. +- `notepad` : format structuré `@notepad`, blocs `@page: Titre`, `@html` / `@endhtml` pour le HTML riche nettoyé, meta `@drawingMode: permanent` et `@drawings: {...}` pour les dessins permanents. Un collage texte simple reste accepté et devient une page sans mise en forme. +- `memos` : une ligne par mémo, avec préfixe `-` optionnel et suffixe couleur `| color=#f6c453` ; les anciens préfixes `*` sont acceptés puis normalisés. - `checklist` : sections `# Catégorie`, lignes `Item: quantité cible`. - `links` : lignes `Titre: https://...` ou URL seule. - `counters` : lignes `Libellé: valeur`, valeurs négatives acceptées. diff --git a/tests/helpers/data-validation.mjs b/tests/helpers/data-validation.mjs index 3ca3a73..46aeaf6 100644 --- a/tests/helpers/data-validation.mjs +++ b/tests/helpers/data-validation.mjs @@ -130,6 +130,12 @@ export function validateSiteContent(site) { "toolboxes.storageHelp.title", "toolboxes.storageHelp.text", "toolboxes.modules.notepad.placeholder", + "toolboxes.modules.notepad.pagesMenuLabel", + "toolboxes.modules.notepad.pagesToggleLabel", + "toolboxes.modules.notepad.pagesToggleText", + "toolboxes.modules.notepad.addPageLabel", + "toolboxes.modules.notepad.renamePageLabel", + "toolboxes.modules.notepad.deletePageLabel", "toolboxes.modules.notepad.toolbarLabel", "toolboxes.modules.notepad.textFormatLabel", "toolboxes.modules.notepad.boldTitle", @@ -167,6 +173,28 @@ export function validateSiteContent(site) { "toolboxes.modules.notepad.exportSuccess", "toolboxes.modules.notepad.importError", "toolboxes.modules.notepad.exportInfo", + "toolboxes.modules.memos.defaultText", + "toolboxes.modules.memos.placeholder", + "toolboxes.modules.memos.addButton", + "toolboxes.modules.memos.emptyLabel", + "toolboxes.modules.memos.editLabel", + "toolboxes.modules.memos.deleteTitle", + "toolboxes.modules.memos.colorLabel", + "toolboxes.modules.memos.reorderTitle", + "toolboxes.modules.memos.tickerLabel", + "toolboxes.modules.memos.hideTickerTitle", + "toolboxes.modules.memos.showTickerTitle", + "toolboxes.modules.memos.importPlaceholder", + "toolboxes.modules.memos.importOpenButton", + "toolboxes.modules.memos.exportOpenButton", + "toolboxes.modules.memos.importModalTitle", + "toolboxes.modules.memos.exportModalTitle", + "toolboxes.modules.memos.importButton", + "toolboxes.modules.memos.exportButton", + "toolboxes.modules.memos.importSuccess", + "toolboxes.modules.memos.exportSuccess", + "toolboxes.modules.memos.importError", + "toolboxes.modules.memos.exportInfo", "toolboxes.modules.checklist.itemPlaceholder", "toolboxes.modules.checklist.sectionPlaceholder", "toolboxes.modules.checklist.quantityLabel", @@ -621,7 +649,7 @@ export function validateSiteContent(site) { }); const libraryToolDocs = valueAt(site, "library.toolDocs"); - ["notepad", "checklist", "counters", "images", "imageAnnotation", "links", "calculator", "table", "goalTree", "timer", "calendar", "taskPlanner", "combos", "equipmentPlanner"].forEach((toolType) => { + ["memos", "notepad", "checklist", "counters", "images", "imageAnnotation", "links", "calculator", "table", "goalTree", "timer", "calendar", "taskPlanner", "combos", "equipmentPlanner"].forEach((toolType) => { const docs = libraryToolDocs?.[toolType]; assert.equal(typeof docs, "object", `library.toolDocs.${toolType} must be an object`); assert.ok(Array.isArray(docs.controls), `library.toolDocs.${toolType}.controls must be an array`); @@ -772,9 +800,8 @@ export function validateSiteContent(site) { const toolboxCreationTemplate = valueAt(site, "toolboxes.creationTemplate"); assert.equal(typeof toolboxCreationTemplate, "object", "toolboxes.creationTemplate must be an object"); - assertNonEmptyString({ toolboxCreationTemplate }, "toolboxCreationTemplate.notepad.title"); - assertNonEmptyString({ toolboxCreationTemplate }, "toolboxCreationTemplate.notepad.text"); - assertNonEmptyString({ toolboxCreationTemplate }, "toolboxCreationTemplate.notepad.html"); + assertNonEmptyString({ toolboxCreationTemplate }, "toolboxCreationTemplate.memos.title"); + assertNonEmptyString({ toolboxCreationTemplate }, "toolboxCreationTemplate.memos.text"); assertNonEmptyString({ toolboxCreationTemplate }, "toolboxCreationTemplate.links.moduleTitle"); assertNonEmptyString({ toolboxCreationTemplate }, "toolboxCreationTemplate.links.title"); @@ -834,7 +861,7 @@ export function validateLibraryData(payload) { assert.deepEqual(new Set(twoColumnIds), moduleIds, "library.toolbox.moduleOrder.two must contain every module exactly once"); assert.equal(twoColumnIds.length, moduleIds.size, "library.toolbox.moduleOrder.two must not contain duplicate modules"); - ["notepad", "checklist", "images", "links", "counters", "combos", "calculator", "table", "goalTree", "timer", "calendar", "taskPlanner", "equipmentPlanner", "imageAnnotation"].forEach((type) => { + ["memos", "notepad", "checklist", "images", "links", "counters", "combos", "calculator", "table", "goalTree", "timer", "calendar", "taskPlanner", "equipmentPlanner", "imageAnnotation"].forEach((type) => { assert.ok(payload.toolbox.modules.some((module) => module.type === type), `library must include a ${type} example`); }); diff --git a/tests/static-toolboxes.test.mjs b/tests/static-toolboxes.test.mjs index 5540764..f7d4a5e 100644 --- a/tests/static-toolboxes.test.mjs +++ b/tests/static-toolboxes.test.mjs @@ -49,7 +49,7 @@ test("toolbox storage, cards and pages are wired", async () => { assert.match(toolboxActions, /importAllToolboxesPayload/); assert.match(toolboxActions, /async function addImageFiles/); assert.match(toolboxActions, /creationTemplate/); - assert.match(toolboxActions, /type: "notepad"/); + assert.match(toolboxActions, /type: "memos"/); assert.match(toolboxActions, /type: "links"/); assert.match(toolboxActions, /#\/library/); assert.match(`${source}\n${toolboxActions}`, /features\/toolboxes\/storage\/toolboxStorage\.js/); diff --git a/tests/toolbox-modules.test.mjs b/tests/toolbox-modules.test.mjs index d9d1bc1..f16a63f 100644 --- a/tests/toolbox-modules.test.mjs +++ b/tests/toolbox-modules.test.mjs @@ -12,7 +12,7 @@ import { getMonthOccurrenceSegments, getWeekGridLayout, getWeekMinutePosition, l import { appendChecklistItems, moveChecklistItem, moveChecklistItemToCategory, renameChecklistSection } from "../website/src/features/toolboxes/modules/checklistUtils.js"; import { formatUpdatedAt, isHtmlEmpty, queryBlockFormat, queryCommandState } from "../website/src/features/toolboxes/modules/notepadUtils.js"; import { getTimePatternRecurrenceMs, getTimePatternTargetMs } from "../website/src/features/toolboxes/modules/timerUtils.js"; -import { compactModuleDataForStorage, createToolboxExportPayload, normalizeCalculatorData, normalizeChecklistData, normalizeCombosData, normalizeCountersData, normalizeEquipmentPlannerData, normalizeGoalTreeData, normalizeImageAnnotationData, normalizeLinksData, normalizeModuleOrder, normalizeNotepadData, normalizeTableData, normalizeTaskPlannerData, normalizeTimerData, normalizeToolbox, normalizeUrl, summarizeEquipmentPlannerData } from "../website/src/features/toolboxes/storage/toolboxStorage.js"; +import { compactModuleDataForStorage, createToolboxExportPayload, normalizeCalculatorData, normalizeChecklistData, normalizeCombosData, normalizeCountersData, normalizeEquipmentPlannerData, normalizeGoalTreeData, normalizeImageAnnotationData, normalizeLinksData, normalizeMemosData, normalizeModuleOrder, normalizeNotepadData, normalizeTableData, normalizeTaskPlannerData, normalizeTimerData, normalizeToolbox, normalizeUrl, summarizeEquipmentPlannerData } from "../website/src/features/toolboxes/storage/toolboxStorage.js"; import { applyGroupedReorderOperation, completeGroupOrder, getBoundaryItemId, getGroupedEntries, moveGroupOrder, moveGroupOrderToEnd, moveGroupOrderToStart, moveItem, moveItemGroup } from "../website/src/hooks/useGroupedReorder.js"; function createTextImportContext() { @@ -20,6 +20,7 @@ function createTextImportContext() { return { uid: (prefix) => `${prefix}-${index += 1}`, normalizeNotepadData, + normalizeMemosData, normalizeChecklistData, normalizeLinksData, normalizeCountersData, @@ -124,6 +125,11 @@ test("text exchange round trips simple toolbox modules and resets checklist prog const countersImport = importModuleText("counters", exportModuleText("counters", counters), context); assert.equal(countersImport.ok, true); assert.deepEqual(countersImport.data.counters.map((counter) => [counter.label, counter.value]), [["Win", 12], ["Loss", -2]]); + + const memos = normalizeMemosData({ items: [{ id: "m1", text: "Route", color: "#f6c453" }, { id: "m2", text: "Potion", color: "#22d3ee" }] }); + const memosImport = importModuleText("memos", exportModuleText("memos", memos), context); + assert.equal(memosImport.ok, true); + assert.deepEqual(memosImport.data.items.map((memo) => [memo.text, memo.color]), [["Route", "#f6c453"], ["Potion", "#22d3ee"]]); }); test("notepad helpers keep empty html and invalid command states stable", () => { @@ -952,8 +958,8 @@ test("notepad data migrates text, sanitizes html and compacts drawings", () => { drawingMode: "temporary", drawings: { strokes: normalized.drawings.strokes } }); - assert.equal(compactTemporary.drawings, undefined); - assert.equal(compactTemporary.drawingMode, undefined); + assert.equal(compactTemporary.pages[0].drawings, undefined); + assert.equal(compactTemporary.pages[0].drawingMode, undefined); const compactPermanent = compactModuleDataForStorage("notepad", { html: "

Note

", @@ -962,9 +968,9 @@ test("notepad data migrates text, sanitizes html and compacts drawings", () => { drawingMode: "permanent", drawings: normalized.drawings }); - assert.equal(compactPermanent.updatedAt, undefined); - assert.equal(compactPermanent.drawingMode, "permanent"); - assert.equal(compactPermanent.drawings.strokes.length, 1); + assert.equal(compactPermanent.pages[0].updatedAt, undefined); + assert.equal(compactPermanent.pages[0].drawingMode, "permanent"); + assert.equal(compactPermanent.pages[0].drawings.strokes.length, 1); }); test("image annotation storage keeps drawing strokes as percent coordinates", () => { diff --git a/website/public/data/library.json b/website/public/data/library.json index 12a54dc..b8bebf3 100644 --- a/website/public/data/library.json +++ b/website/public/data/library.json @@ -3,10 +3,15 @@ "id": "t1", "name": "Librairie", "modules": [ + { + "id": "m0", + "type": "memos", + "title": "Memos" + }, { "id": "m1", "type": "notepad", - "title": "Notes rapides" + "title": "Bloc Note" }, { "id": "m2", @@ -217,22 +222,2553 @@ } ], "moduleOrder": { - "one": ["m1", "m2", "m3", "m4", "m5", "m6", "m7", "m8", "mf", "m9", "md", "me", "ma", "mb", "mc"], - "two": [["m1", "m2", "m3", "m4", "m5", "m6", "ma"], ["m7", "m8", "mf", "m9", "md", "me", "mb", "mc"]] + "one": [ + "m0", + "m1", + "m2", + "m3", + "m4", + "m5", + "m6", + "m7", + "m8", + "mf", + "m9", + "md", + "me", + "ma", + "mb", + "mc" + ], + "two": [ + [ + "m0", + "m1", + "m2", + "m3", + "m4", + "m5", + "m6", + "ma" + ], + [ + "m7", + "m8", + "mf", + "m9", + "md", + "me", + "mb", + "mc" + ] + ] }, "updatedAt": "2026-08-09T07:34:31.425Z", "moduleColumns": 1 }, "modules": { + "m0": { + "items": [ + { + "id": "w1", + "text": "Pour la reproduction, combine Pierre Stase + Nœud Destin pour transmettre nature et IV.", + "color": "#f6c453", + "updatedAt": "2026-08-09T07:34:31.425Z" + }, + { + "id": "w2", + "text": "Les IV obtenus par Hyper Training ne sont pas transmis aux oeufs.", + "color": "#8b5cf6" + }, + { + "id": "w3", + "text": "Pour une équipe Distorsion, vise 0 IV en Vitesse sur les Pokémon lents.", + "color": "#22d3ee" + } + , + { + "id": "w4", + "text": "Utilise les objets Pouvoir pour cibler précisément les EV à entraîner.", + "color": "#d946ef" + } + ] + }, "m1": { - "html": "

Obtenir Mew :


", + "activePageId": "p1o6usa", + "pages": [ + { + "id": "p1o6usa", + "title": "Infos: Obtenir Mew", + "html": "

Obtenir Mew :


", + "text": "Obtenir Mew :\n- Obtenir la CS Coupe à l'intérieur du bateau le S.S. Anne.\n- Faire exprès de perdre un combat (tomber K.O.) pour réapparaître au Centre Pokémon sans faire partir le navire.\n- Revenir sur les quais de Carmin-sur-Mer après avoir débloqué et appris la compétence Surf.\n- Naviguer sur l'eau pour atteindre la zone cachée où repose le véhicule.", + "updatedAt": "", + "drawingMode": "permanent", + "drawings": { + "strokes": [ + { + "id": "stroke-library-1", + "color": "#d946ef", + "width": 4, + "points": [ + { + "x": 623, + "y": 190.2 + }, + { + "x": 625, + "y": 190.2 + }, + { + "x": 627, + "y": 188.2 + }, + { + "x": 629, + "y": 187.2 + }, + { + "x": 631, + "y": 186.2 + }, + { + "x": 633, + "y": 185.2 + }, + { + "x": 635, + "y": 184.2 + }, + { + "x": 637, + "y": 183.2 + }, + { + "x": 639, + "y": 182.2 + }, + { + "x": 641, + "y": 182.2 + }, + { + "x": 644, + "y": 181.2 + }, + { + "x": 646, + "y": 181.2 + }, + { + "x": 649, + "y": 181.2 + }, + { + "x": 651, + "y": 181.2 + }, + { + "x": 654, + "y": 180.2 + }, + { + "x": 656, + "y": 180.2 + }, + { + "x": 658, + "y": 180.2 + }, + { + "x": 660, + "y": 180.2 + }, + { + "x": 662, + "y": 180.2 + } + ] + }, + { + "id": "stroke-library-2", + "color": "#d946ef", + "width": 4, + "points": [ + { + "x": 655, + "y": 180.2 + }, + { + "x": 655, + "y": 178.2 + }, + { + "x": 656, + "y": 176.2 + }, + { + "x": 658, + "y": 174.2 + }, + { + "x": 659, + "y": 172.2 + }, + { + "x": 661, + "y": 171.2 + }, + { + "x": 661, + "y": 169.2 + }, + { + "x": 663, + "y": 169.2 + }, + { + "x": 663, + "y": 167.2 + }, + { + "x": 665, + "y": 167.2 + }, + { + "x": 667, + "y": 165.2 + }, + { + "x": 669, + "y": 165.2 + }, + { + "x": 670, + "y": 163.2 + }, + { + "x": 672, + "y": 163.2 + }, + { + "x": 674, + "y": 163.2 + }, + { + "x": 676, + "y": 163.2 + }, + { + "x": 678, + "y": 163.2 + }, + { + "x": 679, + "y": 165.2 + }, + { + "x": 681, + "y": 166.2 + }, + { + "x": 682, + "y": 168.2 + }, + { + "x": 683, + "y": 170.2 + }, + { + "x": 684, + "y": 172.2 + }, + { + "x": 685, + "y": 174.2 + }, + { + "x": 685, + "y": 176.2 + }, + { + "x": 685, + "y": 178.2 + }, + { + "x": 685, + "y": 180.2 + }, + { + "x": 685, + "y": 182.2 + }, + { + "x": 685, + "y": 184.2 + }, + { + "x": 685, + "y": 186.2 + }, + { + "x": 685, + "y": 188.2 + } + ] + }, + { + "id": "stroke-library-3", + "color": "#d946ef", + "width": 4, + "points": [ + { + "x": 628, + "y": 186.2 + }, + { + "x": 627, + "y": 184.2 + }, + { + "x": 625, + "y": 183.2 + }, + { + "x": 623, + "y": 183.2 + }, + { + "x": 622, + "y": 181.2 + }, + { + "x": 620, + "y": 180.2 + }, + { + "x": 618, + "y": 180.2 + }, + { + "x": 616, + "y": 178.2 + }, + { + "x": 614, + "y": 178.2 + }, + { + "x": 612, + "y": 177.2 + }, + { + "x": 610, + "y": 176.2 + }, + { + "x": 608, + "y": 176.2 + }, + { + "x": 606, + "y": 176.2 + }, + { + "x": 604, + "y": 176.2 + }, + { + "x": 602, + "y": 176.2 + }, + { + "x": 601, + "y": 178.2 + }, + { + "x": 601, + "y": 180.2 + }, + { + "x": 601, + "y": 182.2 + }, + { + "x": 600, + "y": 184.2 + }, + { + "x": 600, + "y": 186.2 + }, + { + "x": 600, + "y": 188.2 + }, + { + "x": 600, + "y": 190.2 + }, + { + "x": 600, + "y": 192.2 + }, + { + "x": 600, + "y": 194.2 + }, + { + "x": 600, + "y": 196.2 + }, + { + "x": 600, + "y": 198.2 + }, + { + "x": 601, + "y": 200.2 + } + ] + }, + { + "id": "stroke-library-4", + "color": "#d946ef", + "width": 4, + "points": [ + { + "x": 600, + "y": 202.2 + }, + { + "x": 602, + "y": 202.2 + }, + { + "x": 603, + "y": 204.2 + }, + { + "x": 603, + "y": 206.2 + }, + { + "x": 604, + "y": 208.2 + }, + { + "x": 605, + "y": 210.2 + } + ] + }, + { + "id": "stroke-library-5", + "color": "#d946ef", + "width": 4, + "points": [ + { + "x": 608, + "y": 208.2 + }, + { + "x": 608, + "y": 210.2 + }, + { + "x": 608, + "y": 212.2 + }, + { + "x": 608, + "y": 214.2 + }, + { + "x": 607, + "y": 216.2 + }, + { + "x": 607, + "y": 218.2 + }, + { + "x": 607, + "y": 220.2 + }, + { + "x": 607, + "y": 222.2 + }, + { + "x": 607, + "y": 224.2 + }, + { + "x": 606, + "y": 226.2 + }, + { + "x": 606, + "y": 228.2 + }, + { + "x": 606, + "y": 230.2 + }, + { + "x": 608, + "y": 231.2 + }, + { + "x": 610, + "y": 232.2 + }, + { + "x": 612, + "y": 233.2 + }, + { + "x": 613, + "y": 235.2 + }, + { + "x": 615, + "y": 235.2 + }, + { + "x": 617, + "y": 236.2 + }, + { + "x": 619, + "y": 237.2 + }, + { + "x": 621, + "y": 237.2 + }, + { + "x": 623, + "y": 238.2 + }, + { + "x": 625, + "y": 239.2 + }, + { + "x": 627, + "y": 239.2 + }, + { + "x": 629, + "y": 239.2 + }, + { + "x": 631, + "y": 240.2 + }, + { + "x": 633, + "y": 240.2 + }, + { + "x": 635, + "y": 240.2 + }, + { + "x": 637, + "y": 240.2 + }, + { + "x": 639, + "y": 241.2 + }, + { + "x": 641, + "y": 241.2 + }, + { + "x": 643, + "y": 241.2 + }, + { + "x": 645, + "y": 241.2 + }, + { + "x": 647, + "y": 241.2 + }, + { + "x": 649, + "y": 242.2 + }, + { + "x": 651, + "y": 242.2 + }, + { + "x": 653, + "y": 242.2 + }, + { + "x": 655, + "y": 242.2 + }, + { + "x": 657, + "y": 242.2 + }, + { + "x": 659, + "y": 242.2 + }, + { + "x": 661, + "y": 242.2 + }, + { + "x": 663, + "y": 242.2 + }, + { + "x": 665, + "y": 241.2 + }, + { + "x": 667, + "y": 241.2 + }, + { + "x": 669, + "y": 240.2 + }, + { + "x": 671, + "y": 240.2 + }, + { + "x": 672, + "y": 238.2 + }, + { + "x": 674, + "y": 238.2 + }, + { + "x": 676, + "y": 237.2 + }, + { + "x": 678, + "y": 236.2 + }, + { + "x": 679, + "y": 234.2 + }, + { + "x": 681, + "y": 233.2 + }, + { + "x": 683, + "y": 232.2 + }, + { + "x": 684, + "y": 230.2 + }, + { + "x": 686, + "y": 229.2 + }, + { + "x": 687, + "y": 227.2 + }, + { + "x": 688, + "y": 225.2 + }, + { + "x": 690, + "y": 225.2 + }, + { + "x": 691, + "y": 223.2 + }, + { + "x": 692, + "y": 221.2 + }, + { + "x": 694, + "y": 220.2 + }, + { + "x": 694, + "y": 218.2 + }, + { + "x": 695, + "y": 216.2 + }, + { + "x": 695, + "y": 214.2 + }, + { + "x": 695, + "y": 212.2 + }, + { + "x": 695, + "y": 210.2 + }, + { + "x": 694, + "y": 208.2 + }, + { + "x": 692, + "y": 207.2 + }, + { + "x": 691, + "y": 205.2 + }, + { + "x": 689, + "y": 204.2 + }, + { + "x": 688, + "y": 202.2 + }, + { + "x": 686, + "y": 201.2 + }, + { + "x": 684, + "y": 200.2 + }, + { + "x": 684, + "y": 198.2 + }, + { + "x": 682, + "y": 197.2 + }, + { + "x": 681, + "y": 195.2 + } + ] + }, + { + "id": "stroke-library-6", + "color": "#f5f7ff", + "width": 4, + "points": [ + { + "x": 628, + "y": 224.2 + }, + { + "x": 630, + "y": 223.2 + }, + { + "x": 631, + "y": 221.2 + }, + { + "x": 633, + "y": 220.2 + }, + { + "x": 635, + "y": 219.2 + }, + { + "x": 637, + "y": 219.2 + }, + { + "x": 639, + "y": 219.2 + }, + { + "x": 641, + "y": 219.2 + }, + { + "x": 643, + "y": 219.2 + }, + { + "x": 645, + "y": 219.2 + }, + { + "x": 647, + "y": 220.2 + }, + { + "x": 647, + "y": 222.2 + }, + { + "x": 649, + "y": 223.2 + } + ] + }, + { + "id": "stroke-library-7", + "color": "#f5f7ff", + "width": 4, + "points": [ + { + "x": 666, + "y": 222.2 + }, + { + "x": 667, + "y": 220.2 + }, + { + "x": 669, + "y": 219.2 + }, + { + "x": 669, + "y": 217.2 + }, + { + "x": 671, + "y": 217.2 + }, + { + "x": 672, + "y": 215.2 + }, + { + "x": 674, + "y": 214.2 + }, + { + "x": 676, + "y": 213.2 + }, + { + "x": 678, + "y": 213.2 + }, + { + "x": 680, + "y": 213.2 + }, + { + "x": 682, + "y": 213.2 + }, + { + "x": 684, + "y": 214.2 + }, + { + "x": 686, + "y": 215.2 + } + ] + }, + { + "id": "stroke-library-8", + "color": "#22d3ee", + "width": 4, + "points": [ + { + "x": 702, + "y": 197.2 + }, + { + "x": 700, + "y": 196.2 + }, + { + "x": 700, + "y": 194.2 + }, + { + "x": 698, + "y": 193.2 + }, + { + "x": 698, + "y": 191.2 + }, + { + "x": 697, + "y": 189.2 + }, + { + "x": 696, + "y": 187.2 + }, + { + "x": 696, + "y": 185.2 + }, + { + "x": 696, + "y": 183.2 + }, + { + "x": 697, + "y": 181.2 + }, + { + "x": 699, + "y": 181.2 + }, + { + "x": 701, + "y": 181.2 + }, + { + "x": 702, + "y": 183.2 + }, + { + "x": 704, + "y": 184.2 + }, + { + "x": 706, + "y": 183.2 + }, + { + "x": 706, + "y": 181.2 + }, + { + "x": 708, + "y": 181.2 + }, + { + "x": 710, + "y": 181.2 + }, + { + "x": 712, + "y": 181.2 + }, + { + "x": 714, + "y": 182.2 + }, + { + "x": 714, + "y": 184.2 + }, + { + "x": 715, + "y": 186.2 + }, + { + "x": 715, + "y": 188.2 + }, + { + "x": 715, + "y": 190.2 + }, + { + "x": 715, + "y": 192.2 + }, + { + "x": 713, + "y": 193.2 + }, + { + "x": 713, + "y": 195.2 + }, + { + "x": 711, + "y": 196.2 + }, + { + "x": 710, + "y": 198.2 + }, + { + "x": 708, + "y": 199.2 + } + ] + } + ] + } + }, + { + "id": "pjztqnj", + "title": "Infos: Obtenir Celebi", + "html": "

Obtenir Celebi :



", + "text": "Obtenir Celebi :\n\n- Avoir battu la Ligue Pokémon au moins une fois et posséder le Pokédex National.\n- Mettre un Pokémon de type Plante (comme Florizarre ou Meganium) au niveau 100 en première position de votre équipe.\n- Avoir un Pokémon connaissant la CS Flash et un autre connaissant la CS Coupe.\n- Se rendre devant l'autel du Bois aux Chênes la nuit (le jeu doit être dans sa phase nocturne).\n- Utiliser la capacité Flash juste devant l'autel pour éclairer l'écran.\n- Appuyer sur A face à l'autel", + "updatedAt": "", + "drawingMode": "permanent", + "drawings": { + "strokes": [ + { + "id": "stroke-library-9", + "color": "#f5f7ff", + "width": 4, + "points": [ + { + "x": 659, + "y": 208.2 + }, + { + "x": 659, + "y": 210.2 + }, + { + "x": 658, + "y": 212.2 + }, + { + "x": 657, + "y": 214.2 + }, + { + "x": 655, + "y": 215.2 + }, + { + "x": 654, + "y": 217.2 + }, + { + "x": 652, + "y": 218.2 + }, + { + "x": 650, + "y": 219.2 + }, + { + "x": 649, + "y": 221.2 + }, + { + "x": 647, + "y": 221.2 + }, + { + "x": 647, + "y": 223.2 + }, + { + "x": 645, + "y": 223.2 + }, + { + "x": 643, + "y": 224.2 + }, + { + "x": 641, + "y": 225.2 + }, + { + "x": 639, + "y": 226.2 + }, + { + "x": 637, + "y": 227.2 + }, + { + "x": 635, + "y": 228.2 + }, + { + "x": 633, + "y": 229.2 + }, + { + "x": 631, + "y": 230.2 + }, + { + "x": 629, + "y": 231.2 + }, + { + "x": 627, + "y": 231.2 + }, + { + "x": 625, + "y": 232.2 + }, + { + "x": 623, + "y": 233.2 + }, + { + "x": 621, + "y": 233.2 + }, + { + "x": 619, + "y": 234.2 + }, + { + "x": 617, + "y": 235.2 + }, + { + "x": 615, + "y": 236.2 + }, + { + "x": 613, + "y": 236.2 + }, + { + "x": 611, + "y": 237.2 + }, + { + "x": 609, + "y": 238.2 + }, + { + "x": 607, + "y": 239.2 + }, + { + "x": 605, + "y": 240.2 + }, + { + "x": 603, + "y": 241.2 + }, + { + "x": 601, + "y": 243.2 + }, + { + "x": 599, + "y": 244.2 + }, + { + "x": 597, + "y": 245.2 + }, + { + "x": 597, + "y": 247.2 + }, + { + "x": 595, + "y": 247.2 + }, + { + "x": 594, + "y": 249.2 + }, + { + "x": 592, + "y": 250.2 + }, + { + "x": 591, + "y": 252.2 + }, + { + "x": 589, + "y": 253.2 + }, + { + "x": 588, + "y": 255.2 + }, + { + "x": 586, + "y": 256.2 + }, + { + "x": 586, + "y": 258.2 + }, + { + "x": 585, + "y": 260.2 + }, + { + "x": 584, + "y": 262.2 + }, + { + "x": 583, + "y": 264.2 + }, + { + "x": 582, + "y": 266.2 + }, + { + "x": 581, + "y": 268.2 + }, + { + "x": 580, + "y": 270.2 + }, + { + "x": 580, + "y": 272.2 + }, + { + "x": 579, + "y": 274.2 + }, + { + "x": 579, + "y": 276.2 + }, + { + "x": 579, + "y": 278.2 + }, + { + "x": 579, + "y": 280.2 + }, + { + "x": 579, + "y": 282.2 + }, + { + "x": 579, + "y": 284.2 + }, + { + "x": 580, + "y": 286.2 + }, + { + "x": 580, + "y": 288.2 + }, + { + "x": 581, + "y": 290.2 + }, + { + "x": 582, + "y": 292.2 + }, + { + "x": 583, + "y": 294.2 + }, + { + "x": 585, + "y": 295.2 + }, + { + "x": 585, + "y": 297.2 + }, + { + "x": 587, + "y": 298.2 + }, + { + "x": 588, + "y": 300.2 + }, + { + "x": 589, + "y": 302.2 + }, + { + "x": 591, + "y": 302.2 + }, + { + "x": 592, + "y": 304.2 + }, + { + "x": 594, + "y": 305.2 + }, + { + "x": 596, + "y": 306.2 + }, + { + "x": 597, + "y": 308.2 + }, + { + "x": 599, + "y": 309.2 + }, + { + "x": 601, + "y": 310.2 + }, + { + "x": 603, + "y": 310.2 + }, + { + "x": 605, + "y": 311.2 + }, + { + "x": 607, + "y": 311.2 + }, + { + "x": 609, + "y": 312.2 + }, + { + "x": 611, + "y": 312.2 + }, + { + "x": 613, + "y": 313.2 + }, + { + "x": 615, + "y": 313.2 + }, + { + "x": 617, + "y": 313.2 + }, + { + "x": 619, + "y": 313.2 + }, + { + "x": 621, + "y": 313.2 + }, + { + "x": 623, + "y": 313.2 + }, + { + "x": 625, + "y": 313.2 + }, + { + "x": 627, + "y": 313.2 + }, + { + "x": 629, + "y": 313.2 + }, + { + "x": 631, + "y": 312.2 + }, + { + "x": 633, + "y": 312.2 + }, + { + "x": 635, + "y": 312.2 + }, + { + "x": 637, + "y": 312.2 + }, + { + "x": 639, + "y": 311.2 + }, + { + "x": 641, + "y": 311.2 + }, + { + "x": 643, + "y": 311.2 + }, + { + "x": 645, + "y": 310.2 + }, + { + "x": 647, + "y": 310.2 + }, + { + "x": 649, + "y": 309.2 + }, + { + "x": 651, + "y": 308.2 + }, + { + "x": 653, + "y": 307.2 + }, + { + "x": 655, + "y": 306.2 + }, + { + "x": 657, + "y": 305.2 + }, + { + "x": 658, + "y": 303.2 + }, + { + "x": 660, + "y": 303.2 + }, + { + "x": 662, + "y": 302.2 + }, + { + "x": 662, + "y": 300.2 + }, + { + "x": 664, + "y": 299.2 + }, + { + "x": 666, + "y": 298.2 + }, + { + "x": 666, + "y": 296.2 + }, + { + "x": 668, + "y": 295.2 + }, + { + "x": 669, + "y": 293.2 + }, + { + "x": 670, + "y": 291.2 + }, + { + "x": 672, + "y": 290.2 + }, + { + "x": 673, + "y": 288.2 + }, + { + "x": 674, + "y": 286.2 + }, + { + "x": 674, + "y": 284.2 + }, + { + "x": 675, + "y": 282.2 + }, + { + "x": 676, + "y": 280.2 + }, + { + "x": 677, + "y": 278.2 + }, + { + "x": 678, + "y": 276.2 + }, + { + "x": 678, + "y": 274.2 + }, + { + "x": 678, + "y": 272.2 + }, + { + "x": 679, + "y": 270.2 + }, + { + "x": 679, + "y": 268.2 + }, + { + "x": 679, + "y": 266.2 + }, + { + "x": 679, + "y": 264.2 + }, + { + "x": 679, + "y": 262.2 + }, + { + "x": 679, + "y": 260.2 + }, + { + "x": 679, + "y": 258.2 + }, + { + "x": 679, + "y": 256.2 + }, + { + "x": 679, + "y": 254.2 + }, + { + "x": 678, + "y": 252.2 + }, + { + "x": 678, + "y": 250.2 + }, + { + "x": 678, + "y": 248.2 + }, + { + "x": 677, + "y": 246.2 + }, + { + "x": 676, + "y": 244.2 + }, + { + "x": 676, + "y": 242.2 + }, + { + "x": 675, + "y": 240.2 + }, + { + "x": 674, + "y": 238.2 + }, + { + "x": 674, + "y": 236.2 + }, + { + "x": 673, + "y": 234.2 + }, + { + "x": 672, + "y": 232.2 + }, + { + "x": 671, + "y": 230.2 + }, + { + "x": 671, + "y": 228.2 + }, + { + "x": 670, + "y": 226.2 + }, + { + "x": 670, + "y": 224.2 + }, + { + "x": 670, + "y": 222.2 + }, + { + "x": 669, + "y": 220.2 + }, + { + "x": 669, + "y": 218.2 + }, + { + "x": 669, + "y": 216.2 + }, + { + "x": 669, + "y": 214.2 + }, + { + "x": 669, + "y": 212.2 + }, + { + "x": 667, + "y": 214.2 + }, + { + "x": 666, + "y": 216.2 + }, + { + "x": 664, + "y": 217.2 + }, + { + "x": 664, + "y": 219.2 + }, + { + "x": 662, + "y": 220.2 + }, + { + "x": 662, + "y": 218.2 + }, + { + "x": 661, + "y": 216.2 + }, + { + "x": 660, + "y": 214.2 + }, + { + "x": 660, + "y": 212.2 + } + ] + }, + { + "id": "stroke-library-10", + "color": "#22d3ee", + "width": 4, + "points": [ + { + "x": 646, + "y": 230.2 + }, + { + "x": 647, + "y": 228.2 + }, + { + "x": 649, + "y": 227.2 + }, + { + "x": 650, + "y": 225.2 + }, + { + "x": 651, + "y": 223.2 + }, + { + "x": 652, + "y": 221.2 + }, + { + "x": 654, + "y": 220.2 + }, + { + "x": 654, + "y": 218.2 + }, + { + "x": 656, + "y": 218.2 + }, + { + "x": 657, + "y": 220.2 + }, + { + "x": 657, + "y": 222.2 + }, + { + "x": 657, + "y": 224.2 + }, + { + "x": 656, + "y": 226.2 + }, + { + "x": 655, + "y": 228.2 + }, + { + "x": 655, + "y": 230.2 + }, + { + "x": 655, + "y": 228.2 + }, + { + "x": 657, + "y": 226.2 + }, + { + "x": 658, + "y": 224.2 + }, + { + "x": 659, + "y": 222.2 + }, + { + "x": 660, + "y": 220.2 + }, + { + "x": 661, + "y": 218.2 + }, + { + "x": 663, + "y": 217.2 + }, + { + "x": 663, + "y": 219.2 + }, + { + "x": 663, + "y": 221.2 + }, + { + "x": 663, + "y": 223.2 + }, + { + "x": 663, + "y": 225.2 + }, + { + "x": 664, + "y": 227.2 + }, + { + "x": 666, + "y": 226.2 + }, + { + "x": 666, + "y": 224.2 + }, + { + "x": 667, + "y": 222.2 + }, + { + "x": 668, + "y": 224.2 + }, + { + "x": 669, + "y": 226.2 + }, + { + "x": 669, + "y": 228.2 + }, + { + "x": 669, + "y": 230.2 + }, + { + "x": 669, + "y": 232.2 + }, + { + "x": 669, + "y": 234.2 + }, + { + "x": 670, + "y": 236.2 + } + ] + }, + { + "id": "stroke-library-11", + "color": "#b4bdd3", + "width": 4, + "points": [ + { + "x": 597, + "y": 267.2 + }, + { + "x": 595, + "y": 267.2 + }, + { + "x": 593, + "y": 266.2 + }, + { + "x": 592, + "y": 264.2 + }, + { + "x": 590, + "y": 263.2 + }, + { + "x": 589, + "y": 261.2 + }, + { + "x": 587, + "y": 260.2 + }, + { + "x": 586, + "y": 258.2 + }, + { + "x": 585, + "y": 256.2 + }, + { + "x": 583, + "y": 255.2 + }, + { + "x": 582, + "y": 253.2 + }, + { + "x": 582, + "y": 251.2 + }, + { + "x": 581, + "y": 249.2 + }, + { + "x": 580, + "y": 247.2 + }, + { + "x": 580, + "y": 245.2 + }, + { + "x": 580, + "y": 243.2 + }, + { + "x": 580, + "y": 241.2 + }, + { + "x": 580, + "y": 239.2 + }, + { + "x": 580, + "y": 237.2 + }, + { + "x": 580, + "y": 235.2 + }, + { + "x": 581, + "y": 233.2 + }, + { + "x": 581, + "y": 231.2 + }, + { + "x": 583, + "y": 231.2 + }, + { + "x": 583, + "y": 233.2 + }, + { + "x": 584, + "y": 235.2 + }, + { + "x": 584, + "y": 237.2 + }, + { + "x": 585, + "y": 239.2 + }, + { + "x": 586, + "y": 241.2 + }, + { + "x": 587, + "y": 243.2 + }, + { + "x": 588, + "y": 245.2 + }, + { + "x": 589, + "y": 247.2 + }, + { + "x": 590, + "y": 249.2 + }, + { + "x": 592, + "y": 250.2 + }, + { + "x": 592, + "y": 252.2 + }, + { + "x": 594, + "y": 253.2 + }, + { + "x": 595, + "y": 255.2 + }, + { + "x": 596, + "y": 257.2 + }, + { + "x": 598, + "y": 258.2 + }, + { + "x": 600, + "y": 259.2 + }, + { + "x": 601, + "y": 261.2 + }, + { + "x": 603, + "y": 261.2 + }, + { + "x": 605, + "y": 262.2 + }, + { + "x": 605, + "y": 264.2 + }, + { + "x": 603, + "y": 265.2 + }, + { + "x": 601, + "y": 266.2 + }, + { + "x": 599, + "y": 266.2 + }, + { + "x": 597, + "y": 266.2 + } + ] + }, + { + "id": "stroke-library-12", + "color": "#b4bdd3", + "width": 4, + "points": [ + { + "x": 650, + "y": 264.2 + }, + { + "x": 648, + "y": 263.2 + }, + { + "x": 646, + "y": 263.2 + }, + { + "x": 645, + "y": 261.2 + }, + { + "x": 643, + "y": 261.2 + }, + { + "x": 641, + "y": 260.2 + }, + { + "x": 639, + "y": 259.2 + }, + { + "x": 637, + "y": 258.2 + }, + { + "x": 636, + "y": 256.2 + }, + { + "x": 634, + "y": 256.2 + }, + { + "x": 633, + "y": 254.2 + }, + { + "x": 631, + "y": 253.2 + }, + { + "x": 630, + "y": 251.2 + }, + { + "x": 629, + "y": 249.2 + }, + { + "x": 628, + "y": 247.2 + }, + { + "x": 626, + "y": 246.2 + }, + { + "x": 626, + "y": 244.2 + }, + { + "x": 625, + "y": 242.2 + }, + { + "x": 624, + "y": 240.2 + }, + { + "x": 623, + "y": 238.2 + }, + { + "x": 622, + "y": 236.2 + }, + { + "x": 621, + "y": 234.2 + }, + { + "x": 621, + "y": 232.2 + }, + { + "x": 620, + "y": 230.2 + }, + { + "x": 620, + "y": 228.2 + }, + { + "x": 620, + "y": 230.2 + }, + { + "x": 619, + "y": 232.2 + }, + { + "x": 619, + "y": 234.2 + }, + { + "x": 618, + "y": 236.2 + }, + { + "x": 618, + "y": 238.2 + }, + { + "x": 618, + "y": 240.2 + }, + { + "x": 618, + "y": 242.2 + }, + { + "x": 619, + "y": 244.2 + }, + { + "x": 619, + "y": 246.2 + }, + { + "x": 620, + "y": 248.2 + }, + { + "x": 621, + "y": 250.2 + }, + { + "x": 622, + "y": 252.2 + }, + { + "x": 623, + "y": 254.2 + }, + { + "x": 624, + "y": 256.2 + }, + { + "x": 625, + "y": 258.2 + }, + { + "x": 626, + "y": 260.2 + }, + { + "x": 628, + "y": 261.2 + }, + { + "x": 628, + "y": 263.2 + }, + { + "x": 630, + "y": 264.2 + }, + { + "x": 632, + "y": 265.2 + }, + { + "x": 633, + "y": 267.2 + }, + { + "x": 635, + "y": 267.2 + }, + { + "x": 637, + "y": 267.2 + }, + { + "x": 639, + "y": 267.2 + }, + { + "x": 641, + "y": 267.2 + }, + { + "x": 643, + "y": 267.2 + }, + { + "x": 644, + "y": 265.2 + }, + { + "x": 646, + "y": 265.2 + } + ] + }, + { + "id": "stroke-library-13", + "color": "#f5f7ff", + "width": 4, + "points": [ + { + "x": 625, + "y": 294.2 + }, + { + "x": 626, + "y": 292.2 + }, + { + "x": 628, + "y": 291.2 + }, + { + "x": 628, + "y": 289.2 + }, + { + "x": 630, + "y": 288.2 + }, + { + "x": 632, + "y": 287.2 + }, + { + "x": 634, + "y": 285.2 + }, + { + "x": 636, + "y": 285.2 + }, + { + "x": 638, + "y": 285.2 + }, + { + "x": 640, + "y": 284.2 + }, + { + "x": 642, + "y": 284.2 + }, + { + "x": 644, + "y": 284.2 + }, + { + "x": 646, + "y": 284.2 + }, + { + "x": 648, + "y": 284.2 + }, + { + "x": 648, + "y": 286.2 + }, + { + "x": 650, + "y": 287.2 + }, + { + "x": 650, + "y": 289.2 + }, + { + "x": 651, + "y": 291.2 + } + ] + }, + { + "id": "stroke-library-14", + "color": "#f5f7ff", + "width": 4, + "points": [ + { + "x": 600, + "y": 292.2 + }, + { + "x": 600, + "y": 290.2 + }, + { + "x": 600, + "y": 288.2 + }, + { + "x": 598, + "y": 287.2 + }, + { + "x": 598, + "y": 285.2 + }, + { + "x": 596, + "y": 284.2 + }, + { + "x": 594, + "y": 283.2 + }, + { + "x": 592, + "y": 283.2 + }, + { + "x": 590, + "y": 283.2 + } + ] + }, + { + "id": "stroke-library-15", + "color": "#d946ef", + "width": 4, + "points": [ + { + "x": 534, + "y": 264.2 + }, + { + "x": 534, + "y": 262.2 + }, + { + "x": 532, + "y": 262.2 + }, + { + "x": 531, + "y": 260.2 + }, + { + "x": 530, + "y": 258.2 + }, + { + "x": 529, + "y": 256.2 + }, + { + "x": 527, + "y": 255.2 + }, + { + "x": 527, + "y": 253.2 + }, + { + "x": 526, + "y": 251.2 + }, + { + "x": 526, + "y": 249.2 + }, + { + "x": 528, + "y": 249.2 + }, + { + "x": 530, + "y": 248.2 + }, + { + "x": 532, + "y": 248.2 + }, + { + "x": 534, + "y": 248.2 + }, + { + "x": 536, + "y": 249.2 + }, + { + "x": 538, + "y": 251.2 + }, + { + "x": 540, + "y": 250.2 + }, + { + "x": 541, + "y": 248.2 + }, + { + "x": 542, + "y": 246.2 + }, + { + "x": 544, + "y": 246.2 + }, + { + "x": 546, + "y": 245.2 + }, + { + "x": 548, + "y": 246.2 + }, + { + "x": 549, + "y": 248.2 + }, + { + "x": 549, + "y": 250.2 + }, + { + "x": 549, + "y": 252.2 + }, + { + "x": 549, + "y": 254.2 + }, + { + "x": 549, + "y": 256.2 + }, + { + "x": 548, + "y": 258.2 + }, + { + "x": 546, + "y": 259.2 + }, + { + "x": 544, + "y": 260.2 + }, + { + "x": 543, + "y": 262.2 + }, + { + "x": 541, + "y": 263.2 + }, + { + "x": 539, + "y": 264.2 + }, + { + "x": 538, + "y": 266.2 + } + ] + }, + { + "id": "stroke-library-16", + "color": "#f5f7ff", + "width": 4, + "points": [ + { + "x": 549, + "y": 272.2 + }, + { + "x": 551, + "y": 272.2 + }, + { + "x": 553, + "y": 273.2 + }, + { + "x": 554, + "y": 275.2 + }, + { + "x": 556, + "y": 276.2 + }, + { + "x": 558, + "y": 277.2 + }, + { + "x": 559, + "y": 279.2 + }, + { + "x": 561, + "y": 280.2 + }, + { + "x": 562, + "y": 282.2 + } + ] + } + ] + } + } + ], + "html": "

Obtenir Mew :


", "text": "Obtenir Mew :\n- Obtenir la CS Coupe à l'intérieur du bateau le S.S. Anne.\n- Faire exprès de perdre un combat (tomber K.O.) pour réapparaître au Centre Pokémon sans faire partir le navire.\n- Revenir sur les quais de Carmin-sur-Mer après avoir débloqué et appris la compétence Surf.\n- Naviguer sur l'eau pour atteindre la zone cachée où repose le véhicule.", - "updatedAt": "2026-08-05T14:24:01.662Z", + "updatedAt": "", "drawingMode": "permanent", "drawings": { "strokes": [ { - "id": "djafoe2", + "id": "stroke-library-1", "color": "#d946ef", "width": 4, "points": [ @@ -315,7 +2851,7 @@ ] }, { - "id": "dw4tlz7", + "id": "stroke-library-2", "color": "#d946ef", "width": 4, "points": [ @@ -442,7 +2978,7 @@ ] }, { - "id": "dpy17ne", + "id": "stroke-library-3", "color": "#d946ef", "width": 4, "points": [ @@ -557,7 +3093,7 @@ ] }, { - "id": "dzcarpd", + "id": "stroke-library-4", "color": "#d946ef", "width": 4, "points": [ @@ -588,7 +3124,7 @@ ] }, { - "id": "d7rpgcd", + "id": "stroke-library-5", "color": "#d946ef", "width": 4, "points": [ @@ -895,7 +3431,7 @@ ] }, { - "id": "dbhsxo0", + "id": "stroke-library-6", "color": "#f5f7ff", "width": 4, "points": [ @@ -954,7 +3490,7 @@ ] }, { - "id": "d37qwn3", + "id": "stroke-library-7", "color": "#f5f7ff", "width": 4, "points": [ @@ -1013,7 +3549,7 @@ ] }, { - "id": "d1t1uzq", + "id": "stroke-library-8", "color": "#22d3ee", "width": 4, "points": [ @@ -1393,13 +3929,13 @@ { "id": "r2", "label": "Lingot de chlorophyte", - "value": 100, + "formula": "base", "parentId": "r1" }, { "id": "r3", "label": "Champignon luisant", - "value": 1500, + "formula": "base*15", "parentId": "r1" }, { @@ -1410,7 +3946,7 @@ { "id": "r5", "label": "Minerai de chlorophyte", - "value": 500, + "formula": "base*5", "parentId": "r4" } ] diff --git a/website/public/data/site.json b/website/public/data/site.json index 322bc11..ff5cd3f 100644 --- a/website/public/data/site.json +++ b/website/public/data/site.json @@ -60,8 +60,78 @@ "buildsCommands": "Les outils pour documenter des builds, organiser des équipements et conserver des séquences de commandes." }, "toolDocs": { + "memos": { + "controls": [ + { + "group": "Mémos", + "icon": "add", + "title": "Ajouter un mémo", + "preview": { + "kind": "iconButton", + "icon": "add" + } + }, + { + "group": "Mémos", + "icon": "drag", + "title": "Déplacer", + "preview": { + "kind": "iconButton", + "icon": "drag" + } + }, + { + "group": "Mémos", + "icon": "dropdown", + "title": "Couleur du mémo", + "preview": { + "kind": "color" + } + }, + { + "group": "Bande", + "icon": "eye-open", + "title": "Afficher la bande déroulante", + "preview": { + "kind": "iconButton", + "icon": "eye-open" + } + } + ], + "features": [ + { + "title": "Infos courtes", + "text": "Post-it rapides pour garder une consigne, une route ou un rappel visible dans la toolbox.", + "details": [ + "Chaque mémo reste volontairement court pour compléter le Bloc Note sans le remplacer.", + "L'édition se fait directement dans la carte du mémo." + ] + }, + { + "title": "Bande déroulante", + "text": "Tous les mémos apparaissent dans une bande compacte quand la bande est active.", + "details": [ + "La bande peut être masquée sans retirer les mémos.", + "Les couleurs restent des accents localisés pour conserver une lecture calme." + ] + } + ], + "importFormat": { + "text": "Chaque ligne crée un mémo. Le suffixe color=#f6c453 conserve la couleur de la carte.", + "example": "- Sauvegarder avant le boss | color=#f6c453\n- Acheter des potions | color=#22d3ee" + } + }, "notepad": { "controls": [ + { + "group": "Pages", + "icon": "pages", + "title": "Affiche le menu des pages", + "preview": { + "kind": "iconButton", + "icon": "pages" + } + }, { "group": "Édition de texte", "icon": "edit", @@ -191,8 +261,8 @@ } ], "importFormat": { - "text": "Le bloc notes utilise un bloc HTML nettoyé pour conserver la mise en forme autorisée, puis une ligne dessins en JSON compact.", - "example": "@notepad\n@html\n

Obtenir Mew :

\n@endhtml\n@drawingMode: permanent\n@drawings: {\"strokes\":[...]}" + "text": "Le Bloc Note utilise des pages avec blocs HTML nettoyés, puis une ligne dessins en JSON compact si nécessaire.", + "example": "@notepad\n@page: Page 1\n@html\n

Obtenir Mew :

\n@endhtml\n@drawingMode: permanent\n@drawings: {\"strokes\":[...]}" } }, "checklist": { @@ -1357,7 +1427,7 @@ }, { "title": "Ajoutez vos outils", - "text": "Choisissez seulement ce dont vous avez besoin : bloc notes, checklist, images, liens, timer ou compteur." + "text": "Choisissez seulement ce dont vous avez besoin : Memos, Bloc Note, checklist, images, liens, timer ou compteur." }, { "title": "Importez une sauvegarde si vous en avez une", @@ -1591,10 +1661,9 @@ "toastPositionRight": "Afficher les alertes en bas à droite", "summary": "Créez des espaces modulaires pour regrouper vos outils de session : notes, suivis, références, captures et futurs outils restent accessibles rapidement, en local, par jeu ou par usage.", "creationTemplate": { - "notepad": { - "title": "Bienvenue", - "text": "Profite bien de ta nouvelle toolbox.", - "html": "

Profite bien de ta nouvelle toolbox.

" + "memos": { + "title": "Memos", + "text": "Profite bien de ta nouvelle toolbox." }, "links": { "moduleTitle": "Liens utiles", @@ -1631,7 +1700,7 @@ "target": "moduleTitle", "icon": "edit", "title": "Renommer un outil", - "text": "Cliquez sur le titre d’un outil, comme Bloc notes, pour l’adapter à votre usage." + "text": "Cliquez sur le titre d’un outil, comme Memos, pour l’adapter à votre usage." }, { "target": "deleteTool", @@ -1659,8 +1728,14 @@ }, "modules": { "notepad": { - "placeholder": "Notes rapides...", - "toolbarLabel": "Mise en page du bloc notes", + "placeholder": "Bloc Note...", + "pagesMenuLabel": "Pages du bloc note", + "pagesToggleLabel": "Afficher les pages du bloc note", + "pagesToggleText": "Afficher les pages du bloc note", + "addPageLabel": "Ajouter une page", + "renamePageLabel": "Renommer", + "deletePageLabel": "Supprimer la page", + "toolbarLabel": "Mise en page du bloc note", "textFormatLabel": "Style du texte", "boldTitle": "Gras", "italicTitle": "Italique", @@ -1686,18 +1761,42 @@ "permanentStatus": "Dessins permanents", "updatedAtPrefix": "Modifié le", "sessionStorageError": "Les annotations temporaires n'ont pas pu être sauvegardées.", - "importPlaceholder": "@notepad\n@html\n

Route boss

\n

Préparer les consommables

\n@endhtml\n@drawingMode: permanent\n@drawings: {\"strokes\":[]}", + "importPlaceholder": "@notepad\n@page: Page 1\n@html\n

Route boss

\n

Préparer les consommables

\n@endhtml\n@drawingMode: permanent\n@drawings: {\"strokes\":[]}", "importOpenButton": "Importer du texte", "exportOpenButton": "Exporter en format texte", - "importModalTitle": "Importer un bloc notes", - "exportModalTitle": "Exporter un bloc notes", + "importModalTitle": "Importer un bloc note", + "exportModalTitle": "Exporter un bloc note", "importButton": "Importer le texte", "exportButton": "Copier le texte", - "importSuccess": "Import bloc notes terminé.", - "exportSuccess": "Export bloc notes copié.", - "importError": "Format de bloc notes invalide.", + "importSuccess": "Import bloc note terminé.", + "exportSuccess": "Export bloc note copié.", + "importError": "Format de bloc note invalide.", "exportInfo": "Le HTML est nettoyé à l'import. Les dessins permanents sont exportés en JSON compact." }, + "memos": { + "defaultText": "Nouveau mémo", + "placeholder": "Mémo court...", + "addButton": "Ajouter", + "emptyLabel": "Ajouter un premier mémo", + "editLabel": "Modifier le mémo", + "deleteTitle": "Supprimer le mémo", + "colorLabel": "Couleur du mémo", + "reorderTitle": "Déplacer", + "tickerLabel": "Mémos", + "hideTickerTitle": "Masquer la bande", + "showTickerTitle": "Afficher la bande", + "importPlaceholder": "- Mémo prioritaire | color=#f6c453\n- Mémo simple | color=#22d3ee", + "importOpenButton": "Importer du texte", + "exportOpenButton": "Exporter en format texte", + "importModalTitle": "Importer des mémos", + "exportModalTitle": "Exporter des mémos", + "importButton": "Importer le texte", + "exportButton": "Copier le texte", + "importSuccess": "Import mémos terminé.", + "exportSuccess": "Export mémos copié.", + "importError": "Format de mémos invalide.", + "exportInfo": "Chaque ligne est exportée avec la couleur du mémo au format color=#hex." + }, "checklist": { "itemPlaceholder": "Nouvel item", "sectionPlaceholder": "Catégorie", diff --git a/website/public/static/icons/memos.svg b/website/public/static/icons/memos.svg new file mode 100644 index 0000000..238e628 --- /dev/null +++ b/website/public/static/icons/memos.svg @@ -0,0 +1,24 @@ + + + + + + + + + \ No newline at end of file diff --git a/website/public/static/icons/pages.svg b/website/public/static/icons/pages.svg new file mode 100644 index 0000000..d9cdcb8 --- /dev/null +++ b/website/public/static/icons/pages.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/website/src/features/toolboxes/ToolboxPages.jsx b/website/src/features/toolboxes/ToolboxPages.jsx index 2d280a0..82c9eae 100644 --- a/website/src/features/toolboxes/ToolboxPages.jsx +++ b/website/src/features/toolboxes/ToolboxPages.jsx @@ -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, diff --git a/website/src/features/toolboxes/modules/MemosModule.jsx b/website/src/features/toolboxes/modules/MemosModule.jsx new file mode 100644 index 0000000..3b7a1f7 --- /dev/null +++ b/website/src/features/toolboxes/modules/MemosModule.jsx @@ -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 ( +
+ {textExchangeOpen && ( +
+ save(nextData)} /> +
+ )} + + {editing && ( +
+ +
+ )} + +
+ + {tickerItems.length > 0 && ( +
+
+ {[...tickerItems, ...tickerItems].map((memo, index) => ( + + {memo.text} + + ))} +
+
+ )} +
+ +
+ {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 ( +
+ +