From c3bab44a4bfbdbf17587b04ce11e60f38223a399 Mon Sep 17 00:00:00 2001 From: Shinuwa Date: Thu, 1 Oct 2026 09:33:18 +0200 Subject: [PATCH] Allow linking an existing toolbox from the game drawer --- docs/FEATURE_CHECKLIST.md | 1 + e2e/toolbox.spec.js | 23 +++++++++++ tests/helpers/data-validation.mjs | 7 ++++ website/public/data/site.json | 9 +++++ .../src/features/toolboxes/ToolboxPages.jsx | 38 ++++++++++++++++++- website/src/styles/_responsive.scss | 4 ++ website/src/styles/overlays/_drawers.scss | 26 +++++++++++++ 7 files changed, 107 insertions(+), 1 deletion(-) diff --git a/docs/FEATURE_CHECKLIST.md b/docs/FEATURE_CHECKLIST.md index 76f6780..0df2abe 100644 --- a/docs/FEATURE_CHECKLIST.md +++ b/docs/FEATURE_CHECKLIST.md @@ -54,6 +54,7 @@ Checklist à suivre lors de l'ajout ou de la modification d'un outil, d'une page - Vérifier la page `/games/`. - Vérifier le chargement à la première visite, la conservation des données pendant la navigation et les erreurs réseau ; sous StrictMode, les requêtes abandonnées ne doivent pas remplacer les données actuelles ni afficher d'erreur d'annulation. - Vérifier que le bouton texte « Afficher la toolbox » devient « Masquer la toolbox » avec le panneau latéral ouvert. +- Sans toolbox liée, vérifier la création ainsi que l'association directe d'une toolbox existante ; le sélecteur ne doit proposer que les toolboxes qui ne sont liées à aucun jeu. - Vérifier le breadcrumb. - Vérifier les textes avec retours à la ligne. - Pour les guides à cartes, vérifier le toggle de lien à côté du « + » dans l'en-tête de chaque checklist compatible : il affiche les commandes, masquées par défaut. Vérifier l'association exclusive, le masquage désactivé sur les autres listes et la mise à jour immédiate depuis le panneau latéral. Refermer les commandes conserve l'association. La checkbox des cartes met à jour toutes les lignes exactes correspondantes, respecte les quantités cibles et ne retourne pas les cartes. diff --git a/e2e/toolbox.spec.js b/e2e/toolbox.spec.js index 2c9e25c..acd6ff1 100644 --- a/e2e/toolbox.spec.js +++ b/e2e/toolbox.spec.js @@ -219,6 +219,29 @@ test("creates a game-linked toolbox from the drawer", async ({ page }) => { await expect(page.getByRole("button", { name: "Masquer la toolbox", exact: true })).toBeVisible(); }); +test("links an available toolbox directly from the empty game drawer", async ({ page }) => { + await createToolbox(page, "Toolbox déjà liée"); + await page.goto("/#/games/mhwilds"); + await page.getByRole("button", { name: "Afficher la toolbox", exact: true }).click(); + + const drawer = page.locator("#toolbox-drawer .drawer-panel"); + await expect(drawer.getByRole("heading", { name: "Ou associer une toolbox déjà existante" })).toBeVisible(); + await drawer.getByLabel("Toolbox disponible").selectOption({ label: "Toolbox déjà liée" }); + await drawer.getByRole("button", { name: "Associer", exact: true }).click(); + await expect(drawer.getByRole("heading", { name: "Toolbox déjà liée", exact: true })).toBeVisible(); + + await createToolbox(page, "Toolbox libre"); + await page.goto("/#/games/diablo4"); + await page.getByRole("button", { name: "Afficher la toolbox", exact: true }).click(); + + await expect(drawer.getByLabel("Toolbox disponible").getByRole("option", { name: "Toolbox déjà liée" })).toHaveCount(0); + await drawer.getByLabel("Toolbox disponible").selectOption({ label: "Toolbox libre" }); + await drawer.getByRole("button", { name: "Associer", exact: true }).click(); + + await expect(drawer.getByRole("heading", { name: "Toolbox libre", exact: true })).toBeVisible(); + await expect(drawer.getByRole("heading", { name: "Ou associer une toolbox déjà existante" })).toHaveCount(0); +}); + test("imports and exports a toolbox payload", async ({ page }) => { await page.goto("/#/toolboxes"); await expect(page.getByRole("main")).toBeVisible(); diff --git a/tests/helpers/data-validation.mjs b/tests/helpers/data-validation.mjs index ab9f772..ac99302 100644 --- a/tests/helpers/data-validation.mjs +++ b/tests/helpers/data-validation.mjs @@ -248,6 +248,13 @@ export function validateSiteContent(site) { "toolboxes.spaces.moveSuccess", "toolboxes.spaces.emptyText", "toolboxes.spaces.limitReached", + "toolboxes.drawer.emptyText", + "toolboxes.drawer.createLabel", + "toolboxes.drawer.linkExistingTitle", + "toolboxes.drawer.selectLabel", + "toolboxes.drawer.selectPlaceholder", + "toolboxes.drawer.noAvailable", + "toolboxes.drawer.linkLabel", "toolboxes.title", "toolboxes.newButton", "toolboxes.libraryLink", diff --git a/website/public/data/site.json b/website/public/data/site.json index 8605234..b1662a4 100644 --- a/website/public/data/site.json +++ b/website/public/data/site.json @@ -1794,6 +1794,15 @@ ] }, "toolboxes": { + "drawer": { + "emptyText": "Aucune toolbox associée à cette page jeu.", + "createLabel": "Créer et associer", + "linkExistingTitle": "Ou associer une toolbox déjà existante", + "selectLabel": "Toolbox disponible", + "selectPlaceholder": "Choisir une toolbox", + "noAvailable": "Aucune toolbox disponible", + "linkLabel": "Associer" + }, "storageStatus": { "loadingTitle": "Chargement des données locales", "mediaLoadingMessage": "Chargement des médias…", diff --git a/website/src/features/toolboxes/ToolboxPages.jsx b/website/src/features/toolboxes/ToolboxPages.jsx index 056a612..7291776 100644 --- a/website/src/features/toolboxes/ToolboxPages.jsx +++ b/website/src/features/toolboxes/ToolboxPages.jsx @@ -486,7 +486,7 @@ export function ToolboxDrawer({ gameId, game, siteContent, toolboxes, links, act {toolbox ? : ( -

Aucune toolbox associée à cette page jeu.

+ )} @@ -495,6 +495,42 @@ export function ToolboxDrawer({ gameId, game, siteContent, toolboxes, links, act ); } +function ToolboxDrawerEmptyState({ gameId, toolboxes, links, actions, content }) { + const linkedToolboxIds = useMemo(() => new Set(Object.values(links).filter(Boolean)), [links]); + const availableToolboxes = useMemo( + () => toolboxes.filter((toolbox) => !linkedToolboxIds.has(toolbox.id)), + [linkedToolboxIds, toolboxes] + ); + const [selectedId, setSelectedId] = useState(""); + const validSelectedId = availableToolboxes.some((toolbox) => toolbox.id === selectedId) ? selectedId : ""; + const hasAvailableToolboxes = availableToolboxes.length > 0; + + return ( +
+
+

{content.emptyText}

+ +
+
+ +
{ + event.preventDefault(); + if (validSelectedId) actions.linkToolboxToGame(gameId, validSelectedId); + }}> + + +
+
+
+ ); +} + function EditableTitle({ value, fallback, onSave, className = "module-title", tutorialTarget = "" }) { const inlineEdit = useInlineEdit({ value, diff --git a/website/src/styles/_responsive.scss b/website/src/styles/_responsive.scss index f762509..633afff 100644 --- a/website/src/styles/_responsive.scss +++ b/website/src/styles/_responsive.scss @@ -429,6 +429,10 @@ max-width: none; } + .drawer-link-existing form { + grid-template-columns: 1fr; + } + .drawer-resize-handle { display: none; } diff --git a/website/src/styles/overlays/_drawers.scss b/website/src/styles/overlays/_drawers.scss index 9f60eee..e89dfd8 100644 --- a/website/src/styles/overlays/_drawers.scss +++ b/website/src/styles/overlays/_drawers.scss @@ -66,6 +66,32 @@ body.is-resizing-drawer * { opacity: 0.56; } +.drawer-empty-toolbox { + display: grid; + gap: var(--space-4); +} + +.drawer-link-existing h2 { + font-size: var(--font-size-md); +} + +.drawer-link-existing form { + display: grid; + grid-template-columns: minmax(0, 1fr) auto; + gap: var(--space-3); + align-items: end; +} + +.drawer-link-existing-field { + display: grid; + gap: var(--space-2); +} + +.drawer-link-existing-field, +.drawer-link-existing .nds-select { + width: 100%; +} + .drawer-close-button { flex: 0 0 40px; }