Merged PR 217: Update master
This commit is contained in:
commit
abcbf6dbfa
35 changed files with 341 additions and 80 deletions
20
docs/#Project/Home.md
Normal file
20
docs/#Project/Home.md
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
|
||||||
|
Bienvenue à la documentation du projet de SVGLayoutDesigner.
|
||||||
|
|
||||||
|
Ici se trouve les documents qui explique en détail l'implémentation
|
||||||
|
des fonctionnalités du projet.
|
||||||
|
|
||||||
|
Sélectionnez un lien ou un fichier pour lire la documentation
|
||||||
|
|
||||||
|
Liens :
|
||||||
|
|
||||||
|
- [Structure du projet](Pages/Project_Structure.md)
|
||||||
|
- [Structure des composants](Pages/ComponentStructure.drawio) (nécessite diagrams.net)
|
||||||
|
- [Dépendences du projet](Pages/Dependencies.md)
|
||||||
|
- [Structure de données des conteneurs](Pages/DataStructure.md)
|
||||||
|
- [Système de comportement](Pages/Behaviors.md)
|
||||||
|
- [Cycle de vie de l'application](Pages/Application.md)
|
||||||
|
- [Implémentation du menu contextuel](Pages/ContextMenu.md)
|
||||||
|
- [Web workers](Pages/WebWorkers.md)
|
||||||
|
- [Système de CI/CD](Pages/Behaviors.md)
|
||||||
|
- [Mise en place du SmartComponent sur Modeler](Pages/SmartComponent.md)
|
179
docs/#Project/Pages/Application.md
Normal file
179
docs/#Project/Pages/Application.md
Normal file
|
@ -0,0 +1,179 @@
|
||||||
|
# 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<string, any> | null) => object | null | undefined {
|
||||||
|
return (key: any, value: object | null) => {
|
||||||
|
if (key === 'parent') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (key === 'containers') {
|
||||||
|
return Array.from((value as Map<string, any>).entries());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (key === 'symbols') {
|
||||||
|
return Array.from((value as Map<string, any>).entries());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (key === 'linkedContainers') {
|
||||||
|
return Array.from(value as Set<string>);
|
||||||
|
}
|
||||||
|
|
||||||
|
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.
|
|
@ -1,3 +1,16 @@
|
||||||
|
> TL;DR
|
||||||
|
> Behaviors.ts définis les comportements qui sont utilisés.
|
||||||
|
|
||||||
|
> Actuellement sont activés par défaut :
|
||||||
|
> - Corps rigide (simple) : Est restraint dans le parent
|
||||||
|
> - Ancrage : Impose la priorité de position et de taille
|
||||||
|
> - Flex : se redimensionne automatiquement
|
||||||
|
|
||||||
|
> Désactivés:
|
||||||
|
> - Corps rigide (complet) : Est restraint par les parents et par ses voisins (fonctionne mais pas intuitif)
|
||||||
|
> - Poussé : quand un conteneur est ajouté au bout de la bande filante, pousse tous les conteneurs à sa gauche (fonctionne mais pas intuitif)
|
||||||
|
> - Swap : échange de place avec un autre conteneur lorsqu'ils sont superposés (buggué ne pas utiliser)
|
||||||
|
|
||||||
# Comportements des conteneurs
|
# Comportements des conteneurs
|
||||||
|
|
||||||
Ce document traite des comportements spéciaux et uniques qu'un conteneur peut avoir.
|
Ce document traite des comportements spéciaux et uniques qu'un conteneur peut avoir.
|
||||||
|
@ -21,8 +34,6 @@ Cependant, il a une règle commune pour tout comportement qui s'applique à ses
|
||||||
|
|
||||||
Il s'agit d'appliquer les comportements spéciaux de ses enfants (rigide ou ancré).
|
Il s'agit d'appliquer les comportements spéciaux de ses enfants (rigide ou ancré).
|
||||||
|
|
||||||
Traduit avec www.DeepL.com/Translator (version gratuite)
|
|
||||||
|
|
||||||
|
|
||||||
## Applications
|
## Applications
|
||||||
|
|
||||||
|
@ -37,11 +48,10 @@ An example would be trying to overlap an element in order to use it as a layer.
|
||||||
|
|
||||||
## Références de code et algorithmes
|
## Références de code et algorithmes
|
||||||
|
|
||||||
Dans le module `PropertiesOperations.ts` dans les fonctions suivantes :
|
Dans le module `ContainerOperations.ts` dans les fonctions suivantes :
|
||||||
- `OnPropertyChange()`
|
- `OnPropertyChange()`
|
||||||
- `OnPropertiesSubmit()`
|
|
||||||
|
|
||||||
et dans le module `ContainerOperation.ts` dans `AddContainer()`,
|
et dans le module `AddContainer.ts` dans `AddContainer()`,
|
||||||
|
|
||||||
il utilise la fonction `ApplyBehaviors` du module `Behaviors.ts` pour appliquer les comportements spéciaux de ses enfants.
|
il utilise la fonction `ApplyBehaviors` du module `Behaviors.ts` pour appliquer les comportements spéciaux de ses enfants.
|
||||||
|
|
||||||
|
@ -168,8 +178,6 @@ Nous avons initialement tout l'espace disponible : laissez `space` être cet esp
|
||||||
|
|
||||||
Pour simplifier l'algorithme lors de l'ajout d'un conteneur, comparons cela à manger une bûche de Noël.
|
Pour simplifier l'algorithme lors de l'ajout d'un conteneur, comparons cela à manger une bûche de Noël.
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
Comme pour le gâteau, il faut le couper et en prendre une part.
|
Comme pour le gâteau, il faut le couper et en prendre une part.
|
||||||
|
|
||||||
Il y a 5 façons possibles de le couper :
|
Il y a 5 façons possibles de le couper :
|
|
@ -5,7 +5,7 @@ This project uses Azure Pipelines to runs automatic tests.
|
||||||
Its `azure-pipelines.yml` configuration file can be found at the root project folder.
|
Its `azure-pipelines.yml` configuration file can be found at the root project folder.
|
||||||
|
|
||||||
|
|
||||||
# Drone.io
|
# Drone.io (deprecated)
|
||||||
|
|
||||||
Due to the limitations of Azure Pipelines (limited free usage, no parallel, no dockerhub...), it might be more useful to use Drone.io.
|
Due to the limitations of Azure Pipelines (limited free usage, no parallel, no dockerhub...), it might be more useful to use Drone.io.
|
||||||
However `pnpm` will not be as useful as in Azure Pipelines since we cannot cache on the parent machine.
|
However `pnpm` will not be as useful as in Azure Pipelines since we cannot cache on the parent machine.
|
55
docs/#Project/Pages/ContextMenu.md
Normal file
55
docs/#Project/Pages/ContextMenu.md
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
> TL;DR: Menu.tsx est le composant qui affiche et qui traite l'event contextmenu sur la page
|
||||||
|
> InitActions de Editor.tsx prépare le modèle pour Menu.tsx
|
||||||
|
> ContextMenuActions définient les actions de l'API
|
||||||
|
|
||||||
|
# Context Menu
|
||||||
|
|
||||||
|
Ce document présente comment le menu contextuel est implémenté.
|
||||||
|
|
||||||
|
|
||||||
|
# Event listener
|
||||||
|
|
||||||
|
Pour implémenter le menu contextuel, il faut en premier ajouter un event listener sur `contextmenu`.
|
||||||
|
|
||||||
|
Cela se fait dans le composant `Menu.tsx` via la fonction `UseMouseEvents()`.
|
||||||
|
|
||||||
|
Elle équipe plusieurs events sur la page en plus de `contextmenu` afin de fermer correctement lorsque l'on clique ailleurs.
|
||||||
|
|
||||||
|
Il n'existe donc qu'un seul menu contextuel pour toute la page.
|
||||||
|
|
||||||
|
|
||||||
|
# Affichage du contenu
|
||||||
|
|
||||||
|
On a vu que `Menu.tsx` s'occupe de traiter l'event `contextmenu`. Regardons maintenant comment elle affiche le menu.
|
||||||
|
|
||||||
|
Ce composant utilise une hashmap `actions: Map<string, IMenuAction[]>` pour lire les différentes actions possible, la clé servant d'identifiant et de pattern.
|
||||||
|
|
||||||
|
En effet, la fonction `AddClassSpecificActions`, obtenant le composant html lit les *classes* et vérifie s'il est présent dans le dictionnaire avec `props.actions.get(className)`.
|
||||||
|
|
||||||
|
S'il est présent, alors on itère sur les différentes actions possible pour cette classe pour ajouter des `MenuItem` représentant une ligne du menu contextuel. Chaque `MenuItem` possède un fonction qui sera exécutée lorsque la ligne est cliquée. Il possède également un texte, un titre qui sera affiché si le curseur survole la ligne, et, optionnellement, un raccourci qui sera affiché à droite de la ligne.
|
||||||
|
|
||||||
|
En plus des actions de classes, il y a aussi des actions universelles comme le `undo` ou `redo` qui sont affichées n'importe où on clique. Celle-ci ont pour id `''`, une chaine de caractères vide. On itère sur cette liste d'action pour ajouter les lignes.
|
||||||
|
|
||||||
|
L'ordre d'affichage est donc défini :
|
||||||
|
|
||||||
|
1) actions de classes
|
||||||
|
2) actions universelles
|
||||||
|
|
||||||
|
L'ordre des classes est l'ordre d'ajout dans le dictionnaire.
|
||||||
|
|
||||||
|
|
||||||
|
# Création du dictionnaire
|
||||||
|
|
||||||
|
Parlons de l'initialisation du dictionnaire.
|
||||||
|
|
||||||
|
Le composant `Menu` est utilisé dans `Editor`. C'est aussi ici que l'ont crée le dictionnaire.
|
||||||
|
|
||||||
|
La fonction `InitActions` s'occupe d'enrichîr le dictionnaire des différentes actions.
|
||||||
|
|
||||||
|
On peut voir qu'au début de la fonction que les actions universelles y sont initialisées. Ensuite, les actions spécifiques aux classes y sont ajoutés avec au début les actions définies dans SVGLayoutDesigner et après, les actions définies dans la configuration de l'API (donc `Diviser remplissage par exemple`).
|
||||||
|
|
||||||
|
Chaque action provenant de l'API utilise la fonction `GetAction` du fichier utilitaire `ContextMenuActions.ts`.
|
||||||
|
|
||||||
|
Cette fonction équipe l'action qui sera exécutée d'une autre fonction appelé `SetContainerList`. Cette autre fonction s'occupe de faire un appel REST vers l'api sur le point d'accès `Configuration.APIConfiguration.apiSetContainerListUrl` ou, si elle n'est pas définie, sur `VITE_API_SET_CONTAINER_LIST_URL`.
|
||||||
|
|
||||||
|
Cela veut dire que pour l'instant toutes les actions provenant de l'API a pour but de remplacer, d'ajouter ou de supprimer des conteneurs.
|
|
@ -1,3 +1,6 @@
|
||||||
|
|
||||||
|
> TL;DR: Ce projet utilise un dictionnaire pour représenter un arbre/graphe
|
||||||
|
|
||||||
# Préface
|
# Préface
|
||||||
|
|
||||||
Ce document explique la structure de données utilisée pour les conteneurs.
|
Ce document explique la structure de données utilisée pour les conteneurs.
|
||||||
|
@ -40,7 +43,7 @@ B: Node = {
|
||||||
|
|
||||||
Donc le graphe est simplement `A <-> B`
|
Donc le graphe est simplement `A <-> B`
|
||||||
|
|
||||||
Ceci est un graphe cyclique que nous ne verrons pas souvent dans ce projet.
|
Ceci est un graphe cyclique que nous ne verrons pas souvent dans ce projet.
|
||||||
En effet, nous avons plutôt quelque chose comme ça:
|
En effet, nous avons plutôt quelque chose comme ça:
|
||||||
|
|
||||||
```
|
```
|
|
@ -1,3 +1,5 @@
|
||||||
|
Source: https://dev.azure.com/techformsa/SmartConfigurator/_wiki?pageId=122&friendlyName=Int%C3%A9grer-le-projet-en-tant-que-composant-dans-Modeler#
|
||||||
|
|
||||||
- [Préface](#préface)
|
- [Préface](#préface)
|
||||||
- [Customiser et build le projet (recommandé)](#customiser-et-build-le-projet-(recommandé))
|
- [Customiser et build le projet (recommandé)](#customiser-et-build-le-projet-(recommandé))
|
||||||
* [Configurer les options de build](#configurer-les-options-de-build)
|
* [Configurer les options de build](#configurer-les-options-de-build)
|
||||||
|
@ -74,29 +76,6 @@ Cliquez sur le menu hamburger et cliquez sur **Download Artifacts** :
|
||||||
|
|
||||||
- Puis copier les fichiers de build dans le dossier. (copiez l'entièreté du dossier `dist` ou extrayez le zip selon la méthode).
|
- Puis copier les fichiers de build dans le dossier. (copiez l'entièreté du dossier `dist` ou extrayez le zip selon la méthode).
|
||||||
|
|
||||||
- Modifiez le fichier `index.html` du composant pour changer les chemins absolus en relatifs:
|
|
||||||
|
|
||||||
```diff
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8" />
|
|
||||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
|
||||||
<link rel="stylesheet" href="style.css">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
||||||
<title>Vite + React + TS</title>
|
|
||||||
- <script type="module" crossorigin src="/assets/index.acd801d1.js"></script>
|
|
||||||
+ <script type="module" crossorigin src="./assets/index.acd801d1.js"></script>
|
|
||||||
- <link rel="stylesheet" href="/assets/index.859481c4.css">
|
|
||||||
+ <link rel="stylesheet" href="./assets/index.859481c4.css">
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="root"></div>
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
```
|
|
||||||
|
|
||||||
- Si **besoin**, modifier le fichier `smartcomponent/svg-layout-designer.html` pour avoir la bonne url relative à la basename de l'url :
|
- Si **besoin**, modifier le fichier `smartcomponent/svg-layout-designer.html` pour avoir la bonne url relative à la basename de l'url :
|
||||||
|
|
||||||
```diff
|
```diff
|
38
docs/#Project/Pages/WebWorkers.md
Normal file
38
docs/#Project/Pages/WebWorkers.md
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
# Web workers
|
||||||
|
|
||||||
|
Cette page explique la raison d'utiliser un web worker.
|
||||||
|
|
||||||
|
|
||||||
|
# Qu'est-ce qu'un web worker ?
|
||||||
|
|
||||||
|
Rapidement, c'est juste un fichier js qui est exécuté côté dans un Thread différent.
|
||||||
|
|
||||||
|
Il attend un réponse, la traite et peut répondre ensuite.
|
||||||
|
|
||||||
|
|
||||||
|
# Pourquoi en utiliser ?
|
||||||
|
|
||||||
|
Cela permet du véritable code asynchrone évitant le freeze du navigateur lorsqu'il fait des calculs compliqués ou lorsqu'il attend.
|
||||||
|
|
||||||
|
Exemple: https://mdn.github.io/dom-examples/web-workers/fibonacci-worker/
|
||||||
|
|
||||||
|
|
||||||
|
# Comment sont-ils utilisés ?
|
||||||
|
|
||||||
|
## Sauvegarde
|
||||||
|
|
||||||
|
Le premier web worker, situé dans [`public/workers/worker.js`](../../../public/workers/worker.js) s'occupe de faire une seule est unique tâche : `JSON.stringify` cependant on fonction de la taille de l'objet à stringifier en JSON cela peut prendre plusieurs secondes auquel cas l'utilisateur peut croire que son navigateur à bloqué.
|
||||||
|
|
||||||
|
On le met donc dans un web worker pour éviter cela.
|
||||||
|
|
||||||
|
Dans `Save.ts`, on crée le web worker avec `new Worker('workers/worker.js')` et on le termine quand il a fini sa tache avec `terminate()`
|
||||||
|
|
||||||
|
## Envois de message
|
||||||
|
|
||||||
|
Le deuxième web worker, situé dans [`public/workers/worker.js`](../../../public/workers/message_worker.js) s'occupe de faire des appels REST en stringifiant l'état de l'application.
|
||||||
|
|
||||||
|
Pour la même raison que la sauvegarde, on le met pour éviter un freeze.
|
||||||
|
|
||||||
|
Il est évidemment moins utile que la sauvegarde qui prends un objet beaucoup plus lourd.
|
||||||
|
|
||||||
|
Contrairement à la sauvegarde, le web worker est crée dans `UI.tsx` avec `UseWorker()` et existe sur tout le long de la durée de vie de l'application. Il est initialisé dans l'utilisation du module `UseWorker.tsx`.
|
|
@ -1,5 +1,5 @@
|
||||||
|
|
||||||
Bienvenue à la documentation développeur de SVGLayoutDesigner.
|
Bienvenue au tutoriel de SVGLayoutDesigner.
|
||||||
|
|
||||||
Cette documentation a pour objectif de familiariser les nouveaux développeur aux outils du projet
|
Cette documentation a pour objectif de familiariser les nouveaux développeur aux outils du projet
|
||||||
et à apprendre à développer des composants sous React.
|
et à apprendre à développer des composants sous React.
|
BIN
docs/assets/yule-log-cake.jpg
(Stored with Git LFS)
BIN
docs/assets/yule-log-cake.jpg
(Stored with Git LFS)
Binary file not shown.
|
@ -10,11 +10,13 @@ const getCircularReplacer = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (key === 'containers') {
|
if (key === 'containers') {
|
||||||
return Array.from(value.entries());
|
return [...value.entries()]
|
||||||
|
.map(([Key, Value]) => ({ Key, Value }));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (key === 'symbols') {
|
if (key === 'symbols') {
|
||||||
return Array.from(value.entries());
|
return [...value.entries()]
|
||||||
|
.map(([Key, Value]) => ({ Key, Value }));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (key === 'linkedContainers') {
|
if (key === 'linkedContainers') {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { IConfiguration } from '../../Interfaces/IConfiguration';
|
import { IConfiguration } from '../../Interfaces/IConfiguration';
|
||||||
import { ISetContainerListRequest } from '../../Interfaces/ISetContainerListRequest';
|
import { ISetContainerListRequest } from '../../Interfaces/ISetContainerListRequest';
|
||||||
import { ISetContainerListResponse } from '../../Interfaces/ISetContainerListResponse';
|
import { ISetContainerListResponse } from '../../Interfaces/ISetContainerListResponse';
|
||||||
import { GetCircularReplacerToDotnet } from '../../utils/saveload';
|
import { GetCircularReplacer } from '../../utils/saveload';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch the configuration from the API
|
* Fetch the configuration from the API
|
||||||
|
@ -35,7 +35,7 @@ export async function FetchConfiguration(): Promise<IConfiguration> {
|
||||||
|
|
||||||
export async function SetContainerList(request: ISetContainerListRequest, configurationUrl?: string): Promise<ISetContainerListResponse> {
|
export async function SetContainerList(request: ISetContainerListRequest, configurationUrl?: string): Promise<ISetContainerListResponse> {
|
||||||
const url = configurationUrl ?? import.meta.env.VITE_API_SET_CONTAINER_LIST_URL;
|
const url = configurationUrl ?? import.meta.env.VITE_API_SET_CONTAINER_LIST_URL;
|
||||||
const dataParsed = JSON.stringify(request, GetCircularReplacerToDotnet());
|
const dataParsed = JSON.stringify(request, GetCircularReplacer());
|
||||||
// The test library cannot use the Fetch API
|
// The test library cannot use the Fetch API
|
||||||
// @ts-expect-error
|
// @ts-expect-error
|
||||||
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
|
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
|
||||||
|
|
|
@ -5,7 +5,7 @@ import { MessageType } from '../../Enums/MessageType';
|
||||||
import { IHistoryState } from '../../Interfaces/IHistoryState';
|
import { IHistoryState } from '../../Interfaces/IHistoryState';
|
||||||
import { IMessage } from '../../Interfaces/IMessage';
|
import { IMessage } from '../../Interfaces/IMessage';
|
||||||
import { DISABLE_API } from '../../utils/default';
|
import { DISABLE_API } from '../../utils/default';
|
||||||
import { GetCircularReplacerToDotnet } from '../../utils/saveload';
|
import { GetCircularReplacer } from '../../utils/saveload';
|
||||||
import { TITLE_BAR_HEIGHT } from '../Sidebar/Sidebar';
|
import { TITLE_BAR_HEIGHT } from '../Sidebar/Sidebar';
|
||||||
|
|
||||||
interface IMessagesProps {
|
interface IMessagesProps {
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { IHistoryState } from '../../Interfaces/IHistoryState';
|
||||||
import { IGetFeedbackRequest } from '../../Interfaces/IGetFeedbackRequest';
|
import { IGetFeedbackRequest } from '../../Interfaces/IGetFeedbackRequest';
|
||||||
import { IGetFeedbackResponse } from '../../Interfaces/IGetFeedbackResponse';
|
import { IGetFeedbackResponse } from '../../Interfaces/IGetFeedbackResponse';
|
||||||
import { IMessage } from '../../Interfaces/IMessage';
|
import { IMessage } from '../../Interfaces/IMessage';
|
||||||
import { GetCircularReplacerToDotnet } from '../../utils/saveload';
|
import { GetCircularReplacer } from '../../utils/saveload';
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
|
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
|
||||||
const myWorker = window.Worker && new Worker('workers/message_worker.js');
|
const myWorker = window.Worker && new Worker('workers/message_worker.js');
|
||||||
|
@ -38,7 +38,7 @@ export function UseAsync(
|
||||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||||
ApplicationState: state
|
ApplicationState: state
|
||||||
};
|
};
|
||||||
const dataParsed = JSON.stringify(request, GetCircularReplacerToDotnet());
|
const dataParsed = JSON.stringify(request, GetCircularReplacer());
|
||||||
fetch(import.meta.env.VITE_API_GET_FEEDBACK_URL, {
|
fetch(import.meta.env.VITE_API_GET_FEEDBACK_URL, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: new Headers({
|
headers: new Headers({
|
||||||
|
|
|
@ -54,7 +54,6 @@ function SetHistory(root: Element | Document,
|
||||||
eventInitDict?: CustomEventInit): void {
|
eventInitDict?: CustomEventInit): void {
|
||||||
const history: IHistoryState[] = eventInitDict?.detail.history;
|
const history: IHistoryState[] = eventInitDict?.detail.history;
|
||||||
const historyCurrentStep: number | undefined = eventInitDict?.detail.historyCurrentStep;
|
const historyCurrentStep: number | undefined = eventInitDict?.detail.historyCurrentStep;
|
||||||
ReviveHistoryAction(history);
|
|
||||||
setNewHistory(history, historyCurrentStep);
|
setNewHistory(history, historyCurrentStep);
|
||||||
const customEvent = new CustomEvent<IEditorState>('setHistory', { detail: editorState });
|
const customEvent = new CustomEvent<IEditorState>('setHistory', { detail: editorState });
|
||||||
root.dispatchEvent(customEvent);
|
root.dispatchEvent(customEvent);
|
||||||
|
|
|
@ -2,6 +2,8 @@ import { IContainerProperties } from './IContainerProperties';
|
||||||
|
|
||||||
export interface IContainerModel {
|
export interface IContainerModel {
|
||||||
children: string[]
|
children: string[]
|
||||||
|
// TODO: Remove parent now that accessing the parent by id is faster.
|
||||||
|
// TODO: Use GetContainerById(container.properties.parentId) as the better alternative.
|
||||||
parent: IContainerModel | null
|
parent: IContainerModel | null
|
||||||
properties: IContainerProperties
|
properties: IContainerProperties
|
||||||
userData: Record<string, string | number>
|
userData: Record<string, string | number>
|
||||||
|
@ -13,6 +15,8 @@ export interface IContainerModel {
|
||||||
*/
|
*/
|
||||||
export class ContainerModel implements IContainerModel {
|
export class ContainerModel implements IContainerModel {
|
||||||
public children: string[];
|
public children: string[];
|
||||||
|
// TODO: Remove parent now that accessing the parent by id is faster.
|
||||||
|
// TODO: Use GetContainerById(container.properties.parentId) as the better alternative.
|
||||||
public parent: IContainerModel | null;
|
public parent: IContainerModel | null;
|
||||||
public properties: IContainerProperties;
|
public properties: IContainerProperties;
|
||||||
public userData: Record<string, string | number>;
|
public userData: Record<string, string | number>;
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
|
/* eslint-disable @typescript-eslint/naming-convention */
|
||||||
import { FindContainerById, MakeDFSIterator } from './itertools';
|
import { FindContainerById, MakeDFSIterator } from './itertools';
|
||||||
import { IEditorState } from '../Interfaces/IEditorState';
|
import { IEditorState } from '../Interfaces/IEditorState';
|
||||||
import { IHistoryState } from '../Interfaces/IHistoryState';
|
import { IHistoryState } from '../Interfaces/IHistoryState';
|
||||||
|
import { IContainerModel } from '../Interfaces/IContainerModel';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Revive the Editor state
|
* Revive the Editor state
|
||||||
|
@ -32,7 +34,9 @@ export function ReviveState(state: IHistoryState): void {
|
||||||
for (const symbol of state.symbols.values()) {
|
for (const symbol of state.symbols.values()) {
|
||||||
symbol.linkedContainers = new Set(symbol.linkedContainers);
|
symbol.linkedContainers = new Set(symbol.linkedContainers);
|
||||||
}
|
}
|
||||||
state.containers = new Map(state.containers);
|
|
||||||
|
const containers: Array<{ Key: string, Value: IContainerModel }> = (state.containers) as any;
|
||||||
|
state.containers = new Map(containers.map(({ Key, Value }: {Key: string, Value: IContainerModel}) => [Key, Value]));
|
||||||
|
|
||||||
const root = FindContainerById(state.containers, state.mainContainer);
|
const root = FindContainerById(state.containers, state.mainContainer);
|
||||||
|
|
||||||
|
@ -40,6 +44,8 @@ export function ReviveState(state: IHistoryState): void {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: remove parent and remove this bloc of code
|
||||||
|
// TODO: See IContainerModel.ts for more detail
|
||||||
const it = MakeDFSIterator(root, state.containers);
|
const it = MakeDFSIterator(root, state.containers);
|
||||||
for (const container of it) {
|
for (const container of it) {
|
||||||
const parentId = container.properties.parentId;
|
const parentId = container.properties.parentId;
|
||||||
|
@ -62,43 +68,13 @@ export function GetCircularReplacer(): (key: any, value: object | Map<string, an
|
||||||
}
|
}
|
||||||
|
|
||||||
if (key === 'containers') {
|
if (key === 'containers') {
|
||||||
return Array.from((value as Map<string, any>).entries());
|
return [...(value as Map<string, any>).entries()]
|
||||||
|
.map(([Key, Value]: [string, any]) => ({ Key, Value }));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (key === 'symbols') {
|
if (key === 'symbols') {
|
||||||
return Array.from((value as Map<string, any>).entries());
|
return [...(value as Map<string, any>).entries()]
|
||||||
}
|
.map(([Key, Value]: [string, any]) => ({ Key, Value }));
|
||||||
|
|
||||||
if (key === 'linkedContainers') {
|
|
||||||
return Array.from(value as Set<string>);
|
|
||||||
}
|
|
||||||
|
|
||||||
return value;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function GetCircularReplacerToDotnet(): (key: any, value: object | Map<string, any> | null) => object | null | undefined {
|
|
||||||
return (key: any, value: object | null) => {
|
|
||||||
if (key === 'parent') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (key === 'containers') {
|
|
||||||
return [...(value as Map<string, any>).entries()].map((keyPair: [string, any]) => {
|
|
||||||
return {
|
|
||||||
Key: keyPair[0],
|
|
||||||
Value: keyPair[1]
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (key === 'symbols') {
|
|
||||||
return [...(value as Map<string, any>).entries()].map((keyPair: [string, any]) => {
|
|
||||||
return {
|
|
||||||
Key: keyPair[0],
|
|
||||||
Value: keyPair[1]
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (key === 'linkedContainers') {
|
if (key === 'linkedContainers') {
|
||||||
|
|
|
@ -5,5 +5,6 @@ import react from '@vitejs/plugin-react';
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [
|
plugins: [
|
||||||
react()
|
react()
|
||||||
]
|
],
|
||||||
|
base: './'
|
||||||
});
|
});
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue