sokko-g/docs/STORAGE_SCHEMA.md
Shinuwa d53d4160af
All checks were successful
Deploy Sokko G / deploy (push) Successful in 7s
Add equipment planner toolbox module
2026-08-09 10:03:55 +02:00

18 KiB

Storage Schema

Ce document décrit les données persistées dans IndexedDB pour les toolboxes. Il doit être mis à jour à chaque ajout ou modification d'outil.

IndexedDB

Base : sokkog

Object stores :

  • kv : données globales simples.
  • modules : données des outils, séparées par toolbox et par module.

Clés principales dans kv :

{
  "toolboxes": [],
  "links": {},
  "setting:toolboxOrder": [],
  "setting:storageBudgetBytes": 536870912,
  "setting:toastPosition": "right"
}

Entrée type dans modules :

{
  "key": "tbx1:mod1",
  "data": {}
}

Toolbox

Les toolboxes sont stockées dans kv/toolboxes.

{
  "id": "tbx1",
  "name": "Monster Hunter Wilds",
  "gameId": "mhwilds",
  "icon": "/static/img/toolbox-icons/toolbox.png",
  "modules": [
    {
      "id": "mod1",
      "type": "checklist",
      "title": "Armures"
    }
  ],
  "layout": "two",
  "updatedAt": "2026-07-25T12:00:00.000Z"
}

Notes :

  • gameId est vide pour une toolbox libre.
  • icon est utilisé uniquement pour les toolboxes libres.
  • layout vaut généralement one ou two.
  • Les données lourdes des outils ne sont pas stockées dans la toolbox, mais dans modules.

Liens Jeu / Toolbox

Les associations entre pages jeux et toolboxes sont stockées dans kv/links.

{
  "mhwilds": "tbx1",
  "diablo4": "tbx2"
}

Outil Bloc Notes

Type : notepad

