# Cycle de vie de l'application # Menu principal Lorsque l'on lance l'application pour la première fois, le premier composant qui s'affiche est `App.tsx`. En fonction de la valeur de `FAST_BOOT`, il peut soit afficher un menu principal (composant `MainMenu`) si `FAST_BOOT=false` ou soit afficher l'editeur directement (composant `Editor`). Lorsque le menu principal est affiché il y a 3 états : *Main*, *Load* ou *Loading*. Lorsqu'on est dans Main, nous avons les deux boutons principaux affiché : *Start from scratch* ou *Load* ## Start from scratch Lorsque l'on clique sur *Start from scratch*, l'action qui se fait est de charger une configuration depuis l'API (ou d'utiliser une configuration préchargée via un event custom). On passe à l'état *Loading*. Après chargement, `isLoaded` est `true` et le composant `Editor` est affiché. ## Load Quand on veut charger un json, on clique sur *Load* et l'état passe à *Load*. Ici, le composant `MainMenu` affichera un bouton pour charger un fichier. Charger le fichier change l'état de la configuration dans `App` et active `isLoaded` qui enfin affiche `Editor`. # Editor Lorsque `Editor` est affiché, il reçoit en props la configuration que l'on a chargé au menu principal. Il reçoit également un historique par défaut pour avoir au moins quelque chose d'affiché. Il reçoit `root` un element HTML où l'on insert SVGLayoutDesigner par `main.tsx`. `root` est utilisé pour lui donner des events qui seront utilisé pour communiquer avec le SmartComponent. Plusieurs sous-composant sont affichés ensuite. Vous pouvez en savoir plus sur [la structure de composants](./ComponentStructure.drawio) avec dragrams.net. # Save and load Pour enregistrer le travail fait, il existe plusieurs méthode de le faire. ## SmartComponent La première, en passant par le SmartComponent `svg-layout-designer.ts`, est de stringifier l'état de l'éditeur et de le sauvegarder quelque part. On utilise `GetEditorAsString` pour obtenir une version stringifié du projet que l'on peut ensuite sauvegarder dans un fichier, bdd ou autre. On peut également utiliser GetEditorState et le sauvegarder tel quel dans le JS mais on ne peut pas le stringifier à un object json ne peut pas avoir deux références (sauf si on a le code source de saveload.ts, utilisant un *replacer* éliminant les références circulaires, voir [JSON par interaction](#json-par-interaction). Pour charger l'état de l'éditeur, il faut en premier le parser avec `JSON.parse` (pas de soucis à ce niveau là); Enfin, on peut ensuite utiliser `LoadEditor` du SmartComponent pour charger l'état. `LoadEditor` est une macro de trois autres appels : - `ReviveEditorState` qui fait revivre tous les doublons de références - `SetEditor` qui charge la configuration de l'application - `SetHistory` qui charge l'historique de l'editeur La raison que l'on utilise `SetHistory` en plus de `SetEditor` est parce que, `SetEditor` ne fait que charger une configuration par défaut de `App.tsx` (exemple: lorsque l'on crée un conteneur, l'éditeur va lire cette configuration). Si l'application est déjà chargée, c'est-à-dire que *isLoaded* de `App` est `true`, alors l'application ne va pas relire `history` et `historyCurrentStep` et l'application n'aura pas *chargé*. C'est pourquoi on a besoin de `SetHistory` pour charger l'état courant dans `Editor.tsx` (à l'opposé de `App.tsx`). Note: Pour rappel, *App* n'est qu'un menu principal. Comme dans un menu principal de jeu vidéo, écraser une sauvegarde ne fait pas charger la sauvegarde. C'est en chargeant la sauvegarde en cours de jeu ou dans le menu principal, que la partie change. ## JSON par interaction On peut charger un fichier JSON manuellement. Pour cela il faut que `FAST_BOOT` de `default.ts` soit désactivé. Dans l'éditeur, on peut exporter une configuration par fichier JSON grâce au composant `Settings.tsx`. Cette sauvegarde, comme pour sauvegarder avec le SmartComponent, utilise `JSON.stringify`. Et donc utilise un *replacer* pour supprimer les dépendances circulaires. Ce *replacer* se trouve dans `worker.js` mais un fallback est également donné dans `saveload.ts` dans le cas où la fonctionnalité de Web Worker n'est pas supportée par le navigateur web. Pour corriger des bugs sur la sauvegarde, il faudra donc modifier ces deux fichiers. Décrivons rapidement ce qu'elle fait : ```typescript export function GetCircularReplacer(): (key: any, value: object | Map | null) => object | null | undefined { return (key: any, value: object | null) => { if (key === 'parent') { return; } if (key === 'containers') { return Array.from((value as Map).entries()); } if (key === 'symbols') { return Array.from((value as Map).entries()); } if (key === 'linkedContainers') { return Array.from(value as Set); } return value; }; } ``` Nous faisons les actions suivantes pour supprimer les types non supportés par le stringify : - nous transformons les *Map* `containers` et `symbols` en tableau de vecteur clés-valeurs - nous transformons les *Set* `linkedContainers` en tableau de clés. Nous supprimons toutes les références de `parent` qui sont déjà référencés dans `containers`. Enfin, nous retournons `value` si tout est bon. Normalement, le JSON retourné ressemble à l'objet qu'était EditorState mais sans les références de parent et avec des tableaux partout. Pour le charger, il faut revenir au menu principal et cliquer sur Load pour charger avec l'input. Charger le fichier JSON, utilisera ce qu'on appelle un *reviver* qui s'occupe de recréer les types non supportés par JSON et de remettre les références dupliquées. Ce reviver se trouve dans `saveload.ts` avec la fonction `Revive()`. `Revive` change la valeur de `historyCurrentStep` avec de faire revivre l'historique `ReviveHistory` itère sur chaque état de `history` pour les revivre. `ReviveState` fait revivre en premier les *Map* et les *Set*. Ensuite, il va itérer sur tous les conteneurs existants, pour remettre leur parent. Comme vous pouvez vous en douter cette algorithme coûte O(n) juste pour revivre les parents. avec *n* le nombre total de conteneurs sur toute la durée de vie de l'application. Surtout qu'il n'est plus très utile depuis que tous les conteneurs sont dans une hash *Map* (et que donc parent est accessible avec un coût O(1)). On pourrait juste utiliser `GetContainerById` au lieu de la référence. ## JSON par requête GET HTTP Pour charger un JSON via HTTP GET, il faut que le serveur web distant autorise notre domaine dans le méchanisme *cross-origin resource sharing (CORS)* et qu'il nous autorise à faire des requêtes *GET*. Après cela pour charger le JSON, il faut que l'url de SVGLayoutDesigner soit paramétrée avec l'url de la resource : ``` http://localhost:5173/?state=http://other-server.com/state.json ``` Après cela, l'éditeur chargera directement le fichier.