1201 lines
43 KiB
Markdown
1201 lines
43 KiB
Markdown
# 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` :
|
|
|
|
```json
|
|
{
|
|
"toolboxes": [],
|
|
"links": {},
|
|
"setting:toolboxOrder": [],
|
|
"setting:storageBudgetBytes": 536870912,
|
|
"setting:toastPosition": "right",
|
|
"setting:autoBackup": {
|
|
"enabled": false,
|
|
"directoryHandle": null,
|
|
"lastBackup": "",
|
|
"lastBackupSignature": "",
|
|
"hasUnsavedChanges": false
|
|
}
|
|
}
|
|
```
|
|
|
|
`setting:autoBackup` conserve le `FileSystemDirectoryHandle` choisi par l'utilisateur,
|
|
le dernier timestamp de réussite, l'empreinte du dernier contenu sauvegardé et un
|
|
indicateur de modifications non sauvegardées. Ces valeurs ne sont jamais incluses
|
|
dans les exports.
|
|
Les sauvegardes utilisent le format de l'export global, sont nommées avec la date
|
|
locale du navigateur et conservent les dix fichiers `sokkog-global-YYYY-MM-DD.json`
|
|
les plus récents. La vérification au démarrage ne demande pas de permission et ne
|
|
crée pas les jours manquants lorsque l'application était fermée. Une nouvelle
|
|
sauvegarde automatique est ignorée si son contenu est identique à la version la
|
|
plus récente, en excluant uniquement l'horodatage d'export. Si le navigateur ne
|
|
conserve pas l'autorisation du dossier entre deux sessions, l'indicateur passe à
|
|
`true` après chaque écriture toolbox confirmée et évite de générer un export au
|
|
démarrage. Une alerte persistante propose alors la sauvegarde. Son bouton fournit
|
|
l'interaction nécessaire à la demande système ; une fois l'accès accordé,
|
|
l'application vérifie précisément le contenu réel du dossier avant toute écriture.
|
|
L'indicateur revient à `false` uniquement lorsque le contenu courant est sauvegardé
|
|
ou confirmé identique. Si l'autorisation est toujours accordée, cette vérification
|
|
reste silencieuse et démarre automatiquement.
|
|
|
|
Entrée type dans `modules` :
|
|
|
|
```json
|
|
{
|
|
"key": "tbx1:mod1",
|
|
"data": {}
|
|
}
|
|
```
|
|
|
|
## Toolbox
|
|
|
|
### Ordre et confirmation des écritures
|
|
|
|
Le démarrage attend la lecture du registre, des liens, des modules hors médias et
|
|
de la position des notifications avant d'ouvrir l'interface. Un échec laisse
|
|
le stockage non prêt : les méthodes d'écriture refusent les opérations, et la
|
|
création, l'import et l'export ne peuvent pas utiliser un état vide incomplet.
|
|
Le bouton de reprise relance uniquement les lectures, sans supprimer de données.
|
|
Une ouverture IndexedDB échouée n'est pas conservée dans le cache de connexion.
|
|
Les erreurs de lecture sont distinctes des erreurs d'écriture et ne sont pas
|
|
présentées systématiquement comme un dépassement de quota.
|
|
|
|
Dans une instance de l'application, le registre, les liens, les modules et la
|
|
position des notifications partagent une file d'écritures. L'interface présente
|
|
les modifications en attente ; en cas d'échec, elle est reconstruite depuis le
|
|
dernier état confirmé, puis les opérations restantes sont réappliquées.
|
|
|
|
La création, l'association, l'import et la suppression d'une toolbox utilisent
|
|
une transaction commune aux stores `kv` et `modules`. Les imports ajoutent leur
|
|
contenu à l'état courant au moment de l'exécution et annoncent leur succès après
|
|
la fin de transaction. Une erreur synchrone pendant sa préparation déclenche
|
|
l'abandon de la transaction. Les écritures ordinaires d'un module restent ciblées
|
|
sur sa clé et ne réécrivent pas les images des autres outils.
|
|
|
|
Les données des outils `images` et `imageAnnotation` sont lues à leur première
|
|
ouverture, en page complète ou dans le panneau latéral. Les minuteurs et calendriers
|
|
restent chargés au démarrage pour assurer les alertes globales. Les couvertures,
|
|
incluses dans le registre des toolboxes, restent également chargées au démarrage.
|
|
Un marqueur uniquement en mémoire distingue un média non lu d'un outil vide ; il
|
|
n'est jamais persisté ni exporté. Les transactions de snapshot conservent les
|
|
entrées non lues, sauf celles explicitement supprimées de l'état courant.
|
|
Une lecture tardive ne réintroduit pas un outil supprimé entre-temps.
|
|
|
|
Pendant la lecture, le contenu de l'outil est bloqué. Un échec affiche une reprise
|
|
locale sans empêcher les autres outils de fonctionner ni autoriser l'écrasement
|
|
du média non lu. Après ouverture, les médias restent en mémoire jusqu'au
|
|
rechargement de l'application ; il n'y a pas d'éviction à la fermeture.
|
|
|
|
Les exports attendent les écritures précédentes dans la même file et lisent les
|
|
médias manquants depuis l'état confirmé, sans les ajouter au cache de l'interface.
|
|
Si cette lecture échoue, aucun fichier partiel n'est téléchargé. Les formats
|
|
IndexedDB et import/export sont inchangés.
|
|
|
|
Cette coordination est locale à l'instance React ; elle ne synchronise pas
|
|
l'édition entre plusieurs onglets.
|
|
|
|
Les estimations de quota demandées après sauvegarde sont regroupées sur une
|
|
fenêtre de 250 ms, avec une seule estimation active. Une écriture intervenant
|
|
pendant le calcul déclenche une nouvelle estimation. Les sauvegardes ne sont
|
|
pas retardées ; en cas d'estimation indisponible, le dernier quota connu est
|
|
conservé.
|
|
|
|
Les toolboxes sont stockées dans `kv/toolboxes`.
|
|
|
|
```json
|
|
{
|
|
"id": "tbx1",
|
|
"name": "Monster Hunter Wilds",
|
|
"gameId": "mhwilds",
|
|
"icon": "/static/img/toolbox-icons/toolbox.png",
|
|
"customCover": "data:image/webp;base64,...",
|
|
"modules": [
|
|
{
|
|
"id": "mod1",
|
|
"type": "checklist",
|
|
"title": "Armures",
|
|
"collapsed": true
|
|
}
|
|
],
|
|
"moduleColumns": 2,
|
|
"moduleOrder": {
|
|
"one": ["mod1"],
|
|
"two": [["mod1"], []]
|
|
},
|
|
"tutorial": true,
|
|
"updatedAt": "2026-07-25T12:00:00.000Z"
|
|
}
|
|
```
|
|
|
|
Notes :
|
|
|
|
- `gameId` est vide pour une toolbox libre.
|
|
- `icon` est utilisé uniquement pour les toolboxes libres.
|
|
- `customCover` est une image rectangulaire optionnelle utilisée comme cover de card pour les toolboxes libres. Elle est stockée en data URL image après recadrage, sans conserver le fichier original.
|
|
- `moduleColumns` vaut `1` ou `2` et contrôle l'affichage de la page toolbox complète ; le panneau latéral utilise toujours l'ordre une colonne.
|
|
- `moduleOrder.one` stocke l'ordre du mode une colonne.
|
|
- `moduleOrder.two` stocke l'ordre explicite des colonnes gauche et droite en mode deux colonnes.
|
|
- Chaque identifiant d'outil présent dans `modules` doit apparaître exactement une fois dans `moduleOrder.one` et exactement une fois dans l'une des deux colonnes de `moduleOrder.two`.
|
|
- Si une ancienne toolbox n'a pas encore `moduleOrder.two`, les outils sont répartis automatiquement depuis l'ordre une colonne avec une moitié en colonne gauche et une moitié en colonne droite.
|
|
- Les données lourdes des outils ne sont pas stockées dans la toolbox, mais dans `modules`.
|
|
- `modules[].collapsed` vaut `true` lorsque seul l'en-tête de l'outil est affiché ; il est omis lorsque l'outil est déplié.
|
|
- `tutorial` est un indicateur temporaire réservé aux toolboxes créées depuis la prise en main. Il déclenche les premiers repères à l'ouverture, puis il est retiré automatiquement du stockage.
|
|
|
|
## Liens Jeu / Toolbox
|
|
|
|
Les associations entre pages jeux et toolboxes sont stockées dans `kv/links`.
|
|
|
|
```json
|
|
{
|
|
"mhwilds": "tbx1",
|
|
"diablo4": "tbx2"
|
|
}
|
|
```
|
|
|
|
## Outil Bloc Note
|
|
|
|
Type : `notepad`
|
|
|
|
```json
|
|
{
|
|
"activePageId": "page1",
|
|
"pages": [
|
|
{
|
|
"id": "page1",
|
|
"title": "Page 1",
|
|
"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 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-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 Mémos
|
|
|
|
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
|
|
|
|
### Association persistante aux guides
|
|
|
|
Les pages Aniimo, monstres MHW, faune MHW et affixes Diablo peuvent refléter une
|
|
checklist de la toolbox actuellement associée au jeu. Chaque checklist compatible
|
|
affiche un toggle de lien à côté du « + » de son en-tête dans le panneau latéral.
|
|
Il ouvre une section masquée par défaut en haut du contenu :
|
|
bouton toggle d'association/dissociation et switch de masquage, actif seulement pour
|
|
la liste associée. Associer une autre liste remplace la précédente et conserve le
|
|
réglage de masquage ; dissocier réaffiche tous les éléments. Replier les commandes
|
|
ne modifie pas l'association ; cet état d'affichage reste local au composant.
|
|
Cette liaison ne modifie ni les données de checklist ni les liens persistants jeu/toolbox.
|
|
|
|
`localStorage["sokkog:guide-checklists"]` contient un objet indexé par
|
|
`aniimo/aniimos`, `mhwilds/monsters`, `mhwilds/endemic` ou `diablo4/affixes`.
|
|
Chaque entrée contient `{ toolboxId, moduleId, hideCompleted }`. Aucune liaison
|
|
n'est choisie automatiquement. La navigation, le rechargement et une nouvelle
|
|
session du navigateur la conservent ;
|
|
la dissociation, la suppression du module ou le changement de toolbox la retirent.
|
|
Sans accès au stockage local, l'état reste en mémoire dans l'application.
|
|
Ces associations ne sont pas exportées et ne se synchronisent pas entre onglets.
|
|
|
|
La reconnaissance compare les libellés complets sans tenir compte de la casse,
|
|
des accents ni des espaces répétés. Une checklist partielle suffit, avec au moins
|
|
une correspondance dans toutes les données du guide, indépendamment des filtres.
|
|
Les lignes étrangères sont ignorées. Une correspondance est terminée lorsque
|
|
toutes ses lignes atteignent leur propre `qtyTarget` ; les doublons ne sont pas
|
|
additionnés. Renommer une ligne peut supprimer sa correspondance. Une liste encore
|
|
sélectionnée mais sans correspondance reste liée, avec un message explicite.
|
|
Une checkbox sur la carte d'un guide peut modifier la progression de toutes les
|
|
lignes exactes qui lui correspondent : cocher porte `qtyCurrent` à `qtyTarget`,
|
|
décocher le remet à `0`. Les données passent par le flux React/IndexedDB existant ;
|
|
aucune copie de progression n'est conservée dans l'association du guide.
|
|
|
|
La copie Aniimo utilise `Nom:1` en affichage regroupé et `Nom — Forme:1` en
|
|
affichage séparé. Les noms et formes français et anglais sont reconnus. Une ligne
|
|
sans forme couvre toutes les variantes ; une ligne spécifique à une forme prend
|
|
priorité sur la ligne générale. Le masquage des cochés précède le regroupement ;
|
|
le style d'une carte groupée dépend de la variante active. La copie et les
|
|
compteurs portent sur les résultats effectivement affichés.
|
|
|
|
### Données persistantes
|
|
|
|
Type : `checklist`
|
|
|
|
Format simple, sans catégorie :
|
|
|
|
```json
|
|
{
|
|
"items": [
|
|
{
|
|
"id": "item1",
|
|
"label": "Potion",
|
|
"qtyTarget": 10,
|
|
"qtyCurrent": 3
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
Format avec catégories :
|
|
|
|
```json
|
|
{
|
|
"hideCompletedItems": true,
|
|
"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`.
|
|
- `hideCompletedItems` est omis si `false`.
|
|
- `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`
|
|
|
|
```json
|
|
{
|
|
"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"]
|
|
}
|
|
```
|
|
|
|
## Outil Arbre D'objectifs
|
|
|
|
Type : `goalTree`
|
|
|
|
```json
|
|
{
|
|
"lastResetAt": 1786226400000,
|
|
"orientation": "horizontal",
|
|
"nodes": [
|
|
{
|
|
"id": "goal1",
|
|
"parentId": "",
|
|
"title": "Breuvage de rage",
|
|
"kind": "step",
|
|
"icon": "cube",
|
|
"color": "gold",
|
|
"qtyTarget": 10,
|
|
"qtyCurrent": 0,
|
|
"formula": "",
|
|
"progressMode": "auto",
|
|
"resetType": "none",
|
|
"resetTime": "00:00",
|
|
"weeklyResetDay": 1,
|
|
"checkedAt": 0,
|
|
"collapsed": false
|
|
},
|
|
{
|
|
"id": "goal2",
|
|
"parentId": "goal1",
|
|
"parentIds": ["goal1"],
|
|
"title": "Élixir de destruction",
|
|
"kind": "step",
|
|
"icon": "flask",
|
|
"color": "cyan",
|
|
"qtyTarget": 1,
|
|
"qtyCurrent": 0,
|
|
"formula": "base * 3",
|
|
"resetType": "weekly",
|
|
"resetTime": "05:00",
|
|
"weeklyResetDay": 1,
|
|
"checkedAt": 0,
|
|
"collapsed": false
|
|
},
|
|
{
|
|
"id": "goal3",
|
|
"parentId": "goal1",
|
|
"title": "Liste de farm",
|
|
"kind": "checklist",
|
|
"items": [
|
|
{
|
|
"id": "item1",
|
|
"label": "Élément",
|
|
"formula": "1",
|
|
"qtyTarget": 1,
|
|
"qtyCurrent": 1
|
|
},
|
|
{
|
|
"id": "item2",
|
|
"label": "Faire cela",
|
|
"formula": "base",
|
|
"qtyTarget": 5,
|
|
"qtyCurrent": 2
|
|
}
|
|
],
|
|
"resetType": "none",
|
|
"resetTime": "00:00",
|
|
"weeklyResetDay": 1,
|
|
"checkedAt": 0,
|
|
"collapsed": false
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
Stockage compact :
|
|
|
|
- `lastResetAt` est omis si vide.
|
|
- `orientation` est omis si la valeur vaut `horizontal`.
|
|
- `orientation` vaut `horizontal` ou `vertical`.
|
|
- `parentId` est omis pour les racines.
|
|
- `parentId` reste le parent principal de placement dans le schéma.
|
|
- `parentIds` contient les parents/prérequis pris en compte pour les liaisons de convergence ; il est omis si aucun prérequis supplémentaire n'existe.
|
|
- les calculs `base`, le placement, la suppression de branche et l'agrégation de progression restent basés sur `parentId`.
|
|
- un objectif peut donc être placé sous un parent principal tout en ayant d'autres prérequis, afin de représenter une convergence de branches.
|
|
- `kind` est omis si la valeur vaut `step`.
|
|
- `kind` vaut `step`, `text` ou `checklist`.
|
|
- `icon` est omis si la valeur vaut `cube`.
|
|
- `icon` vaut `cube`, `fire`, `flask`, `grass`, `leaf`, `meat`, `powder`, `tear` ou `ball`.
|
|
- `color` est omis si la valeur vaut `gold`.
|
|
- `color` vaut `gold`, `cyan`, `violet`, `pink`, `green`, `gray`, `red`, `orange`, `yellow`, `blue` ou `indigo`.
|
|
- `qtyTarget` est omis si la valeur vaut `1`.
|
|
- `qtyCurrent` est omis si la valeur vaut `0`.
|
|
- `formula` est omis si vide.
|
|
- `progressMode` est utilisé uniquement par les objectifs `step`; il vaut `auto` ou `manual`.
|
|
- `progressMode` est omis tant qu'aucun choix explicite n'a été fait; par défaut, une étape avec enfants calcule sa progression automatiquement, sauf si tous ses enfants directs ont une cible de `1`, auquel cas elle se comporte en mise à jour manuelle.
|
|
- en `auto`, une étape avec enfants calcule sa progression depuis ses enfants; en `manual`, elle utilise sa propre `qtyCurrent`.
|
|
- `items` est omis si vide ou si `kind` vaut `step`.
|
|
- `items[].label` contient la ligne affichee dans un noeud `text` ou `checklist`; il peut etre vide pendant l'edition.
|
|
- `items[].formula` est utilise uniquement par `checklist` pour calculer le total de la ligne ; la variable `base` reference la cible calculee du parent direct.
|
|
- `items[].qtyTarget` est utilise uniquement par `checklist` et omis si la valeur vaut `1`.
|
|
- `items[].qtyCurrent` est utilise uniquement par `checklist` et omis si la valeur vaut `0`.
|
|
- `resetType` est omis si la valeur vaut `none`.
|
|
- `resetType` vaut `none`, `daily` ou `weekly`.
|
|
- `resetTime` est omis si la valeur vaut `00:00`.
|
|
- `weeklyResetDay` est omis si la valeur vaut `1`.
|
|
- `checkedAt` est omis si vide.
|
|
- `collapsed` est omis si `false`.
|
|
- Les parents invalides ou cycliques sont ramenés à la racine pendant la normalisation.
|
|
|
|
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`
|
|
|
|
```json
|
|
{
|
|
"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`
|
|
|
|
```json
|
|
{
|
|
"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 :
|
|
|
|
```json
|
|
{
|
|
"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 Calendrier
|
|
|
|
Type : `calendar`
|
|
|
|
```json
|
|
{
|
|
"initialized": true,
|
|
"viewMode": "month",
|
|
"weekStartsOn": 1,
|
|
"events": [
|
|
{
|
|
"id": "calendar1",
|
|
"title": "Raid",
|
|
"startDate": "2026-08-14",
|
|
"endDate": "2026-08-16",
|
|
"repeat": "monthly",
|
|
"monthlyRepeatBy": "weekday",
|
|
"excludedOccurrences": ["2026-09-11"],
|
|
"alertMode": "site",
|
|
"alertOffsetMinutes": [10, 5, 0],
|
|
"color": "violet"
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
Stockage compact :
|
|
|
|
- `viewMode` vaut `week` ou `month` ; `week` est omis dans ce cas.
|
|
- `initialized` indique que le choix du type de calendrier a été validé dans l'outil ; il est conservé pour garder un calendrier vide utilisable.
|
|
- `weekStartsOn` vaut `1` par défaut et est omis dans ce cas.
|
|
- `events` est omis tant qu'aucun événement valide n'est configuré.
|
|
- les événements sans titre ou sans cible de jour/date valide sont supprimés ; `startMinutes` reste requis en hebdomadaire et vaut `0` par défaut en mensuel.
|
|
- `dayIndex` vaut `0` pour lundi, `1` pour mardi, jusqu'à `6` pour dimanche.
|
|
- en calendrier mensuel, `startDate` et `endDate` sont des dates locales `YYYY-MM-DD` ; `endDate` est omis si la plage dure un seul jour.
|
|
- en calendrier mensuel, `monthlyRepeatBy` vaut `monthDay` ou `weekday` ; `monthDay` est la valeur par défaut et est omise dans ce cas.
|
|
- en calendrier mensuel, `repeat` vaut `none`, `weekly` ou `monthly` ; `monthly` est la valeur par défaut et est omise dans ce cas.
|
|
- `repeat=weekly` est réservé aux entrées mensuelles par `weekday` ; si une entrée par `monthDay` demande `weekly`, elle est normalisée en `none`.
|
|
- une entrée mensuelle répétée par `monthDay` n'a pas d'occurrence dans un mois où le jour demandé n'existe pas.
|
|
- une entrée mensuelle répétée par `weekday` et `monthly` se répète sur le même rang de jour de semaine que `startDate` ; par exemple le premier lundi du mois.
|
|
- `excludedOccurrences` contient des dates locales `YYYY-MM-DD` correspondant aux occurrences supprimées individuellement.
|
|
- `startMinutes` et `endMinutes` sont des minutes depuis `00:00` dans la journée ; en hebdomadaire, l'interface saisit les heures au pas de 5 minutes tout en gardant une grille visuelle de 30 minutes ; en mensuel, les champs horaires ne sont pas exposés et `startMinutes` est omis s'il vaut `0`.
|
|
- `endMinutes` est conservé s'il produit une fin postérieure au début ; en mensuel, il peut être inférieur à `startMinutes` si la fin est sur un jour ultérieur.
|
|
- en hebdomadaire, `repeat` n'est pas stocké dans le format cible : il est déduit de `repeatable` (`weekly` par défaut, `none` si `repeatable=false`).
|
|
- en hebdomadaire, `repeatable` vaut `true` par défaut et est omis dans ce cas ; si `false`, l'entrée est supprimée quand son échéance de la semaine courante est terminée.
|
|
- `alertMode` vaut `off`, `visible` ou `site`, et est omis si `off`.
|
|
- `alertOffsetMinutes` est une liste composée de `10`, `5` ou `0` (`0` = au début et pendant l'événement pour les reprises de session) ; il est omis si l'alerte est désactivée ou si la liste vaut `[10]`.
|
|
- `color` vaut `violet`, `gold`, `cyan`, `pink`, `green` ou `gray`, et est omis si `violet`.
|
|
|
|
Import/export texte :
|
|
|
|
- le format texte commence par `@viewMode: week` ou `@viewMode: month`, puis une ligne `event | ...` par entrée ;
|
|
- en hebdomadaire, les champs `day`, `start` et `end` décrivent le jour de semaine et les heures locales (`HH:mm`) ;
|
|
- en mensuel, `repeatBy=monthDay` ou `repeatBy=weekday` utilise `startDate`/`endDate` et `repeat=none|weekly|monthly`, par exemple `repeatBy=weekday | repeat=monthly | startDate=2026-08-03 | endDate=2026-08-05` ;
|
|
- l'import texte remappe toujours les identifiants d'événements.
|
|
|
|
## Outil Task Planner
|
|
|
|
Type : `taskPlanner`
|
|
|
|
```json
|
|
{
|
|
"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`
|
|
|
|
```json
|
|
{
|
|
"categoryOrder": ["Builds", "Guides"],
|
|
"collapsedCategories": ["Guides"],
|
|
"links": [
|
|
{
|
|
"id": "link1",
|
|
"title": "Build rogue",
|
|
"category": "Builds",
|
|
"url": "https://example.com/build"
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
Stockage compact :
|
|
|
|
- `title` est facultatif.
|
|
- `title` est omis si vide.
|
|
- `category` est facultatif et sert à l'autocomplete de création.
|
|
- `category` est omis si vide.
|
|
- `url` est obligatoire.
|
|
- `categoryOrder` stocke l'ordre d'affichage des catégories, y compris les catégories vides.
|
|
- `collapsedCategories` stocke les noms des catégories repliées.
|
|
|
|
## Outil Compteurs
|
|
|
|
Type : `counters`
|
|
|
|
```json
|
|
{
|
|
"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 Randomizer
|
|
|
|
Type : `randomizer`
|
|
|
|
```json
|
|
{
|
|
"activeTab": "wheel",
|
|
"dice": {
|
|
"faces": 6,
|
|
"rolls": 3,
|
|
"results": [2, 5, 1],
|
|
"history": [
|
|
{
|
|
"id": "random1",
|
|
"at": "2026-08-30T12:00:00.000Z",
|
|
"results": [2, 5, 1]
|
|
}
|
|
]
|
|
},
|
|
"wheel": {
|
|
"draws": 2,
|
|
"entries": [
|
|
{
|
|
"id": "entry1",
|
|
"label": "Route farm",
|
|
"weight": 3
|
|
}
|
|
],
|
|
"results": ["Route farm"],
|
|
"history": []
|
|
},
|
|
"teams": {
|
|
"teamCount": 2,
|
|
"playerSelectionMode": "all",
|
|
"playerSelectionCount": 2,
|
|
"players": [
|
|
{
|
|
"id": "player1",
|
|
"name": "Alice",
|
|
"score": 10,
|
|
"active": true
|
|
}
|
|
],
|
|
"exclusions": [
|
|
{
|
|
"id": "exclusion1",
|
|
"playerAId": "player1",
|
|
"playerBIds": ["player2", "player3"],
|
|
"mode": "source"
|
|
}
|
|
],
|
|
"teams": [],
|
|
"history": []
|
|
}
|
|
}
|
|
```
|
|
|
|
Stockage compact :
|
|
|
|
- `activeTab` est omis quand l'onglet actif est `dice`.
|
|
- `dice`, `wheel` et `teams` sont omis quand ils ne contiennent que leurs valeurs par défaut et aucun résultat.
|
|
- l'historique de chaque onglet est limité aux 20 derniers tirages.
|
|
- les entrées de roue sans libellé sont ignorées, et `weight` est borné de 1 à 999.
|
|
- les joueurs sans nom sont ignorés, et `score` est borné de 0 à 9999.
|
|
- `active` vaut `true` par défaut sur un joueur.
|
|
- `playerSelectionMode` vaut `all` ou `count`; en mode `count`, `playerSelectionCount` indique combien de joueurs actifs sont choisis aléatoirement avant la répartition.
|
|
- chaque exclusion décrit un nom principal `playerAId`, une ou plusieurs cibles `playerBIds` et un `mode`.
|
|
- `mode: "source"` interdit uniquement `playerAId` avec chaque cible; les cibles peuvent rester ensemble.
|
|
- `mode: "group"` interdit chaque nom de la combinaison avec tous les autres noms de cette combinaison.
|
|
- les exclusions invalides, dupliquées ou qui référencent un joueur absent sont ignorées.
|
|
|
|
## Outil Simulateur de chance
|
|
|
|
Type : `rngSimulator`
|
|
|
|
```json
|
|
{
|
|
"activeRateId": "rate1",
|
|
"rates": [
|
|
{
|
|
"id": "rate1",
|
|
"label": "Drop rare",
|
|
"rate": "2.500",
|
|
"costPerDraw": "150",
|
|
"multiplier": 50,
|
|
"occurrenceValue": 30,
|
|
"occurrenceUnit": "perHour",
|
|
"realDraws": 120
|
|
},
|
|
{
|
|
"id": "rate2",
|
|
"label": "Ultra rare",
|
|
"rate": "0.000004"
|
|
}
|
|
],
|
|
"settings": {
|
|
"simulationDraws": 1000
|
|
}
|
|
}
|
|
```
|
|
|
|
Stockage compact :
|
|
|
|
- `rates` contient les taux enregistrés du module; un taux sans libellé est ignoré.
|
|
- `rate` est une chaîne en pourcentage afin de conserver la précision saisie, par exemple `"2.500"` ou `"0.000004"`.
|
|
- un taux doit être strictement supérieur à `0` et inférieur ou égal à `100`; les valeurs supérieures sont bornées à `"100"`.
|
|
- `costPerDraw` est une chaîne décimale optionnelle, associée au taux, utilisée comme unité libre pour les calculs de coût.
|
|
- `activeRateId` référence un taux existant; sinon le premier taux est sélectionné.
|
|
- `rates[].multiplier` est un bonus ou malus en pourcentage associé au taux, vaut 0 par défaut et le taux effectif est borné entre 0% et 100%.
|
|
- `rates[].occurrenceValue` est numérique, positif ou nul, associé au taux, et vaut 0 par défaut.
|
|
- `rates[].occurrenceUnit` vaut `perHour` ou `perDay`, et vaut `perHour` par défaut.
|
|
- les anciennes données `occurrenceUnit: "perMonth"` sont converties en `perDay`, avec `occurrenceValue` divisé par 30.
|
|
- `rates[].realDraws` est le total persistant de tirages réellement effectués pour ce taux, borné de 0 à 1 000 000 000.
|
|
- `settings.simulationDraws` est borné de 1 à 1 000 000 et vaut 1000 par défaut.
|
|
- `settings` est omis si `simulationDraws` vaut sa valeur par défaut.
|
|
- `rates[].costPerDraw` est omis si vide ou nul.
|
|
- `rates[].multiplier` est omis si sa valeur vaut 0.
|
|
- `rates[].occurrenceValue` et `rates[].occurrenceUnit` sont omis si `occurrenceValue` vaut 0.
|
|
- `rates[].realDraws` est omis si sa valeur vaut 0.
|
|
- l'historique des simulations n'est pas persisté en IndexedDB; il est limité aux 20 dernières entrées en `sessionStorage`.
|
|
|
|
Import/export texte :
|
|
|
|
```text
|
|
@activeRateId: rate1
|
|
[rates]
|
|
- Drop rare | rate=2.500 | active=true | costPerDraw=150 | multiplier=50 | occurrenceValue=30 | occurrenceUnit=perHour | realDraws=120
|
|
|
|
[settings]
|
|
simulationDraws: 1000
|
|
```
|
|
|
|
## Outil Combos
|
|
|
|
Type : `combos`
|
|
|
|
```json
|
|
{
|
|
"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`
|
|
|
|
```json
|
|
{
|
|
"scrollResults": true,
|
|
"entries": [
|
|
{
|
|
"id": "calc1",
|
|
"label": "Lingots de fer",
|
|
"value": 100,
|
|
"formula": "100"
|
|
},
|
|
{
|
|
"id": "calc2",
|
|
"parentId": "calc1",
|
|
"label": "Minerais de fer",
|
|
"value": 500,
|
|
"formula": "base*5"
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
Stockage compact :
|
|
|
|
- `scrollResults` est omis si `false`.
|
|
- `parentId` est omis si vide.
|
|
- `label` peut être vide.
|
|
- `value` est numérique.
|
|
- `formula` est omis si vide ; dans un calcul enfant, le mot `base` référence la valeur recalculée du parent.
|
|
- Si un `parentId` ne pointe vers aucune entrée existante, il est réinitialisé à vide.
|
|
|
|
## Outil Tableau
|
|
|
|
Type : `table`
|
|
|
|
```json
|
|
{
|
|
"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 :
|
|
|
|
```json
|
|
{
|
|
"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"
|
|
}
|
|
]
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
## Import / Export texte des outils
|
|
|
|
Les outils compatibles proposent aussi un échange par copier-coller depuis le toggle import/export de leur header. Ce format est un format UI distinct du stockage IndexedDB et de l'export JSON global.
|
|
|
|
Règles communes :
|
|
|
|
- l'import valide tout le texte avant de sauvegarder ;
|
|
- une erreur de format ne modifie pas les données existantes ;
|
|
- les IDs internes sont recréés à l'import ;
|
|
- l'identité de contenu est évaluée après normalisation ;
|
|
- les états de progression ne sont pas repris : checklist à `qtyCurrent: 0`, tâches non effectuées et `checkedAt: 0`.
|
|
|
|
Formats :
|
|
|
|
- `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` : sections `# Catégorie`, lignes `Titre: https://...` ou URL seule; le suffixe optionnel `| category=Catégorie` reste accepté à l'import.
|
|
- `counters` : lignes `Libellé: valeur`, valeurs négatives acceptées.
|
|
- `calculator` : lignes `Libellé: valeur | formula=base*5`, indentation de deux espaces pour les enfants, meta `@scrollResults: true`. Le suffixe `formula=...` est optionnel et les anciens exports `Libellé: valeur` restent acceptés.
|
|
- `table` : TSV pur accepté ; l'export peut ajouter `@size`, `@columns`, `@rows` puis `@tsv` pour préserver dimensions et intitulés.
|
|
- `combos` : sections `# Catégorie`, lignes `Nom | device=... | kind:value > kind:value+kind:value`, avec suffixes `[hold]` ou `[2s]`.
|
|
- `taskPlanner` : sections `# Catégorie`, lignes indentées `- daily Titre`, `- weekly Titre` ou `- unique Titre`, metas `@description`, `@dailyResetTime`, `@weeklyResetDay`, `@prerequisite`.
|
|
- `equipmentPlanner` : types `# Type | icon=...`, équipements `## Nom | icon=... | active=true`, sous-sections `Stats`, `Sockets`, `Craft`.
|
|
- `images` : blocs `# Libellé` puis `data:image/...;base64,...`.
|
|
- `imageAnnotation` : image base64, lignes `@marker: x,y,label` et `@drawings: {...}` pour les dessins permanents.
|
|
- `timer` : metas globales, lignes `lap | ...` et `countdown | ...`; les échéances temporelles sont recalculées à l'import.
|
|
|
|
## 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`.
|