From 6e1472d2fb28f60d550dc37f0466ba4726624985 Mon Sep 17 00:00:00 2001 From: Shinuwa Date: Fri, 24 Jul 2026 09:37:32 +0200 Subject: [PATCH] content & style opti, new tool annotation --- README.md | 12 +- tests/helpers/data-validation.mjs | 56 ++++ tests/static-smoke.test.mjs | 36 ++- website/public/data/games.json | 2 +- website/public/data/site.json | 72 ++++- website/public/static/icons/map.svg | 4 + .../toolboxes/modules/CalculatorModule.jsx | 36 +-- .../toolboxes/modules/ChecklistModule.jsx | 17 +- .../toolboxes/modules/CountersModule.jsx | 10 +- .../modules/ImageAnnotationModule.jsx | 128 ++++++++ .../toolboxes/modules/LinksModule.jsx | 8 +- .../toolboxes/modules/ScreenshotsModule.jsx | 40 ++- .../src/features/toolboxes/modules/index.jsx | 12 +- website/src/main.jsx | 254 +++++++++++++++- website/src/styles/_base.scss | 189 +++++++----- website/src/styles/_games.scss | 15 +- website/src/styles/_icons.scss | 5 + website/src/styles/_overlays.scss | 104 ++++++- website/src/styles/_responsive.scss | 8 + website/src/styles/_toolboxes.scss | 283 +++++++++++++++--- website/src/utils/bodyScrollLock.js | 15 + 21 files changed, 1112 insertions(+), 194 deletions(-) create mode 100644 website/public/static/icons/map.svg create mode 100644 website/src/features/toolboxes/modules/ImageAnnotationModule.jsx create mode 100644 website/src/utils/bodyScrollLock.js diff --git a/README.md b/README.md index 8574003..80f38d3 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ Sokko G propose des toolboxes modulaires stockées dans le navigateur, ainsi que ## Fonctionnalités - Toolboxes locales stockées dans `localStorage`. -- Outils disponibles : bloc notes, checklist avec quantités, screenshots. +- Outils disponibles : bloc notes, checklist avec quantités, images, annotation d’images, liens, compteurs et calculateur. - Import/export d’une toolbox. - Import/export global de toutes les toolboxes. - Association locale entre une page jeu et une toolbox. @@ -70,6 +70,7 @@ Les contenus maintenus à la main sont regroupés dans `website/public/data`. | `website/public/data/mhwilds/endemic_life.json` | Données de la faune Monster Hunter Wilds. | | `website/public/data/mhwilds/i18n/fr.json` | Traductions françaises des données MHWilds. | | `website/public/data/mhwilds/i18n/en.json` | Traductions anglaises des données MHWilds. | +| `website/public/data/diablo4/affixes_types.json` | Données et filtres des affixes Diablo IV. | | `website/public/data/toolbox-icons.json` | Manifeste des icônes SVG réutilisables. | Les images publiques sont dans `website/public/static`. @@ -81,6 +82,7 @@ Les pages jeux sont dans `website/src/features/games`. - `website/src/features/games/GamesPage.jsx` : liste des jeux disponibles. - `website/src/features/games/GameRoute.jsx` : route vers la page du jeu demandé. - `website/src/features/games/mhwilds/` : vues et composants propres à Monster Hunter Wilds. +- `website/src/features/games/diablo4/` : vues et composants propres à Diablo IV. Pour ajouter un jeu : @@ -95,9 +97,11 @@ Chaque outil possède son propre fichier de composant : - `website/src/features/toolboxes/modules/NotepadModule.jsx` - `website/src/features/toolboxes/modules/ChecklistModule.jsx` -- `website/src/features/toolboxes/modules/ScreenshotsModule.jsx` +- `website/src/features/toolboxes/modules/ScreenshotsModule.jsx` : outil Images, conservé sous ce nom technique pour le stockage existant. - `website/src/features/toolboxes/modules/LinksModule.jsx` - `website/src/features/toolboxes/modules/CountersModule.jsx` +- `website/src/features/toolboxes/modules/CalculatorModule.jsx` +- `website/src/features/toolboxes/modules/ImageAnnotationModule.jsx` Pour ajouter ou maintenir un outil : @@ -124,16 +128,20 @@ Pour ajouter ou maintenir un outil : ├── components/ ├── features/ │ ├── games/ + │ │ ├── diablo4/ │ │ └── mhwilds/ │ └── toolboxes/ │ └── modules/ └── styles/ ├── _base.scss ├── _cards.scss + ├── _diablo4.scss ├── _games.scss ├── _home.scss ├── _icons.scss + ├── _mixins.scss ├── _mhwilds.scss + ├── _nebula.scss ├── _overlays.scss ├── _responsive.scss ├── _shell.scss diff --git a/tests/helpers/data-validation.mjs b/tests/helpers/data-validation.mjs index f457a2f..dfe8f73 100644 --- a/tests/helpers/data-validation.mjs +++ b/tests/helpers/data-validation.mjs @@ -69,14 +69,70 @@ export function validateSiteContent(site) { "toolboxes.storageHelp.text", "toolboxes.modules.notepad.placeholder", "toolboxes.modules.checklist.itemPlaceholder", + "toolboxes.modules.checklist.quantityLabel", + "toolboxes.modules.checklist.addButton", "toolboxes.modules.checklist.importPlaceholder", + "toolboxes.modules.checklist.importButton", + "toolboxes.modules.checklist.decrementLabel", + "toolboxes.modules.checklist.incrementLabel", + "toolboxes.modules.checklist.currentQuantityLabel", + "toolboxes.modules.checklist.deleteTitle", + "toolboxes.modules.screenshots.addImages", + "toolboxes.modules.screenshots.pastePlaceholder", + "toolboxes.modules.screenshots.pasteAriaLabel", + "toolboxes.modules.screenshots.imageAlt", + "toolboxes.modules.screenshots.previewAriaLabel", + "toolboxes.modules.screenshots.annotateAriaLabel", + "toolboxes.modules.screenshots.annotateTitle", + "toolboxes.modules.screenshots.deleteAriaLabel", + "toolboxes.modules.screenshots.deleteTitle", "toolboxes.modules.links.titlePlaceholder", "toolboxes.modules.links.urlPlaceholder", + "toolboxes.modules.links.addButton", "toolboxes.modules.links.importPlaceholder", + "toolboxes.modules.links.importButton", + "toolboxes.modules.links.copyTitle", + "toolboxes.modules.links.copiedTitle", + "toolboxes.modules.links.deleteTitle", "toolboxes.modules.counters.labelPlaceholder", + "toolboxes.modules.counters.addButton", + "toolboxes.modules.counters.decrementLabel", + "toolboxes.modules.counters.incrementLabel", + "toolboxes.modules.counters.resetTitle", + "toolboxes.modules.counters.deleteTitle", + "toolboxes.modules.calculator.expressionLabel", "toolboxes.modules.calculator.expressionPlaceholder", + "toolboxes.modules.calculator.resultLabel", + "toolboxes.modules.calculator.labelLabel", "toolboxes.modules.calculator.labelPlaceholder", + "toolboxes.modules.calculator.saveButton", + "toolboxes.modules.calculator.rootButton", + "toolboxes.modules.calculator.resetTitle", + "toolboxes.modules.calculator.copyTitle", + "toolboxes.modules.calculator.copiedTitle", "toolboxes.modules.calculator.scrollableTitle", + "toolboxes.modules.calculator.emptyResults", + "toolboxes.modules.calculator.readonlyValueTitle", + "toolboxes.modules.calculator.useEntryTitle", + "toolboxes.modules.calculator.renameTitle", + "toolboxes.modules.calculator.deleteTitle", + "toolboxes.modules.imageAnnotation.addImage", + "toolboxes.modules.imageAnnotation.replaceImage", + "toolboxes.modules.imageAnnotation.pastePlaceholder", + "toolboxes.modules.imageAnnotation.pasteAriaLabel", + "toolboxes.modules.imageAnnotation.stageAriaLabel", + "toolboxes.modules.imageAnnotation.addMarkerAriaLabel", + "toolboxes.modules.imageAnnotation.imageAlt", + "toolboxes.modules.imageAnnotation.previewTitle", + "toolboxes.modules.imageAnnotation.previewAriaLabel", + "toolboxes.modules.imageAnnotation.markerPrefix", + "toolboxes.modules.imageAnnotation.markersSingular", + "toolboxes.modules.imageAnnotation.markersPlural", + "toolboxes.modules.imageAnnotation.markerPlaceholder", + "toolboxes.modules.imageAnnotation.emptyImage", + "toolboxes.modules.imageAnnotation.emptyMarkers", + "toolboxes.modules.imageAnnotation.deleteImageTitle", + "toolboxes.modules.imageAnnotation.deleteMarkerTitle", "toolboxes.emptyTitle", "toolboxes.emptyText" ].forEach((path) => assertNonEmptyString(site, path)); diff --git a/tests/static-smoke.test.mjs b/tests/static-smoke.test.mjs index b7bb879..8827d14 100644 --- a/tests/static-smoke.test.mjs +++ b/tests/static-smoke.test.mjs @@ -19,6 +19,7 @@ test("react application defines the expected local toolbox primitives", async () const styleCards = await readFile("website/src/styles/_cards.scss", "utf8"); const styleHome = await readFile("website/src/styles/_home.scss", "utf8"); const styleToolboxes = await readFile("website/src/styles/_toolboxes.scss", "utf8"); + const styleOverlays = await readFile("website/src/styles/_overlays.scss", "utf8"); const styleIcons = await readFile("website/src/styles/_icons.scss", "utf8"); const styleGames = await readFile("website/src/styles/_games.scss", "utf8"); const styleMhwilds = await readFile("website/src/styles/_mhwilds.scss", "utf8"); @@ -46,10 +47,12 @@ test("react application defines the expected local toolbox primitives", async () const calculatorModule = await readFile("website/src/features/toolboxes/modules/CalculatorModule.jsx", "utf8"); const checklistModule = await readFile("website/src/features/toolboxes/modules/ChecklistModule.jsx", "utf8"); const screenshotsModule = await readFile("website/src/features/toolboxes/modules/ScreenshotsModule.jsx", "utf8"); + const imageAnnotationModule = await readFile("website/src/features/toolboxes/modules/ImageAnnotationModule.jsx", "utf8"); const linksModule = await readFile("website/src/features/toolboxes/modules/LinksModule.jsx", "utf8"); const countersModule = await readFile("website/src/features/toolboxes/modules/CountersModule.jsx", "utf8"); const textImport = await readFile("website/src/features/toolboxes/modules/textImport.js", "utf8"); const reorderHook = await readFile("website/src/hooks/usePointerReorder.js", "utf8"); + const bodyScrollLock = await readFile("website/src/utils/bodyScrollLock.js", "utf8"); assert.match(source, /sokkog:toolboxes/); assert.match(source, /sokkog:game-toolbox-links/); @@ -60,7 +63,9 @@ test("react application defines the expected local toolbox primitives", async () assert.match(styles, /@use "mhwilds"/); assert.match(styles, /@use "diablo4"/); assert.match(styleTokens, /--gradient-brand/); - assert.match(styleBase, /\.card-icon-button,\n\.drawer-action-button/); + assert.match(styleBase, /:is\(/); + assert.match(styleBase, /\.card-icon-button/); + assert.match(styleBase, /\.drawer-action-button/); assert.match(styleNebula, /@function star-field/); assert.match(styleNebula, /body::after/); assert.match(styleBase, /\.import-icon-button/); @@ -141,6 +146,8 @@ test("react application defines the expected local toolbox primitives", async () assert.match(moduleRegistry, /links:/); assert.match(moduleRegistry, /counters:/); assert.match(moduleRegistry, /calculator:/); + assert.match(moduleRegistry, /imageAnnotation:/); + assert.match(moduleRegistry, /Annotation d'images/); assert.match(moduleRegistry, /editable: false/); assert.match(moduleRegistry, /module-edit-button/); assert.match(notepadModule, /export function NotepadModule/); @@ -162,6 +169,7 @@ test("react application defines the expected local toolbox primitives", async () assert.match(calculatorModule, /CalculatorEntries/); assert.match(checklistModule, /export function ChecklistModule/); assert.match(screenshotsModule, /export function ScreenshotsModule/); + assert.match(imageAnnotationModule, /export function ImageAnnotationModule/); assert.match(linksModule, /export function LinksModule/); assert.match(countersModule, /export function CountersModule/); assert.match(textImport, /export function parseColonImportLines/); @@ -225,18 +233,43 @@ test("react application defines the expected local toolbox primitives", async () assert.match(source, /normalizeLinksData/); assert.match(source, /normalizeCountersData/); assert.match(source, /normalizeCalculatorData/); + assert.match(source, /normalizeImageAnnotationData/); + assert.match(source, /compactModuleDataForStorage\(type, value\)/); assert.match(source, /scrollResults/); assert.match(checklistModule, /ChecklistItem/); assert.match(checklistModule, /parseColonImportLines/); assert.match(checklistModule, /Number\.parseInt/); assert.match(checklistModule, /checklist-qty-current/); + assert.match(checklistModule, /checklist-delete-button danger/); assert.match(linksModule, /parseColonImportLines/); assert.match(linksModule, /context\.normalizeUrl/); assert.match(screenshotsModule, /clipboardData/); + assert.match(screenshotsModule, /createImageAnnotationModule/); + assert.match(screenshotsModule, /shot-annotate-button/); + assert.match(imageAnnotationModule, /annotation-marker/); + assert.match(imageAnnotationModule, /setImageFromFiles/); + assert.match(imageAnnotationModule, /context\.compressImageFile/); + assert.match(imageAnnotationModule, /context\.setScreenshot/); + assert.match(imageAnnotationModule, /createMarkerId/); + assert.match(source, /uid\("marker"\)/); assert.match(source, /async function addScreenshotFiles/); assert.match(source, /function ScreenshotViewer/); + assert.match(source, /useModalScrollLock/); + assert.match(bodyScrollLock, /modalLocks/); + assert.match(bodyScrollLock, /is-modal-open/); + assert.match(moduleRegistry, /lockBodyScroll/); + assert.match(source, /screenshot-viewer-media/); + assert.match(source, /screenshot-viewer-image-frame/); + assert.match(source, /screenshot-viewer-marker/); assert.match(source, /function openImageInNewTab/); + assert.match(source, /!hasMarkers &&/); assert.match(source, / { await readFile("website/public/static/icons/copy.svg", "utf8"); await readFile("website/public/static/icons/abacus.svg", "utf8"); await readFile("website/public/static/icons/calculator.svg", "utf8"); + await readFile("website/public/static/icons/map.svg", "utf8"); await readFile("website/public/static/icons/enter.svg", "utf8"); await readFile("website/public/static/icons/export.svg", "utf8"); await readFile("website/public/static/icons/scrollable.svg", "utf8"); diff --git a/website/public/data/games.json b/website/public/data/games.json index 233becd..43c3255 100644 --- a/website/public/data/games.json +++ b/website/public/data/games.json @@ -31,7 +31,7 @@ "items": [ "Capturer les drops rares", "Mettre à jour les crafts suivis", - "Archiver les screenshots importants" + "Archiver les images importantes" ] } ] diff --git a/website/public/data/site.json b/website/public/data/site.json index fe40310..a623be4 100644 --- a/website/public/data/site.json +++ b/website/public/data/site.json @@ -88,13 +88,13 @@ "importAll": "Importer tout", "exportAll": "Exporter tout", "importOne": "Importer une toolbox", - "summary": "Créez des espaces modulaires pour regrouper vos outils de session : notes, suivis, références, captures et futurs modules restent accessibles rapidement, en local, par jeu ou par usage.", + "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.", "storageHelp": { "title": "Sauvegardes locales", "text": "Vos toolboxes restent sur cet appareil, dans ce navigateur. Rien n’est envoyé sur un serveur.", "items": [ "Un nettoyage du navigateur ou un changement d’appareil peut supprimer les données.", - "Surveillez le quota, surtout si vous ajoutez des screenshots.", + "Surveillez le quota, surtout si vous ajoutez des images.", "Faites un export global régulier pour garder une sauvegarde." ] }, @@ -104,20 +104,80 @@ }, "checklist": { "itemPlaceholder": "Nouvel item", - "importPlaceholder": "Importer plusieurs items...\nPotion:10\nMéga potion:5" + "quantityLabel": "Quantité cible", + "addButton": "Ajouter", + "importPlaceholder": "Importer plusieurs items...\nPotion:10\nMéga potion:5", + "importButton": "Importer le texte", + "decrementLabel": "Retirer une quantité", + "incrementLabel": "Ajouter une quantité", + "currentQuantityLabel": "Quantité actuelle", + "deleteTitle": "Supprimer" + }, + "screenshots": { + "addImages": "Ajouter des images", + "pastePlaceholder": "Coller une image ici", + "pasteAriaLabel": "Coller une image depuis le presse-papiers", + "imageAlt": "Image", + "previewAriaLabel": "Agrandir l'image", + "annotateAriaLabel": "Annoter l'image", + "annotateTitle": "Annoter", + "deleteAriaLabel": "Supprimer l'image", + "deleteTitle": "Supprimer" }, "links": { "titlePlaceholder": "Nom du lien", "urlPlaceholder": "https://...", - "importPlaceholder": "Importer plusieurs liens...\nBuild:https://example.com/build?class=rogue\nMap:https://example.com/map" + "addButton": "Ajouter", + "importPlaceholder": "Importer plusieurs liens...\nBuild:https://example.com/build?class=rogue\nMap:https://example.com/map", + "importButton": "Importer le texte", + "copyTitle": "Copier", + "copiedTitle": "Copié", + "deleteTitle": "Supprimer" }, "counters": { - "labelPlaceholder": "Nom du compteur" + "labelPlaceholder": "Nom du compteur", + "addButton": "Ajouter", + "decrementLabel": "Décrémenter", + "incrementLabel": "Incrémenter", + "resetTitle": "Réinitialiser", + "deleteTitle": "Supprimer" }, "calculator": { + "expressionLabel": "Calcul", "expressionPlaceholder": "10*10", + "resultLabel": "Résultat", + "labelLabel": "Libellé", "labelPlaceholder": "ex: Lingots de fer", - "scrollableTitle": "Liste scrollable" + "saveButton": "Enregistrer", + "rootButton": "Revenir à la racine", + "resetTitle": "Réinitialiser", + "copyTitle": "Copier pour checklist", + "copiedTitle": "Copié", + "scrollableTitle": "Liste scrollable", + "emptyResults": "Aucun résultat enregistré.", + "readonlyValueTitle": "Quantité non modifiable", + "useEntryTitle": "Utiliser comme base", + "renameTitle": "Renommer", + "deleteTitle": "Supprimer" + }, + "imageAnnotation": { + "addImage": "Ajouter une image", + "replaceImage": "Remplacer l'image", + "pastePlaceholder": "Coller une image ici", + "pasteAriaLabel": "Coller une image à annoter depuis le presse-papiers", + "stageAriaLabel": "Ajouter un marqueur sur l'image", + "addMarkerAriaLabel": "Ajouter un marqueur sur l'image", + "imageAlt": "Image annotée", + "previewTitle": "Agrandir", + "previewAriaLabel": "Agrandir l'image annotée", + "markerPrefix": "Marqueur", + "markersSingular": "marqueur", + "markersPlural": "marqueurs", + "markerPlaceholder": "Libellé du marqueur", + "emptyImage": "Ajoutez une image pour commencer l'annotation.", + "emptyMarkers": "Agrandissez l'image pour ajouter un marqueur.", + "deleteImageTitle": "Supprimer l'image", + "deleteMarkerTitle": "Supprimer" } }, "emptyTitle": "Aucune toolbox", diff --git a/website/public/static/icons/map.svg b/website/public/static/icons/map.svg new file mode 100644 index 0000000..f7d54bb --- /dev/null +++ b/website/public/static/icons/map.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/website/src/features/toolboxes/modules/CalculatorModule.jsx b/website/src/features/toolboxes/modules/CalculatorModule.jsx index 4e87cb8..ce6ad24 100644 --- a/website/src/features/toolboxes/modules/CalculatorModule.jsx +++ b/website/src/features/toolboxes/modules/CalculatorModule.jsx @@ -119,18 +119,18 @@ export function CalculatorModule({ toolboxId, moduleId, context }) {
- Résultat + {textContent.resultLabel || "Résultat"} {result == null ? "-" : formatResult(result)}
- +
{activeParent && ( )} - -
@@ -173,7 +173,7 @@ export function CalculatorModule({ toolboxId, moduleId, context }) { ); } -function CalculatorEntries({ entries, parentId, activeParentId, onUse, onRename, onDelete }) { +function CalculatorEntries({ entries, parentId, activeParentId, textContent, onUse, onRename, onDelete }) { const children = getChildren(entries, parentId); const [editingId, setEditingId] = useState(""); if (!children.length) return null; @@ -185,31 +185,31 @@ function CalculatorEntries({ entries, parentId, activeParentId, onUse, onRename,
{entry.id === editingId ? ( <> - + {formatResult(entry.value)} - setEditingId("")} /> + setEditingId("")} /> ) : ( - )} - -
- + ))} ); } -function EditableCalculatorLabel({ entry, onRename, onDone }) { +function EditableCalculatorLabel({ entry, textContent, onRename, onDone }) { const [label, setLabel] = useState(entry.label); const inputRef = useRef(null); @@ -237,7 +237,7 @@ function EditableCalculatorLabel({ entry, onRename, onDone }) { onDone(); } }} - aria-label={`Renommer ${entry.label}`} + aria-label={`${textContent.renameTitle || "Renommer"} ${entry.label}`} /> ); } diff --git a/website/src/features/toolboxes/modules/ChecklistModule.jsx b/website/src/features/toolboxes/modules/ChecklistModule.jsx index d42612e..cb31aeb 100644 --- a/website/src/features/toolboxes/modules/ChecklistModule.jsx +++ b/website/src/features/toolboxes/modules/ChecklistModule.jsx @@ -1,4 +1,5 @@ import { useState } from "react"; +import { Icon } from "../../../components/Icon.jsx"; import { parseColonImportLines } from "./textImport.js"; export function ChecklistModule({ toolboxId, moduleId, context, editing }) { @@ -40,8 +41,8 @@ export function ChecklistModule({ toolboxId, moduleId, context, editing }) {
setLabel(event.target.value)} /> - setQty(event.target.value)} aria-label="Quantité cible" /> - + setQty(event.target.value)} aria-label={textContent.quantityLabel || "Quantité cible"} /> +