@esfaenza/flow-builder 20.0.0 → 20.3.2
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 +393 -354
- package/fesm2022/esfaenza-flow-builder.mjs +1459 -244
- package/fesm2022/esfaenza-flow-builder.mjs.map +1 -1
- package/index.d.ts +575 -61
- package/package.json +1 -1
- package/styles/flow-builder.css +25 -0
package/README.md
CHANGED
|
@@ -1,354 +1,393 @@
|
|
|
1
|
-
# flow-builder
|
|
2
|
-
|
|
3
|
-
Editor visuale per i flow descritti in [`FRONTEND.md`](../../FRONTEND.md): un componente
|
|
4
|
-
**standalone Angular** che disegna il grafo, edita ogni tipo di elemento, valida, versiona e
|
|
5
|
-
prova un flow.
|
|
6
|
-
|
|
7
|
-
Requisiti: **Angular ≥ 18** (sviluppato e verificato su 19.2), TypeScript.
|
|
8
|
-
|
|
9
|
-
---
|
|
10
|
-
|
|
11
|
-
## Indice
|
|
12
|
-
|
|
13
|
-
1. [Integrazione in tre passi](#integrazione-in-tre-passi)
|
|
14
|
-
2. [L'interfaccia verso il backend](#linterfaccia-verso-il-backend)
|
|
15
|
-
3. [Cosa fa il builder](#cosa-fa-il-builder)
|
|
16
|
-
4. [Architettura](#architettura)
|
|
17
|
-
5. [Dipendenze esterne](#dipendenze-esterne)
|
|
18
|
-
6. [Tema e personalizzazione](#tema-e-personalizzazione)
|
|
19
|
-
7. [Le trappole della §13, una per una](#le-trappole-della-13-una-per-una)
|
|
20
|
-
8. [Limiti noti](#limiti-noti)
|
|
21
|
-
|
|
22
|
-
---
|
|
23
|
-
|
|
24
|
-
## Integrazione in tre passi
|
|
25
|
-
|
|
26
|
-
### 1. Fornire l'implementazione dell'interfaccia
|
|
27
|
-
|
|
28
|
-
`FlowBuilderApi` e' l'unica porta verso il backend: **tutto** il traffico di dati passa da
|
|
29
|
-
lì, e nessun componente della libreria conosce HTTP.
|
|
30
|
-
|
|
31
|
-
```ts
|
|
32
|
-
import { FlowBuilderApi, HttpFlowBuilderApi, FLOW_BUILDER_HTTP_CONFIG } from 'flow-builder';
|
|
33
|
-
|
|
34
|
-
bootstrapApplication(App, {
|
|
35
|
-
providers: [
|
|
36
|
-
provideHttpClient(),
|
|
37
|
-
{ provide: FLOW_BUILDER_HTTP_CONFIG, useValue: { baseUrl: '/api' } },
|
|
38
|
-
{ provide: FlowBuilderApi, useClass: HttpFlowBuilderApi },
|
|
39
|
-
],
|
|
40
|
-
});
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
`HttpFlowBuilderApi` usa le rotte **proposte** dalla §6. Se le vostre differiscono, ci sono
|
|
44
|
-
due strade e nessuna delle due tocca l'editor:
|
|
45
|
-
|
|
46
|
-
```ts
|
|
47
|
-
// 1. estendere e sovrascrivere i soli metodi con rotta diversa
|
|
48
|
-
class NostraApi extends HttpFlowBuilderApi {
|
|
49
|
-
override loadFlow(flowName: string, version?: number) { /* … */ }
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// 2. implementare `FlowBuilderApi` da zero: e' una classe astratta, quindi il compilatore
|
|
53
|
-
// elenca esattamente i metodi che mancano
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
I metodi con implementazione di default rifiutano con `MissingService`: un ambiente che non
|
|
57
|
-
espone `parseFlow`, `exportFlow` o le primitive di esecuzione resta usabile, con il comando
|
|
58
|
-
corrispondente degradato invece di rotto.
|
|
59
|
-
|
|
60
|
-
### 2. Includere i due fogli di stile
|
|
61
|
-
|
|
62
|
-
```json
|
|
63
|
-
// angular.json → architect.build.options.styles
|
|
64
|
-
"styles": [
|
|
65
|
-
"node_modules/@foblex/flow/styles/default.scss",
|
|
66
|
-
"node_modules/flow-builder/styles/flow-builder.css",
|
|
67
|
-
"src/styles.css"
|
|
68
|
-
]
|
|
69
|
-
```
|
|
70
|
-
|
|
71
|
-
Servono entrambi. `flow-builder.css` contiene le variabili del tema, i controlli di form e
|
|
72
|
-
gli stili degli archi: i path SVG delle connessioni sono creati a runtime e non portano
|
|
73
|
-
l'attributo di scope di Angular, quindi un CSS incapsulato non li raggiunge.
|
|
74
|
-
|
|
75
|
-
### 3. Montare il componente
|
|
76
|
-
|
|
77
|
-
```html
|
|
78
|
-
<fb-flow-builder
|
|
79
|
-
[flowName]="'ApprovazioneOrdine'"
|
|
80
|
-
[version]="null"
|
|
81
|
-
[author]="utenteCorrente"
|
|
82
|
-
(saved)="onSaved($event)"
|
|
83
|
-
(activated)="onActivated($event)"
|
|
84
|
-
/>
|
|
85
|
-
```
|
|
86
|
-
|
|
87
|
-
| Input | Significato |
|
|
88
|
-
|---|---|
|
|
89
|
-
| `flowName` | il flow da aprire; `null` → si parte da un flow nuovo |
|
|
90
|
-
| `version` | la versione da aprire; assente → l'attiva se c'e', altrimenti l'ultima (§6.1) |
|
|
91
|
-
| `author` | registrato sulla versione e mostrato nell'elenco |
|
|
92
|
-
| `defaultProcessType` | `processType` iniziale di un flow nuovo |
|
|
93
|
-
| `inspectorMode` | `'dialog'` (predefinito) apre il dettaglio dell'elemento in una finestra sopra il canvas, come il flow builder di Salesforce; `'panel'` lo tiene nel pannello laterale |
|
|
94
|
-
|
|
95
|
-
| Output | Quando |
|
|
96
|
-
|---|---|
|
|
97
|
-
| `saved` | dopo un salvataggio riuscito, con `FlowSaveResult` |
|
|
98
|
-
| `activated` | dopo un'attivazione riuscita |
|
|
99
|
-
|
|
100
|
-
Il componente vuole un'altezza: `<fb-flow-builder style="height: 100vh">`, oppure un
|
|
101
|
-
contenitore flex. Gli store sono provider **del componente**, quindi due builder sulla stessa
|
|
102
|
-
pagina editano due flow indipendenti.
|
|
103
|
-
|
|
104
|
-
---
|
|
105
|
-
|
|
106
|
-
## L'interfaccia verso il backend
|
|
107
|
-
|
|
108
|
-
Metodi raggruppati come nella specifica. Gli **astratti** sono obbligatori; i **concreti**
|
|
109
|
-
sono opzionali e rifiutano con `MissingService` se non sovrascritti.
|
|
110
|
-
|
|
111
|
-
| §6.1 Lettura | |
|
|
112
|
-
|---|---|
|
|
113
|
-
| `listFlows(query?)` | astratto |
|
|
114
|
-
| `listVersions(flowName)` | astratto |
|
|
115
|
-
| `loadFlow(flowName, version?)` | astratto |
|
|
116
|
-
| `exportFlow(flowName, query?)` | opzionale |
|
|
117
|
-
| `parseFlow(definition)` | opzionale — serve all'import di un file |
|
|
118
|
-
|
|
119
|
-
| §6.2 Scrittura | |
|
|
120
|
-
|---|---|
|
|
121
|
-
| `createFlow(request)` | astratto |
|
|
122
|
-
| `saveFlow(flowName, request)` | astratto |
|
|
123
|
-
| `createVersion(flowName, request?)` | astratto |
|
|
124
|
-
| `activateVersion(flowName, version)` | astratto |
|
|
125
|
-
| `deactivateFlow(flowName)` | astratto |
|
|
126
|
-
| `cloneFlow`, `deleteVersion`, `deleteFlow` | opzionali |
|
|
127
|
-
|
|
128
|
-
| §6.3 Validazione | |
|
|
129
|
-
|---|---|
|
|
130
|
-
| `validateDefinition(definition)` | astratto — e' il pannello dei problemi |
|
|
131
|
-
| `validateVersion(flowName, version)` | opzionale |
|
|
132
|
-
|
|
133
|
-
| §6.4 Dizionari e cataloghi | |
|
|
134
|
-
|---|---|
|
|
135
|
-
| `getDictionaries()` | astratto |
|
|
136
|
-
| `getReferences(query)` / `getWritableReferences(query)` | astratti |
|
|
137
|
-
| `getConnectorTargets(definition, excluding?)` | astratto |
|
|
138
|
-
| `getOutline(definition)` | astratto |
|
|
139
|
-
| `listObjects()`, `listFields(object, usage?)` | astratti |
|
|
140
|
-
| `listActionTypes` / `listActions` / `listActionParameters` | astratti |
|
|
141
|
-
| `listScripts` / `listScriptParameters` | astratti |
|
|
142
|
-
| `listForms` / `listFormParameters` | astratti |
|
|
143
|
-
| `listEnumTypes`, `listEvents`, `listSubflowCandidates` | astratti |
|
|
144
|
-
| `describeObject`, `listFieldValues` | opzionali |
|
|
145
|
-
|
|
146
|
-
| §6.5 Esecuzione | |
|
|
147
|
-
|---|---|
|
|
148
|
-
| `startInterview`, `respondToScreen`, `resumeInterview`, `inspectInterview`, `abandonInterview` | opzionali — senza, il pannello «Prova» segnala che la funzione non c'e' |
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
`
|
|
154
|
-
`
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
`
|
|
173
|
-
`
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
**
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
**
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
`
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
**
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
1
|
+
# flow-builder
|
|
2
|
+
|
|
3
|
+
Editor visuale per i flow descritti in [`FRONTEND.md`](../../FRONTEND.md): un componente
|
|
4
|
+
**standalone Angular** che disegna il grafo, edita ogni tipo di elemento, valida, versiona e
|
|
5
|
+
prova un flow.
|
|
6
|
+
|
|
7
|
+
Requisiti: **Angular ≥ 18** (sviluppato e verificato su 19.2), TypeScript.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Indice
|
|
12
|
+
|
|
13
|
+
1. [Integrazione in tre passi](#integrazione-in-tre-passi)
|
|
14
|
+
2. [L'interfaccia verso il backend](#linterfaccia-verso-il-backend)
|
|
15
|
+
3. [Cosa fa il builder](#cosa-fa-il-builder)
|
|
16
|
+
4. [Architettura](#architettura)
|
|
17
|
+
5. [Dipendenze esterne](#dipendenze-esterne)
|
|
18
|
+
6. [Tema e personalizzazione](#tema-e-personalizzazione)
|
|
19
|
+
7. [Le trappole della §13, una per una](#le-trappole-della-13-una-per-una)
|
|
20
|
+
8. [Limiti noti](#limiti-noti)
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Integrazione in tre passi
|
|
25
|
+
|
|
26
|
+
### 1. Fornire l'implementazione dell'interfaccia
|
|
27
|
+
|
|
28
|
+
`FlowBuilderApi` e' l'unica porta verso il backend: **tutto** il traffico di dati passa da
|
|
29
|
+
lì, e nessun componente della libreria conosce HTTP.
|
|
30
|
+
|
|
31
|
+
```ts
|
|
32
|
+
import { FlowBuilderApi, HttpFlowBuilderApi, FLOW_BUILDER_HTTP_CONFIG } from 'flow-builder';
|
|
33
|
+
|
|
34
|
+
bootstrapApplication(App, {
|
|
35
|
+
providers: [
|
|
36
|
+
provideHttpClient(),
|
|
37
|
+
{ provide: FLOW_BUILDER_HTTP_CONFIG, useValue: { baseUrl: '/api' } },
|
|
38
|
+
{ provide: FlowBuilderApi, useClass: HttpFlowBuilderApi },
|
|
39
|
+
],
|
|
40
|
+
});
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
`HttpFlowBuilderApi` usa le rotte **proposte** dalla §6. Se le vostre differiscono, ci sono
|
|
44
|
+
due strade e nessuna delle due tocca l'editor:
|
|
45
|
+
|
|
46
|
+
```ts
|
|
47
|
+
// 1. estendere e sovrascrivere i soli metodi con rotta diversa
|
|
48
|
+
class NostraApi extends HttpFlowBuilderApi {
|
|
49
|
+
override loadFlow(flowName: string, version?: number) { /* … */ }
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// 2. implementare `FlowBuilderApi` da zero: e' una classe astratta, quindi il compilatore
|
|
53
|
+
// elenca esattamente i metodi che mancano
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
I metodi con implementazione di default rifiutano con `MissingService`: un ambiente che non
|
|
57
|
+
espone `parseFlow`, `exportFlow` o le primitive di esecuzione resta usabile, con il comando
|
|
58
|
+
corrispondente degradato invece di rotto.
|
|
59
|
+
|
|
60
|
+
### 2. Includere i due fogli di stile
|
|
61
|
+
|
|
62
|
+
```json
|
|
63
|
+
// angular.json → architect.build.options.styles
|
|
64
|
+
"styles": [
|
|
65
|
+
"node_modules/@foblex/flow/styles/default.scss",
|
|
66
|
+
"node_modules/flow-builder/styles/flow-builder.css",
|
|
67
|
+
"src/styles.css"
|
|
68
|
+
]
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Servono entrambi. `flow-builder.css` contiene le variabili del tema, i controlli di form e
|
|
72
|
+
gli stili degli archi: i path SVG delle connessioni sono creati a runtime e non portano
|
|
73
|
+
l'attributo di scope di Angular, quindi un CSS incapsulato non li raggiunge.
|
|
74
|
+
|
|
75
|
+
### 3. Montare il componente
|
|
76
|
+
|
|
77
|
+
```html
|
|
78
|
+
<fb-flow-builder
|
|
79
|
+
[flowName]="'ApprovazioneOrdine'"
|
|
80
|
+
[version]="null"
|
|
81
|
+
[author]="utenteCorrente"
|
|
82
|
+
(saved)="onSaved($event)"
|
|
83
|
+
(activated)="onActivated($event)"
|
|
84
|
+
/>
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
| Input | Significato |
|
|
88
|
+
|---|---|
|
|
89
|
+
| `flowName` | il flow da aprire; `null` → si parte da un flow nuovo |
|
|
90
|
+
| `version` | la versione da aprire; assente → l'attiva se c'e', altrimenti l'ultima (§6.1) |
|
|
91
|
+
| `author` | registrato sulla versione e mostrato nell'elenco |
|
|
92
|
+
| `defaultProcessType` | `processType` iniziale di un flow nuovo |
|
|
93
|
+
| `inspectorMode` | `'dialog'` (predefinito) apre il dettaglio dell'elemento in una finestra sopra il canvas, come il flow builder di Salesforce; `'panel'` lo tiene nel pannello laterale |
|
|
94
|
+
|
|
95
|
+
| Output | Quando |
|
|
96
|
+
|---|---|
|
|
97
|
+
| `saved` | dopo un salvataggio riuscito, con `FlowSaveResult` |
|
|
98
|
+
| `activated` | dopo un'attivazione riuscita |
|
|
99
|
+
|
|
100
|
+
Il componente vuole un'altezza: `<fb-flow-builder style="height: 100vh">`, oppure un
|
|
101
|
+
contenitore flex. Gli store sono provider **del componente**, quindi due builder sulla stessa
|
|
102
|
+
pagina editano due flow indipendenti.
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## L'interfaccia verso il backend
|
|
107
|
+
|
|
108
|
+
Metodi raggruppati come nella specifica. Gli **astratti** sono obbligatori; i **concreti**
|
|
109
|
+
sono opzionali e rifiutano con `MissingService` se non sovrascritti.
|
|
110
|
+
|
|
111
|
+
| §6.1 Lettura | |
|
|
112
|
+
|---|---|
|
|
113
|
+
| `listFlows(query?)` | astratto |
|
|
114
|
+
| `listVersions(flowName)` | astratto |
|
|
115
|
+
| `loadFlow(flowName, version?)` | astratto |
|
|
116
|
+
| `exportFlow(flowName, query?)` | opzionale |
|
|
117
|
+
| `parseFlow(definition)` | opzionale — serve all'import di un file |
|
|
118
|
+
|
|
119
|
+
| §6.2 Scrittura | |
|
|
120
|
+
|---|---|
|
|
121
|
+
| `createFlow(request)` | astratto |
|
|
122
|
+
| `saveFlow(flowName, request)` | astratto |
|
|
123
|
+
| `createVersion(flowName, request?)` | astratto |
|
|
124
|
+
| `activateVersion(flowName, version)` | astratto |
|
|
125
|
+
| `deactivateFlow(flowName)` | astratto |
|
|
126
|
+
| `cloneFlow`, `deleteVersion`, `deleteFlow` | opzionali |
|
|
127
|
+
|
|
128
|
+
| §6.3 Validazione | |
|
|
129
|
+
|---|---|
|
|
130
|
+
| `validateDefinition(definition)` | astratto — e' il pannello dei problemi |
|
|
131
|
+
| `validateVersion(flowName, version)` | opzionale |
|
|
132
|
+
|
|
133
|
+
| §6.4 Dizionari e cataloghi | |
|
|
134
|
+
|---|---|
|
|
135
|
+
| `getDictionaries(processType?)` | astratto — `processType` filtra le globali (§4.1) |
|
|
136
|
+
| `getReferences(query)` / `getWritableReferences(query)` | astratti |
|
|
137
|
+
| `getConnectorTargets(definition, excluding?)` | astratto |
|
|
138
|
+
| `getOutline(definition)` | astratto |
|
|
139
|
+
| `listObjects()`, `listFields(object, usage?)` | astratti |
|
|
140
|
+
| `listActionTypes` / `listActions` / `listActionParameters` | astratti |
|
|
141
|
+
| `listScripts` / `listScriptParameters` | astratti |
|
|
142
|
+
| `listForms` / `listFormParameters` | astratti |
|
|
143
|
+
| `listEnumTypes`, `listEvents`, `listSubflowCandidates` | astratti |
|
|
144
|
+
| `describeObject`, `listFieldValues` | opzionali |
|
|
145
|
+
|
|
146
|
+
| §6.5 Esecuzione | |
|
|
147
|
+
|---|---|
|
|
148
|
+
| `startInterview`, `respondToScreen`, `resumeInterview`, `inspectInterview`, `abandonInterview` | opzionali — senza, il pannello «Prova» segnala che la funzione non c'e' |
|
|
149
|
+
| `completeStageStep(request)` | opzionale — conclude uno step di orchestrazione (§5.13) |
|
|
150
|
+
|
|
151
|
+
**Errori.** Ogni metodo, in caso di rifiuto, deve fallire con un `FlowApiError`
|
|
152
|
+
categorizzato (§10). L'editor si comporta in base alla `category`, mai leggendo il
|
|
153
|
+
messaggio: `NotEditable` fa comparire «Nuova versione» al posto di «Salva»,
|
|
154
|
+
`VersionConflict` apre il banner con «ricarica» / «salva come nuova versione»,
|
|
155
|
+
`ValidationFailed` apre il pannello dei problemi con il payload.
|
|
156
|
+
|
|
157
|
+
```ts
|
|
158
|
+
throw new FlowApiError('VersionConflict', 'Ha salvato mrossi.', undefined, 'mrossi');
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Un'implementazione completa e commentata sta in
|
|
162
|
+
[`projects/demo/src/app/backend/in-memory-flow-builder-api.ts`](../demo/src/app/backend/in-memory-flow-builder-api.ts):
|
|
163
|
+
copre tutte le primitive, il ciclo di vita, la concorrenza ottimistica e un esecutore finto,
|
|
164
|
+
senza una sola richiesta di rete.
|
|
165
|
+
|
|
166
|
+
---
|
|
167
|
+
|
|
168
|
+
## Cosa fa il builder
|
|
169
|
+
|
|
170
|
+
**Canvas.** Node draggabili con l'ingresso sul bordo superiore e **un'uscita per ramo sul
|
|
171
|
+
bordo inferiore**, in fila e nell'ordine del modello, secondo la mappa della §3.5:
|
|
172
|
+
`connector`, `faultConnector`, `timeoutConnector`,
|
|
173
|
+
`rules[].connector`, `nextValueConnector` / `noMoreValuesConnector`,
|
|
174
|
+
`waitEvents[].connector`, `scheduledPaths[].connector`. Archi colorati per `kind` (Fault
|
|
175
|
+
rosso, Rule viola ed etichettato, Default tratteggiato, LoopNext verde), `isGoTo` reso come
|
|
176
|
+
salto, pan/zoom, minimap, selezione, creazione e riassegnazione di archi trascinando,
|
|
177
|
+
drag-and-drop dalla palette, comando «Riordina» che ricalcola e **scrive** le coordinate nel
|
|
178
|
+
documento. I node non raggiungibili e i rami dichiarati senza destinazione sono marcati
|
|
179
|
+
sul canvas.
|
|
180
|
+
|
|
181
|
+
Il lato da cui gli archi entrano ed escono e' dichiarato (`fConnectorConnectableSide`:
|
|
182
|
+
`TOP` sull'ingresso, `BOTTOM` sulle uscite), non calcolato: lasciandolo calcolare a
|
|
183
|
+
@foblex/flow, un ramo che risale — il ritorno di un Loop, per esempio — uscirebbe di fianco e
|
|
184
|
+
il grafo perderebbe la lettura dall'alto verso il basso. La larghezza del node cresce a
|
|
185
|
+
scaglioni col numero di rami, perche' le etichette delle uscite stanno in fila: arriva da una
|
|
186
|
+
**classe** (`.fb-node--outlets-N`) e non da uno stile inline, perche' @foblex/flow riscrive
|
|
187
|
+
l'attributo `style` del node a ogni spostamento e uno `[style.width]` verrebbe cancellato. La
|
|
188
|
+
tabella delle larghezze e' la stessa che il comando «Riordina» passa a dagre: se divergessero,
|
|
189
|
+
i node larghi resterebbero sovrapposti.
|
|
190
|
+
|
|
191
|
+
**Dettaglio dell'elemento.** Con `inspectorMode="dialog"` (predefinito) il form si apre in una
|
|
192
|
+
finestra sopra il canvas: doppio click sul node, bottone «✎» sul node, comando «Dettaglio»
|
|
193
|
+
nella top bar, click su un rilievo nel pannello dei problemi, o creazione di un elemento dalla
|
|
194
|
+
palette. Il click singolo **seleziona soltanto**: un click capita anche solo per spostare un
|
|
195
|
+
node. La dialog non ha «Annulla» — le modifiche entrano nel documento mentre si digita,
|
|
196
|
+
esattamente come nel pannello, e tornare indietro e' compito dell'annulla dell'editor. Vive
|
|
197
|
+
dentro il componente e non in fondo al `body`: la libreria e' innestabile in una pagina
|
|
198
|
+
qualsiasi.
|
|
199
|
+
|
|
200
|
+
**Palette.** Costruita da `elementTypes`: nessun tipo e' cablato, e i due non supportati
|
|
201
|
+
(`Step`, `Experiment`) sono nascosti tramite `isSupported`. L'etichetta del ramo di fault arriva
|
|
202
|
+
dalla mappa delle uscite invece di essere «ramo di errore» per tutti: su uno stage di
|
|
203
|
+
orchestrazione quel ramo e' lo **step rifiutato**, non un guasto.
|
|
204
|
+
|
|
205
|
+
**Inspector.** Un form per ogni tipo supportato: Start, Screen, Assignment, Decision, Loop,
|
|
206
|
+
Collection Processor, Get / Create / Update / Delete / Rollback Records, Action, Script,
|
|
207
|
+
Subflow, Wait, Custom Error, Transform, Orchestrated Stage. Più l'intestazione comune con la
|
|
208
|
+
rinomina, che **riscrive tutti i riferimenti** all'elemento.
|
|
209
|
+
|
|
210
|
+
Sullo stage di orchestrazione il form dice le tre cose che il modello non mostra: gli step
|
|
211
|
+
**non sono una sequenza** (le frecce riordinano l'esame, non l'esecuzione), ingresso e uscita
|
|
212
|
+
non sono simmetriche (ingresso falso = step saltato, uscita falsa = stage in **stallo**), e
|
|
213
|
+
`faultConnector` e' il ramo dello **step rifiutato**, non un ramo di guasto — senza, un rifiuto
|
|
214
|
+
fa fallire l'interview. Una condizione che referenzia l'output di un altro step viene segnalata
|
|
215
|
+
con la via d'uscita: scrivere quel risultato in una variabile.
|
|
216
|
+
|
|
217
|
+
**Risorse.** I sette tipi, con i vincoli visibili: `dataType` obbligatorio, `objectType` per
|
|
218
|
+
`Object` / `Enum`, `scale` solo sui numerici, costanti che non possono referenziare risorse,
|
|
219
|
+
`stageOrder` distinti.
|
|
220
|
+
|
|
221
|
+
**Oggetti e campi.** Ogni punto che chiede un'entita' o un suo campo usa lo stesso controllo:
|
|
222
|
+
`<fb-object-picker>` e `<fb-field-picker>`, una casella con autocomplete che filtra il catalogo
|
|
223
|
+
mentre si scrive. Un `<select>` andava bene con dieci entita', non con trecento; e dove il
|
|
224
|
+
catalogo non arriva il controllo resta lo stesso, con un avviso, invece di degradare in un
|
|
225
|
+
campo di testo cieco. La scrittura libera e' voluta — i percorsi di relazione
|
|
226
|
+
(`Cliente.Citta`) non sono enumerabili e il catalogo dell'host puo' essere incompleto — ma un
|
|
227
|
+
nome fuori catalogo viene segnalato subito, prima che il backend risponda `OBJECT_UNKNOWN` o
|
|
228
|
+
`FIELD_UNKNOWN`. Il campo si chiede sempre con l'uso giusto (`filterable`, `sortable`,
|
|
229
|
+
`updateable`), che decide sia l'elenco sia il testo dell'avviso: "non filtrabile" e "non
|
|
230
|
+
aggiornabile" sono errori diversi. Restano `<select>` i dizionari chiusi — tipi di dato,
|
|
231
|
+
operatori, enumerazioni — dove non c'e' niente da scrivere a mano.
|
|
232
|
+
|
|
233
|
+
Lo stesso controllo, nella variante `<fb-name-picker>`, vale per i nomi che dipendono dal
|
|
234
|
+
contesto e non da un catalogo di schema: il flow di uno step in background e dei suoi
|
|
235
|
+
evaluation flow, il flow invocato da un Subflow, le variabili di input e output **del flow
|
|
236
|
+
invocato**. Qui l'elenco arriva da chi ospita il picker (`listSubflowCandidates`, le variabili
|
|
237
|
+
della definizione caricata), e un elenco vuoto resta "non lo so": il campo si scrive a mano e
|
|
238
|
+
non si accusa nessun nome. Con l'elenco popolato, invece, un nome che non c'e' e' segnalato —
|
|
239
|
+
ed e' la differenza rispetto al `datalist` che questi campi usavano prima, che accettava
|
|
240
|
+
qualunque cosa in silenzio.
|
|
241
|
+
|
|
242
|
+
**Problemi.** Pannello con filtro per gravita', navigazione all'elemento con un clic,
|
|
243
|
+
validazione su pausa di digitazione (debounce 400 ms) e l'avvertenza che l'assenza di rilievi
|
|
244
|
+
non e' una garanzia di correttezza.
|
|
245
|
+
|
|
246
|
+
**Versioni.** Elenco con stato, «Nuova versione» al posto di «Salva» sulle versioni chiuse,
|
|
247
|
+
attivazione bloccata finche' ci sono errori, eliminazione dietro una conferma esplicita che
|
|
248
|
+
spiega il rischio per le esecuzioni sospese.
|
|
249
|
+
|
|
250
|
+
**Prova.** Interview con form generato dalle variabili di input, form generico per gli screen
|
|
251
|
+
(valori in ingresso più i campi da restituire), `canGoBack` / `canFinish` / `canPause` come
|
|
252
|
+
verita' runtime, traccia navigabile e tabella delle risorse — con l'avviso in chiaro che la
|
|
253
|
+
traccia riporta anche i dati personali. Su un'orchestrazione sospesa distingue le due attese
|
|
254
|
+
(`isWaitingForEvent` / `isWaitingForStageStep`) e consente di concludere uno step al posto
|
|
255
|
+
dell'assegnatario, rifiuto compreso: la risposta puo' portare una `interviewKey` **nuova**, e il
|
|
256
|
+
pannello sostituisce quella vecchia invece di conservarla.
|
|
257
|
+
|
|
258
|
+
**Import/export.** Export JSON indentato; import via `POST /flows/parse`, che normalizza il
|
|
259
|
+
documento o lo rifiuta con `InvalidDefinition`.
|
|
260
|
+
|
|
261
|
+
---
|
|
262
|
+
|
|
263
|
+
## Architettura
|
|
264
|
+
|
|
265
|
+
```
|
|
266
|
+
src/lib/
|
|
267
|
+
model/ i tipi: documento Flow, dizionari, payload API, errori categorizzati
|
|
268
|
+
api/ FlowBuilderApi (astratta = token DI) + HttpFlowBuilderApi
|
|
269
|
+
core/
|
|
270
|
+
element-outlets.ts la mappa "tipo di elemento → uscite" (§3.5), in un posto solo
|
|
271
|
+
flow-document.store.ts il documento come fonte di verita': flatten, CRUD, undo/redo
|
|
272
|
+
flow-dictionary.store.ts cache dei dizionari e memoizzazione dei cataloghi
|
|
273
|
+
flow-validation.store.ts debounce, indicizzazione dei rilievi per elemento e campo
|
|
274
|
+
flow-editor-session.ts versioni, salvataggio, concorrenza ottimistica
|
|
275
|
+
flow-layout.service.ts auto-layout con dagre
|
|
276
|
+
flow-name.util.ts nomi: regexp, namespace unico, slug dalla label
|
|
277
|
+
condition-logic.util.ts riscrittura di conditionLogic su cancella / riordina
|
|
278
|
+
condition-types.util.ts quali tipi si confrontano, e i numeri in cultura invariante
|
|
279
|
+
stage-step.util.ts gli step di uno stage: nomi, output non condizionabili
|
|
280
|
+
ui/
|
|
281
|
+
flow-builder.component.ts il componente da montare
|
|
282
|
+
canvas/ palette/ inspector/ resources/ problems/ versions/ debug/ shared/
|
|
283
|
+
styles/
|
|
284
|
+
flow-builder.css variabili del tema, controlli di form, stili degli archi
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
Due scelte di progetto che vale la pena conoscere.
|
|
288
|
+
|
|
289
|
+
**Il documento e' la fonte di verita' unica.** Canvas, inspector e pannelli sono funzioni del
|
|
290
|
+
`FlowDefinition` in memoria, e ogni gesto e' una mutazione del documento. Le primitive del
|
|
291
|
+
backend non vengono mai chiamate dallo store, così l'editing resta sincrono e reattivo anche
|
|
292
|
+
mentre una validazione e' in volo.
|
|
293
|
+
|
|
294
|
+
**Local-first per il disegno, backend per la verita'.** Archi e raggiungibilita' sono
|
|
295
|
+
calcolati anche in locale, perche' trascinare un arco deve riflettersi subito;
|
|
296
|
+
`POST /flows/outline` viene richiamato con debounce e, quando arriva, il suo `isReachable`
|
|
297
|
+
sovrascrive il calcolo locale. Chiedere l'outline a ogni movimento del mouse renderebbe il
|
|
298
|
+
canvas dipendente dalla latenza.
|
|
299
|
+
|
|
300
|
+
---
|
|
301
|
+
|
|
302
|
+
## Dipendenze esterne
|
|
303
|
+
|
|
304
|
+
Oltre ad Angular:
|
|
305
|
+
|
|
306
|
+
| Pacchetto | Perche' |
|
|
307
|
+
|---|---|
|
|
308
|
+
| `@foblex/flow` (+ `@foblex/platform`, `@foblex/mediator`, `@foblex/2d`, `@foblex/utils`) | rendering del grafo, pan/zoom, gesti su node e connessioni. Usata in *classic mode*: la libreria disegna e riconosce i gesti, lo stato resta nostro |
|
|
309
|
+
| `dagre` | auto-layout gerarchico del comando «Riordina» |
|
|
310
|
+
|
|
311
|
+
Nessun design system: i controlli sono input nativi con binding espliciti e CSS custom, così
|
|
312
|
+
il builder si integra nel tema dell'app ospite senza imporre il proprio.
|
|
313
|
+
|
|
314
|
+
---
|
|
315
|
+
|
|
316
|
+
## Tema e personalizzazione
|
|
317
|
+
|
|
318
|
+
Le variabili si sovrascrivono sul contenitore:
|
|
319
|
+
|
|
320
|
+
```css
|
|
321
|
+
.mio-contenitore {
|
|
322
|
+
--fb-accent: #7048c4;
|
|
323
|
+
--fb-surface: #ffffff;
|
|
324
|
+
--fb-border: #dcdfe4;
|
|
325
|
+
--fb-error: #b3261e;
|
|
326
|
+
--fb-edge-fault: #b3261e;
|
|
327
|
+
}
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
Elenco completo in `styles/flow-builder.css`. Per la variante scura basta
|
|
331
|
+
`data-fb-theme="dark"` su un antenato.
|
|
332
|
+
|
|
333
|
+
Nota sugli archi: il colore si imposta valorizzando le custom properties del tema di
|
|
334
|
+
@foblex/flow (`--ff-connection-color`, `--ff-marker-color`) invece di sovrascrivere le sue
|
|
335
|
+
regole, perche' i selettori hanno la stessa specificita' e chi vince dipenderebbe dall'ordine
|
|
336
|
+
con cui l'app ospite carica i fogli di stile. Lo stesso vale per il resto del disegno che fa
|
|
337
|
+
la libreria: `flow-builder.css` mappa i propri colori sui token `--ff-*` (sfondo del canvas,
|
|
338
|
+
pattern, minimappa, spessore degli archi) e ne annulla due — le maniglie di riassegnazione
|
|
339
|
+
(`--ff-connection-drag-handle-*`), che disegnate formavano una macchia sopra la punta della
|
|
340
|
+
freccia. Il colore dell'uscita di un ramo passa da `--ff-connector-connected-color`, unico
|
|
341
|
+
modo di sopravvivere alla regola di foblex per i connettori collegati, che ha specificita'
|
|
342
|
+
(0,4,1) e non si batte con una classe.
|
|
343
|
+
|
|
344
|
+
L'aspetto dei node segue l'esempio *call-center* di @foblex/flow: card con badge dell'icona
|
|
345
|
+
colorato per categoria, titolo, sottotitolo e uscite sul bordo inferiore. I glifi sono
|
|
346
|
+
caratteri Unicode in `core/element-icons.ts`, condivisi fra palette e canvas: nessun asset da
|
|
347
|
+
distribuire e nessuna dipendenza da un font di icone dell'applicazione ospite.
|
|
348
|
+
|
|
349
|
+
---
|
|
350
|
+
|
|
351
|
+
## Le trappole della §13, una per una
|
|
352
|
+
|
|
353
|
+
| # | Trappola | Dove e' gestita |
|
|
354
|
+
|---|---|---|
|
|
355
|
+
| 1 | **Operatori unari**: `rightValue` e' l'esito atteso, non il termine di confronto | `ConditionEditorComponent`: per gli operatori con `isUnary` il campo «confronta con» non esiste; c'e' un selettore «è / NON è» e la frase «questa condizione e' vera quando: *Esito NON è vuoto*». Il flag arriva dal dizionario, non da una lista cablata |
|
|
356
|
+
| 2 | `assignNextValueToReference` di un Collection Processor **non** e' la destinazione | `CollectionProcessorInspectorComponent`: il campo si chiama «Elemento in esame», e un callout dice che il risultato e' l'output automatico dell'elemento |
|
|
357
|
+
| 3 | Gli **output automatici** sono riferimenti validi anche se non dichiarati | `ReferencePickerComponent` chiede l'elenco a `POST /flows/references`, che li include; non ricostruisce nulla lato client |
|
|
358
|
+
| 4 | Cancellare una condizione richiede di **riscrivere `conditionLogic`** | `condition-logic.util.ts`: `removeCondition` e `moveCondition` rimappano gli indici 1-based; un termine rimasto orfano diventa `?` e viene segnalato |
|
|
359
|
+
| 5 | Node e risorse condividono **un unico spazio di nomi** | `FlowDocumentStore.usedNames` unisce i due insiemi e `checkFlowName` confronta case-insensitive; pannello risorse e inspector usano lo stesso controllo |
|
|
360
|
+
| 6 | `processType` non e' decorativo | Banner in cima all'editor per `AutoLaunched` + screen, `Orchestration` senza stage, `Screen` senza screen; la palette segnala lo Screen incompatibile. Il `processType` viaggia anche a `getDictionaries`, perche' decide quali globali esistono |
|
|
361
|
+
| 7 | Le **regole di una Decision sono ordinate** | `DecisionInspectorComponent`: regole numerate, frecce di riordino, callout «valutate dall'alto verso il basso, si ferma alla prima vera» |
|
|
362
|
+
| 8 | **Rinominare non aggiorna i riferimenti** | `FlowDocumentStore.renameNode` cammina il documento e riscrive ogni campo di riferimento, incluso il caso navigato (`Vecchio.Campo` → `Nuovo.Campo`); l'inspector dice quante occorrenze verranno toccate |
|
|
363
|
+
| 9 | `Step` ed `Experiment` non sono supportati; `OrchestratedStage` lo e' | I due non supportati sono filtrati dalla palette via `isSupported` e, se arrivano da un import, l'inspector li mostra in sola lettura spiegando perche'. Lo stage ha il suo form: step non presentati come sequenza, e la condizione che referenzia l'output di un altro step segnalata con la via d'uscita |
|
|
364
|
+
| 10 | Un `Delete` senza filtri e' un **errore**, un `Update` senza filtri un **avviso** | `RecordWriteInspectorComponent`: severita' diverse, e l'update di massa chiede una conferma esplicita |
|
|
365
|
+
| 11 | Non cablare enum e operatori | Ogni tendina legge da `getDictionaries()`; le mappe locali intervengono solo come fallback quando il dizionario non e' disponibile |
|
|
366
|
+
| 12 | Il **token di continuazione e' opaco** | `DebugPanelComponent` lo rimanda tale e quale, non lo legge e non lo mette in nessun URL |
|
|
367
|
+
| 13 | **Testo e numero non si confrontano** (`CONDITION_TYPE_MISMATCH`) | `ConditionEditorComponent` conosce il tipo del lato sinistro da `POST /flows/references`: filtra gli operatori applicabili, guida il campo del letterale del secondo operando e segnala la coppia vietata. La regola sta in `core/condition-types.util.ts`, che salta gli operatori con semantica propria (`Contains`, `In`, …). I numeri si scrivono in cultura invariante: `1234,50` viene tradotto in `1234.50`, non troncato |
|
|
368
|
+
| 14 | **`None` non e' un operatore, e' un "da completare"** | Condizione evidenziata come incompleta, con il conteggio in testa alla sezione e la frase «la bozza si salva, l'attivazione no»; il validatore della demo lo emette come **errore** |
|
|
369
|
+
| 15 | **Un valore data senza `Z` significa ora locale** | `ValueEditorComponent` ha un selettore «Ora locale / UTC (Z)», dice che cambiare fuso riscrive l'orario e non lo converte, e toglie il suffisso solo per il controllo `datetime-local`, che con il fuso resterebbe vuoto |
|
|
370
|
+
|
|
371
|
+
Altre regole del contratto rispettate: liste vuote omesse invece di scritte come `[]` (§2),
|
|
372
|
+
`filterLogic` mostrato **solo** dove il modello lo prevede (§4.4), `fullName` / `status` mai
|
|
373
|
+
usati per rinominare o attivare (§2), un solo campo valorizzato in
|
|
374
|
+
`FlowElementReferenceOrValue` con segnalazione e normalizzazione dei documenti ambigui (§4.2),
|
|
375
|
+
`Transform.connector` e `start` trattati come array per fedelta' storica (§3.5), salvataggio
|
|
376
|
+
possibile anche con errori e attivazione bloccata dagli errori (§8).
|
|
377
|
+
|
|
378
|
+
---
|
|
379
|
+
|
|
380
|
+
## Limiti noti
|
|
381
|
+
|
|
382
|
+
- **Le uscite indicizzate cambiano chiave se si riordinano le regole**: la chiave di un
|
|
383
|
+
connettore contiene l'indice (`rule:0`), quindi riordinare le regole di una Decision
|
|
384
|
+
riassegna gli id dei connettori sul canvas. Le destinazioni seguono la regola, perche'
|
|
385
|
+
vivono dentro `rules[i].connector`, ma un arco selezionato perde la selezione.
|
|
386
|
+
- **`relatedRecords` di Get Records** e' esposto in sola lettura con un avviso: e' modellato
|
|
387
|
+
ma non tradotto in query.
|
|
388
|
+
- **L'esecutore della demo non valuta le condizioni**: prende il primo ramo disponibile e lo
|
|
389
|
+
scrive nella traccia. È un mock per esercitare il pannello, non il motore.
|
|
390
|
+
- **Nessun test automatico**: la libreria e' stata verificata a mano sull'app demo (canvas,
|
|
391
|
+
inspector, salvataggio, attivazione, interview completa). Casi su `condition-logic.util`,
|
|
392
|
+
`flow-name.util`, `element-outlets` e `FlowDocumentStore` sarebbero il primo investimento
|
|
393
|
+
sensato.
|