{
  "html": "<p>Notes <strong>rapides</strong>...</p>",
  "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 ;
  • 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.

Outil Checklist

Type : checklist

Format simple, sans catégorie :

{
  "items": [
    {
      "id": "item1",
      "label": "Potion",
      "qtyTarget": 10,
      "qtyCurrent": 3
    }
  ]
}

Format avec catégories :

{
  "hideCompletedSections": true,
  "hideCompletedSectionsFully": false,
  "sections": [
    {
      "id": "section1",
      "title": "Rey Dau alpha",
      "hideWhenComplete": false,
      "collapsed": false,
      "items": [
        {
          "id": "item1",
          "label": "Casque",
          "qtyTarget": 1,
          "qtyCurrent": 0
        }
      ]
    }
  ]
}

Stockage compact :

  • qtyTarget est omis si la valeur vaut 1.
  • qtyCurrent est omis si la valeur vaut 0.
  • hideCompletedSections est omis si false.
  • hideCompletedSectionsFully est omis si false.
  • title est omis si vide.
  • hideWhenComplete est omis si non défini.
  • collapsed est omis si false.
  • les sections sans item sont ignorées.

Outil Planificateur D'équipements

Type : equipmentPlanner

{
  "types": [
    {
      "id": "type1",
      "title": "Armes",
      "icon": "sword",
      "collapsed": false
    }
  ],
  "equipments": [
    {
      "id": "equipment1",
      "typeId": "type1",
      "name": "Épée runique",
      "icon": "sword",
      "active": true,
      "obtain": "Forge après le boss",
      "characteristics": [
        {
          "id": "trait1",
          "category": "Stats",
          "icon": "sword",
          "name": "Force",
          "value": 12
        }
      ],
      "socketItems": [
        {
          "id": "socket1",
          "name": "Joyau attaque",
          "shape": "jewel",
          "color": "yellow",
          "bonuses": [
            {
              "id": "bonus1",
              "category": "Compétences",
              "name": "Attaque",
              "value": 1
            }
          ]
        }
      ],
      "socketLinks": [
        {
          "id": "link1",
          "fromSocketItemId": "socket1",
          "toSocketItemId": "socket2"
        }
      ],
      "materials": [
        {
          "id": "material1",
          "name": "Minerai rare",
          "qty": 4
        }
      ],
      "categoryOrder": ["Stats", "Compétences"],
      "collapsedCategories": []
    }
  ],
  "typeOrder": ["type1"]
}

Stockage compact :

  • les types sans équipement sont ignorés ;
  • icon des types vaut boot, chest-armor, helmet, glove, shield, sword, gun, earring, necklace ou ring ;
  • icon des équipements utilise la même liste et pilote l'icône affichée sur la ligne de l'équipement ;
  • active est omis quand la valeur vaut true ;
  • obtain, characteristics, socketItems, socketLinks, materials, categoryOrder et collapsedCategories sont omis si vides ;
  • collapsed est omis si false ;
  • qty des matériaux est normalisé à minimum 1 ;
  • les caractéristiques et bonus sans nom sont ignorés ;
  • icon des caractéristiques directes vaut sword ou shield ;
  • shape vaut ball ou jewel, avec jewel par défaut ;
  • color vaut red, orange, amber, yellow, lime, green, teal, cyan, blue, indigo, violet, purple, pink, rose, white, gray ou black ;
  • les liens de sertissage invalides, dupliqués ou hors équipement sont ignorés ;
  • seuls les équipements actifs contribuent au résumé.

Outil Images

Type : images

{
  "images": [
    {
      "id": "image1",
      "label": "Map zone nord",
      "dataUrl": "data:image/webp;base64,..."
    }
  ]
}

Stockage compact :

  • label est facultatif.
  • label est omis si vide.
  • createdAt n'est pas stocké.
  • Le nom de fichier original n'est pas stocké.

Outil Annotation d'images

Type : imageAnnotation

{
  "image": "data:image/webp;base64,...",
  "markers": [
    {
      "id": "marker1",
      "x": 42.5,
      "y": 68,
      "label": "Entrée"
    }
  ],
  "drawings": {
    "strokes": [
      {
        "id": "stroke1",
        "color": "#f6c453",
        "width": 4,
        "points": [
          { "x": 18.5, "y": 32 },
          { "x": 24, "y": 40.5 }
        ]
      }
    ]
  }
}

Stockage compact :

  • un seul visuel par outil.
  • x et y sont des pourcentages entre 0 et 100.
  • label est facultatif.
  • label est omis si vide.
  • drawings est facultatif et omis si aucun trait valide n'existe.
  • les points des dessins sont stockés en pourcentages entre 0 et 100 pour rester alignés en aperçu réduit et en plein écran.
  • les dessins sont éditables uniquement depuis la modale plein écran ; l'aperçu réduit les affiche en lecture seule.
  • le mode temporaire des dessins d'image utilise sessionStorage et ne modifie pas drawings dans IndexedDB tant que l'utilisateur ne repasse pas en stockage permanent.

Outil Timer

Type : timer

Structure initiale :

{
  "activeTab": "stopwatch",
  "scrollResults": true,
  "sortResults": true,
  "stopwatch": {
    "elapsedMs": 12000,
    "startedAt": 1760000000000,
    "laps": [
      {
        "id": "timer1",
        "label": "Phase 1",
        "elapsedMs": 9000
      }
    ]
  },
  "countdowns": [
    {
      "id": "timer2",
      "label": "Boss",
      "type": "daily_time",
      "alertMode": "visible",
      "autoRefresh": true,
      "time": "12:03:00",
      "targetAt": 1760000000000
    },
    {
      "id": "timer3",
      "label": "Event",
      "type": "time_pattern",
      "pattern": "X:45:00"
    },
    {
      "id": "timer4",
      "label": "Event",
      "type": "interval",
      "autoRefresh": true,
      "intervalMs": 1800000,
      "startMode": "time",
      "startTime": "06:00:00",
      "anchorAt": 1760000000000,
      "targetAt": 1760001800000
    }
  ]
}

Types de compte à rebours :

  • duration : durée ponctuelle, avec durationMs et targetAt.
  • daily_time : occurrence à une heure précise, avec time au format HH:MM:SS, targetAt et autoRefresh optionnel.
  • time_pattern : prochaine occurrence automatique correspondant à un pattern HH:MM:SS, où chaque segment peut valoir X.
  • interval : intervalle basé sur intervalMs, anchorAt, targetAt, startMode, startTime et autoRefresh optionnel. startMode vaut now ou time; startTime est présent uniquement avec time. autoRefresh n'est conservé que si l'intervalle dure au moins 5 minutes ; les anciens intervalles sans targetAt restent interprétés comme répétables.

Pour time_pattern, un segment X situé après un segment plus large fixé cible le premier instant de l'occurrence. Exemple : X:24:X cible hh:24:00 et se répète toutes les heures.

Stockage compact :

  • activeTab est omis si la valeur vaut stopwatch.
  • scrollResults est omis si false.
  • sortResults est omis si false.
  • stopwatch est omis tant que le chrono est à zéro et qu'aucune étape n'est enregistrée.
  • elapsedMs est omis si 0.
  • startedAt est omis si le chrono n'est pas lancé.
  • laps est omis tant qu'aucune étape n'est enregistrée.
  • countdowns est omis tant qu'aucun compte à rebours n'est configuré.
  • countdowns[].alertMode vaut off, visible ou site, et est omis si off.
  • countdowns[].autoRefresh est omis si false.
  • countdowns[].startMode et countdowns[].startTime sont omis pour les intervalles qui démarrent à la création.
  • L'activation d'une alerte est bloquée dans l'interface si la fréquence configurée est inférieure à 5 minutes pour les comptes à rebours récurrents (time_pattern et interval). Le blocage ne dépend pas du temps restant avant la prochaine échéance.

Outil Task Planner

Type : taskPlanner

{
  "weeklyResetDay": 1,
  "resetTime": "00:00",
  "lastResetAt": 1760000000000,
  "hideCompleted": true,
  "categoryOrder": ["Raid", "Farm"],
  "collapsedCategories": ["Farm"],
  "tasks": [
    {
      "id": "task1",
      "title": "Quête 1",
      "description": "Préparer la route.",
      "category": "Raid",
      "type": "daily",
      "dailyResetTime": "06:30",
      "checked": true,
      "checkedAt": 1760000000000
    },
    {
      "id": "task2",
      "title": "Quête 2",
      "type": "weekly",
      "weeklyResetDay": 3
    }
  ],
  "relations": [
    {
      "id": "relation1",
      "fromTaskId": "task1",
      "toTaskId": "task2"
    },
    {
      "id": "relation2",
      "fromTaskId": "task2",
      "toTaskId": "task1",
      "prerequisite": true
    }
  ]
}

Notes :

  • type vaut unique, daily ou weekly.
  • weeklyResetDay suit les jours JavaScript : 0 dimanche, 1 lundi, ..., 6 samedi.
  • resetTime est global à l'outil et vaut HH:MM.
  • hideCompleted masque les tâches effectuées à l'affichage, ainsi que les catégories dont toutes les tâches sont effectuées.
  • dailyResetTime sur une tâche quotidienne surcharge l'heure globale.
  • weeklyResetDay sur une tâche hebdo surcharge le jour global.
  • category sur une tâche racine est facultatif. Les tâches enfants héritent de la catégorie effective de leur parent et ne stockent pas de catégorie propre.
  • Les tâches catégorisées sont regroupées par catégorie au niveau racine.
  • categoryOrder stocke l'ordre d'affichage des catégories utilisées. Les catégories absentes des tâches sont retirées à la normalisation.
  • collapsedCategories stocke les noms des catégories repliées.
  • relations stocke les parents directionnels. fromTaskId désigne la tâche enfant, toTaskId son parent.
  • une tâche ne conserve qu'un seul parent.
  • prerequisite: true marque aussi ce parent comme pré requis.
  • Les pré requis sont non bloquants : ils affichent un avertissement si le parent requis n'est pas effectué.
  • Le drag & drop conserve l'ordre manuel, et peut aussi ajouter une tâche racine à une catégorie existante ou la sortir vers les tâches sans catégorie.

Stockage compact :

  • weeklyResetDay global est omis si la valeur vaut 1.
  • resetTime est omis si la valeur vaut 00:00.
  • lastResetAt est omis si la valeur vaut 0.
  • hideCompleted est omis si false.
  • description est omis si vide.
  • category est omis si vide ou si la tâche a un parent.
  • categoryOrder est omis si aucune catégorie n'est utilisée.
  • collapsedCategories est omis si aucune catégorie n'est repliée.
  • checked et checkedAt sont omis si la tâche n'est pas effectuée.
  • dailyResetTime unitaire est omis si non défini ou identique à l'heure globale.
  • weeklyResetDay unitaire est omis si non défini.
  • relations est omis si vide.
  • relations[].prerequisite est omis si false.

Outil Liens

Type : links

{
  "links": [
    {
      "id": "link1",
      "title": "Build rogue",
      "url": "https://example.com/build"
    }
  ]
}

Stockage compact :

  • title est facultatif.
  • title est omis si vide.
  • url est obligatoire.

Outil Compteurs

Type : counters

{
  "counters": [
    {
      "id": "counter1",
      "label": "Victoire",
      "value": 12
    },
    {
      "id": "counter2",
      "label": "Défaite",
      "value": -2
    }
  ]
}

Notes :

  • value est numérique.
  • Les valeurs négatives sont acceptées.
  • Les compteurs sans libellé ne sont pas conservés.

Outil Combos

Type : combos

{
  "device": "playstation",
  "categoryOrder": ["Neutral"],
  "collapsedCategories": ["Advanced"],
  "combos": [
    {
      "id": "combo0",
      "name": "Anti air",
      "device": "playstation",
      "inputs": [
        [
          { "kind": "direction", "value": "down", "holdMs": 2000 },
          { "kind": "button", "value": "triangle", "hold": true }
        ]
      ]
    },
    {
      "id": "combo1",
      "name": "Hadoken",
      "category": "Neutral",
      "device": "playstation",
      "inputs": [
        [{ "kind": "direction", "value": "down" }],
        [{ "kind": "direction", "value": "down-forward" }],
        [{ "kind": "button", "value": "cross" }]
      ]
    }
  ]
}

Notes :

  • device vaut playstation, xbox, switch, n64 ou keyboardMouse.
  • combos[].device peut figer le périphérique de rendu d'un combo, afin qu'il conserve ses couleurs si l'outil change de périphérique.
  • combos[].category est facultatif ; les catégories sont déduites automatiquement de cette valeur.
  • inputs est une liste d'étapes successives.
  • chaque étape contient une ou plusieurs entrées simultanées.
  • kind vaut button, direction, key ou mouse.
  • hold est facultatif et indique qu'une entrée doit être maintenue sans durée précise.
  • holdMs est facultatif et indique qu'une entrée doit être maintenue pendant cette durée en millisecondes ; l'interface limite la saisie à des secondes entières de 1 à 99.
  • categoryOrder stocke l'ordre d'affichage des catégories utilisées.
  • collapsedCategories stocke les noms des catégories repliées.
  • les catégories restent locales à l'outil ; l'utilisateur peut ajouter plusieurs outils Combos pour séparer des usages.

Stockage compact :

  • device est omis si la valeur vaut playstation.
  • category est omise si vide.
  • hold est omis si absent ou faux.
  • holdMs est omis si absent ou nul.
  • categoryOrder est omis si aucune catégorie n'est utilisée.
  • collapsedCategories est omis si aucune catégorie n'est repliée.
  • combos est omis si aucun combo valide n'existe.
  • les combos sans nom et sans touche sont supprimés.
  • les catégories et noms de combo sont limités à 80 caractères.
  • chaque combo conserve au maximum 64 étapes.

Outil Calculateur

Type : calculator

{
  "scrollResults": true,
  "entries": [
    {
      "id": "calc1",
      "label": "Lingots de fer",
      "value": 100
    },
    {
      "id": "calc2",
      "parentId": "calc1",
      "label": "Minerais de fer",
      "value": 500
    }
  ]
}

Stockage compact :

  • scrollResults est omis si false.
  • parentId est omis si vide.
  • label peut être vide.
  • value est numérique.
  • Si un parentId ne pointe vers aucune entrée existante, il est réinitialisé à vide.

Outil Tableau

Type : table

{
  "rows": 12,
  "columns": 8,
  "cells": {
    "A1": "Texte libre",
    "B1": "100",
    "C1": "=A1+B1",
    "D1": "=(B1+20)/2"
  },
  "rowLabels": {
    "0": "Boss",
    "1": "Phase 2"
  },
  "columnLabels": {
    "0": "Item",
    "1": "Quantité"
  }
}

Stockage compact :

  • rows est omis si la valeur vaut 10.
  • columns est omis si la valeur vaut 6.
  • cells conserve uniquement les cellules non vides et dans les limites du tableau.
  • les espaces saisis dans une cellule non vide sont conservés.
  • rows est limité entre 1 et 50.
  • columns est limité entre 1 et 20.
  • Les valeurs de cellules sont stockées sous forme de texte brut, formules incluses.
  • Les résultats calculés ne sont pas persistés.
  • rowLabels et columnLabels conservent uniquement les intitulés visuels personnalisés.
  • Les intitulés personnalisés n'impactent pas les références de formule, qui restent basées sur les adresses A1, C2, etc.

Export

L'export d'une toolbox remappe les identifiants pour produire des IDs courts et indépendants.

Exemple :

{
  "toolbox": {
    "id": "t1",
    "name": "MHW",
    "modules": [
      {
        "id": "m1",
        "type": "checklist",
        "title": "Armures"
      }
    ],
    "updatedAt": "2026-07-25T12:00:00.000Z"
  },
  "modules": {
    "m1": {
      "items": [
        {
          "id": "i1",
          "label": "Casque"
        }
      ]
    }
  }
}

A maintenir à chaque update

Quand un outil change de structure :

  • mettre à jour ce document ;
  • mettre à jour les normalisations dans website/src/features/toolboxes/storage/toolboxStorage.js ;
  • mettre à jour les textes dans website/public/data/site.json si nécessaire ;
  • mettre à jour la validation dans tests/helpers/data-validation.mjs si nécessaire ;
  • vérifier import/export ;
  • lancer npm run check.