@imenam/mcp-design 1.0.10 → 1.0.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +67 -5
- package/dist/shared/feedback.d.ts +128 -0
- package/dist/shared/feedback.js +165 -0
- package/dist/shared/node-defaults.js +6 -1
- package/dist/shared/node-fields.d.ts +2 -0
- package/dist/shared/node-fields.js +5 -1
- package/dist/shared/render.d.ts +30 -5
- package/dist/shared/render.js +58 -11
- package/dist/shared/schemas/design.schema.d.ts +36 -1
- package/dist/shared/schemas/design.schema.js +10 -1
- package/dist/shared/schemas/feedback.schema.d.ts +191 -0
- package/dist/shared/schemas/feedback.schema.js +87 -0
- package/dist/shared/tree.d.ts +2 -0
- package/dist/shared/tree.js +4 -0
- package/dist/src/core/design-store.d.ts +4 -5
- package/dist/src/core/design-store.js +7 -56
- package/dist/src/core/export.d.ts +2 -1
- package/dist/src/core/export.js +35 -7
- package/dist/src/core/feedback-store.d.ts +73 -0
- package/dist/src/core/feedback-store.js +308 -0
- package/dist/src/core/file-lock.d.ts +8 -0
- package/dist/src/core/file-lock.js +61 -0
- package/dist/src/core/image-store.d.ts +45 -0
- package/dist/src/core/image-store.js +175 -0
- package/dist/src/core/node-factory.js +16 -0
- package/dist/src/core/screenshot.d.ts +6 -0
- package/dist/src/core/screenshot.js +32 -3
- package/dist/src/gui-worker.js +113 -1
- package/dist/src/index.js +231 -3
- package/dist/src/tools/add-image.d.ts +3 -0
- package/dist/src/tools/add-image.js +65 -0
- package/dist/src/tools/add-nodes.js +2 -0
- package/dist/src/tools/delete-design.d.ts +3 -1
- package/dist/src/tools/delete-design.js +4 -1
- package/dist/src/tools/export-design.js +1 -1
- package/dist/src/tools/get-feedback.d.ts +3 -0
- package/dist/src/tools/get-feedback.js +62 -0
- package/dist/src/tools/mark-feedback-processed.d.ts +8 -0
- package/dist/src/tools/mark-feedback-processed.js +29 -0
- package/dist/src/tools/screenshot-design.js +1 -0
- package/dist/src/tools/update-nodes.js +4 -1
- package/dist/src/version.d.ts +1 -1
- package/dist/src/version.js +16 -2
- package/gui/dist/assets/index-DNUHDrGi.css +1 -0
- package/gui/dist/assets/index-uk5sDkVA.js +15 -0
- package/gui/dist/assets/{search-CSwiX0lv.js → search-jSqY2fPS.js} +1 -1
- package/gui/dist/index.html +2 -2
- package/package.json +2 -3
- package/gui/dist/assets/index-DBZNXgF_.js +0 -10
- package/gui/dist/assets/index-PQqxUQLx.css +0 -1
- package/scripts/generate-version.js +0 -20
package/README.md
CHANGED
|
@@ -4,10 +4,13 @@ Un canvas de design pilotable par un agent, avec une interface graphique où l'u
|
|
|
4
4
|
|
|
5
5
|
L'agent compose une planche (formes, texte) via des outils MCP. L'utilisateur ouvre la GUI, déplace, retaille, recolore, réécrit. L'agent relit ensuite **exactement ce qui a changé** et l'intègre dans le code de l'application.
|
|
6
6
|
|
|
7
|
+
L'utilisateur peut aussi, sans toucher au design, **épingler des remarques** sur une page, un élément ou une zone. Il les transmet en un retour nommé que l'agent lit avec `get_feedback`, captures comprises ; l'agent déclare ensuite ce qu'il a traité avec `mark_feedback_processed`, et l'utilisateur suit l'avancement de chaque retour dans la page « Retours ».
|
|
8
|
+
|
|
7
9
|
```
|
|
8
10
|
Agent ──add_nodes/update_nodes──▶ Design ◀──glisser-déposer── Utilisateur (GUI)
|
|
9
11
|
▲ │
|
|
10
|
-
|
|
12
|
+
├──────────── get_changes ──── diff depuis la dernière baseline ──────┤
|
|
13
|
+
└──────────── get_feedback ─── remarques transmises (retour n°N) ─────┘
|
|
11
14
|
```
|
|
12
15
|
|
|
13
16
|
---
|
|
@@ -72,7 +75,7 @@ Exemple d'entrée générée :
|
|
|
72
75
|
| `APP_NAME` | Nom affiché dans le dashboard du proxy. `PROXY_APP_NAME` puis `GUI_NAME` sont acceptés en repli. |
|
|
73
76
|
| `APP_PATH_PREFIX` | Préfixe de namespace ajouté au chemin **par défaut** (ex. `/abc123` → `/abc123/design-gui`). Sert à isoler plusieurs instances sous un même proxy. |
|
|
74
77
|
| `APP_GROUP` | Section repliable du dashboard du proxy (facultatif). |
|
|
75
|
-
| `MCP_DESIGN_DATA_DIR` | Répertoire de stockage des designs. `MCP_DATA_DIR` est accepté en repli. Défaut : `<package>/.design-data`. |
|
|
78
|
+
| `MCP_DESIGN_DATA_DIR` | Répertoire de stockage des designs et des remarques. `MCP_DATA_DIR` est accepté en repli. Défaut : `<package>/.design-data`. |
|
|
76
79
|
| `MCP_LOG_DIR` | Répertoire des logs. Défaut : `<data>/logs`. |
|
|
77
80
|
|
|
78
81
|
`PROXY_URL` et `APP_PORT` s'excluent : `PROXY_URL` prime si les deux sont renseignés. **Si aucune des deux n'est définie, la GUI est simplement désactivée** — les outils MCP continuent de fonctionner.
|
|
@@ -90,7 +93,7 @@ Chaque requête traverse la garde de [`@imenam/mcp-gui-interface`](https://www.n
|
|
|
90
93
|
- à tout en-tête `Host` autre que `localhost`, `127.0.0.1` ou `::1` (protection contre le DNS rebinding) ;
|
|
91
94
|
- aux modifications (méthodes autres que `GET`, `HEAD`, `OPTIONS`) venues d'un autre site, d'après `Sec-Fetch-Site` ou, à défaut, `Origin` (sont admis : la machine elle-même, le proxy de `PROXY_URL`).
|
|
92
95
|
|
|
93
|
-
Un design est un simple fichier JSON dans `<data>/designs/<id>.json` — lisible, diffable, versionnable.
|
|
96
|
+
Un design est un simple fichier JSON dans `<data>/designs/<id>.json` — lisible, diffable, versionnable. Les images qu'il affiche sont dans `<data>/images/`.
|
|
94
97
|
|
|
95
98
|
---
|
|
96
99
|
|
|
@@ -112,6 +115,7 @@ Un design est un simple fichier JSON dans `<data>/designs/<id>.json` — lisible
|
|
|
112
115
|
| Outil | Effet |
|
|
113
116
|
|---|---|
|
|
114
117
|
| `add_nodes` | Ajoute un ou plusieurs éléments par-dessus les autres. |
|
|
118
|
+
| `add_image` | Importe une image (chemin absolu d'un fichier PNG, JPEG, GIF, WebP ou SVG) et la pose par-dessus les autres éléments. |
|
|
115
119
|
| `update_nodes` | Patche des éléments existants — seuls les champs fournis changent. |
|
|
116
120
|
| `delete_nodes` | Supprime des éléments par identifiant. Supprimer un groupe emporte son contenu. |
|
|
117
121
|
| `duplicate_nodes` | Copie des éléments ou des groupes avec tout leur contenu, sous de nouveaux identifiants. `offset_x`/`offset_y` et `count` disposent une série de copies, `parent_id` les dépose dans un groupe. Avec `target_design_id`, la copie va dans une **autre planche** : les éléments gardent leur position à l'écran, et les composants reproduits par les instances copiées sont copiés avec elles, une seule fois, à la racine. |
|
|
@@ -119,7 +123,7 @@ Un design est un simple fichier JSON dans `<data>/designs/<id>.json` — lisible
|
|
|
119
123
|
| `ungroup_nodes` | Dissout des groupes : leur contenu remonte d'un niveau, toujours sans bouger. |
|
|
120
124
|
| `detach_instances` | Rompt le lien d'une instance avec son composant : son contenu devient des éléments ordinaires. |
|
|
121
125
|
|
|
122
|
-
|
|
126
|
+
Huit types d'éléments : `rect`, `ellipse`, `line`, `text`, `icon`, `image`, `group` et `instance`. Seul `type` est obligatoire — sauf pour `icon`, qui a besoin du nom de l'icône, et `image`, qui a besoin du nom d'une image importée ; tout le reste tombe sur des valeurs par défaut visibles, donc `{ "type": "rect" }` suffit à créer quelque chose qu'on voit à l'écran.
|
|
123
127
|
|
|
124
128
|
**Champs communs** — `name`, `parentId`, `x`, `y`, `width`, `height` (en pixels, origine en haut à gauche), `rotation` (degrés, autour du centre de la boîte), `opacity` (0–1).
|
|
125
129
|
|
|
@@ -132,6 +136,7 @@ Sept types d'éléments : `rect`, `ellipse`, `line`, `text`, `icon`, `group` et
|
|
|
132
136
|
| `line` | `stroke`, `strokeWidth` — le trait va de `(x, y)` à `(x + width, y + height)`, donc les deltas négatifs encodent la direction |
|
|
133
137
|
| `text` | `text`, `color`, `fontSize`, `fontFamily`, `fontWeight`, `fontStyle`, `textAlign`, `lineHeight` (en px) |
|
|
134
138
|
| `icon` | `icon` (obligatoire — le nom du dessin), `fill`, `stroke`, `strokeWidth` |
|
|
139
|
+
| `image` | `image` (obligatoire — le nom de l'image dans la médiathèque), `fit` (`cover` par défaut, `contain`, `fill`) |
|
|
135
140
|
| `group` | aucun — c'est un pur conteneur |
|
|
136
141
|
| `instance` | `componentId` (obligatoire — le groupe reproduit) ; rien d'autre, son apparence vient de ce groupe |
|
|
137
142
|
|
|
@@ -161,6 +166,25 @@ Côté GUI, l'outil « Icône » de la barre d'outils (raccourci `I`) ouvre un s
|
|
|
161
166
|
|
|
162
167
|
La bibliothèque est figée dans le dépôt par `npm run generate-icons` ([`scripts/generate-icons.js`](scripts/generate-icons.js)) : ni le serveur publié ni le bundle de la GUI ne dépendent de Lucide à l'exécution, et la géométrie d'un design ne peut pas changer au gré d'un `npm install`.
|
|
163
168
|
|
|
169
|
+
### Images
|
|
170
|
+
|
|
171
|
+
`add_image` pose une vraie image — photo, logo, illustration, capture — là où une icône ou des formes ne suffisent pas.
|
|
172
|
+
|
|
173
|
+
```json
|
|
174
|
+
{ "design_id": "landing", "file_path": "C:/projets/site/assets/hero.jpg", "x": 0, "y": 80, "width": 1440 }
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
- **Le fichier est copié** dans `<MCP_DESIGN_DATA_DIR>/images/`, sous un nom tiré de l'empreinte de son contenu (`img_3f9a1c2b7d4e5f60.jpg`). Le design ne stocke que ce nom : le JSON reste lisible, une même image importée deux fois n'est stockée qu'une fois, et la planche ne dépend plus de l'emplacement du fichier d'origine. La médiathèque est commune à toutes les planches du dossier de données.
|
|
178
|
+
- **Taille** : sans `width` ni `height`, la boîte prend les dimensions naturelles de l'image ; avec une seule des deux, l'autre suit ses proportions.
|
|
179
|
+
- **`fit`** dit comment l'image remplit sa boîte : `cover` la remplit en rognant le débordement, `contain` la montre en entier, `fill` l'étire.
|
|
180
|
+
- **Réutiliser une image** : la réponse d'`add_image` donne son nom ; un nœud `{ "type": "image", "image": "img_…" }` dans `add_nodes` la repose sans second import. Un nom absent de la médiathèque est refusé à l'écriture.
|
|
181
|
+
- Le format est reconnu d'après le contenu du fichier, pas d'après son extension ; 20 Mo au plus.
|
|
182
|
+
- Derrière [`@imenam/mcp-http-gateway`](https://www.npmjs.com/package/@imenam/mcp-http-gateway), l'outil déclare la paire `file_path` / `file_content` : le relais lit le fichier sur la machine de l'agent et en transmet le contenu (dossier autorisé par `GATEWAY_INLINE_ROOTS`).
|
|
183
|
+
|
|
184
|
+
Le canvas charge les images depuis le worker (`GET /api/images/<nom>`). `screenshot_design`, les captures des remarques et `export_design` les intègrent en data URI : un export `svg`, `html` ou `jsx` s'ouvre tel quel, sans la médiathèque — mais il pèse le poids des images qu'il contient ; pour une planche illustrée, préférez `destination_path` à un retour en ligne.
|
|
185
|
+
|
|
186
|
+
Dans la GUI, une image se déplace, se redimensionne et se pivote comme les autres éléments ; l'inspecteur règle son cadrage.
|
|
187
|
+
|
|
164
188
|
### Hiérarchie
|
|
165
189
|
|
|
166
190
|
Chaque élément porte un `parentId` : `null` à la racine de la planche, sinon l'identifiant d'un `group`. **Les coordonnées sont exprimées dans l'espace du parent** : un élément à la racine se positionne par rapport à la planche, un élément dans un groupe par rapport à l'origine de ce groupe.
|
|
@@ -241,6 +265,17 @@ Un lot est intégralement validé avant la moindre écriture : une entrée inval
|
|
|
241
265
|
|
|
242
266
|
**`get_changes` n'acquitte jamais ce qu'il montre.** Seul `mark_synced` déplace la baseline, et il ne doit être appelé qu'une fois les modifications réellement reportées dans le code. Un simple appel de lecture ne peut donc pas faire disparaître un diff que l'agent n'a pas encore traité.
|
|
243
267
|
|
|
268
|
+
### Lire les remarques de l'utilisateur
|
|
269
|
+
|
|
270
|
+
| Outil | Effet |
|
|
271
|
+
|---|---|
|
|
272
|
+
| `get_feedback` | Renvoie le plus ancien retour qui a encore des remarques à traiter (le dernier quand tout est traité), ou celui demandé par `round`. Pour chaque remarque : son identifiant, la page, puis la page entière, l'élément visé (id, nom, chemin, boîte) ou la zone et les éléments cités, le texte et son état de traitement. Les captures d'éléments et de zones suivent **en images**, numérotées comme dans le texte ; `captures: false` les omet. La réponse se termine par la liste de tous les retours et de leur avancement. |
|
|
273
|
+
| `mark_feedback_processed` | Déclare traitées des remarques d'un retour (`round`, `remark_ids` — toutes si omis), avec une `note` sur ce qui a été fait. C'est ce que l'utilisateur voit dans la page « Retours ». |
|
|
274
|
+
|
|
275
|
+
Un retour est **figé** au moment où l'utilisateur clique sur « Terminer les retours » : `get_feedback` peut le relire autant de fois que nécessaire, rien ne change. Seuls son nom (renommable dans la GUI) et le suivi du traitement évoluent ; **lire un retour ne le marque jamais traité**, seul `mark_feedback_processed` (ou l'utilisateur) le fait. Le texte est celui que la GUI affiche en aperçu avant l'enregistrement — même fonction de mise en forme, donc même contenu. Tant qu'aucun retour n'existe, l'outil dit combien de remarques attendent encore dans la GUI.
|
|
276
|
+
|
|
277
|
+
Les remarques vivent dans `<MCP_DESIGN_DATA_DIR>/feedback/remarks.json`, les captures PNG dans `<MCP_DESIGN_DATA_DIR>/feedback/captures/` : le même dossier de données que les designs, celui fixé par la configuration du projet.
|
|
278
|
+
|
|
244
279
|
### GUI
|
|
245
280
|
|
|
246
281
|
| Outil | Effet |
|
|
@@ -263,6 +298,13 @@ screenshot_design { design_id: "landing" } ← ce que ça donne réellement,
|
|
|
263
298
|
export_design { design_id: "landing", format: "html" }
|
|
264
299
|
… l'agent reporte les valeurs dans le code de l'application …
|
|
265
300
|
mark_synced { design_id: "landing" } ← acquittement
|
|
301
|
+
|
|
302
|
+
… l'utilisateur passe en mode Remarques, commente, clique « Terminer les retours »,
|
|
303
|
+
puis dit à l'agent qu'il a terminé …
|
|
304
|
+
|
|
305
|
+
get_feedback {} ← retour à traiter : remarques + captures
|
|
306
|
+
… l'agent applique les remarques …
|
|
307
|
+
mark_feedback_processed { round: 1, remark_ids: ["rm_…"], note: "…" } ← suivi visible dans la GUI
|
|
266
308
|
```
|
|
267
309
|
|
|
268
310
|
---
|
|
@@ -290,6 +332,26 @@ La sauvegarde est automatique et différée. Une modification faite par l'agent
|
|
|
290
332
|
|
|
291
333
|
Le badge « n à synchroniser » d'une planche indique le nombre de modifications que l'agent n'a pas encore acquittées.
|
|
292
334
|
|
|
335
|
+
### Mode Remarques
|
|
336
|
+
|
|
337
|
+
La bascule **Édition / Remarques** de la barre d'outils (raccourci `M`) passe le canvas en lecture : rien ne s'y déplace, et le rail de droite affiche les remarques à la place de l'inspecteur.
|
|
338
|
+
|
|
339
|
+
- **Remarque sur un élément** — un clic vise le **groupe le plus haut**, comme en édition ; un nouveau clic à l'intérieur descend d'un niveau, `Alt+clic` vise l'élément exact. La bulle de saisie affiche le chemin de l'élément : un clic sur un niveau vise ce parent.
|
|
340
|
+
- **Remarque sur une zone** — glisser trace un rectangle. La bulle montre l'aperçu de la zone et les **éléments détectés** : un élément entièrement compris est cité tel quel, un groupe qui déborde est ouvert pour citer ceux de ses éléments que la zone touche, un élément qui déborde est marqué « en partie » et décoché par défaut.
|
|
341
|
+
- **Remarque sur la page** — depuis le panneau, une seule remarque en cours par page.
|
|
342
|
+
- **Captures** — pour un élément ou une zone, « Joindre une capture » (coché par défaut) enregistre un PNG de ce que l'utilisateur commentait, rendu par le serveur au moment de la remarque.
|
|
343
|
+
- **Pastilles** — chaque remarque d'élément ou de zone porte un numéro sur la planche ; survoler une carte du panneau met sa cible en évidence, cliquer une pastille désigne sa carte.
|
|
344
|
+
- **Terminer les retours** — demande un **nom** pour le retour (« Retour n°N » proposé), choisit le périmètre (cette page ou toutes) et les remarques à transmettre, et montre exactement le texte que l'agent recevra. L'enregistrement fige un **retour numéroté et nommé**.
|
|
345
|
+
- **Après l'envoi** — les remarques restent visibles en lecture seule, marquées « Transmise · *nom du retour* », puis « Traitée » une fois traitées : pastille creuse, zone en pointillés. Une case du panneau les masque. Une remarque dont l'élément a disparu depuis le signale.
|
|
346
|
+
|
|
347
|
+
### Page « Retours »
|
|
348
|
+
|
|
349
|
+
Le bouton **Retours** de la barre d'outils (avec le nombre de retours pas encore entièrement traités) ouvre la liste de tous les retours, du plus récent au plus ancien, chacun avec son état — *À traiter*, *En cours* (n/total), *Traité* — et une barre d'avancement. Le détail d'un retour reprend ses remarques page par page, avec leur capture, et pour chacune : *À traiter*, ou *Traitée par l'agent / par vous* avec la date et la note de l'agent. Le nom du retour se modifie en place ; une case coche ou rouvre une remarque à la main, et « Tout marquer comme traité » (ou « Tout rouvrir ») agit sur tout le retour affiché ; le même bouton en haut de la page traite d'un coup tous les retours en attente, après confirmation. Une remarque déjà traitée par l'agent garde son auteur et sa note ; « Ouvrir la page » ramène sur la planche en mode Remarques. `Échap` ferme la page.
|
|
350
|
+
|
|
351
|
+
Les retours créés avant l'arrivée des noms reçoivent « Retour n°N », et aucune de leurs remarques n'est considérée comme traitée.
|
|
352
|
+
|
|
353
|
+
`Ctrl+Entrée` enregistre une remarque, `Échap` la ferme, puis quitte le mode. Le badge violet d'une page dans la liste compte ses remarques non transmises.
|
|
354
|
+
|
|
293
355
|
---
|
|
294
356
|
|
|
295
357
|
## Architecture
|
|
@@ -301,7 +363,7 @@ Deux processus, conformément au standard de l'écosystème (`@imenam/mcp-gui-in
|
|
|
301
363
|
|
|
302
364
|
Les deux communiquent par IPC. Le worker se termine proprement quand le master disparaît (canal IPC fermé, PID parent absent) et quand une autre instance est déjà enregistrée (HTTP 409), ce qui évite les GUI orphelines.
|
|
303
365
|
|
|
304
|
-
`shared/render.ts` contient le rendu SVG d'un élément, `shared/tree.ts` la résolution de la hiérarchie en coordonnées document, `shared/instances.ts` l'expansion des instances en groupes, `shared/node-defaults.ts` les valeurs par défaut et `shared/icons/` la bibliothèque d'icônes. Tous sont importés **à la fois** par l'exporteur côté serveur et par le canvas côté navigateur : ce que l'utilisateur voit est exactement ce que `export_design` — et donc `screenshot_design`, qui rasterise ce même SVG via resvg — produit.
|
|
366
|
+
`shared/feedback.ts` porte le modèle des remarques, la détection des éléments d'une zone et la mise en forme d'un retour ; le serveur et la GUI l'importent tous deux, ce qui garantit que l'aperçu affiché est le texte transmis. `shared/render.ts` contient le rendu SVG d'un élément, `shared/tree.ts` la résolution de la hiérarchie en coordonnées document, `shared/instances.ts` l'expansion des instances en groupes, `shared/node-defaults.ts` les valeurs par défaut et `shared/icons/` la bibliothèque d'icônes. Tous sont importés **à la fois** par l'exporteur côté serveur et par le canvas côté navigateur : ce que l'utilisateur voit est exactement ce que `export_design` — et donc `screenshot_design`, qui rasterise ce même SVG via resvg — produit.
|
|
305
367
|
|
|
306
368
|
Les invariants de hiérarchie — parent existant et de type `group`, absence de cycle (y compris dans le graphe composant/instance), taille des groupes et des instances recalculée, ordre préfixe — sont appliqués dans `DesignStore.mutate`, donc sur **tout** chemin d'écriture : outil MCP comme sauvegarde depuis la GUI.
|
|
307
369
|
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Remarques de l'utilisateur sur un design, et retours transmis à l'agent.
|
|
3
|
+
*
|
|
4
|
+
* Une remarque vise la page entière, un élément, ou une zone rectangulaire de la
|
|
5
|
+
* planche. Tant qu'elle est un brouillon, l'utilisateur la modifie librement ;
|
|
6
|
+
* « Terminer les retours » fige les remarques cochées dans un retour numéroté,
|
|
7
|
+
* nommé, que l'agent lit avec `get_feedback`. Une remarque transmise ne se
|
|
8
|
+
* modifie plus : le retour qui la contient reste exactement ce que l'agent a
|
|
9
|
+
* reçu. Seul son traitement évolue, déclaré par l'agent avec
|
|
10
|
+
* `mark_feedback_processed` ou par l'utilisateur dans la page des retours.
|
|
11
|
+
*
|
|
12
|
+
* Comme `render.ts` et `tree.ts`, ce module ne dépend d'aucune librairie : il
|
|
13
|
+
* est importé par le serveur MCP comme par la GUI, qui s'en sert pour afficher
|
|
14
|
+
* l'aperçu exact du retour avant de l'enregistrer.
|
|
15
|
+
*/
|
|
16
|
+
import type { RenderNode } from "./render.js";
|
|
17
|
+
import { type Box } from "./tree.js";
|
|
18
|
+
export type RemarkStatus = "draft" | "sent";
|
|
19
|
+
/** Élément de la planche cité par une remarque. */
|
|
20
|
+
export interface RemarkElement {
|
|
21
|
+
id: string;
|
|
22
|
+
name: string;
|
|
23
|
+
type: RenderNode["type"];
|
|
24
|
+
}
|
|
25
|
+
export type RemarkTarget = {
|
|
26
|
+
kind: "page";
|
|
27
|
+
} | {
|
|
28
|
+
kind: "node";
|
|
29
|
+
element: RemarkElement;
|
|
30
|
+
/** Noms des groupes englobants puis de l'élément, de la racine vers lui. */
|
|
31
|
+
path: string[];
|
|
32
|
+
/** Boîte de l'élément au moment de la remarque, en coordonnées document. */
|
|
33
|
+
box: Box;
|
|
34
|
+
} | {
|
|
35
|
+
kind: "area";
|
|
36
|
+
/** Zone tracée, en coordonnées document. */
|
|
37
|
+
box: Box;
|
|
38
|
+
/** Éléments de la zone que l'utilisateur a choisi de citer. */
|
|
39
|
+
elements: RemarkElement[];
|
|
40
|
+
};
|
|
41
|
+
export type RemarkKind = RemarkTarget["kind"];
|
|
42
|
+
export interface Remark {
|
|
43
|
+
id: string;
|
|
44
|
+
designId: string;
|
|
45
|
+
/** Numéro de la pastille sur la planche ; null pour la remarque de page. */
|
|
46
|
+
number: number | null;
|
|
47
|
+
target: RemarkTarget;
|
|
48
|
+
text: string;
|
|
49
|
+
/** Nom du fichier PNG de la capture, ou null sans capture. */
|
|
50
|
+
capture: string | null;
|
|
51
|
+
status: RemarkStatus;
|
|
52
|
+
/** Retour qui contient la remarque, une fois transmise. */
|
|
53
|
+
round: number | null;
|
|
54
|
+
createdAt: string;
|
|
55
|
+
updatedAt: string;
|
|
56
|
+
}
|
|
57
|
+
/** Traitement d'une remarque transmise : qui l'a déclarée traitée, quand, et ce qui a été fait. */
|
|
58
|
+
export interface RemarkProcessing {
|
|
59
|
+
at: string;
|
|
60
|
+
by: "agent" | "user";
|
|
61
|
+
note: string | null;
|
|
62
|
+
}
|
|
63
|
+
/** Remarque telle qu'elle figure dans un retour transmis. */
|
|
64
|
+
export interface RoundRemark {
|
|
65
|
+
id: string;
|
|
66
|
+
designId: string;
|
|
67
|
+
designName: string;
|
|
68
|
+
number: number | null;
|
|
69
|
+
target: RemarkTarget;
|
|
70
|
+
text: string;
|
|
71
|
+
capture: string | null;
|
|
72
|
+
/** Null tant que la remarque n'est pas traitée. */
|
|
73
|
+
processed: RemarkProcessing | null;
|
|
74
|
+
}
|
|
75
|
+
export interface FeedbackRound {
|
|
76
|
+
number: number;
|
|
77
|
+
/** Nom donné par l'utilisateur en terminant sa revue. */
|
|
78
|
+
title: string;
|
|
79
|
+
submittedAt: string;
|
|
80
|
+
remarks: RoundRemark[];
|
|
81
|
+
}
|
|
82
|
+
export type RoundStatus = "pending" | "in_progress" | "done";
|
|
83
|
+
/** Avancement d'un retour : à traiter, en cours, ou entièrement traité. */
|
|
84
|
+
export declare function roundProgress(round: Pick<FeedbackRound, "remarks">): {
|
|
85
|
+
done: number;
|
|
86
|
+
total: number;
|
|
87
|
+
status: RoundStatus;
|
|
88
|
+
};
|
|
89
|
+
/** Nom proposé pour un retour avant que l'utilisateur ne le change. */
|
|
90
|
+
export declare function defaultRoundTitle(number: number): string;
|
|
91
|
+
/** Élément touché par une zone. `partial` : il en déborde. */
|
|
92
|
+
export interface AreaHit extends RemarkElement {
|
|
93
|
+
partial: boolean;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Éléments qu'une zone recouvre, au niveau le plus utile pour les nommer.
|
|
97
|
+
*
|
|
98
|
+
* Un élément entièrement compris dans la zone est cité tel quel, sans détailler
|
|
99
|
+
* son contenu. Un groupe qui en déborde est ouvert, pour citer ceux de ses
|
|
100
|
+
* éléments que la zone touche. Un élément sans contenu qui en déborde (forme,
|
|
101
|
+
* texte, instance) est cité comme partiel. Les instances ne sont jamais
|
|
102
|
+
* ouvertes : leur contenu n'existe pas en stockage.
|
|
103
|
+
*/
|
|
104
|
+
export declare function elementsInArea(nodes: RenderNode[], area: Box): AreaHit[];
|
|
105
|
+
/** Noms des groupes englobants puis de l'élément, de la racine vers lui. */
|
|
106
|
+
export declare function nodePath(nodes: RenderNode[], id: string): string[];
|
|
107
|
+
/** Cible d'une remarque sur un élément, calculée sur l'état courant du design. */
|
|
108
|
+
export declare function nodeTarget(nodes: RenderNode[], id: string): Extract<RemarkTarget, {
|
|
109
|
+
kind: "node";
|
|
110
|
+
}>;
|
|
111
|
+
export declare function roundBox(box: Box): Box;
|
|
112
|
+
/**
|
|
113
|
+
* Ordre de lecture d'un lot de remarques : les pages dans l'ordre où elles
|
|
114
|
+
* apparaissent, puis dans chaque page la remarque de page d'abord et les autres
|
|
115
|
+
* par numéro de pastille. Le serveur fige un retour dans cet ordre, la GUI
|
|
116
|
+
* affiche son aperçu dans le même.
|
|
117
|
+
*/
|
|
118
|
+
export declare function orderRemarks<T extends Pick<Remark, "designId" | "number">>(remarks: T[]): T[];
|
|
119
|
+
export declare const PATH_SEPARATOR = " \u203A ";
|
|
120
|
+
/**
|
|
121
|
+
* Texte d'un retour, tel que l'agent le reçoit.
|
|
122
|
+
*
|
|
123
|
+
* La GUI l'affiche en aperçu avant l'enregistrement : c'est la même fonction,
|
|
124
|
+
* donc le même texte. Chaque remarque porte son identifiant, que l'agent passe
|
|
125
|
+
* à `mark_feedback_processed`. Les captures sont numérotées dans l'ordre des
|
|
126
|
+
* remarques ; `get_feedback` joint les images dans cet ordre.
|
|
127
|
+
*/
|
|
128
|
+
export declare function formatFeedbackRound(round: Pick<FeedbackRound, "number" | "title" | "submittedAt" | "remarks">): string;
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Remarques de l'utilisateur sur un design, et retours transmis à l'agent.
|
|
3
|
+
*
|
|
4
|
+
* Une remarque vise la page entière, un élément, ou une zone rectangulaire de la
|
|
5
|
+
* planche. Tant qu'elle est un brouillon, l'utilisateur la modifie librement ;
|
|
6
|
+
* « Terminer les retours » fige les remarques cochées dans un retour numéroté,
|
|
7
|
+
* nommé, que l'agent lit avec `get_feedback`. Une remarque transmise ne se
|
|
8
|
+
* modifie plus : le retour qui la contient reste exactement ce que l'agent a
|
|
9
|
+
* reçu. Seul son traitement évolue, déclaré par l'agent avec
|
|
10
|
+
* `mark_feedback_processed` ou par l'utilisateur dans la page des retours.
|
|
11
|
+
*
|
|
12
|
+
* Comme `render.ts` et `tree.ts`, ce module ne dépend d'aucune librairie : il
|
|
13
|
+
* est importé par le serveur MCP comme par la GUI, qui s'en sert pour afficher
|
|
14
|
+
* l'aperçu exact du retour avant de l'enregistrer.
|
|
15
|
+
*/
|
|
16
|
+
import { expandInstances } from "./instances.js";
|
|
17
|
+
import { ancestorsOf, boundsOf, boxesIntersect, childrenByParent, computeLayout } from "./tree.js";
|
|
18
|
+
/** Avancement d'un retour : à traiter, en cours, ou entièrement traité. */
|
|
19
|
+
export function roundProgress(round) {
|
|
20
|
+
const total = round.remarks.length;
|
|
21
|
+
const done = round.remarks.filter((remark) => remark.processed !== null).length;
|
|
22
|
+
return { done, total, status: done === 0 ? "pending" : done === total ? "done" : "in_progress" };
|
|
23
|
+
}
|
|
24
|
+
/** Nom proposé pour un retour avant que l'utilisateur ne le change. */
|
|
25
|
+
export function defaultRoundTitle(number) {
|
|
26
|
+
return `Retour n°${number}`;
|
|
27
|
+
}
|
|
28
|
+
function contains(outer, inner) {
|
|
29
|
+
return (inner.x >= outer.x &&
|
|
30
|
+
inner.y >= outer.y &&
|
|
31
|
+
inner.x + inner.width <= outer.x + outer.width &&
|
|
32
|
+
inner.y + inner.height <= outer.y + outer.height);
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Éléments qu'une zone recouvre, au niveau le plus utile pour les nommer.
|
|
36
|
+
*
|
|
37
|
+
* Un élément entièrement compris dans la zone est cité tel quel, sans détailler
|
|
38
|
+
* son contenu. Un groupe qui en déborde est ouvert, pour citer ceux de ses
|
|
39
|
+
* éléments que la zone touche. Un élément sans contenu qui en déborde (forme,
|
|
40
|
+
* texte, instance) est cité comme partiel. Les instances ne sont jamais
|
|
41
|
+
* ouvertes : leur contenu n'existe pas en stockage.
|
|
42
|
+
*/
|
|
43
|
+
export function elementsInArea(nodes, area) {
|
|
44
|
+
const layout = computeLayout(expandInstances(nodes));
|
|
45
|
+
const children = childrenByParent(nodes);
|
|
46
|
+
const hits = [];
|
|
47
|
+
const visit = (node) => {
|
|
48
|
+
const box = boundsOf(layout, node.id);
|
|
49
|
+
if (box.width <= 0 && box.height <= 0)
|
|
50
|
+
return;
|
|
51
|
+
if (!boxesIntersect(box, area))
|
|
52
|
+
return;
|
|
53
|
+
const element = { id: node.id, name: node.name, type: node.type };
|
|
54
|
+
if (contains(area, box)) {
|
|
55
|
+
hits.push({ ...element, partial: false });
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
const content = node.type === "group" ? (children.get(node.id) ?? []) : [];
|
|
59
|
+
if (content.length > 0)
|
|
60
|
+
content.forEach(visit);
|
|
61
|
+
else
|
|
62
|
+
hits.push({ ...element, partial: true });
|
|
63
|
+
};
|
|
64
|
+
(children.get(null) ?? []).forEach(visit);
|
|
65
|
+
return hits;
|
|
66
|
+
}
|
|
67
|
+
/** Noms des groupes englobants puis de l'élément, de la racine vers lui. */
|
|
68
|
+
export function nodePath(nodes, id) {
|
|
69
|
+
const node = nodes.find((candidate) => candidate.id === id);
|
|
70
|
+
if (!node)
|
|
71
|
+
return [];
|
|
72
|
+
return [...ancestorsOf(nodes, id).reverse().map((ancestor) => ancestor.name), node.name];
|
|
73
|
+
}
|
|
74
|
+
/** Cible d'une remarque sur un élément, calculée sur l'état courant du design. */
|
|
75
|
+
export function nodeTarget(nodes, id) {
|
|
76
|
+
const node = nodes.find((candidate) => candidate.id === id);
|
|
77
|
+
if (!node)
|
|
78
|
+
throw new Error(`Node "${id}" not found in the design.`);
|
|
79
|
+
const box = boundsOf(computeLayout(expandInstances(nodes)), id);
|
|
80
|
+
return {
|
|
81
|
+
kind: "node",
|
|
82
|
+
element: { id: node.id, name: node.name, type: node.type },
|
|
83
|
+
path: nodePath(nodes, id),
|
|
84
|
+
box: roundBox(box),
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
export function roundBox(box) {
|
|
88
|
+
const round = (value) => Math.round(value * 100) / 100;
|
|
89
|
+
return { x: round(box.x), y: round(box.y), width: round(box.width), height: round(box.height) };
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Ordre de lecture d'un lot de remarques : les pages dans l'ordre où elles
|
|
93
|
+
* apparaissent, puis dans chaque page la remarque de page d'abord et les autres
|
|
94
|
+
* par numéro de pastille. Le serveur fige un retour dans cet ordre, la GUI
|
|
95
|
+
* affiche son aperçu dans le même.
|
|
96
|
+
*/
|
|
97
|
+
export function orderRemarks(remarks) {
|
|
98
|
+
const pages = [...new Set(remarks.map((remark) => remark.designId))];
|
|
99
|
+
return [...remarks].sort((a, b) => pages.indexOf(a.designId) - pages.indexOf(b.designId) || (a.number ?? 0) - (b.number ?? 0));
|
|
100
|
+
}
|
|
101
|
+
export const PATH_SEPARATOR = " › ";
|
|
102
|
+
function formatBox(box) {
|
|
103
|
+
const round = (value) => Math.round(value);
|
|
104
|
+
return `x ${round(box.x)}, y ${round(box.y)}, ${round(box.width)}×${round(box.height)}`;
|
|
105
|
+
}
|
|
106
|
+
function formatElement(element) {
|
|
107
|
+
return `${element.id} "${element.name}" (${element.type})`;
|
|
108
|
+
}
|
|
109
|
+
function indent(text, prefix) {
|
|
110
|
+
return text
|
|
111
|
+
.split("\n")
|
|
112
|
+
.map((line) => `${prefix}${line}`)
|
|
113
|
+
.join("\n");
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Texte d'un retour, tel que l'agent le reçoit.
|
|
117
|
+
*
|
|
118
|
+
* La GUI l'affiche en aperçu avant l'enregistrement : c'est la même fonction,
|
|
119
|
+
* donc le même texte. Chaque remarque porte son identifiant, que l'agent passe
|
|
120
|
+
* à `mark_feedback_processed`. Les captures sont numérotées dans l'ordre des
|
|
121
|
+
* remarques ; `get_feedback` joint les images dans cet ordre.
|
|
122
|
+
*/
|
|
123
|
+
export function formatFeedbackRound(round) {
|
|
124
|
+
const pages = new Map();
|
|
125
|
+
for (const remark of round.remarks) {
|
|
126
|
+
const list = pages.get(remark.designId);
|
|
127
|
+
if (list)
|
|
128
|
+
list.push(remark);
|
|
129
|
+
else
|
|
130
|
+
pages.set(remark.designId, [remark]);
|
|
131
|
+
}
|
|
132
|
+
const progress = roundProgress(round);
|
|
133
|
+
const lines = [
|
|
134
|
+
`Feedback round ${round.number} "${round.title}" — submitted ${round.submittedAt} — ` +
|
|
135
|
+
`${round.remarks.length} remark(s) on ${pages.size} page(s), ${progress.done} processed.`,
|
|
136
|
+
"Coordinates are design pixels in the artboard's space, as in get_design.",
|
|
137
|
+
];
|
|
138
|
+
let image = 0;
|
|
139
|
+
for (const [designId, remarks] of pages) {
|
|
140
|
+
lines.push("", `Page "${remarks[0].designName}" (${designId})`);
|
|
141
|
+
for (const remark of remarks) {
|
|
142
|
+
const capture = remark.capture ? ` — capture: image ${++image}` : "";
|
|
143
|
+
const target = remark.target;
|
|
144
|
+
let head;
|
|
145
|
+
if (target.kind === "page") {
|
|
146
|
+
head = `- ${remark.id} [whole page]`;
|
|
147
|
+
}
|
|
148
|
+
else if (target.kind === "node") {
|
|
149
|
+
head =
|
|
150
|
+
`- ${remark.id} [#${remark.number} · element] ${formatElement(target.element)} — ` +
|
|
151
|
+
`path: ${target.path.join(PATH_SEPARATOR)} — box ${formatBox(target.box)}${capture}`;
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
const elements = target.elements.length > 0 ? target.elements.map(formatElement).join(", ") : "no element selected";
|
|
155
|
+
head = `- ${remark.id} [#${remark.number} · area] box ${formatBox(target.box)} — elements: ${elements}${capture}`;
|
|
156
|
+
}
|
|
157
|
+
lines.push(head, indent(remark.text, " "));
|
|
158
|
+
if (remark.processed) {
|
|
159
|
+
const note = remark.processed.note ? `: ${remark.processed.note}` : "";
|
|
160
|
+
lines.push(` ✓ processed by ${remark.processed.by} at ${remark.processed.at}${note}`);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return lines.join("\n");
|
|
165
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { defaultLineHeight } from "./render.js";
|
|
2
2
|
import { DEFAULT_ICON_STROKE_WIDTH, ICON_GRID } from "./icons/index.js";
|
|
3
|
-
export const NODE_KINDS = ["rect", "ellipse", "line", "text", "icon", "group", "instance"];
|
|
3
|
+
export const NODE_KINDS = ["rect", "ellipse", "line", "text", "icon", "image", "group", "instance"];
|
|
4
4
|
/** Types que l'utilisateur peut dessiner directement ; un groupe se cree a partir d'une selection. */
|
|
5
5
|
export const DRAWABLE_KINDS = ["rect", "ellipse", "line", "text", "icon"];
|
|
6
6
|
export const NODE_DEFAULTS = {
|
|
@@ -34,6 +34,9 @@ export const NODE_DEFAULTS = {
|
|
|
34
34
|
stroke: "#1a1a18",
|
|
35
35
|
strokeWidth: DEFAULT_ICON_STROKE_WIDTH,
|
|
36
36
|
},
|
|
37
|
+
// Une image importee sans taille prend ses dimensions naturelles : celles-ci
|
|
38
|
+
// ne servent qu'a un noeud ecrit a la main sans largeur ni hauteur.
|
|
39
|
+
image: { width: 200, height: 150, fit: "cover" },
|
|
37
40
|
// Un groupe n'a pas de taille propre : elle est deduite de son contenu.
|
|
38
41
|
group: { width: 0, height: 0 },
|
|
39
42
|
// Une instance non plus : sa taille est celle du composant qu'elle reproduit.
|
|
@@ -46,6 +49,7 @@ export const NODE_STYLE_FIELDS = {
|
|
|
46
49
|
line: ["stroke", "strokeWidth"],
|
|
47
50
|
text: ["text", "color", "fontSize", "fontFamily", "fontWeight", "fontStyle", "textAlign", "lineHeight"],
|
|
48
51
|
icon: ["icon", "fill", "stroke", "strokeWidth"],
|
|
52
|
+
image: ["image", "fit"],
|
|
49
53
|
group: [],
|
|
50
54
|
// Rien ne se surcharge dans une instance : son apparence est celle de son
|
|
51
55
|
// composant. Seul le lien lui-meme se change, ce qui l'echange de composant.
|
|
@@ -72,6 +76,7 @@ const LABELS = {
|
|
|
72
76
|
line: "Ligne",
|
|
73
77
|
text: "Texte",
|
|
74
78
|
icon: "Icône",
|
|
79
|
+
image: "Image",
|
|
75
80
|
group: "Groupe",
|
|
76
81
|
instance: "Instance",
|
|
77
82
|
};
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
export const NODE_FIELD_DOCS = {
|
|
10
10
|
id: "Explicit id. Generated when omitted; must be unique within the design.",
|
|
11
|
-
type: "Kind of node: rect, ellipse, line, text, icon, group or instance.",
|
|
11
|
+
type: "Kind of node: rect, ellipse, line, text, icon, image, group or instance.",
|
|
12
12
|
name: "Layer name shown in the GUI. Purely descriptive.",
|
|
13
13
|
parentId: "Id of the containing group, or null for the root level of the artboard. " +
|
|
14
14
|
"Changing it reparents the node — its x/y are then read in the new parent's space, so it moves on screen. " +
|
|
@@ -40,6 +40,10 @@ export const NODE_FIELD_DOCS = {
|
|
|
40
40
|
"the result is sharp at any size, restyles in one field, and stays readable to whoever reads the design later. " +
|
|
41
41
|
"Call search_icons to find a name from a description (it also understands synonyms, so \"basket\" finds \"shopping-cart\"). " +
|
|
42
42
|
"The node's box is square: the drawing is scaled uniformly to fit it and centred, never stretched.",
|
|
43
|
+
image: "Image nodes only: name of the stored image file, e.g. \"img_3f9a1c2b7d4e.png\". It is returned by add_image, which is the way " +
|
|
44
|
+
"to bring a new picture into the design; reuse the same name to place that picture again without importing it twice.",
|
|
45
|
+
fit: "Image nodes only: how the picture fills its box. cover (default) fills the whole box and crops the overflow, " +
|
|
46
|
+
"contain shows the whole picture inside the box, leaving empty bands, fill stretches it to the box.",
|
|
43
47
|
componentId: "Instance nodes only: id of the GROUP this instance mirrors. The group's content is NOT copied — it is resolved " +
|
|
44
48
|
"at render time, so editing the group updates every instance of it at once, and a repeated block stays a single " +
|
|
45
49
|
"node to read and to change. Whenever the same block appears more than once (cards, list rows, menu items, " +
|
package/dist/shared/render.d.ts
CHANGED
|
@@ -6,13 +6,13 @@
|
|
|
6
6
|
* Ce module ne dépend volontairement d'aucune librairie (pas de zod) pour rester
|
|
7
7
|
* importable depuis le bundle GUI.
|
|
8
8
|
*/
|
|
9
|
-
import { normalizedBox } from "./tree.js";
|
|
9
|
+
import { normalizedBox, type Box } from "./tree.js";
|
|
10
10
|
import { type IconElement } from "./icons/index.js";
|
|
11
11
|
export { normalizedBox };
|
|
12
12
|
export type { Box, Point } from "./tree.js";
|
|
13
13
|
export interface RenderNode {
|
|
14
14
|
id: string;
|
|
15
|
-
type: "rect" | "ellipse" | "line" | "text" | "icon" | "group" | "instance";
|
|
15
|
+
type: "rect" | "ellipse" | "line" | "text" | "icon" | "image" | "group" | "instance";
|
|
16
16
|
name: string;
|
|
17
17
|
/** Groupe conteneur, ou null a la racine. Les coordonnees sont relatives a ce parent. */
|
|
18
18
|
parentId: string | null;
|
|
@@ -30,6 +30,10 @@ export interface RenderNode {
|
|
|
30
30
|
radius?: number;
|
|
31
31
|
/** Nom de l'icone dans la bibliotheque embarquee, p.ex. "shopping-cart". Type `icon` uniquement. */
|
|
32
32
|
icon?: string;
|
|
33
|
+
/** Nom du fichier dans la mediatheque, p.ex. "img_3f9a1c2b7d4e.png". Type `image` uniquement. */
|
|
34
|
+
image?: string;
|
|
35
|
+
/** Remplissage de la boite par l'image. Type `image` uniquement. */
|
|
36
|
+
fit?: "cover" | "contain" | "fill";
|
|
33
37
|
text?: string;
|
|
34
38
|
color?: string;
|
|
35
39
|
fontSize?: number;
|
|
@@ -131,13 +135,28 @@ export declare function iconElementsOf(node: RenderNode): IconElement[];
|
|
|
131
135
|
* en noir sur une icone blanche.
|
|
132
136
|
*/
|
|
133
137
|
export declare function iconStyle(node: RenderNode): Record<string, string>;
|
|
138
|
+
/**
|
|
139
|
+
* Adresse d'une image de la mediatheque dans le SVG produit.
|
|
140
|
+
*
|
|
141
|
+
* Elle depend de qui lit le SVG : le canvas la charge depuis l'API du worker,
|
|
142
|
+
* resvg et les exports autonomes ont besoin des octets en data URI.
|
|
143
|
+
*/
|
|
144
|
+
export type ImageHref = (image: string) => string;
|
|
145
|
+
export interface RenderOptions {
|
|
146
|
+
/** Obligatoire des que la planche contient une image. */
|
|
147
|
+
imageHref?: ImageHref;
|
|
148
|
+
}
|
|
149
|
+
/** Valeur de `preserveAspectRatio` correspondant au `fit` du noeud. */
|
|
150
|
+
export declare function imageAspect(node: RenderNode): string;
|
|
151
|
+
/** Adresse de l'image du noeud ; echoue si l'appelant n'a pas dit comment la resoudre. */
|
|
152
|
+
export declare function imageSource(node: RenderNode, options: RenderOptions): string;
|
|
134
153
|
export declare function iconElementToSvg([tag, attributes]: IconElement): string;
|
|
135
154
|
/**
|
|
136
155
|
* Markup SVG d'un seul noeud, sans wrapper — utilisé par l'export et par le canvas.
|
|
137
156
|
* Un groupe ne produit rien par lui-meme : c'est un conteneur, rendu par
|
|
138
157
|
* `documentToSvg` (serveur) ou par le canvas (navigateur).
|
|
139
158
|
*/
|
|
140
|
-
export declare function nodeToSvg(node: RenderNode): string;
|
|
159
|
+
export declare function nodeToSvg(node: RenderNode, options?: RenderOptions): string;
|
|
141
160
|
export interface RenderDocument {
|
|
142
161
|
name: string;
|
|
143
162
|
width: number;
|
|
@@ -145,7 +164,13 @@ export interface RenderDocument {
|
|
|
145
164
|
background: string;
|
|
146
165
|
nodes: RenderNode[];
|
|
147
166
|
}
|
|
148
|
-
export declare function documentToSvg(doc: RenderDocument): string;
|
|
167
|
+
export declare function documentToSvg(doc: RenderDocument, options?: RenderOptions): string;
|
|
168
|
+
/**
|
|
169
|
+
* SVG cadré sur une zone rectangulaire de la planche, en coordonnées document.
|
|
170
|
+
* Tout ce qui recoupe la zone y figure, coupé à ses bords ; les éléments
|
|
171
|
+
* semi-transparents qui ne la recoupent pas sont omis.
|
|
172
|
+
*/
|
|
173
|
+
export declare function regionToSvg(doc: RenderDocument, region: Box, options?: RenderOptions): string;
|
|
149
174
|
/**
|
|
150
175
|
* SVG cadré sur un seul élément — l'élément et son contenu, rien d'autre.
|
|
151
176
|
*
|
|
@@ -153,4 +178,4 @@ export declare function documentToSvg(doc: RenderDocument): string;
|
|
|
153
178
|
* position document : le viewBox peut alors être exprimé dans le même repère
|
|
154
179
|
* que `computeLayout`, donc directement sur la boîte englobante de l'élément.
|
|
155
180
|
*/
|
|
156
|
-
export declare function subtreeToSvg(doc: RenderDocument, nodeId: string, padding?: number): string;
|
|
181
|
+
export declare function subtreeToSvg(doc: RenderDocument, nodeId: string, padding?: number, options?: RenderOptions): string;
|