@esfaenza/flow-builder 20.3.34 → 20.3.35
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 +33 -3
- package/fesm2022/esfaenza-flow-builder.mjs +863 -106
- package/fesm2022/esfaenza-flow-builder.mjs.map +1 -1
- package/index.d.ts +142 -5
- package/package.json +1 -1
- package/styles/flow-builder.css +23 -7
|
@@ -3488,14 +3488,25 @@ class FlowDocumentStore {
|
|
|
3488
3488
|
*
|
|
3489
3489
|
* `silent` per il trascinamento: il chiamante registra un solo passo alla fine.
|
|
3490
3490
|
*/
|
|
3491
|
+
/**
|
|
3492
|
+
* `from` e' l'origine da cui misurare lo spostamento, e serve per i riquadri **auto-dimensionati**
|
|
3493
|
+
* (§3.6): lì il rettangolo disegnato e' l'ingombro dei membri, che dopo che qualcuno ha spostato un
|
|
3494
|
+
* membro non coincide piu' con `locationX`/`locationY`. Misurando dal documento, lo spostamento
|
|
3495
|
+
* della cornice portava i membri di un delta che nessuno aveva chiesto. Chi disegna il rettangolo
|
|
3496
|
+
* lo conosce: e' il canvas, e lo passa. Assente, si misura dal documento come prima.
|
|
3497
|
+
*/
|
|
3491
3498
|
moveGroup(name, x, y, options) {
|
|
3492
3499
|
const reference = this.groups().find((candidate) => candidate.name === name);
|
|
3493
3500
|
if (!reference) {
|
|
3494
3501
|
return;
|
|
3495
3502
|
}
|
|
3496
3503
|
const rounded = { x: Math.round(x), y: Math.round(y) };
|
|
3497
|
-
const
|
|
3498
|
-
|
|
3504
|
+
const origin = options?.from ?? {
|
|
3505
|
+
x: reference.group.locationX ?? 0,
|
|
3506
|
+
y: reference.group.locationY ?? 0,
|
|
3507
|
+
};
|
|
3508
|
+
const deltaX = rounded.x - Math.round(origin.x);
|
|
3509
|
+
const deltaY = rounded.y - Math.round(origin.y);
|
|
3499
3510
|
const members = new Set(reference.group.members ?? []);
|
|
3500
3511
|
this.update((draft) => {
|
|
3501
3512
|
const group = draft.groups?.[reference.index];
|
|
@@ -4961,6 +4972,42 @@ function canvasGroupId(groupName) {
|
|
|
4961
4972
|
function parseCanvasGroupId(id) {
|
|
4962
4973
|
return id.startsWith('group:') ? id.slice(6) : null;
|
|
4963
4974
|
}
|
|
4975
|
+
/**
|
|
4976
|
+
* Il **node segnaposto** di un riquadro chiuso (§3.6).
|
|
4977
|
+
*
|
|
4978
|
+
* Terzo prefisso, accanto a `node:` e `group:`, e per la stessa ragione: gli eventi della libreria
|
|
4979
|
+
* portano tutto nello stesso elenco, e un riquadro chiuso non e' ne' un elemento del documento ne'
|
|
4980
|
+
* una cornice — spostarlo sposta i **membri**, cancellarlo cancella il riquadro.
|
|
4981
|
+
*/
|
|
4982
|
+
function canvasFoldId(groupName) {
|
|
4983
|
+
return `fold:${groupName}`;
|
|
4984
|
+
}
|
|
4985
|
+
function parseCanvasFoldId(id) {
|
|
4986
|
+
return id.startsWith('fold:') ? id.slice(5) : null;
|
|
4987
|
+
}
|
|
4988
|
+
/**
|
|
4989
|
+
* I connettori del segnaposto: uno in ingresso e **uno solo** in uscita.
|
|
4990
|
+
*
|
|
4991
|
+
* Uno solo perche' il segnaposto non ha rami: gli archi che escono dal riquadro appartengono a
|
|
4992
|
+
* elementi diversi che stanno dentro, e disegnarne un connettore per ciascuno rimetterebbe sul
|
|
4993
|
+
* canvas la complessita' che chiudere il riquadro serviva a togliere. L'arco porta nell'etichetta
|
|
4994
|
+
* il nome dell'elemento interno, che e' l'informazione che serve.
|
|
4995
|
+
*
|
|
4996
|
+
* Il nome dietro il prefisso e' quello del **riquadro**, quindi `parseSourceConnectorId` risponde
|
|
4997
|
+
* `fold:<riquadro>`, che non e' il nome di nessun elemento: chi gestisce i gesti degli archi lo
|
|
4998
|
+
* riconosce con {@link isFoldReference} e rifiuta, invece di scrivere un connector su un node che
|
|
4999
|
+
* non esiste.
|
|
5000
|
+
*/
|
|
5001
|
+
function foldTargetConnectorId(groupName) {
|
|
5002
|
+
return targetConnectorId(canvasFoldId(groupName));
|
|
5003
|
+
}
|
|
5004
|
+
function foldSourceConnectorId(groupName) {
|
|
5005
|
+
return sourceConnectorId(canvasFoldId(groupName), 'fold');
|
|
5006
|
+
}
|
|
5007
|
+
/** `true` se il nome estratto da un connettore e' un segnaposto e non un elemento del documento. */
|
|
5008
|
+
function isFoldReference(name) {
|
|
5009
|
+
return !!name && name.startsWith('fold:');
|
|
5010
|
+
}
|
|
4964
5011
|
|
|
4965
5012
|
/**
|
|
4966
5013
|
* Il canvas del grafo.
|
|
@@ -4985,6 +5032,14 @@ function parseCanvasGroupId(id) {
|
|
|
4985
5032
|
*/
|
|
4986
5033
|
const FLOW_NODE_WIDTH = 240;
|
|
4987
5034
|
const FLOW_NODE_HEIGHT = 116;
|
|
5035
|
+
/**
|
|
5036
|
+
* L'ingombro della pastiglia di un riquadro chiuso (§3.6). Gli stessi valori stanno nel CSS
|
|
5037
|
+
* (`.fb-fold`): la misura serve **due volte** — a disegnarla e a stimarla per l'inquadratura — e la
|
|
5038
|
+
* larghezza non puo' essere uno stile inline, perche' @foblex/flow riscrive l'attributo `style` del
|
|
5039
|
+
* node a ogni riposizionamento.
|
|
5040
|
+
*/
|
|
5041
|
+
const FLOW_FOLD_WIDTH = 260;
|
|
5042
|
+
const FLOW_FOLD_HEIGHT = 64;
|
|
4988
5043
|
/**
|
|
4989
5044
|
* La larghezza del node per numero di rami: le uscite stanno in fila sul bordo inferiore,
|
|
4990
5045
|
* quindi una Decision con quattro regole ha bisogno di piu' spazio di un Assignment.
|
|
@@ -4996,6 +5051,20 @@ const FLOW_NODE_HEIGHT = 116;
|
|
|
4996
5051
|
* restare uguali: la stessa misura serve a disegnare il node e a stimarlo per dagre.
|
|
4997
5052
|
*/
|
|
4998
5053
|
const OUTLET_WIDTHS = [240, 240, 240, 304, 380, 456, 520];
|
|
5054
|
+
/**
|
|
5055
|
+
* La peggiore di due gravita' (§7). Serve al segnaposto di un riquadro chiuso, che riassume i
|
|
5056
|
+
* rilievi di cio' che nasconde: `SEVERITY_BUCKETS` e' ordinato per gravita' decrescente, quindi
|
|
5057
|
+
* vince chi ha l'indice piu' basso.
|
|
5058
|
+
*/
|
|
5059
|
+
function worstSeverity(left, right) {
|
|
5060
|
+
if (!left) {
|
|
5061
|
+
return right;
|
|
5062
|
+
}
|
|
5063
|
+
if (!right) {
|
|
5064
|
+
return left;
|
|
5065
|
+
}
|
|
5066
|
+
return SEVERITY_BUCKETS.indexOf(left) <= SEVERITY_BUCKETS.indexOf(right) ? left : right;
|
|
5067
|
+
}
|
|
4999
5068
|
/**
|
|
5000
5069
|
* Margine attorno al flow quando lo si inquadra: senza, i node di bordo toccano il bordo
|
|
5001
5070
|
* della viewport e sembrano tagliati. In basso a destra ci sono minimappa e pulsante, ma il
|
|
@@ -5121,7 +5190,17 @@ class FlowCanvasComponent {
|
|
|
5121
5190
|
* Con un solo node (un flow appena creato ha solo lo Start) non si inquadra niente: la
|
|
5122
5191
|
* vista si spalancherebbe su una card sola.
|
|
5123
5192
|
*/
|
|
5193
|
+
/**
|
|
5194
|
+
* L'elemento su cui andare appena i node sono stati disegnati: lo scrive `bringIntoView` quando
|
|
5195
|
+
* per mostrarlo ha dovuto aprire un riquadro chiuso.
|
|
5196
|
+
*/
|
|
5197
|
+
pendingFocus = null;
|
|
5124
5198
|
onNodesRendered() {
|
|
5199
|
+
const pending = this.pendingFocus;
|
|
5200
|
+
if (pending) {
|
|
5201
|
+
this.pendingFocus = null;
|
|
5202
|
+
this.bringIntoView(pending);
|
|
5203
|
+
}
|
|
5125
5204
|
if (this.hasFramedOnce || this.nodes().length < 2) {
|
|
5126
5205
|
return;
|
|
5127
5206
|
}
|
|
@@ -5134,6 +5213,19 @@ class FlowCanvasComponent {
|
|
|
5134
5213
|
* 100% non vuole essere rimpicciolito per aver cercato un nome.
|
|
5135
5214
|
*/
|
|
5136
5215
|
bringIntoView(name) {
|
|
5216
|
+
/**
|
|
5217
|
+
* L'elemento e' dentro un riquadro **chiuso**: non c'e' niente da inquadrare finche' non lo si
|
|
5218
|
+
* apre. Aprirlo e' l'unica cosa sensata — la ricerca e il clic su un problema promettono di
|
|
5219
|
+
* portare *sull'elemento*, e una vista centrata su una pastiglia non mostra cio' che si cercava.
|
|
5220
|
+
* Il centramento aspetta il render: `centerGroupOrNode` non fa niente finche' la libreria non ha
|
|
5221
|
+
* misurato i node, e avviserebbe con FF1009 senza spostare niente.
|
|
5222
|
+
*/
|
|
5223
|
+
const folded = this.foldedByMember().get(name);
|
|
5224
|
+
if (folded) {
|
|
5225
|
+
this.pendingFocus = name;
|
|
5226
|
+
this.openGroup(folded);
|
|
5227
|
+
return;
|
|
5228
|
+
}
|
|
5137
5229
|
/**
|
|
5138
5230
|
* Anche i **riquadri** (§3.6) sono un bersaglio: un rilievo della loro famiglia porta il
|
|
5139
5231
|
* nome di un riquadro, e senza questo ramo il clic sul problema selezionava una cornice che
|
|
@@ -5287,6 +5379,14 @@ class FlowCanvasComponent {
|
|
|
5287
5379
|
right = Math.max(right, node.position.x + flowNodeWidth(node.outlets.length));
|
|
5288
5380
|
bottom = Math.max(bottom, node.position.y + FLOW_NODE_HEIGHT);
|
|
5289
5381
|
}
|
|
5382
|
+
// Le pastiglie dei riquadri chiusi sono disegnate come i node: fuori dal conto, un flow tutto
|
|
5383
|
+
// chiuso si inquadrerebbe su cio' che resta aperto.
|
|
5384
|
+
for (const fold of this.folds()) {
|
|
5385
|
+
left = Math.min(left, fold.position.x);
|
|
5386
|
+
top = Math.min(top, fold.position.y);
|
|
5387
|
+
right = Math.max(right, fold.position.x + FLOW_FOLD_WIDTH);
|
|
5388
|
+
bottom = Math.max(bottom, fold.position.y + FLOW_FOLD_HEIGHT);
|
|
5389
|
+
}
|
|
5290
5390
|
const width = right - left;
|
|
5291
5391
|
const height = bottom - top;
|
|
5292
5392
|
if (width <= 0 || height <= 0) {
|
|
@@ -5324,7 +5424,12 @@ class FlowCanvasComponent {
|
|
|
5324
5424
|
isReachable: true,
|
|
5325
5425
|
subtitle: this.startSubtitle(),
|
|
5326
5426
|
}));
|
|
5427
|
+
const hidden = this.foldedByMember();
|
|
5327
5428
|
for (const reference of this.store.nodes()) {
|
|
5429
|
+
// Membro di un riquadro **chiuso**: al suo posto si disegna la pastiglia del riquadro.
|
|
5430
|
+
if (hidden.has(reference.name)) {
|
|
5431
|
+
continue;
|
|
5432
|
+
}
|
|
5328
5433
|
result.push(this.toCanvasNode({
|
|
5329
5434
|
name: reference.name,
|
|
5330
5435
|
type: reference.type,
|
|
@@ -5371,21 +5476,75 @@ class FlowCanvasComponent {
|
|
|
5371
5476
|
}),
|
|
5372
5477
|
};
|
|
5373
5478
|
}
|
|
5374
|
-
/**
|
|
5479
|
+
/**
|
|
5480
|
+
* Gli archi con destinazione: solo questi diventano `<f-connection>`.
|
|
5481
|
+
*
|
|
5482
|
+
* Un riquadro **chiuso** (§3.6) cambia tre cose, e sono la ragione per cui questo non e' piu' una
|
|
5483
|
+
* `map` sulle uscite del documento. Un arco fra due membri dello stesso riquadro chiuso **non si
|
|
5484
|
+
* disegna**: entrambi i capi sono nascosti, e un arco fra due punti che non ci sono e' un arco che
|
|
5485
|
+
* parte dall'angolo del canvas. Un arco che attraversa il confine si riattacca al **segnaposto**,
|
|
5486
|
+
* che e' l'unico modo di nascondere un node senza rompere l'arco: un connettore con `display:
|
|
5487
|
+
* none` ha geometria 0×0 (regola 4 di @foblex/flow), quindi il node si nasconde solo se l'arco
|
|
5488
|
+
* trova un connettore vero altrove. E due archi che, riattaccati, avrebbero gli stessi capi
|
|
5489
|
+
* diventano **uno**: sarebbero due tratti sovrapposti, indistinguibili e cliccabili a caso.
|
|
5490
|
+
*/
|
|
5375
5491
|
edges = computed(() => {
|
|
5376
|
-
const
|
|
5377
|
-
|
|
5378
|
-
|
|
5379
|
-
|
|
5380
|
-
|
|
5381
|
-
|
|
5382
|
-
|
|
5383
|
-
|
|
5384
|
-
|
|
5385
|
-
|
|
5386
|
-
|
|
5387
|
-
|
|
5492
|
+
const hidden = this.foldedByMember();
|
|
5493
|
+
const visible = new Set(this.nodes().map((node) => node.name));
|
|
5494
|
+
const result = [];
|
|
5495
|
+
const drawn = new Set();
|
|
5496
|
+
for (const edge of this.store.edges()) {
|
|
5497
|
+
const to = edge.to;
|
|
5498
|
+
if (!to) {
|
|
5499
|
+
continue;
|
|
5500
|
+
}
|
|
5501
|
+
const fromFold = hidden.get(edge.from);
|
|
5502
|
+
const toFold = hidden.get(to);
|
|
5503
|
+
if ((!fromFold && !visible.has(edge.from)) || (!toFold && !visible.has(to))) {
|
|
5504
|
+
continue;
|
|
5505
|
+
}
|
|
5506
|
+
if (fromFold && fromFold === toFold) {
|
|
5507
|
+
// Interno al riquadro chiuso: non c'e' niente da disegnare fra due elementi nascosti.
|
|
5508
|
+
continue;
|
|
5509
|
+
}
|
|
5510
|
+
const sourceId = fromFold
|
|
5511
|
+
? foldSourceConnectorId(fromFold)
|
|
5512
|
+
: sourceConnectorId(edge.from, edge.outletKey);
|
|
5513
|
+
const targetId = toFold ? foldTargetConnectorId(toFold) : targetConnectorId(to);
|
|
5514
|
+
const key = `${sourceId}>${targetId}`;
|
|
5515
|
+
if (drawn.has(key)) {
|
|
5516
|
+
continue;
|
|
5517
|
+
}
|
|
5518
|
+
drawn.add(key);
|
|
5519
|
+
result.push({
|
|
5520
|
+
id: edge.id,
|
|
5521
|
+
sourceId,
|
|
5522
|
+
targetId,
|
|
5523
|
+
kind: edge.kind,
|
|
5524
|
+
label: this.edgeLabel(edge.from, to, edge.label, !!fromFold, !!toFold),
|
|
5525
|
+
isGoTo: !!edge.isGoTo,
|
|
5526
|
+
isFolded: !!fromFold || !!toFold,
|
|
5527
|
+
});
|
|
5528
|
+
}
|
|
5529
|
+
return result;
|
|
5388
5530
|
}, ...(ngDevMode ? [{ debugName: "edges" }] : []));
|
|
5531
|
+
/**
|
|
5532
|
+
* L'etichetta di un arco. Sugli archi normali e' quella del ramo; su un arco riattaccato a un
|
|
5533
|
+
* segnaposto e' il **nome dell'elemento nascosto**, perche' quella e' l'unica informazione che
|
|
5534
|
+
* chiudere il riquadro ha portato via: «da dove esce» e «dove entra» non si leggono piu' dai capi.
|
|
5535
|
+
*/
|
|
5536
|
+
edgeLabel(from, to, label, fromFold, toFold) {
|
|
5537
|
+
if (fromFold && toFold) {
|
|
5538
|
+
return `${from} → ${to}`;
|
|
5539
|
+
}
|
|
5540
|
+
if (fromFold) {
|
|
5541
|
+
return label ? `${from} · ${label}` : from;
|
|
5542
|
+
}
|
|
5543
|
+
if (toFold) {
|
|
5544
|
+
return label ? `${label} · ${to}` : to;
|
|
5545
|
+
}
|
|
5546
|
+
return label ?? null;
|
|
5547
|
+
}
|
|
5389
5548
|
// -------------------------------------------------------------------------
|
|
5390
5549
|
// Riquadri di raggruppamento (§3.6)
|
|
5391
5550
|
// -------------------------------------------------------------------------
|
|
@@ -5416,7 +5575,11 @@ class FlowCanvasComponent {
|
|
|
5416
5575
|
*/
|
|
5417
5576
|
groups = computed(() => {
|
|
5418
5577
|
const known = new Set(this.nodeRects().map((entry) => entry.name));
|
|
5419
|
-
return this.store.groups()
|
|
5578
|
+
return this.store.groups()
|
|
5579
|
+
// Un riquadro chiuso e' disegnato da `folds()`: cornice e pastiglia sono **alternative**,
|
|
5580
|
+
// e disegnarle entrambe metterebbe due titoli nello stesso punto.
|
|
5581
|
+
.filter((reference) => !reference.group.isCollapsed)
|
|
5582
|
+
.map((reference) => {
|
|
5420
5583
|
const group = reference.group;
|
|
5421
5584
|
const rect = groupRect(group, this.rectsOf(group.members));
|
|
5422
5585
|
const members = group.members ?? [];
|
|
@@ -5426,18 +5589,87 @@ class FlowCanvasComponent {
|
|
|
5426
5589
|
label: group.label || reference.name || 'Riquadro',
|
|
5427
5590
|
description: group.description ?? null,
|
|
5428
5591
|
position: { x: rect.x, y: rect.y },
|
|
5429
|
-
|
|
5430
|
-
// con `display: none` ha geometria 0×0 e l'arco si attaccherebbe nel punto sbagliato,
|
|
5431
|
-
// quindi chiudere il riquadro toglie di mezzo la cornice, non il contenuto.
|
|
5432
|
-
size: { width: rect.width, height: group.isCollapsed ? GROUP_HEADER_HEIGHT : rect.height },
|
|
5592
|
+
size: { width: rect.width, height: rect.height },
|
|
5433
5593
|
colorClass: groupColorClass(group.color),
|
|
5434
|
-
isCollapsed:
|
|
5594
|
+
isCollapsed: false,
|
|
5435
5595
|
memberCount: members.filter((name) => known.has(name)).length,
|
|
5436
5596
|
unknownMembers: members.filter((name) => !known.has(name)),
|
|
5437
5597
|
isAutoSized: hasAutoSize(group),
|
|
5438
5598
|
};
|
|
5439
5599
|
});
|
|
5440
5600
|
}, ...(ngDevMode ? [{ debugName: "groups" }] : []));
|
|
5601
|
+
/**
|
|
5602
|
+
* Chi nasconde chi: nome del membro → nome del riquadro chiuso che lo copre.
|
|
5603
|
+
*
|
|
5604
|
+
* Un elemento in **due** riquadri chiusi finisce nel primo in ordine di documento: sarebbe
|
|
5605
|
+
* disegnato due volte, e due pastiglie che si prendono lo stesso arco fanno sparire un arco senza
|
|
5606
|
+
* dire niente. La sovrapposizione e' legittima (i riquadri sono commenti, §3.6), quindi la regola
|
|
5607
|
+
* deve essere deterministica, non impedita.
|
|
5608
|
+
*/
|
|
5609
|
+
foldedByMember = computed(() => {
|
|
5610
|
+
const map = new Map();
|
|
5611
|
+
for (const reference of this.store.groups()) {
|
|
5612
|
+
if (!reference.group.isCollapsed) {
|
|
5613
|
+
continue;
|
|
5614
|
+
}
|
|
5615
|
+
for (const member of reference.group.members ?? []) {
|
|
5616
|
+
if (!map.has(member)) {
|
|
5617
|
+
map.set(member, reference.name);
|
|
5618
|
+
}
|
|
5619
|
+
}
|
|
5620
|
+
}
|
|
5621
|
+
return map;
|
|
5622
|
+
}, ...(ngDevMode ? [{ debugName: "foldedByMember" }] : []));
|
|
5623
|
+
/**
|
|
5624
|
+
* I riquadri chiusi, disegnati come node segnaposto.
|
|
5625
|
+
*
|
|
5626
|
+
* La pastiglia sta nell'angolo in alto a sinistra del rettangolo che la cornice occupava: e' il
|
|
5627
|
+
* punto da cui il riquadro si e' chiuso, quindi riaprendolo il contenuto ricompare dove era. Il
|
|
5628
|
+
* rettangolo lo da' `groupRect`, non `width`/`height` del documento: a 0 significano «calcolala
|
|
5629
|
+
* sui membri» (§3.6), e i membri esistono anche mentre sono nascosti.
|
|
5630
|
+
*/
|
|
5631
|
+
folds = computed(() => {
|
|
5632
|
+
const known = new Set(this.nodeRects().map((entry) => entry.name));
|
|
5633
|
+
return this.store.groups()
|
|
5634
|
+
.filter((reference) => !!reference.group.isCollapsed)
|
|
5635
|
+
.map((reference) => {
|
|
5636
|
+
const group = reference.group;
|
|
5637
|
+
const rect = groupRect(group, this.rectsOf(group.members));
|
|
5638
|
+
const members = group.members ?? [];
|
|
5639
|
+
const inside = members.filter((name) => known.has(name));
|
|
5640
|
+
// I rilievi degli elementi nascosti, sommati: chiudere un riquadro non deve nascondere un
|
|
5641
|
+
// errore, altrimenti il badge del pannello dei problemi conterebbe cose invisibili.
|
|
5642
|
+
let issueCount = 0;
|
|
5643
|
+
let severity = null;
|
|
5644
|
+
for (const name of inside) {
|
|
5645
|
+
issueCount += (this.validation.issuesByElement().get(name) ?? []).length;
|
|
5646
|
+
severity = worstSeverity(severity, this.validation.severityOf(name));
|
|
5647
|
+
}
|
|
5648
|
+
return {
|
|
5649
|
+
name: reference.name,
|
|
5650
|
+
id: canvasFoldId(reference.name),
|
|
5651
|
+
label: group.label || reference.name || 'Riquadro',
|
|
5652
|
+
description: group.description ?? null,
|
|
5653
|
+
position: { x: rect.x, y: rect.y },
|
|
5654
|
+
colorClass: groupColorClass(group.color),
|
|
5655
|
+
memberCount: inside.length,
|
|
5656
|
+
issueCount,
|
|
5657
|
+
severity,
|
|
5658
|
+
unknownMembers: members.filter((name) => !known.has(name)),
|
|
5659
|
+
};
|
|
5660
|
+
});
|
|
5661
|
+
}, ...(ngDevMode ? [{ debugName: "folds" }] : []));
|
|
5662
|
+
isFoldSelected(fold) {
|
|
5663
|
+
return this.selectedGroupName() === fold.name;
|
|
5664
|
+
}
|
|
5665
|
+
/** L'angolo da cui il riquadro e' stato disegnato, aperto o chiuso: e' l'origine dello spostamento. */
|
|
5666
|
+
drawnOrigin(name) {
|
|
5667
|
+
const group = this.groups().find((candidate) => candidate.name === name);
|
|
5668
|
+
if (group) {
|
|
5669
|
+
return group.position;
|
|
5670
|
+
}
|
|
5671
|
+
return this.folds().find((candidate) => candidate.name === name)?.position;
|
|
5672
|
+
}
|
|
5441
5673
|
/**
|
|
5442
5674
|
* Ricalcola l'appartenenza dai rettangoli: e' il gesto «trascino un node dentro o fuori dal
|
|
5443
5675
|
* riquadro» della §3.6, regola 2 — la geometria non **e'** l'appartenenza, la **calcola**, e
|
|
@@ -5456,6 +5688,15 @@ class FlowCanvasComponent {
|
|
|
5456
5688
|
const known = new Set(all.map((entry) => entry.name));
|
|
5457
5689
|
const movedSet = new Set(moved);
|
|
5458
5690
|
for (const reference of this.store.groups()) {
|
|
5691
|
+
/**
|
|
5692
|
+
* Un riquadro **chiuso** non cambia i suoi membri per un trascinamento: i suoi elementi non
|
|
5693
|
+
* sono sullo schermo, e il rettangolo che occupava e' coperto dalla pastiglia. Senza questo
|
|
5694
|
+
* salto, trascinare un elemento sopra la pastiglia lo faceva **sparire** — entrava fra i
|
|
5695
|
+
* membri di un riquadro chiuso, e il solo modo di ritrovarlo era riaprirlo.
|
|
5696
|
+
*/
|
|
5697
|
+
if (reference.group.isCollapsed) {
|
|
5698
|
+
continue;
|
|
5699
|
+
}
|
|
5459
5700
|
const auto = hasAutoSize(reference.group);
|
|
5460
5701
|
const memberNames = (reference.group.members ?? []).filter((name) => !(auto && movedSet.has(name)));
|
|
5461
5702
|
const rect = groupRect(reference.group, this.rectsOf(memberNames));
|
|
@@ -5463,7 +5704,20 @@ class FlowCanvasComponent {
|
|
|
5463
5704
|
this.store.setGroupMembers(reference.name, members, { silent: true });
|
|
5464
5705
|
}
|
|
5465
5706
|
}
|
|
5466
|
-
/** Apre
|
|
5707
|
+
/** Apre un riquadro chiuso. Passo di storico normale, come la chiusura a mano: e' la stessa modifica. */
|
|
5708
|
+
openGroup(name) {
|
|
5709
|
+
const reference = this.store.groups().find((candidate) => candidate.name === name);
|
|
5710
|
+
if (!reference?.group.isCollapsed) {
|
|
5711
|
+
return;
|
|
5712
|
+
}
|
|
5713
|
+
this.store.updateGroup(reference.index, (group) => {
|
|
5714
|
+
group.isCollapsed = undefined;
|
|
5715
|
+
});
|
|
5716
|
+
}
|
|
5717
|
+
/**
|
|
5718
|
+
* Apre e chiude il riquadro. Non tocca i membri: chiuso e aperto sono due **disegni** dello
|
|
5719
|
+
* stesso riquadro (§3.6) — chiuso, i membri non si disegnano e al loro posto c'e' la pastiglia.
|
|
5720
|
+
*/
|
|
5467
5721
|
toggleGroupCollapsed(event, name) {
|
|
5468
5722
|
event.stopPropagation();
|
|
5469
5723
|
const reference = this.store.groups().find((candidate) => candidate.name === name);
|
|
@@ -5575,8 +5829,15 @@ class FlowCanvasComponent {
|
|
|
5575
5829
|
*/
|
|
5576
5830
|
_insertionEdgeId = signal(null, ...(ngDevMode ? [{ debugName: "_insertionEdgeId" }] : []));
|
|
5577
5831
|
insertionEdgeId = this._insertionEdgeId.asReadonly();
|
|
5578
|
-
/**
|
|
5579
|
-
|
|
5832
|
+
/**
|
|
5833
|
+
* Gli id degli archi su cui si puo' innestare: il hit test accetta solo questi.
|
|
5834
|
+
*
|
|
5835
|
+
* Gli archi di un riquadro chiuso restano fuori di proposito. Innestare vuol dire mettersi **in
|
|
5836
|
+
* mezzo** a due elementi, e di quell'arco uno dei due non si vede: il flow si ricollegherebbe
|
|
5837
|
+
* attorno a un elemento che non e' sullo schermo, che non e' una scelta ma una sorpresa. Per
|
|
5838
|
+
* innestare lì si apre il riquadro.
|
|
5839
|
+
*/
|
|
5840
|
+
edgeIds = computed(() => new Set(this.edges().filter((edge) => !edge.isFolded).map((edge) => edge.id)), ...(ngDevMode ? [{ debugName: "edgeIds" }] : []));
|
|
5580
5841
|
/** L'ascoltatore attivo solo durante un trascinamento dalla palette. */
|
|
5581
5842
|
pointerListener = null;
|
|
5582
5843
|
/**
|
|
@@ -5670,6 +5931,14 @@ class FlowCanvasComponent {
|
|
|
5670
5931
|
return;
|
|
5671
5932
|
}
|
|
5672
5933
|
const target = event.targetId ? parseTargetConnectorId(event.targetId) : null;
|
|
5934
|
+
/**
|
|
5935
|
+
* I connettori di una pastiglia non sono connettori di nessun elemento: dietro ce ne stanno
|
|
5936
|
+
* diversi, e non c'e' modo di sapere quale l'utente intendeva. Il gesto si rifiuta invece di
|
|
5937
|
+
* scrivere sul primo che capita — per collegare un elemento nascosto si apre il riquadro.
|
|
5938
|
+
*/
|
|
5939
|
+
if (isFoldReference(source.nodeName) || isFoldReference(target)) {
|
|
5940
|
+
return;
|
|
5941
|
+
}
|
|
5673
5942
|
if (target === source.nodeName) {
|
|
5674
5943
|
// `CONNECTOR_SELF_LOOP`: un node che punta a se stesso non e' mai voluto.
|
|
5675
5944
|
return;
|
|
@@ -5682,15 +5951,22 @@ class FlowCanvasComponent {
|
|
|
5682
5951
|
if (!previousSource) {
|
|
5683
5952
|
return;
|
|
5684
5953
|
}
|
|
5954
|
+
// Un capo su una pastiglia: vedi `onCreateConnection`, dietro non c'e' un solo elemento.
|
|
5955
|
+
if (isFoldReference(previousSource.nodeName)) {
|
|
5956
|
+
return;
|
|
5957
|
+
}
|
|
5685
5958
|
if (event.endpoint === 'target') {
|
|
5686
5959
|
const nextTarget = event.nextTargetId ? parseTargetConnectorId(event.nextTargetId) : null;
|
|
5960
|
+
if (isFoldReference(nextTarget)) {
|
|
5961
|
+
return;
|
|
5962
|
+
}
|
|
5687
5963
|
this.store.setConnector(previousSource.nodeName, previousSource.outletKey, nextTarget ?? undefined);
|
|
5688
5964
|
return;
|
|
5689
5965
|
}
|
|
5690
5966
|
// Capo sorgente spostato: l'arco cambia uscita, la destinazione resta.
|
|
5691
5967
|
const nextSource = event.nextSourceId ? parseSourceConnectorId(event.nextSourceId) : null;
|
|
5692
5968
|
const target = parseTargetConnectorId(event.previousTargetId);
|
|
5693
|
-
if (!nextSource || !target) {
|
|
5969
|
+
if (!nextSource || !target || isFoldReference(nextSource.nodeName) || isFoldReference(target)) {
|
|
5694
5970
|
return;
|
|
5695
5971
|
}
|
|
5696
5972
|
this.store.removeConnector(previousSource.nodeName, previousSource.outletKey);
|
|
@@ -5716,6 +5992,17 @@ class FlowCanvasComponent {
|
|
|
5716
5992
|
const groupName = parseCanvasGroupId(moved.id);
|
|
5717
5993
|
if (groupName) {
|
|
5718
5994
|
moves.push({ name: groupName, position: moved.position, isGroup: true });
|
|
5995
|
+
continue;
|
|
5996
|
+
}
|
|
5997
|
+
/**
|
|
5998
|
+
* La pastiglia di un riquadro chiuso e' un node per la libreria, ma spostarla e' spostare il
|
|
5999
|
+
* **riquadro**: i suoi membri vengono dietro, come con la cornice aperta. Senza questo ramo il
|
|
6000
|
+
* gesto non faceva niente e la pastiglia tornava al suo posto — il riquadro chiuso sarebbe
|
|
6001
|
+
* stato l'unica cosa del canvas che non si puo' spostare.
|
|
6002
|
+
*/
|
|
6003
|
+
const foldName = parseCanvasFoldId(moved.id);
|
|
6004
|
+
if (foldName) {
|
|
6005
|
+
moves.push({ name: foldName, position: moved.position, isGroup: true });
|
|
5719
6006
|
}
|
|
5720
6007
|
}
|
|
5721
6008
|
if (!moves.length) {
|
|
@@ -5724,8 +6011,16 @@ class FlowCanvasComponent {
|
|
|
5724
6011
|
moves.forEach((moved, index) => {
|
|
5725
6012
|
const options = { silent: index > 0 };
|
|
5726
6013
|
if (moved.isGroup) {
|
|
5727
|
-
|
|
5728
|
-
|
|
6014
|
+
/**
|
|
6015
|
+
* Il riquadro si porta dietro i suoi membri: e' cio' per cui `members` esiste (§3.6).
|
|
6016
|
+
* L'origine da cui misurare e' quella **disegnata** e non quella del documento: su un
|
|
6017
|
+
* riquadro auto-dimensionato le due cose divergono appena qualcuno sposta un membro, e
|
|
6018
|
+
* misurare dal documento spostava i membri di un delta sbagliato.
|
|
6019
|
+
*/
|
|
6020
|
+
this.store.moveGroup(moved.name, moved.position.x, moved.position.y, {
|
|
6021
|
+
...options,
|
|
6022
|
+
from: this.drawnOrigin(moved.name),
|
|
6023
|
+
});
|
|
5729
6024
|
}
|
|
5730
6025
|
else {
|
|
5731
6026
|
this.store.moveNode(moved.name, moved.position.x, moved.position.y, options);
|
|
@@ -5753,7 +6048,7 @@ class FlowCanvasComponent {
|
|
|
5753
6048
|
* porterebbe via il form che si stava guardando.
|
|
5754
6049
|
*/
|
|
5755
6050
|
const groups = event.nodeIds
|
|
5756
|
-
.map((id) => parseCanvasGroupId(id))
|
|
6051
|
+
.map((id) => parseCanvasGroupId(id) ?? parseCanvasFoldId(id))
|
|
5757
6052
|
.filter((name) => !!name);
|
|
5758
6053
|
if (groups.length === 1 && !names.length) {
|
|
5759
6054
|
this.groupSelected.emit(groups[0]);
|
|
@@ -5770,8 +6065,9 @@ class FlowCanvasComponent {
|
|
|
5770
6065
|
this.nodeRemoveRequested.emit(name);
|
|
5771
6066
|
continue;
|
|
5772
6067
|
}
|
|
5773
|
-
// Cancellare un riquadro non cancella cio' che contiene: e' un commento (§3.6).
|
|
5774
|
-
|
|
6068
|
+
// Cancellare un riquadro non cancella cio' che contiene: e' un commento (§3.6). Vale anche
|
|
6069
|
+
// per un riquadro **chiuso**: gli elementi tornano visibili, non spariscono con la pastiglia.
|
|
6070
|
+
const groupName = parseCanvasGroupId(id) ?? parseCanvasFoldId(id);
|
|
5775
6071
|
if (groupName) {
|
|
5776
6072
|
this.groupRemoveRequested.emit(groupName);
|
|
5777
6073
|
}
|
|
@@ -5842,6 +6138,13 @@ class FlowCanvasComponent {
|
|
|
5842
6138
|
targetIdOf(node) {
|
|
5843
6139
|
return targetConnectorId(node.name);
|
|
5844
6140
|
}
|
|
6141
|
+
/** I connettori della pastiglia di un riquadro chiuso: gli id li costruisce il modello, non il template. */
|
|
6142
|
+
foldTargetIdOf(fold) {
|
|
6143
|
+
return foldTargetConnectorId(fold.name);
|
|
6144
|
+
}
|
|
6145
|
+
foldSourceIdOf(fold) {
|
|
6146
|
+
return foldSourceConnectorId(fold.name);
|
|
6147
|
+
}
|
|
5845
6148
|
/**
|
|
5846
6149
|
* Selezionato = corrente **oppure** parte del blocco selezionato. Senza il secondo caso, un
|
|
5847
6150
|
* rettangolo che prende cinque elementi ne evidenziava uno, e i comandi che agiscono su tutti
|
|
@@ -5887,7 +6190,7 @@ class FlowCanvasComponent {
|
|
|
5887
6190
|
* esisterebbe. Registrarlo col preset di default non cambia niente di per se'.
|
|
5888
6191
|
*/
|
|
5889
6192
|
withControlScheme(F_DEFAULT_CONTROL_SCHEME)),
|
|
5890
|
-
], viewQueries: [{ propertyName: "canvas", first: true, predicate: FCanvasComponent, descendants: true, isSignal: true }], ngImport: i0, template: "<!--\r\n Gerarchia obbligatoria: f-flow > f-canvas > fNode / f-connection.\r\n I `@for` sono direttamente dentro <f-canvas>: nessun wrapper, quindi non serve\r\n `ngProjectAs` (che sarebbe indispensabile con blocchi annidati).\r\n-->\r\n<f-flow\r\n fDraggable\r\n (fCreateConnection)=\"onCreateConnection($event)\"\r\n (fReassignConnection)=\"onReassignConnection($event)\"\r\n (fMoveNodes)=\"onMoveNodes($event)\"\r\n (fSelectionChange)=\"onSelectionChange($event)\"\r\n (fDeleteSelected)=\"onDeleteSelected($event)\"\r\n (fCreateNode)=\"onCreateNode($event)\"\r\n (fNodesRendered)=\"onNodesRendered()\"\r\n (fDragStarted)=\"onDragStarted($event)\"\r\n (fDragEnded)=\"onDragEnded()\"\r\n>\r\n <!--\r\n `#canvas` + `(fCanvasChange)`: il primo serve per chiamare `fitToScreen()`, il secondo per\r\n sapere che l'utente ha mosso la vista. `[debounceTime]` non si imposta: l'evento serve solo\r\n ad accendere un flag, non a ricalcolare niente.\r\n -->\r\n <f-canvas fZoom #canvas (fCanvasChange)=\"onCanvasChange()\">\r\n <f-background>\r\n <f-circle-pattern />\r\n </f-background>\r\n\r\n <!--\r\n \u00A73.6 \u2014 i riquadri di raggruppamento. Vanno **sotto** i node, e non c'e' niente da fare per\r\n ottenerlo: `[fGroup]` viene proiettato nel layer dei gruppi, che nell'ordine della libreria\r\n sta sotto connessioni e node.\r\n\r\n Sono commenti: nessun connettore, nessun arco, nessuna raggiungibilita'. Il corpo del\r\n riquadro ha `pointer-events: none` (nel CSS) e solo la barra del titolo risponde: senza,\r\n una cornice grande quanto mezzo canvas si mangerebbe i click destinati alla panoramica e\r\n alla selezione a rettangolo, e i node dentro non si potrebbero piu' prendere.\r\n -->\r\n @for (group of groups(); track group.name) {\r\n <div\r\n fGroup\r\n [fGroupId]=\"group.id\"\r\n [fGroupPosition]=\"group.position\"\r\n [fGroupSize]=\"group.size\"\r\n [fGroupDraggingDisabled]=\"!isEditable()\"\r\n [class]=\"'fb-group ' + group.colorClass\"\r\n [class.fb-group--collapsed]=\"group.isCollapsed\"\r\n [class.fb-group--selected]=\"isGroupSelected(group)\"\r\n (fGroupSizeChange)=\"onGroupResized(group.name, $event)\"\r\n >\r\n <!--\r\n `fDragHandle` sulla sola barra: il riquadro si sposta dal titolo, come una finestra.\r\n Trascinandolo da dentro si sposterebbe la cornice ogni volta che si prova a prendere\r\n un node che le sta sopra.\r\n -->\r\n <div\r\n class=\"fb-group__bar\"\r\n fDragHandle\r\n [title]=\"group.description || group.label\"\r\n (click)=\"onGroupClick($event, group.name)\"\r\n >\r\n <button\r\n type=\"button\"\r\n fDragBlocker\r\n class=\"fb-group__fold\"\r\n [attr.aria-label]=\"group.isCollapsed ? 'Apri il riquadro' : 'Chiudi il riquadro'\"\r\n [title]=\"\r\n group.isCollapsed\r\n ? 'Apri il riquadro'\r\n : 'Chiudi il riquadro: resta la barra del titolo, gli elementi restano dove sono'\r\n \"\r\n (click)=\"toggleGroupCollapsed($event, group.name)\"\r\n >\r\n {{ group.isCollapsed ? '\u25B8' : '\u25BE' }}\r\n </button>\r\n <span class=\"fb-group__title\">{{ group.label }}</span>\r\n <span class=\"fb-group__count\" [title]=\"group.memberCount + ' elementi nel riquadro'\">\r\n {{ group.memberCount }}\r\n </span>\r\n @if (group.unknownMembers.length > 0) {\r\n <!--\r\n Membri che nominano un elemento che non c'e' piu'. Non e' un difetto (\u00A73.6): e' cio'\r\n che resta dopo una cancellazione, ed e' un avviso \u2014 l'attivazione non si blocca.\r\n -->\r\n <span\r\n class=\"fb-group__stale\"\r\n [title]=\"'Elementi non piu\u2019 esistenti: ' + group.unknownMembers.join(', ')\"\r\n >\r\n {{ group.unknownMembers.length }} da ripulire\r\n </span>\r\n }\r\n @if (isEditable()) {\r\n <button\r\n type=\"button\"\r\n fDragBlocker\r\n class=\"fb-group__remove\"\r\n [attr.aria-label]=\"'Elimina il riquadro ' + group.label\"\r\n title=\"Elimina il riquadro: gli elementi che contiene restano\"\r\n (click)=\"onGroupRemoveClick($event, group.name)\"\r\n >\r\n \u00D7\r\n </button>\r\n }\r\n </div>\r\n\r\n @if (!group.isCollapsed && group.description) {\r\n <p class=\"fb-group__note\">{{ group.description }}</p>\r\n }\r\n\r\n @if (isEditable() && !group.isCollapsed) {\r\n <!--\r\n Una sola maniglia, in basso a destra: quattro bersagli su una cornice che sta sotto\r\n i node si prendono per sbaglio. Ridimensionare **dichiara** la geometria, che e' il\r\n modo di uscire dal \u00ABcalcolala sui membri\u00BB (\u00A73.6).\r\n -->\r\n <div\r\n fResizeHandle\r\n [fResizeHandleType]=\"resizeHandles.RIGHT_BOTTOM\"\r\n class=\"fb-group__resize\"\r\n title=\"Ridimensiona il riquadro\"\r\n ></div>\r\n }\r\n </div>\r\n }\r\n\r\n @for (edge of edges(); track edge.id) {\r\n <f-connection\r\n [fConnectionId]=\"edge.id\"\r\n [fSourceId]=\"edge.sourceId\"\r\n [fTargetId]=\"edge.targetId\"\r\n fBehavior=\"floating\"\r\n [fType]=\"edge.isGoTo ? 'segment' : 'bezier'\"\r\n [class]=\"'fb-edge fb-edge--' + edge.kind + (edge.isGoTo ? ' fb-edge--goto' : '')\"\r\n [class.fb-edge--insert]=\"edge.id === insertionEdgeId()\"\r\n >\r\n <f-connection-marker-arrow [type]=\"markerEnd\" />\r\n @if (edge.label) {\r\n <div fConnectionContent class=\"fb-edge-label\">{{ edge.label }}</div>\r\n }\r\n </f-connection>\r\n }\r\n\r\n @for (node of nodes(); track node.id) {\r\n <div\r\n fNode\r\n fDragHandle\r\n [fNodeId]=\"node.id\"\r\n [fNodePosition]=\"node.position\"\r\n [class]=\"'fb-node ' + categoryClass(node) + ' ' + node.widthClass\"\r\n [class.fb-node--start]=\"node.isStart\"\r\n [class.fb-node--selected]=\"isSelected(node)\"\r\n [class.fb-node--unreachable]=\"!node.isReachable\"\r\n [class.fb-node--error]=\"node.severity === 'Error'\"\r\n [class.fb-node--warning]=\"node.severity === 'Warning'\"\r\n (dblclick)=\"onNodeDoubleClick(node.name)\"\r\n >\r\n <!--\r\n Lo Start non ha ingresso: e' il punto di partenza (\u00A73.4).\r\n `fConnectorConnectableSide` e' cio' che fa entrare l'arco dall'alto: senza, foblex\r\n calcola il lato e un arco che scende dal node sopra potrebbe agganciarsi di fianco.\r\n -->\r\n @if (!node.isStart) {\r\n <div\r\n fConnector\r\n fConnectorType=\"target\"\r\n [fConnectorId]=\"targetIdOf(node)\"\r\n [fConnectorConnectableSide]=\"sides.TOP\"\r\n fConnectorMultiple=\"true\"\r\n class=\"fb-connector fb-connector--in\"\r\n title=\"Ingresso\"\r\n ></div>\r\n }\r\n\r\n <div class=\"fb-node__head\">\r\n <span class=\"fb-node__icon\" aria-hidden=\"true\">{{ node.icon }}</span>\r\n <div class=\"fb-node__text\">\r\n <span class=\"fb-node__title\" [title]=\"node.description || node.label\">{{ node.label }}</span>\r\n <span class=\"fb-node__sub\">\r\n {{ node.typeLabel }}\r\n @if (node.subtitle) {\r\n <span class=\"fb-node__sub-dot\">\u00B7</span>{{ node.subtitle }}\r\n }\r\n </span>\r\n </div>\r\n @if (node.hasAutomaticOutput) {\r\n <!-- L'elemento espone il proprio risultato sotto il proprio nome (\u00A74.5). -->\r\n <span class=\"fb-node__auto\" title=\"Espone un output automatico referenziabile come \u00AB{{ node.name }}\u00BB\">\r\n \u0192\r\n </span>\r\n }\r\n <!--\r\n `fDragBlocker` impedisce che il pointerdown sul bottone diventi un trascinamento\r\n del node: senza, aprire il dettaglio sposterebbe l'elemento di qualche pixel.\r\n -->\r\n <button\r\n type=\"button\"\r\n fDragBlocker\r\n class=\"fb-node__edit\"\r\n [attr.aria-label]=\"'Apri il dettaglio di ' + node.label\"\r\n title=\"Apri il dettaglio\"\r\n (click)=\"onEditClick($event, node.name)\"\r\n >\r\n \u270E\r\n </button>\r\n @if (!node.isStart && isEditable()) {\r\n <!--\r\n Duplica: sta accanto a \u270E e non solo nell'inspector perche' e' il gesto con cui si\r\n riusa un elemento gi\u00E0 configurato, e cercarlo dentro il form dell'elemento da\r\n copiare e' il posto in cui non lo si cerca. Compare come \u00AB\u00D7\u00BB, sul solo node\r\n selezionato: aggiunge un elemento al documento, quindi non deve trovarsi sotto il\r\n puntatore di chi sta solo attraversando il grafo.\r\n -->\r\n <button\r\n type=\"button\"\r\n fDragBlocker\r\n class=\"fb-node__duplicate\"\r\n [attr.aria-label]=\"'Duplica ' + node.label\"\r\n title=\"Duplica questo elemento (Ctrl+D)\"\r\n (click)=\"onDuplicateClick($event, node.name)\"\r\n >\r\n \u29C9\r\n </button>\r\n <!--\r\n Si mostra solo sul node **selezionato**, non al passaggio del mouse come \u270E:\r\n cancellare non e' reversibile con un altro clic, e un comando distruttivo che\r\n appare sotto il puntatore mentre si attraversa il grafo si preme per sbaglio.\r\n Lo Start non lo espone: un flow senza ingresso non esisterebbe.\r\n -->\r\n <button\r\n type=\"button\"\r\n fDragBlocker\r\n class=\"fb-node__remove\"\r\n [attr.aria-label]=\"'Elimina ' + node.label\"\r\n title=\"Elimina questo elemento (si annulla con \u21B6)\"\r\n (click)=\"onRemoveClick($event, node.name)\"\r\n >\r\n \u00D7\r\n </button>\r\n }\r\n </div>\r\n\r\n <div class=\"fb-node__meta\">\r\n @if (!node.isStart) {\r\n <span class=\"fb-node__name\" [title]=\"'Nome tecnico: ' + node.name\">{{ node.name }}</span>\r\n }\r\n @if (!node.isReachable) {\r\n <span class=\"fb-badge fb-badge--unreachable\" title=\"Nessun percorso raggiunge questo elemento dallo Start\">\r\n scollegato\r\n </span>\r\n }\r\n @if (node.issueCount > 0) {\r\n <span\r\n class=\"fb-badge\"\r\n [class.fb-badge--error]=\"node.severity === 'Error'\"\r\n [class.fb-badge--warning]=\"node.severity === 'Warning'\"\r\n [class.fb-badge--info]=\"node.severity === 'Info'\"\r\n [title]=\"node.issueCount + ' rilievi di validazione'\"\r\n >\r\n {{ node.issueCount }}\r\n </span>\r\n }\r\n @if (node.danglingOutlets.length > 0) {\r\n <span\r\n class=\"fb-badge fb-badge--dangling\"\r\n [title]=\"'Rami dichiarati senza destinazione: ' + danglingLabels(node)\"\r\n >\r\n ramo incompleto\r\n </span>\r\n }\r\n </div>\r\n\r\n <!--\r\n Le uscite stanno sul bordo **inferiore**, una per ramo, nell'ordine in cui il modello\r\n le dichiara: per una Decision e' l'ordine di valutazione delle regole, che e'\r\n semantico (\u00A75.4), e da sinistra a destra si legge come la lista nell'inspector.\r\n L'etichetta si mostra solo quando i rami sono piu' di uno: su un `next` unico\r\n direbbe soltanto \u00ABSuccessivo\u00BB.\r\n -->\r\n <div class=\"fb-node__outlets\" [class.fb-node__outlets--labelled]=\"node.showsOutletLabels\">\r\n @for (outlet of node.outlets; track outlet.key) {\r\n <div class=\"fb-outlet\">\r\n @if (node.showsOutletLabels) {\r\n <span class=\"fb-outlet__label\" [title]=\"outlet.label\">{{ outlet.label }}</span>\r\n }\r\n <div\r\n fConnector\r\n fConnectorType=\"source\"\r\n [fConnectorId]=\"connectorIdOf(node, outlet.key)\"\r\n [fConnectorConnectableSide]=\"sides.BOTTOM\"\r\n [class]=\"'fb-connector fb-connector--out fb-connector--' + outlet.kind\"\r\n [title]=\"outlet.label\"\r\n ></div>\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n }\r\n\r\n <!-- Anteprima dell'arco durante il trascinamento. -->\r\n <f-connection-for-create fBehavior=\"floating\" fType=\"bezier\" class=\"fb-edge fb-edge--creating\">\r\n <f-connection-marker-arrow [type]=\"markerEnd\" />\r\n </f-connection-for-create>\r\n\r\n <f-selection-area />\r\n </f-canvas>\r\n\r\n <!--\r\n Fuori da <f-canvas> come la minimappa: dentro, la trasformazione del canvas se lo\r\n porterebbe via insieme ai node. `fDragBlocker` perche' il pointerdown non inizi una\r\n panoramica invece di premere il bottone.\r\n -->\r\n <!--\r\n Sempre nel DOM, nascosto con una classe e non con `@if`: dentro una proiezione di contenuto\r\n un blocco di controllo e' una complicazione gratuita, e `visibility: hidden` lo toglie\r\n anche dall'ordine di tabulazione. Il costo e' un bottone in piu' nel DOM, non un rischio.\r\n -->\r\n <div class=\"fb-viewport\" fDragBlocker role=\"group\" aria-label=\"Vista\">\r\n <!--\r\n Lo zoom ha bisogno di **comandi e di un numero**: con la sola rotella si finisce all'8% su\r\n un flow grande e sembra che il canvas si sia svuotato, senza un modo evidente di tornare.\r\n Il numero e' un bottone: riporta al 100% sull'elemento corrente.\r\n -->\r\n <button type=\"button\" class=\"fb-btn fb-viewport__btn\" title=\"Riduci (rotella indietro)\" (click)=\"zoomOut()\">\r\n \u2212\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-viewport__level\"\r\n title=\"Torna al 100% sull\u2019elemento selezionato\"\r\n (click)=\"zoomToActual()\"\r\n >\r\n {{ zoomPercent() }}%\r\n </button>\r\n <button type=\"button\" class=\"fb-btn fb-viewport__btn\" title=\"Ingrandisci (rotella avanti)\" (click)=\"zoomIn()\">\r\n +\r\n </button>\r\n <!--\r\n La modalita\u2019 selezione. Esiste perche\u2019 i due gesti che la libreria offre di serie \u2014\r\n `Ctrl`+clic per aggiungere, `Shift`+trascina per il rettangolo \u2014 non si vedono: senza un\r\n interruttore, su un flow grande si finisce a spostare un elemento alla volta. Accesa, il\r\n trascinamento sul canvas vuoto **seleziona** e la vista si sposta col tasto centrale.\r\n -->\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-viewport__select\"\r\n [class.fb-viewport__select--on]=\"isSelecting()\"\r\n [attr.aria-pressed]=\"isSelecting()\"\r\n [title]=\"\r\n isSelecting()\r\n ? 'Modalita\u2019 selezione attiva: trascina sul canvas per selezionare piu\u2019 elementi. La vista si sposta col tasto centrale del mouse'\r\n : 'Attiva la selezione a rettangolo: trascinando sul canvas selezioni piu\u2019 elementi invece di spostare la vista (Shift+trascina fa lo stesso, sempre)'\r\n \"\r\n (click)=\"toggleSelectionMode()\"\r\n >\r\n <span class=\"fb-viewport__icon\" aria-hidden=\"true\">\u25A4</span>\r\n Seleziona\r\n </button>\r\n <!--\r\n Sempre nel DOM, nascosto con una classe e non con `@if`: dentro una proiezione di contenuto\r\n un blocco di controllo e' una complicazione gratuita, e `visibility: hidden` lo toglie\r\n anche dall'ordine di tabulazione. Il costo e' un bottone in piu' nel DOM, non un rischio.\r\n -->\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-viewport__fit\"\r\n [class.fb-viewport__fit--hidden]=\"!hasMovedViewport()\"\r\n title=\"Rimetti il flow in vista. Su un flow molto grande inquadra l\u2019inizio, perche\u2019 l\u2019intero non sarebbe leggibile\"\r\n (click)=\"resetViewport()\"\r\n >\r\n <span class=\"fb-viewport__icon\" aria-hidden=\"true\">\u2922</span>\r\n Inquadra\r\n </button>\r\n </div>\r\n\r\n @if (selectionCount() > 1) {\r\n <!--\r\n Il dato che rende utile la selezione: che si spostano **insieme**. Senza dirlo, dopo aver\r\n selezionato cinque elementi si continua a trascinarne uno per volta.\r\n -->\r\n <p class=\"fb-select-hint\" role=\"status\">\r\n {{ selectionCount() }} elementi selezionati: trascinane uno per spostarli tutti \u00B7 Ctrl+clic\r\n aggiunge o toglie \u00B7 Canc li elimina\r\n </p>\r\n } @else if (isSelecting()) {\r\n <p class=\"fb-select-hint\" role=\"status\">\r\n Trascina sul canvas per selezionare \u00B7 la vista si sposta col tasto centrale\r\n </p>\r\n }\r\n\r\n @if (insertionEdgeId()) {\r\n <!-- Il rilascio su un arco non e' un gesto che si indovina: mentre e' possibile, si dice. -->\r\n <p class=\"fb-insert-hint\" role=\"status\">Rilascia qui per inserire l\u2019elemento in questo percorso</p>\r\n }\r\n\r\n <f-minimap [fMinSize]=\"1200\" class=\"fb-minimap\" />\r\n</f-flow>\r\n", styles: [":host{display:block;position:relative;width:100%;height:100%;overflow:hidden;background:var(--fb-canvas-bg, #f4f5f7)}f-flow{display:block;width:100%;height:100%}.fb-minimap{position:absolute;right:14px;bottom:14px;width:150px;height:300px;max-height:40%;border:1px solid var(--fb-border, #e2e5eb);border-radius:var(--fb-radius, 10px);background:var(--fb-surface, #fff);box-shadow:var(--fb-shadow-md, 0 6px 18px rgb(16 24 40 / 10%));overflow:hidden}.fb-viewport{position:absolute;right:14px;bottom:322px;z-index:1;display:flex;align-items:center;gap:4px;flex-wrap:wrap;justify-content:flex-end;max-width:200px}.fb-viewport__btn,.fb-viewport__level,.fb-viewport__fit{border-radius:var(--fb-radius-xs, 6px);box-shadow:var(--fb-shadow-md, 0 6px 18px rgb(16 24 40 / 10%));font-size:11px}.fb-viewport__btn{width:24px;padding:2px 0;font-size:14px;line-height:1}.fb-viewport__level{min-width:52px;font-variant-numeric:tabular-nums}.fb-viewport__fit--hidden{opacity:0;visibility:hidden}.fb-viewport__fit{transition:opacity .15s ease,visibility .15s ease}.fb-viewport__icon{font-size:13px;line-height:1;color:var(--fb-text-muted, #667085)}.fb-insert-hint{position:absolute;bottom:18px;left:50%;transform:translate(-50%);z-index:1;margin:0;padding:6px 12px;border-radius:999px;background:var(--fb-accent, #2f6feb);box-shadow:var(--fb-shadow-md, 0 6px 18px rgb(16 24 40 / 10%));color:#fff;font-size:11px;font-weight:600;pointer-events:none}.fb-select-hint{position:absolute;bottom:18px;left:50%;transform:translate(-50%);z-index:1;margin:0;padding:6px 12px;border-radius:999px;border:1px solid var(--fb-border, #e2e5eb);background:var(--fb-surface, #fff);box-shadow:var(--fb-shadow-md, 0 6px 18px rgb(16 24 40 / 10%));color:var(--fb-text-muted, #667085);font-size:11px;font-weight:600;pointer-events:none}.fb-viewport__select{border-radius:var(--fb-radius-xs, 6px);box-shadow:var(--fb-shadow-md, 0 6px 18px rgb(16 24 40 / 10%));font-size:11px}.fb-viewport__select--on{border-color:var(--fb-accent, #2f6feb);background:var(--fb-accent, #2f6feb);color:#fff}.fb-node{position:absolute;display:flex;flex-direction:column;box-sizing:border-box;width:240px;padding:0;border:1px solid var(--fb-border, #e2e5eb);border-radius:var(--fb-radius-lg, 12px);background:var(--fb-surface, #fff);box-shadow:var(--fb-shadow-sm, 0 1px 2px rgb(16 24 40 / 6%));font:inherit;cursor:grab;-webkit-user-select:none;user-select:none;transition:box-shadow .12s ease,border-color .12s ease}.fb-node--outlets-3{width:304px}.fb-node--outlets-4{width:380px}.fb-node--outlets-5{width:456px}.fb-node--outlets-6{width:520px}.fb-node:hover{box-shadow:var(--fb-shadow-md, 0 6px 18px rgb(16 24 40 / 10%))}.fb-node--selected{border-color:var(--fb-accent, #4f6ef7);box-shadow:0 0 0 3px color-mix(in srgb,var(--fb-accent, #4f6ef7) 22%,transparent)}.fb-node--unreachable{border-style:dashed;opacity:.8}.fb-node--error{border-color:var(--fb-error, #c9372c)}.fb-node--warning{border-color:var(--fb-warning, #b7791f)}.fb-node__head{display:flex;align-items:center;gap:8px;padding:10px 10px 6px 12px}.fb-node__icon{display:grid;place-items:center;flex:0 0 auto;width:28px;height:28px;border-radius:var(--fb-radius-sm, 8px);background:var(--fb-node-accent, #667085);color:#fff;font-size:14px;line-height:1}.cat-start{--fb-node-accent: #22a06b}.cat-screen{--fb-node-accent: #3b82f6}.cat-logic{--fb-node-accent: #8b5cf6}.cat-data{--fb-node-accent: #06b6d4}.cat-action{--fb-node-accent: #f59e0b}.cat-flow{--fb-node-accent: #14b8a6}.cat-other{--fb-node-accent: #667085}.fb-node__text{flex:1;min-width:0}.fb-node__title{display:block;font-size:13px;font-weight:600;line-height:17px;color:var(--fb-text, #1a1c23);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fb-node__sub{display:block;margin-top:1px;font-size:11px;line-height:14px;color:var(--fb-text-muted, #6b7086);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fb-node__sub-dot{margin:0 4px;color:var(--fb-text-subtle, #98a2b3)}.fb-node__auto{flex:0 0 auto;font-size:12px;font-weight:700;color:var(--fb-accent, #4f6ef7);cursor:help}.fb-node__edit{display:grid;place-items:center;flex:0 0 auto;width:22px;height:22px;padding:0;border:0;border-radius:var(--fb-radius-xs, 6px);background:transparent;color:var(--fb-text-subtle, #98a2b3);font:inherit;font-size:12px;cursor:pointer;opacity:0;transition:opacity .12s ease,background .12s ease}.fb-node:hover .fb-node__edit,.fb-node--selected .fb-node__edit,.fb-node__edit:focus-visible{opacity:1}.fb-node__edit:hover{background:var(--fb-surface-alt, #f7f8fa);color:var(--fb-text, #1a1c23)}.fb-node__duplicate,.fb-node__remove{display:grid;place-items:center;flex:0 0 auto;width:22px;height:22px;padding:0;border:0;border-radius:var(--fb-radius-xs, 6px);background:transparent;color:var(--fb-text-subtle, #98a2b3);font:inherit;font-size:15px;line-height:1;cursor:pointer;opacity:0;transition:opacity .12s ease,background .12s ease,color .12s ease}.fb-node--selected .fb-node__duplicate,.fb-node--selected .fb-node__remove,.fb-node__duplicate:focus-visible,.fb-node__remove:focus-visible{opacity:1}.fb-node__remove:hover{background:color-mix(in srgb,var(--fb-error, #c9372c) 12%,transparent);color:var(--fb-error, #c9372c)}.fb-node__duplicate:hover{background:color-mix(in srgb,var(--fb-accent, #3b6ef2) 12%,transparent);color:var(--fb-accent, #3b6ef2)}.fb-node__meta{display:flex;flex-wrap:wrap;align-items:center;gap:4px;min-height:14px;padding:0 12px 6px}.fb-node__name{flex:0 1 auto;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:10px;color:var(--fb-text-subtle, #98a2b3);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fb-badge{padding:1px 6px;border-radius:10px;background:var(--fb-badge-bg, #eef0f4);font-size:10px;font-weight:600;color:var(--fb-text-muted, #6b7086);white-space:nowrap;cursor:help}.fb-badge--error{background:color-mix(in srgb,var(--fb-error, #c9372c) 14%,transparent);color:var(--fb-error, #c9372c)}.fb-badge--warning{background:color-mix(in srgb,var(--fb-warning, #b7791f) 16%,transparent);color:var(--fb-warning, #b7791f)}.fb-badge--info{background:color-mix(in srgb,var(--fb-accent, #4f6ef7) 12%,transparent);color:var(--fb-accent, #4f6ef7)}.fb-badge--unreachable,.fb-badge--dangling{background:color-mix(in srgb,var(--fb-warning, #b7791f) 12%,transparent);color:var(--fb-warning, #b7791f)}.fb-node__outlets{display:flex;align-items:flex-end;justify-content:space-evenly;gap:4px;padding:0 8px 4px}.fb-node__outlets--labelled{padding-top:5px;border-top:1px solid var(--fb-border-subtle, #eef0f4)}.fb-outlet{display:flex;flex:1 1 0;flex-direction:column;align-items:center;gap:2px;min-width:0}.fb-outlet__label{max-width:100%;font-size:10px;line-height:13px;color:var(--fb-text-muted, #6b7086);text-align:center;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fb-connector{box-sizing:border-box;width:12px;height:12px;flex:0 0 auto;border:2px solid var(--fb-surface, #fff);border-radius:50%;background:var(--fb-connector, #98a2b3);cursor:crosshair;transition:transform .12s ease,box-shadow .12s ease}f-flow .fb-connector--out{position:relative;inset:auto;margin-bottom:-12px}.fb-connector--out:hover{transform:scale(1.2)}f-flow .fb-connector--in{position:absolute;inset:-2px auto auto 50%;width:24px;height:4px;border:0;border-radius:2px;transform:translate(-50%);background:var(--fb-border-strong, #cfd4de)}.fb-connector--out{--ff-connector-connected-color: var(--fb-connector, #98a2b3)}.fb-connector--Next,.fb-connector--Start,.fb-connector--LoopNext{--fb-connector: var(--fb-edge-next, #98a2b3)}.fb-connector--Rule,.fb-connector--WaitEvent{--fb-connector: var(--fb-edge-rule, #8b5cf6)}.fb-connector--Default,.fb-connector--LoopEnd{--fb-connector: var(--fb-edge-default, #06b6d4)}.fb-connector--Fault{--fb-connector: var(--fb-edge-fault, #dc2626)}.fb-connector--Timeout,.fb-connector--ScheduledPath{--fb-connector: var(--fb-edge-timeout, #f59e0b)}.fb-connector.f-connector-connectable{box-shadow:0 0 0 4px color-mix(in srgb,var(--fb-accent, #4f6ef7) 28%,transparent)}.fb-group{position:absolute;box-sizing:border-box;border:1px solid var(--fb-group-border, #d6dae1);border-radius:var(--fb-radius, 10px);background:var(--fb-group-fill, rgb(148 163 184 / 10%));pointer-events:none;overflow:hidden}.fb-group--selected{border-color:var(--fb-accent, #2f6feb);box-shadow:0 0 0 2px color-mix(in srgb,var(--fb-accent, #2f6feb) 25%,transparent)}.fb-group__bar{display:flex;align-items:center;gap:6px;height:30px;padding:0 6px;border-bottom:1px solid var(--fb-group-border, #d6dae1);background:var(--fb-group-bar, rgb(148 163 184 / 22%));color:var(--fb-text, #1d2939);font-size:12px;font-weight:600;pointer-events:auto;cursor:move;-webkit-user-select:none;user-select:none}.fb-group--collapsed .fb-group__bar{border-bottom:none}.fb-group__fold,.fb-group__remove{flex:none;width:18px;height:18px;padding:0;border:none;border-radius:var(--fb-radius-xs, 6px);background:transparent;color:inherit;font:inherit;font-size:12px;line-height:1;cursor:pointer}.fb-group__fold:hover,.fb-group__remove:hover{background:#10182814}.fb-group__remove{margin-left:auto;font-size:14px}.fb-group__title{overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.fb-group__count{flex:none;padding:0 5px;border-radius:8px;background:#10182814;font-size:10px;font-weight:600;font-variant-numeric:tabular-nums}.fb-group__stale{flex:none;padding:0 5px;border-radius:8px;background:color-mix(in srgb,var(--fb-warning, #b7791f) 14%,transparent);color:var(--fb-warning, #b7791f);font-size:10px;font-weight:600}.fb-group__note{margin:6px 8px 0;color:var(--fb-text-muted, #667085);font-size:11px;line-height:1.35;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2;overflow:hidden}.fb-group__resize{position:absolute;right:0;bottom:0;width:14px;height:14px;border-right:2px solid var(--fb-group-border, #d6dae1);border-bottom:2px solid var(--fb-group-border, #d6dae1);border-bottom-right-radius:var(--fb-radius, 10px);pointer-events:auto;cursor:nwse-resize}.fb-group--giallo{--fb-group-border: #e0c26a;--fb-group-fill: rgb(234 179 8 / 10%);--fb-group-bar: rgb(234 179 8 / 24%)}.fb-group--verde{--fb-group-border: #86c79b;--fb-group-fill: rgb(34 160 89 / 10%);--fb-group-bar: rgb(34 160 89 / 22%)}.fb-group--blu{--fb-group-border: #93b4ea;--fb-group-fill: rgb(47 111 235 / 9%);--fb-group-bar: rgb(47 111 235 / 20%)}.fb-group--viola{--fb-group-border: #bda6e6;--fb-group-fill: rgb(139 92 246 / 10%);--fb-group-bar: rgb(139 92 246 / 22%)}.fb-group--rosso{--fb-group-border: #e6a3a3;--fb-group-fill: rgb(214 69 69 / 9%);--fb-group-bar: rgb(214 69 69 / 20%)}.fb-group--grigio,.fb-group--neutro{--fb-group-border: #d6dae1;--fb-group-fill: rgb(148 163 184 / 10%);--fb-group-bar: rgb(148 163 184 / 22%)}\n"], dependencies: [{ kind: "ngmodule", type: FFlowModule }, { kind: "component", type: i1.FFlowComponent, selector: "f-flow", inputs: ["fFlowId", "fCache"], outputs: ["fNodesRendered", "fFullRendered", "fLoaded"] }, { kind: "component", type: i1.FCanvasComponent, selector: "f-canvas", inputs: ["position", "scale", "debounceTime", "fLayers"], outputs: ["fCanvasChange"] }, { kind: "component", type: i1.FBackgroundComponent, selector: "f-background" }, { kind: "component", type: i1.FCirclePatternComponent, selector: "f-circle-pattern", inputs: ["id", "color", "radius"] }, { kind: "directive", type: i1.FZoomDirective, selector: "f-canvas[fZoom]", inputs: ["fZoom", "fWheelTrigger", "fDblClickTrigger", "fZoomMinimum", "fZoomMaximum", "fZoomStep", "fPinchStep", "fZoomDblClickStep"] }, { kind: "component", type: i1.FSelectionArea, selector: "f-selection-area", inputs: ["fTrigger"] }, { kind: "directive", type: i1.FConnectionContent, selector: "[fConnectionContent]", inputs: ["position", "offset", "align"] }, { kind: "component", type: i1.FConnectionMarkerArrow, selector: "f-connection-marker-arrow", inputs: ["type"] }, { kind: "component", type: i1.FConnectionComponent, selector: "f-connection", inputs: ["fConnectionId", "fSourceId", "fTargetId", "fOutputId", "fInputId", "fRadius", "fOffset", "fBehavior", "fType", "fSelectionDisabled", "fReassignableStart", "fReassignDisabled", "fSourceSide", "fTargetSide", "fInputSide", "fOutputSide"], exportAs: ["fComponent"] }, { kind: "component", type: i1.FConnectionForCreateComponent, selector: "f-connection-for-create", inputs: ["fRadius", "fOffset", "fBehavior", "fType", "fInputSide", "fOutputSide"] }, { kind: "directive", type: i1.FConnectorDirective, selector: "[fConnector]", inputs: ["fConnectorId", "fConnectorType", "fConnectorDisabled", "fConnectorMultiple", "fConnectorCategory", "fConnectorConnectableSide", "fConnectorSelfConnectable", "fCanBeConnectedTo", "fConnectionFromOutlet"], exportAs: ["fConnector"] }, { kind: "component", type: i1.FMinimapComponent, selector: "f-minimap", inputs: ["fMinSize", "fNodeRenderLimit"], exportAs: ["fComponent"] }, { kind: "directive", type: i1.FGroupDirective, selector: "[fGroup]", inputs: ["fGroupId", "fGroupParentId", "fGroupPosition", "fGroupSize", "fGroupRotate", "fConnectOnNode", "fMinimapClass", "fGroupDraggingDisabled", "fGroupSelectionDisabled", "fIncludePadding", "fAutoExpandOnChildHit", "fAutoSizeToFitChildren"], outputs: ["fGroupPositionChange", "fGroupSizeChange", "fGroupRotateChange"], exportAs: ["fComponent"] }, { kind: "directive", type: i1.FNodeDirective, selector: "[fNode]", inputs: ["fNodeId", "fNodeParentId", "fNodePosition", "fNodeSize", "fNodeRotate", "fConnectOnNode", "fMinimapClass", "fNodeDraggingDisabled", "fNodeSelectionDisabled", "fIncludePadding", "fAutoExpandOnChildHit", "fAutoSizeToFitChildren"], outputs: ["fNodePositionChange", "fNodeSizeChange", "fNodeRotateChange"], exportAs: ["fComponent"] }, { kind: "directive", type: i1.FDragHandleDirective, selector: "[fDragHandle]" }, { kind: "directive", type: i1.FResizeHandleDirective, selector: "[fResizeHandle]", inputs: ["fResizeHandleType"] }, { kind: "directive", type: i1.FDragBlockerDirective, selector: "[fDragBlocker]" }, { kind: "directive", type: i1.FDraggableDirective, selector: "f-flow[fDraggable]", inputs: ["fDraggableDisabled", "fDropToGroup", "fMultiSelectTrigger", "fReassignConnectionTrigger", "fCreateConnectionTrigger", "fConnectionWaypointsTrigger", "fMoveControlPointTrigger", "fNodeResizeTrigger", "fNodeRotateTrigger", "fNodeMoveTrigger", "fCanvasMoveTrigger", "fExternalItemTrigger", "fEmitOnNodeIntersect", "vCellSize", "hCellSize", "fCellSizeWhileDragging"], outputs: ["fSelectionChange", "fDeleteSelected", "fNodeIntersectedWithConnections", "fNodeConnectionsIntersection", "fCreateNode", "fMoveNodes", "fReassignConnection", "fCreateConnection", "fConnectionWaypointsChanged", "fDropToGroup", "fDragStarted", "fDragEnded"], exportAs: ["fDraggable"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
6193
|
+
], viewQueries: [{ propertyName: "canvas", first: true, predicate: FCanvasComponent, descendants: true, isSignal: true }], ngImport: i0, template: "<!--\r\n Gerarchia obbligatoria: f-flow > f-canvas > fNode / f-connection.\r\n I `@for` sono direttamente dentro <f-canvas>: nessun wrapper, quindi non serve\r\n `ngProjectAs` (che sarebbe indispensabile con blocchi annidati).\r\n-->\r\n<f-flow\r\n fDraggable\r\n (fCreateConnection)=\"onCreateConnection($event)\"\r\n (fReassignConnection)=\"onReassignConnection($event)\"\r\n (fMoveNodes)=\"onMoveNodes($event)\"\r\n (fSelectionChange)=\"onSelectionChange($event)\"\r\n (fDeleteSelected)=\"onDeleteSelected($event)\"\r\n (fCreateNode)=\"onCreateNode($event)\"\r\n (fNodesRendered)=\"onNodesRendered()\"\r\n (fDragStarted)=\"onDragStarted($event)\"\r\n (fDragEnded)=\"onDragEnded()\"\r\n>\r\n <!--\r\n `#canvas` + `(fCanvasChange)`: il primo serve per chiamare `fitToScreen()`, il secondo per\r\n sapere che l'utente ha mosso la vista. `[debounceTime]` non si imposta: l'evento serve solo\r\n ad accendere un flag, non a ricalcolare niente.\r\n -->\r\n <f-canvas fZoom #canvas (fCanvasChange)=\"onCanvasChange()\">\r\n <f-background>\r\n <f-circle-pattern />\r\n </f-background>\r\n\r\n <!--\r\n \u00A73.6 \u2014 i riquadri di raggruppamento. Vanno **sotto** i node, e non c'e' niente da fare per\r\n ottenerlo: `[fGroup]` viene proiettato nel layer dei gruppi, che nell'ordine della libreria\r\n sta sotto connessioni e node.\r\n\r\n Sono commenti: nessun connettore, nessun arco, nessuna raggiungibilita'. Il corpo del\r\n riquadro ha `pointer-events: none` (nel CSS) e solo la barra del titolo risponde: senza,\r\n una cornice grande quanto mezzo canvas si mangerebbe i click destinati alla panoramica e\r\n alla selezione a rettangolo, e i node dentro non si potrebbero piu' prendere.\r\n -->\r\n @for (group of groups(); track group.name) {\r\n <div\r\n fGroup\r\n [fGroupId]=\"group.id\"\r\n [fGroupPosition]=\"group.position\"\r\n [fGroupSize]=\"group.size\"\r\n [fGroupDraggingDisabled]=\"!isEditable()\"\r\n [class]=\"'fb-group ' + group.colorClass\"\r\n [class.fb-group--selected]=\"isGroupSelected(group)\"\r\n (fGroupSizeChange)=\"onGroupResized(group.name, $event)\"\r\n >\r\n <!--\r\n `fDragHandle` sulla sola barra: il riquadro si sposta dal titolo, come una finestra.\r\n Trascinandolo da dentro si sposterebbe la cornice ogni volta che si prova a prendere\r\n un node che le sta sopra.\r\n -->\r\n <div\r\n class=\"fb-group__bar\"\r\n fDragHandle\r\n [title]=\"group.description || group.label\"\r\n (click)=\"onGroupClick($event, group.name)\"\r\n >\r\n <button\r\n type=\"button\"\r\n fDragBlocker\r\n class=\"fb-group__fold\"\r\n aria-label=\"Chiudi il riquadro\"\r\n title=\"Chiudi il riquadro: gli elementi si nascondono e gli archi che lo attraversano si attaccano alla pastiglia\"\r\n (click)=\"toggleGroupCollapsed($event, group.name)\"\r\n >\r\n \u25BE\r\n </button>\r\n <span class=\"fb-group__title\">{{ group.label }}</span>\r\n <span class=\"fb-group__count\" [title]=\"group.memberCount + ' elementi nel riquadro'\">\r\n {{ group.memberCount }}\r\n </span>\r\n @if (group.unknownMembers.length > 0) {\r\n <!--\r\n Membri che nominano un elemento che non c'e' piu'. Non e' un difetto (\u00A73.6): e' cio'\r\n che resta dopo una cancellazione, ed e' un avviso \u2014 l'attivazione non si blocca.\r\n -->\r\n <span\r\n class=\"fb-group__stale\"\r\n [title]=\"'Elementi non piu\u2019 esistenti: ' + group.unknownMembers.join(', ')\"\r\n >\r\n {{ group.unknownMembers.length }} da ripulire\r\n </span>\r\n }\r\n @if (isEditable()) {\r\n <button\r\n type=\"button\"\r\n fDragBlocker\r\n class=\"fb-group__remove\"\r\n [attr.aria-label]=\"'Elimina il riquadro ' + group.label\"\r\n title=\"Elimina il riquadro: gli elementi che contiene restano\"\r\n (click)=\"onGroupRemoveClick($event, group.name)\"\r\n >\r\n \u00D7\r\n </button>\r\n }\r\n </div>\r\n\r\n @if (group.description) {\r\n <p class=\"fb-group__note\">{{ group.description }}</p>\r\n }\r\n\r\n @if (isEditable()) {\r\n <!--\r\n Una sola maniglia, in basso a destra: quattro bersagli su una cornice che sta sotto\r\n i node si prendono per sbaglio. Ridimensionare **dichiara** la geometria, che e' il\r\n modo di uscire dal \u00ABcalcolala sui membri\u00BB (\u00A73.6).\r\n -->\r\n <div\r\n fResizeHandle\r\n [fResizeHandleType]=\"resizeHandles.RIGHT_BOTTOM\"\r\n class=\"fb-group__resize\"\r\n title=\"Ridimensiona il riquadro\"\r\n ></div>\r\n }\r\n </div>\r\n }\r\n\r\n <!--\r\n \u00A73.6 \u2014 un riquadro **chiuso**: una pastiglia al posto dei suoi elementi.\r\n\r\n \u00C8 un `fNode` e non un `fGroup` per una ragione sola, ed e' la regola 4 di @foblex/flow: per\r\n nascondere i node bisogna che gli archi che entravano e uscivano trovino un connettore\r\n **vero**, altrimenti hanno geometria 0\u00D70 e si attaccano all'angolo del canvas. La pastiglia e'\r\n quel connettore. Un solo connettore in uscita: dietro ci stanno elementi diversi, e il nome di\r\n quello che esce sta nell'etichetta dell'arco.\r\n -->\r\n @for (fold of folds(); track fold.id) {\r\n <div\r\n fNode\r\n fDragHandle\r\n [fNodeId]=\"fold.id\"\r\n [fNodePosition]=\"fold.position\"\r\n [class]=\"'fb-fold ' + fold.colorClass\"\r\n [class.fb-fold--selected]=\"isFoldSelected(fold)\"\r\n [class.fb-fold--error]=\"fold.severity === 'Error'\"\r\n [class.fb-fold--warning]=\"fold.severity === 'Warning'\"\r\n (dblclick)=\"toggleGroupCollapsed($event, fold.name)\"\r\n >\r\n <div\r\n fConnector\r\n fConnectorType=\"target\"\r\n [fConnectorId]=\"foldTargetIdOf(fold)\"\r\n [fConnectorConnectableSide]=\"sides.TOP\"\r\n fConnectorMultiple=\"true\"\r\n class=\"fb-connector fb-connector--in\"\r\n title=\"Ingresso del riquadro chiuso\"\r\n ></div>\r\n\r\n <div class=\"fb-fold__head\">\r\n <button\r\n type=\"button\"\r\n fDragBlocker\r\n class=\"fb-fold__open\"\r\n [attr.aria-label]=\"'Apri il riquadro ' + fold.label\"\r\n title=\"Apri il riquadro: gli elementi tornano visibili al loro posto\"\r\n (click)=\"toggleGroupCollapsed($event, fold.name)\"\r\n >\r\n \u25B8\r\n </button>\r\n <span class=\"fb-fold__title\" [title]=\"fold.description || fold.label\">{{ fold.label }}</span>\r\n @if (isEditable()) {\r\n <button\r\n type=\"button\"\r\n fDragBlocker\r\n class=\"fb-fold__remove\"\r\n [attr.aria-label]=\"'Elimina il riquadro ' + fold.label\"\r\n title=\"Elimina il riquadro: gli elementi che contiene restano\"\r\n (click)=\"onGroupRemoveClick($event, fold.name)\"\r\n >\r\n \u00D7\r\n </button>\r\n }\r\n </div>\r\n\r\n <div class=\"fb-fold__meta\">\r\n <span class=\"fb-fold__count\">\r\n {{ fold.memberCount }} {{ fold.memberCount === 1 ? 'elemento nascosto' : 'elementi nascosti' }}\r\n </span>\r\n @if (fold.issueCount > 0) {\r\n <!-- I rilievi di cio' che e' nascosto: chiudere un riquadro non nasconde un errore (\u00A77). -->\r\n <span\r\n class=\"fb-badge\"\r\n [class.fb-badge--error]=\"fold.severity === 'Error'\"\r\n [class.fb-badge--warning]=\"fold.severity === 'Warning'\"\r\n [class.fb-badge--info]=\"fold.severity === 'Info'\"\r\n [title]=\"fold.issueCount + ' rilievi negli elementi nascosti'\"\r\n >\r\n {{ fold.issueCount }}\r\n </span>\r\n }\r\n @if (fold.unknownMembers.length > 0) {\r\n <span\r\n class=\"fb-badge fb-badge--dangling\"\r\n [title]=\"'Elementi non piu\u2019 esistenti: ' + fold.unknownMembers.join(', ')\"\r\n >\r\n {{ fold.unknownMembers.length }} da ripulire\r\n </span>\r\n }\r\n </div>\r\n\r\n <div class=\"fb-fold__outlets\">\r\n <div\r\n fConnector\r\n fConnectorType=\"source\"\r\n [fConnectorId]=\"foldSourceIdOf(fold)\"\r\n [fConnectorConnectableSide]=\"sides.BOTTOM\"\r\n class=\"fb-connector fb-connector--out fb-connector--Next\"\r\n title=\"Uscite del riquadro chiuso\"\r\n ></div>\r\n </div>\r\n </div>\r\n }\r\n\r\n @for (edge of edges(); track edge.id) {\r\n <f-connection\r\n [fConnectionId]=\"edge.id\"\r\n [fSourceId]=\"edge.sourceId\"\r\n [fTargetId]=\"edge.targetId\"\r\n fBehavior=\"floating\"\r\n [fType]=\"edge.isGoTo ? 'segment' : 'bezier'\"\r\n [class]=\"'fb-edge fb-edge--' + edge.kind + (edge.isGoTo ? ' fb-edge--goto' : '')\"\r\n [class.fb-edge--insert]=\"edge.id === insertionEdgeId()\"\r\n [class.fb-edge--folded]=\"edge.isFolded\"\r\n >\r\n <f-connection-marker-arrow [type]=\"markerEnd\" />\r\n @if (edge.label) {\r\n <div fConnectionContent class=\"fb-edge-label\">{{ edge.label }}</div>\r\n }\r\n </f-connection>\r\n }\r\n\r\n @for (node of nodes(); track node.id) {\r\n <div\r\n fNode\r\n fDragHandle\r\n [fNodeId]=\"node.id\"\r\n [fNodePosition]=\"node.position\"\r\n [class]=\"'fb-node ' + categoryClass(node) + ' ' + node.widthClass\"\r\n [class.fb-node--start]=\"node.isStart\"\r\n [class.fb-node--selected]=\"isSelected(node)\"\r\n [class.fb-node--unreachable]=\"!node.isReachable\"\r\n [class.fb-node--error]=\"node.severity === 'Error'\"\r\n [class.fb-node--warning]=\"node.severity === 'Warning'\"\r\n (dblclick)=\"onNodeDoubleClick(node.name)\"\r\n >\r\n <!--\r\n Lo Start non ha ingresso: e' il punto di partenza (\u00A73.4).\r\n `fConnectorConnectableSide` e' cio' che fa entrare l'arco dall'alto: senza, foblex\r\n calcola il lato e un arco che scende dal node sopra potrebbe agganciarsi di fianco.\r\n -->\r\n @if (!node.isStart) {\r\n <div\r\n fConnector\r\n fConnectorType=\"target\"\r\n [fConnectorId]=\"targetIdOf(node)\"\r\n [fConnectorConnectableSide]=\"sides.TOP\"\r\n fConnectorMultiple=\"true\"\r\n class=\"fb-connector fb-connector--in\"\r\n title=\"Ingresso\"\r\n ></div>\r\n }\r\n\r\n <div class=\"fb-node__head\">\r\n <span class=\"fb-node__icon\" aria-hidden=\"true\">{{ node.icon }}</span>\r\n <div class=\"fb-node__text\">\r\n <span class=\"fb-node__title\" [title]=\"node.description || node.label\">{{ node.label }}</span>\r\n <span class=\"fb-node__sub\">\r\n {{ node.typeLabel }}\r\n @if (node.subtitle) {\r\n <span class=\"fb-node__sub-dot\">\u00B7</span>{{ node.subtitle }}\r\n }\r\n </span>\r\n </div>\r\n @if (node.hasAutomaticOutput) {\r\n <!-- L'elemento espone il proprio risultato sotto il proprio nome (\u00A74.5). -->\r\n <span class=\"fb-node__auto\" title=\"Espone un output automatico referenziabile come \u00AB{{ node.name }}\u00BB\">\r\n \u0192\r\n </span>\r\n }\r\n <!--\r\n `fDragBlocker` impedisce che il pointerdown sul bottone diventi un trascinamento\r\n del node: senza, aprire il dettaglio sposterebbe l'elemento di qualche pixel.\r\n -->\r\n <button\r\n type=\"button\"\r\n fDragBlocker\r\n class=\"fb-node__edit\"\r\n [attr.aria-label]=\"'Apri il dettaglio di ' + node.label\"\r\n title=\"Apri il dettaglio\"\r\n (click)=\"onEditClick($event, node.name)\"\r\n >\r\n \u270E\r\n </button>\r\n @if (!node.isStart && isEditable()) {\r\n <!--\r\n Duplica: sta accanto a \u270E e non solo nell'inspector perche' e' il gesto con cui si\r\n riusa un elemento gi\u00E0 configurato, e cercarlo dentro il form dell'elemento da\r\n copiare e' il posto in cui non lo si cerca. Compare come \u00AB\u00D7\u00BB, sul solo node\r\n selezionato: aggiunge un elemento al documento, quindi non deve trovarsi sotto il\r\n puntatore di chi sta solo attraversando il grafo.\r\n -->\r\n <button\r\n type=\"button\"\r\n fDragBlocker\r\n class=\"fb-node__duplicate\"\r\n [attr.aria-label]=\"'Duplica ' + node.label\"\r\n title=\"Duplica questo elemento (Ctrl+D)\"\r\n (click)=\"onDuplicateClick($event, node.name)\"\r\n >\r\n \u29C9\r\n </button>\r\n <!--\r\n Si mostra solo sul node **selezionato**, non al passaggio del mouse come \u270E:\r\n cancellare non e' reversibile con un altro clic, e un comando distruttivo che\r\n appare sotto il puntatore mentre si attraversa il grafo si preme per sbaglio.\r\n Lo Start non lo espone: un flow senza ingresso non esisterebbe.\r\n -->\r\n <button\r\n type=\"button\"\r\n fDragBlocker\r\n class=\"fb-node__remove\"\r\n [attr.aria-label]=\"'Elimina ' + node.label\"\r\n title=\"Elimina questo elemento (si annulla con \u21B6)\"\r\n (click)=\"onRemoveClick($event, node.name)\"\r\n >\r\n \u00D7\r\n </button>\r\n }\r\n </div>\r\n\r\n <div class=\"fb-node__meta\">\r\n @if (!node.isStart) {\r\n <span class=\"fb-node__name\" [title]=\"'Nome tecnico: ' + node.name\">{{ node.name }}</span>\r\n }\r\n @if (!node.isReachable) {\r\n <span class=\"fb-badge fb-badge--unreachable\" title=\"Nessun percorso raggiunge questo elemento dallo Start\">\r\n scollegato\r\n </span>\r\n }\r\n @if (node.issueCount > 0) {\r\n <span\r\n class=\"fb-badge\"\r\n [class.fb-badge--error]=\"node.severity === 'Error'\"\r\n [class.fb-badge--warning]=\"node.severity === 'Warning'\"\r\n [class.fb-badge--info]=\"node.severity === 'Info'\"\r\n [title]=\"node.issueCount + ' rilievi di validazione'\"\r\n >\r\n {{ node.issueCount }}\r\n </span>\r\n }\r\n @if (node.danglingOutlets.length > 0) {\r\n <span\r\n class=\"fb-badge fb-badge--dangling\"\r\n [title]=\"'Rami dichiarati senza destinazione: ' + danglingLabels(node)\"\r\n >\r\n ramo incompleto\r\n </span>\r\n }\r\n </div>\r\n\r\n <!--\r\n Le uscite stanno sul bordo **inferiore**, una per ramo, nell'ordine in cui il modello\r\n le dichiara: per una Decision e' l'ordine di valutazione delle regole, che e'\r\n semantico (\u00A75.4), e da sinistra a destra si legge come la lista nell'inspector.\r\n L'etichetta si mostra solo quando i rami sono piu' di uno: su un `next` unico\r\n direbbe soltanto \u00ABSuccessivo\u00BB.\r\n -->\r\n <div class=\"fb-node__outlets\" [class.fb-node__outlets--labelled]=\"node.showsOutletLabels\">\r\n @for (outlet of node.outlets; track outlet.key) {\r\n <div class=\"fb-outlet\">\r\n @if (node.showsOutletLabels) {\r\n <span class=\"fb-outlet__label\" [title]=\"outlet.label\">{{ outlet.label }}</span>\r\n }\r\n <div\r\n fConnector\r\n fConnectorType=\"source\"\r\n [fConnectorId]=\"connectorIdOf(node, outlet.key)\"\r\n [fConnectorConnectableSide]=\"sides.BOTTOM\"\r\n [class]=\"'fb-connector fb-connector--out fb-connector--' + outlet.kind\"\r\n [title]=\"outlet.label\"\r\n ></div>\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n }\r\n\r\n <!-- Anteprima dell'arco durante il trascinamento. -->\r\n <f-connection-for-create fBehavior=\"floating\" fType=\"bezier\" class=\"fb-edge fb-edge--creating\">\r\n <f-connection-marker-arrow [type]=\"markerEnd\" />\r\n </f-connection-for-create>\r\n\r\n <f-selection-area />\r\n </f-canvas>\r\n\r\n <!--\r\n Fuori da <f-canvas> come la minimappa: dentro, la trasformazione del canvas se lo\r\n porterebbe via insieme ai node. `fDragBlocker` perche' il pointerdown non inizi una\r\n panoramica invece di premere il bottone.\r\n -->\r\n <!--\r\n Sempre nel DOM, nascosto con una classe e non con `@if`: dentro una proiezione di contenuto\r\n un blocco di controllo e' una complicazione gratuita, e `visibility: hidden` lo toglie\r\n anche dall'ordine di tabulazione. Il costo e' un bottone in piu' nel DOM, non un rischio.\r\n -->\r\n <div class=\"fb-viewport\" fDragBlocker role=\"group\" aria-label=\"Vista\">\r\n <!--\r\n Lo zoom ha bisogno di **comandi e di un numero**: con la sola rotella si finisce all'8% su\r\n un flow grande e sembra che il canvas si sia svuotato, senza un modo evidente di tornare.\r\n Il numero e' un bottone: riporta al 100% sull'elemento corrente.\r\n -->\r\n <button type=\"button\" class=\"fb-btn fb-viewport__btn\" title=\"Riduci (rotella indietro)\" (click)=\"zoomOut()\">\r\n \u2212\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-viewport__level\"\r\n title=\"Torna al 100% sull\u2019elemento selezionato\"\r\n (click)=\"zoomToActual()\"\r\n >\r\n {{ zoomPercent() }}%\r\n </button>\r\n <button type=\"button\" class=\"fb-btn fb-viewport__btn\" title=\"Ingrandisci (rotella avanti)\" (click)=\"zoomIn()\">\r\n +\r\n </button>\r\n <!--\r\n La modalita\u2019 selezione. Esiste perche\u2019 i due gesti che la libreria offre di serie \u2014\r\n `Ctrl`+clic per aggiungere, `Shift`+trascina per il rettangolo \u2014 non si vedono: senza un\r\n interruttore, su un flow grande si finisce a spostare un elemento alla volta. Accesa, il\r\n trascinamento sul canvas vuoto **seleziona** e la vista si sposta col tasto centrale.\r\n -->\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-viewport__select\"\r\n [class.fb-viewport__select--on]=\"isSelecting()\"\r\n [attr.aria-pressed]=\"isSelecting()\"\r\n [title]=\"\r\n isSelecting()\r\n ? 'Modalita\u2019 selezione attiva: trascina sul canvas per selezionare piu\u2019 elementi. La vista si sposta col tasto centrale del mouse'\r\n : 'Attiva la selezione a rettangolo: trascinando sul canvas selezioni piu\u2019 elementi invece di spostare la vista (Shift+trascina fa lo stesso, sempre)'\r\n \"\r\n (click)=\"toggleSelectionMode()\"\r\n >\r\n <span class=\"fb-viewport__icon\" aria-hidden=\"true\">\u25A4</span>\r\n Seleziona\r\n </button>\r\n <!--\r\n Sempre nel DOM, nascosto con una classe e non con `@if`: dentro una proiezione di contenuto\r\n un blocco di controllo e' una complicazione gratuita, e `visibility: hidden` lo toglie\r\n anche dall'ordine di tabulazione. Il costo e' un bottone in piu' nel DOM, non un rischio.\r\n -->\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-viewport__fit\"\r\n [class.fb-viewport__fit--hidden]=\"!hasMovedViewport()\"\r\n title=\"Rimetti il flow in vista. Su un flow molto grande inquadra l\u2019inizio, perche\u2019 l\u2019intero non sarebbe leggibile\"\r\n (click)=\"resetViewport()\"\r\n >\r\n <span class=\"fb-viewport__icon\" aria-hidden=\"true\">\u2922</span>\r\n Inquadra\r\n </button>\r\n </div>\r\n\r\n @if (selectionCount() > 1) {\r\n <!--\r\n Il dato che rende utile la selezione: che si spostano **insieme**. Senza dirlo, dopo aver\r\n selezionato cinque elementi si continua a trascinarne uno per volta.\r\n -->\r\n <p class=\"fb-select-hint\" role=\"status\">\r\n {{ selectionCount() }} elementi selezionati: trascinane uno per spostarli tutti \u00B7 Ctrl+clic\r\n aggiunge o toglie \u00B7 Canc li elimina\r\n </p>\r\n } @else if (isSelecting()) {\r\n <p class=\"fb-select-hint\" role=\"status\">\r\n Trascina sul canvas per selezionare \u00B7 la vista si sposta col tasto centrale\r\n </p>\r\n }\r\n\r\n @if (insertionEdgeId()) {\r\n <!-- Il rilascio su un arco non e' un gesto che si indovina: mentre e' possibile, si dice. -->\r\n <p class=\"fb-insert-hint\" role=\"status\">Rilascia qui per inserire l\u2019elemento in questo percorso</p>\r\n }\r\n\r\n <f-minimap [fMinSize]=\"1200\" class=\"fb-minimap\" />\r\n</f-flow>\r\n", styles: [":host{display:block;position:relative;width:100%;height:100%;overflow:hidden;background:var(--fb-canvas-bg, #f4f5f7)}f-flow{display:block;width:100%;height:100%}.fb-minimap{position:absolute;right:14px;bottom:14px;width:150px;height:300px;max-height:40%;border:1px solid var(--fb-border, #e2e5eb);border-radius:var(--fb-radius, 10px);background:var(--fb-surface, #fff);box-shadow:var(--fb-shadow-md, 0 6px 18px rgb(16 24 40 / 10%));overflow:hidden}.fb-viewport{position:absolute;right:14px;bottom:322px;z-index:1;display:flex;align-items:center;gap:4px;flex-wrap:wrap;justify-content:flex-end;max-width:200px}.fb-viewport__btn,.fb-viewport__level,.fb-viewport__fit{border-radius:var(--fb-radius-xs, 6px);box-shadow:var(--fb-shadow-md, 0 6px 18px rgb(16 24 40 / 10%));font-size:11px}.fb-viewport__btn{width:24px;padding:2px 0;font-size:14px;line-height:1}.fb-viewport__level{min-width:52px;font-variant-numeric:tabular-nums}.fb-viewport__fit--hidden{opacity:0;visibility:hidden}.fb-viewport__fit{transition:opacity .15s ease,visibility .15s ease}.fb-viewport__icon{font-size:13px;line-height:1;color:var(--fb-text-muted, #667085)}.fb-insert-hint{position:absolute;bottom:18px;left:50%;transform:translate(-50%);z-index:1;margin:0;padding:6px 12px;border-radius:999px;background:var(--fb-accent, #2f6feb);box-shadow:var(--fb-shadow-md, 0 6px 18px rgb(16 24 40 / 10%));color:#fff;font-size:11px;font-weight:600;pointer-events:none}.fb-select-hint{position:absolute;bottom:18px;left:50%;transform:translate(-50%);z-index:1;margin:0;padding:6px 12px;border-radius:999px;border:1px solid var(--fb-border, #e2e5eb);background:var(--fb-surface, #fff);box-shadow:var(--fb-shadow-md, 0 6px 18px rgb(16 24 40 / 10%));color:var(--fb-text-muted, #667085);font-size:11px;font-weight:600;pointer-events:none}.fb-viewport__select{border-radius:var(--fb-radius-xs, 6px);box-shadow:var(--fb-shadow-md, 0 6px 18px rgb(16 24 40 / 10%));font-size:11px}.fb-viewport__select--on{border-color:var(--fb-accent, #2f6feb);background:var(--fb-accent, #2f6feb);color:#fff}.fb-node{position:absolute;display:flex;flex-direction:column;box-sizing:border-box;width:240px;padding:0;border:1px solid var(--fb-border, #e2e5eb);border-radius:var(--fb-radius-lg, 12px);background:var(--fb-surface, #fff);box-shadow:var(--fb-shadow-sm, 0 1px 2px rgb(16 24 40 / 6%));font:inherit;cursor:grab;-webkit-user-select:none;user-select:none;transition:box-shadow .12s ease,border-color .12s ease}.fb-node--outlets-3{width:304px}.fb-node--outlets-4{width:380px}.fb-node--outlets-5{width:456px}.fb-node--outlets-6{width:520px}.fb-node:hover{box-shadow:var(--fb-shadow-md, 0 6px 18px rgb(16 24 40 / 10%))}.fb-node--selected{border-color:var(--fb-accent, #4f6ef7);box-shadow:0 0 0 3px color-mix(in srgb,var(--fb-accent, #4f6ef7) 22%,transparent)}.fb-node--unreachable{border-style:dashed;opacity:.8}.fb-node--error{border-color:var(--fb-error, #c9372c)}.fb-node--warning{border-color:var(--fb-warning, #b7791f)}.fb-node__head{display:flex;align-items:center;gap:8px;padding:10px 10px 6px 12px}.fb-node__icon{display:grid;place-items:center;flex:0 0 auto;width:28px;height:28px;border-radius:var(--fb-radius-sm, 8px);background:var(--fb-node-accent, #667085);color:#fff;font-size:14px;line-height:1}.cat-start{--fb-node-accent: #22a06b}.cat-screen{--fb-node-accent: #3b82f6}.cat-logic{--fb-node-accent: #8b5cf6}.cat-data{--fb-node-accent: #06b6d4}.cat-action{--fb-node-accent: #f59e0b}.cat-flow{--fb-node-accent: #14b8a6}.cat-other{--fb-node-accent: #667085}.fb-node__text{flex:1;min-width:0}.fb-node__title{display:block;font-size:13px;font-weight:600;line-height:17px;color:var(--fb-text, #1a1c23);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fb-node__sub{display:block;margin-top:1px;font-size:11px;line-height:14px;color:var(--fb-text-muted, #6b7086);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fb-node__sub-dot{margin:0 4px;color:var(--fb-text-subtle, #98a2b3)}.fb-node__auto{flex:0 0 auto;font-size:12px;font-weight:700;color:var(--fb-accent, #4f6ef7);cursor:help}.fb-node__edit{display:grid;place-items:center;flex:0 0 auto;width:22px;height:22px;padding:0;border:0;border-radius:var(--fb-radius-xs, 6px);background:transparent;color:var(--fb-text-subtle, #98a2b3);font:inherit;font-size:12px;cursor:pointer;opacity:0;transition:opacity .12s ease,background .12s ease}.fb-node:hover .fb-node__edit,.fb-node--selected .fb-node__edit,.fb-node__edit:focus-visible{opacity:1}.fb-node__edit:hover{background:var(--fb-surface-alt, #f7f8fa);color:var(--fb-text, #1a1c23)}.fb-node__duplicate,.fb-node__remove{display:grid;place-items:center;flex:0 0 auto;width:22px;height:22px;padding:0;border:0;border-radius:var(--fb-radius-xs, 6px);background:transparent;color:var(--fb-text-subtle, #98a2b3);font:inherit;font-size:15px;line-height:1;cursor:pointer;opacity:0;transition:opacity .12s ease,background .12s ease,color .12s ease}.fb-node--selected .fb-node__duplicate,.fb-node--selected .fb-node__remove,.fb-node__duplicate:focus-visible,.fb-node__remove:focus-visible{opacity:1}.fb-node__remove:hover{background:color-mix(in srgb,var(--fb-error, #c9372c) 12%,transparent);color:var(--fb-error, #c9372c)}.fb-node__duplicate:hover{background:color-mix(in srgb,var(--fb-accent, #3b6ef2) 12%,transparent);color:var(--fb-accent, #3b6ef2)}.fb-node__meta{display:flex;flex-wrap:wrap;align-items:center;gap:4px;min-height:14px;padding:0 12px 6px}.fb-node__name{flex:0 1 auto;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:10px;color:var(--fb-text-subtle, #98a2b3);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fb-badge{padding:1px 6px;border-radius:10px;background:var(--fb-badge-bg, #eef0f4);font-size:10px;font-weight:600;color:var(--fb-text-muted, #6b7086);white-space:nowrap;cursor:help}.fb-badge--error{background:color-mix(in srgb,var(--fb-error, #c9372c) 14%,transparent);color:var(--fb-error, #c9372c)}.fb-badge--warning{background:color-mix(in srgb,var(--fb-warning, #b7791f) 16%,transparent);color:var(--fb-warning, #b7791f)}.fb-badge--info{background:color-mix(in srgb,var(--fb-accent, #4f6ef7) 12%,transparent);color:var(--fb-accent, #4f6ef7)}.fb-badge--unreachable,.fb-badge--dangling{background:color-mix(in srgb,var(--fb-warning, #b7791f) 12%,transparent);color:var(--fb-warning, #b7791f)}.fb-node__outlets{display:flex;align-items:flex-end;justify-content:space-evenly;gap:4px;padding:0 8px 4px}.fb-node__outlets--labelled{padding-top:5px;border-top:1px solid var(--fb-border-subtle, #eef0f4)}.fb-outlet{display:flex;flex:1 1 0;flex-direction:column;align-items:center;gap:2px;min-width:0}.fb-outlet__label{max-width:100%;font-size:10px;line-height:13px;color:var(--fb-text-muted, #6b7086);text-align:center;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fb-connector{box-sizing:border-box;width:12px;height:12px;flex:0 0 auto;border:2px solid var(--fb-surface, #fff);border-radius:50%;background:var(--fb-connector, #98a2b3);cursor:crosshair;transition:transform .12s ease,box-shadow .12s ease}f-flow .fb-connector--out{position:relative;inset:auto;margin-bottom:-12px}.fb-connector--out:hover{transform:scale(1.2)}f-flow .fb-connector--in{position:absolute;inset:-2px auto auto 50%;width:24px;height:4px;border:0;border-radius:2px;transform:translate(-50%);background:var(--fb-border-strong, #cfd4de)}.fb-connector--out{--ff-connector-connected-color: var(--fb-connector, #98a2b3)}.fb-connector--Next,.fb-connector--Start,.fb-connector--LoopNext{--fb-connector: var(--fb-edge-next, #98a2b3)}.fb-connector--Rule,.fb-connector--WaitEvent{--fb-connector: var(--fb-edge-rule, #8b5cf6)}.fb-connector--Default,.fb-connector--LoopEnd{--fb-connector: var(--fb-edge-default, #06b6d4)}.fb-connector--Fault{--fb-connector: var(--fb-edge-fault, #dc2626)}.fb-connector--Timeout,.fb-connector--ScheduledPath{--fb-connector: var(--fb-edge-timeout, #f59e0b)}.fb-connector.f-connector-connectable{box-shadow:0 0 0 4px color-mix(in srgb,var(--fb-accent, #4f6ef7) 28%,transparent)}.fb-group{position:absolute;box-sizing:border-box;border:1px solid var(--fb-group-border, #d6dae1);border-radius:var(--fb-radius, 10px);background:var(--fb-group-fill, rgb(148 163 184 / 10%));pointer-events:none;overflow:hidden}.fb-group--selected{border-color:var(--fb-accent, #2f6feb);box-shadow:0 0 0 2px color-mix(in srgb,var(--fb-accent, #2f6feb) 25%,transparent)}.fb-group__bar{display:flex;align-items:center;gap:6px;height:30px;padding:0 6px;border-bottom:1px solid var(--fb-group-border, #d6dae1);background:var(--fb-group-bar, rgb(148 163 184 / 22%));color:var(--fb-text, #1d2939);font-size:12px;font-weight:600;pointer-events:auto;cursor:move;-webkit-user-select:none;user-select:none}.fb-fold{position:absolute;box-sizing:border-box;display:flex;flex-direction:column;justify-content:center;width:260px;min-height:64px;padding:6px 8px;border:1px solid var(--fb-group-border, #d6dae1);border-left:4px solid var(--fb-group-border, #d6dae1);border-radius:var(--fb-radius-lg, 12px);background:var(--fb-group-bar, rgb(148 163 184 / 22%));box-shadow:var(--fb-shadow-sm, 0 1px 2px rgb(16 24 40 / 6%));color:var(--fb-text, #1d2939);font:inherit;font-size:12px;cursor:grab;-webkit-user-select:none;user-select:none}.fb-fold:hover{box-shadow:var(--fb-shadow-md, 0 6px 18px rgb(16 24 40 / 10%))}.fb-fold--selected{border-color:var(--fb-accent, #4f6ef7);box-shadow:0 0 0 3px color-mix(in srgb,var(--fb-accent, #4f6ef7) 22%,transparent)}.fb-fold--error{border-color:var(--fb-error, #c9372c)}.fb-fold--warning{border-color:var(--fb-warning, #b7791f)}.fb-fold__head{display:flex;align-items:center;gap:6px}.fb-fold__title{flex:1;min-width:0;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;font-weight:600}.fb-fold__open,.fb-fold__remove{flex:none;width:18px;height:18px;padding:0;border:none;border-radius:var(--fb-radius-xs, 6px);background:transparent;color:inherit;font:inherit;font-size:12px;line-height:1;cursor:pointer}.fb-fold__remove{font-size:14px}.fb-fold__open:hover,.fb-fold__remove:hover{background:#10182814}.fb-fold__meta{display:flex;align-items:center;gap:6px;margin-top:2px;color:var(--fb-text-muted, #667085);font-size:11px}.fb-fold__count{overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.fb-fold__outlets{display:flex;justify-content:center;margin-top:4px}.fb-group__fold,.fb-group__remove{flex:none;width:18px;height:18px;padding:0;border:none;border-radius:var(--fb-radius-xs, 6px);background:transparent;color:inherit;font:inherit;font-size:12px;line-height:1;cursor:pointer}.fb-group__fold:hover,.fb-group__remove:hover{background:#10182814}.fb-group__remove{margin-left:auto;font-size:14px}.fb-group__title{overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.fb-group__count{flex:none;padding:0 5px;border-radius:8px;background:#10182814;font-size:10px;font-weight:600;font-variant-numeric:tabular-nums}.fb-group__stale{flex:none;padding:0 5px;border-radius:8px;background:color-mix(in srgb,var(--fb-warning, #b7791f) 14%,transparent);color:var(--fb-warning, #b7791f);font-size:10px;font-weight:600}.fb-group__note{margin:6px 8px 0;color:var(--fb-text-muted, #667085);font-size:11px;line-height:1.35;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2;overflow:hidden}.fb-group__resize{position:absolute;right:0;bottom:0;width:14px;height:14px;border-right:2px solid var(--fb-group-border, #d6dae1);border-bottom:2px solid var(--fb-group-border, #d6dae1);border-bottom-right-radius:var(--fb-radius, 10px);pointer-events:auto;cursor:nwse-resize}.fb-group--giallo{--fb-group-border: #e0c26a;--fb-group-fill: rgb(234 179 8 / 10%);--fb-group-bar: rgb(234 179 8 / 24%)}.fb-group--verde{--fb-group-border: #86c79b;--fb-group-fill: rgb(34 160 89 / 10%);--fb-group-bar: rgb(34 160 89 / 22%)}.fb-group--blu{--fb-group-border: #93b4ea;--fb-group-fill: rgb(47 111 235 / 9%);--fb-group-bar: rgb(47 111 235 / 20%)}.fb-group--viola{--fb-group-border: #bda6e6;--fb-group-fill: rgb(139 92 246 / 10%);--fb-group-bar: rgb(139 92 246 / 22%)}.fb-group--rosso{--fb-group-border: #e6a3a3;--fb-group-fill: rgb(214 69 69 / 9%);--fb-group-bar: rgb(214 69 69 / 20%)}.fb-group--grigio,.fb-group--neutro{--fb-group-border: #d6dae1;--fb-group-fill: rgb(148 163 184 / 10%);--fb-group-bar: rgb(148 163 184 / 22%)}\n"], dependencies: [{ kind: "ngmodule", type: FFlowModule }, { kind: "component", type: i1.FFlowComponent, selector: "f-flow", inputs: ["fFlowId", "fCache"], outputs: ["fNodesRendered", "fFullRendered", "fLoaded"] }, { kind: "component", type: i1.FCanvasComponent, selector: "f-canvas", inputs: ["position", "scale", "debounceTime", "fLayers"], outputs: ["fCanvasChange"] }, { kind: "component", type: i1.FBackgroundComponent, selector: "f-background" }, { kind: "component", type: i1.FCirclePatternComponent, selector: "f-circle-pattern", inputs: ["id", "color", "radius"] }, { kind: "directive", type: i1.FZoomDirective, selector: "f-canvas[fZoom]", inputs: ["fZoom", "fWheelTrigger", "fDblClickTrigger", "fZoomMinimum", "fZoomMaximum", "fZoomStep", "fPinchStep", "fZoomDblClickStep"] }, { kind: "component", type: i1.FSelectionArea, selector: "f-selection-area", inputs: ["fTrigger"] }, { kind: "directive", type: i1.FConnectionContent, selector: "[fConnectionContent]", inputs: ["position", "offset", "align"] }, { kind: "component", type: i1.FConnectionMarkerArrow, selector: "f-connection-marker-arrow", inputs: ["type"] }, { kind: "component", type: i1.FConnectionComponent, selector: "f-connection", inputs: ["fConnectionId", "fSourceId", "fTargetId", "fOutputId", "fInputId", "fRadius", "fOffset", "fBehavior", "fType", "fSelectionDisabled", "fReassignableStart", "fReassignDisabled", "fSourceSide", "fTargetSide", "fInputSide", "fOutputSide"], exportAs: ["fComponent"] }, { kind: "component", type: i1.FConnectionForCreateComponent, selector: "f-connection-for-create", inputs: ["fRadius", "fOffset", "fBehavior", "fType", "fInputSide", "fOutputSide"] }, { kind: "directive", type: i1.FConnectorDirective, selector: "[fConnector]", inputs: ["fConnectorId", "fConnectorType", "fConnectorDisabled", "fConnectorMultiple", "fConnectorCategory", "fConnectorConnectableSide", "fConnectorSelfConnectable", "fCanBeConnectedTo", "fConnectionFromOutlet"], exportAs: ["fConnector"] }, { kind: "component", type: i1.FMinimapComponent, selector: "f-minimap", inputs: ["fMinSize", "fNodeRenderLimit"], exportAs: ["fComponent"] }, { kind: "directive", type: i1.FGroupDirective, selector: "[fGroup]", inputs: ["fGroupId", "fGroupParentId", "fGroupPosition", "fGroupSize", "fGroupRotate", "fConnectOnNode", "fMinimapClass", "fGroupDraggingDisabled", "fGroupSelectionDisabled", "fIncludePadding", "fAutoExpandOnChildHit", "fAutoSizeToFitChildren"], outputs: ["fGroupPositionChange", "fGroupSizeChange", "fGroupRotateChange"], exportAs: ["fComponent"] }, { kind: "directive", type: i1.FNodeDirective, selector: "[fNode]", inputs: ["fNodeId", "fNodeParentId", "fNodePosition", "fNodeSize", "fNodeRotate", "fConnectOnNode", "fMinimapClass", "fNodeDraggingDisabled", "fNodeSelectionDisabled", "fIncludePadding", "fAutoExpandOnChildHit", "fAutoSizeToFitChildren"], outputs: ["fNodePositionChange", "fNodeSizeChange", "fNodeRotateChange"], exportAs: ["fComponent"] }, { kind: "directive", type: i1.FDragHandleDirective, selector: "[fDragHandle]" }, { kind: "directive", type: i1.FResizeHandleDirective, selector: "[fResizeHandle]", inputs: ["fResizeHandleType"] }, { kind: "directive", type: i1.FDragBlockerDirective, selector: "[fDragBlocker]" }, { kind: "directive", type: i1.FDraggableDirective, selector: "f-flow[fDraggable]", inputs: ["fDraggableDisabled", "fDropToGroup", "fMultiSelectTrigger", "fReassignConnectionTrigger", "fCreateConnectionTrigger", "fConnectionWaypointsTrigger", "fMoveControlPointTrigger", "fNodeResizeTrigger", "fNodeRotateTrigger", "fNodeMoveTrigger", "fCanvasMoveTrigger", "fExternalItemTrigger", "fEmitOnNodeIntersect", "vCellSize", "hCellSize", "fCellSizeWhileDragging"], outputs: ["fSelectionChange", "fDeleteSelected", "fNodeIntersectedWithConnections", "fNodeConnectionsIntersection", "fCreateNode", "fMoveNodes", "fReassignConnection", "fCreateConnection", "fConnectionWaypointsChanged", "fDropToGroup", "fDragStarted", "fDragEnded"], exportAs: ["fDraggable"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
5891
6194
|
}
|
|
5892
6195
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.28", ngImport: i0, type: FlowCanvasComponent, decorators: [{
|
|
5893
6196
|
type: Component,
|
|
@@ -5900,7 +6203,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.28", ngImpo
|
|
|
5900
6203
|
* esisterebbe. Registrarlo col preset di default non cambia niente di per se'.
|
|
5901
6204
|
*/
|
|
5902
6205
|
withControlScheme(F_DEFAULT_CONTROL_SCHEME)),
|
|
5903
|
-
], template: "<!--\r\n Gerarchia obbligatoria: f-flow > f-canvas > fNode / f-connection.\r\n I `@for` sono direttamente dentro <f-canvas>: nessun wrapper, quindi non serve\r\n `ngProjectAs` (che sarebbe indispensabile con blocchi annidati).\r\n-->\r\n<f-flow\r\n fDraggable\r\n (fCreateConnection)=\"onCreateConnection($event)\"\r\n (fReassignConnection)=\"onReassignConnection($event)\"\r\n (fMoveNodes)=\"onMoveNodes($event)\"\r\n (fSelectionChange)=\"onSelectionChange($event)\"\r\n (fDeleteSelected)=\"onDeleteSelected($event)\"\r\n (fCreateNode)=\"onCreateNode($event)\"\r\n (fNodesRendered)=\"onNodesRendered()\"\r\n (fDragStarted)=\"onDragStarted($event)\"\r\n (fDragEnded)=\"onDragEnded()\"\r\n>\r\n <!--\r\n `#canvas` + `(fCanvasChange)`: il primo serve per chiamare `fitToScreen()`, il secondo per\r\n sapere che l'utente ha mosso la vista. `[debounceTime]` non si imposta: l'evento serve solo\r\n ad accendere un flag, non a ricalcolare niente.\r\n -->\r\n <f-canvas fZoom #canvas (fCanvasChange)=\"onCanvasChange()\">\r\n <f-background>\r\n <f-circle-pattern />\r\n </f-background>\r\n\r\n <!--\r\n \u00A73.6 \u2014 i riquadri di raggruppamento. Vanno **sotto** i node, e non c'e' niente da fare per\r\n ottenerlo: `[fGroup]` viene proiettato nel layer dei gruppi, che nell'ordine della libreria\r\n sta sotto connessioni e node.\r\n\r\n Sono commenti: nessun connettore, nessun arco, nessuna raggiungibilita'. Il corpo del\r\n riquadro ha `pointer-events: none` (nel CSS) e solo la barra del titolo risponde: senza,\r\n una cornice grande quanto mezzo canvas si mangerebbe i click destinati alla panoramica e\r\n alla selezione a rettangolo, e i node dentro non si potrebbero piu' prendere.\r\n -->\r\n @for (group of groups(); track group.name) {\r\n <div\r\n fGroup\r\n [fGroupId]=\"group.id\"\r\n [fGroupPosition]=\"group.position\"\r\n [fGroupSize]=\"group.size\"\r\n [fGroupDraggingDisabled]=\"!isEditable()\"\r\n [class]=\"'fb-group ' + group.colorClass\"\r\n [class.fb-group--collapsed]=\"group.isCollapsed\"\r\n [class.fb-group--selected]=\"isGroupSelected(group)\"\r\n (fGroupSizeChange)=\"onGroupResized(group.name, $event)\"\r\n >\r\n <!--\r\n `fDragHandle` sulla sola barra: il riquadro si sposta dal titolo, come una finestra.\r\n Trascinandolo da dentro si sposterebbe la cornice ogni volta che si prova a prendere\r\n un node che le sta sopra.\r\n -->\r\n <div\r\n class=\"fb-group__bar\"\r\n fDragHandle\r\n [title]=\"group.description || group.label\"\r\n (click)=\"onGroupClick($event, group.name)\"\r\n >\r\n <button\r\n type=\"button\"\r\n fDragBlocker\r\n class=\"fb-group__fold\"\r\n [attr.aria-label]=\"group.isCollapsed ? 'Apri il riquadro' : 'Chiudi il riquadro'\"\r\n [title]=\"\r\n group.isCollapsed\r\n ? 'Apri il riquadro'\r\n : 'Chiudi il riquadro: resta la barra del titolo, gli elementi restano dove sono'\r\n \"\r\n (click)=\"toggleGroupCollapsed($event, group.name)\"\r\n >\r\n {{ group.isCollapsed ? '\u25B8' : '\u25BE' }}\r\n </button>\r\n <span class=\"fb-group__title\">{{ group.label }}</span>\r\n <span class=\"fb-group__count\" [title]=\"group.memberCount + ' elementi nel riquadro'\">\r\n {{ group.memberCount }}\r\n </span>\r\n @if (group.unknownMembers.length > 0) {\r\n <!--\r\n Membri che nominano un elemento che non c'e' piu'. Non e' un difetto (\u00A73.6): e' cio'\r\n che resta dopo una cancellazione, ed e' un avviso \u2014 l'attivazione non si blocca.\r\n -->\r\n <span\r\n class=\"fb-group__stale\"\r\n [title]=\"'Elementi non piu\u2019 esistenti: ' + group.unknownMembers.join(', ')\"\r\n >\r\n {{ group.unknownMembers.length }} da ripulire\r\n </span>\r\n }\r\n @if (isEditable()) {\r\n <button\r\n type=\"button\"\r\n fDragBlocker\r\n class=\"fb-group__remove\"\r\n [attr.aria-label]=\"'Elimina il riquadro ' + group.label\"\r\n title=\"Elimina il riquadro: gli elementi che contiene restano\"\r\n (click)=\"onGroupRemoveClick($event, group.name)\"\r\n >\r\n \u00D7\r\n </button>\r\n }\r\n </div>\r\n\r\n @if (!group.isCollapsed && group.description) {\r\n <p class=\"fb-group__note\">{{ group.description }}</p>\r\n }\r\n\r\n @if (isEditable() && !group.isCollapsed) {\r\n <!--\r\n Una sola maniglia, in basso a destra: quattro bersagli su una cornice che sta sotto\r\n i node si prendono per sbaglio. Ridimensionare **dichiara** la geometria, che e' il\r\n modo di uscire dal \u00ABcalcolala sui membri\u00BB (\u00A73.6).\r\n -->\r\n <div\r\n fResizeHandle\r\n [fResizeHandleType]=\"resizeHandles.RIGHT_BOTTOM\"\r\n class=\"fb-group__resize\"\r\n title=\"Ridimensiona il riquadro\"\r\n ></div>\r\n }\r\n </div>\r\n }\r\n\r\n @for (edge of edges(); track edge.id) {\r\n <f-connection\r\n [fConnectionId]=\"edge.id\"\r\n [fSourceId]=\"edge.sourceId\"\r\n [fTargetId]=\"edge.targetId\"\r\n fBehavior=\"floating\"\r\n [fType]=\"edge.isGoTo ? 'segment' : 'bezier'\"\r\n [class]=\"'fb-edge fb-edge--' + edge.kind + (edge.isGoTo ? ' fb-edge--goto' : '')\"\r\n [class.fb-edge--insert]=\"edge.id === insertionEdgeId()\"\r\n >\r\n <f-connection-marker-arrow [type]=\"markerEnd\" />\r\n @if (edge.label) {\r\n <div fConnectionContent class=\"fb-edge-label\">{{ edge.label }}</div>\r\n }\r\n </f-connection>\r\n }\r\n\r\n @for (node of nodes(); track node.id) {\r\n <div\r\n fNode\r\n fDragHandle\r\n [fNodeId]=\"node.id\"\r\n [fNodePosition]=\"node.position\"\r\n [class]=\"'fb-node ' + categoryClass(node) + ' ' + node.widthClass\"\r\n [class.fb-node--start]=\"node.isStart\"\r\n [class.fb-node--selected]=\"isSelected(node)\"\r\n [class.fb-node--unreachable]=\"!node.isReachable\"\r\n [class.fb-node--error]=\"node.severity === 'Error'\"\r\n [class.fb-node--warning]=\"node.severity === 'Warning'\"\r\n (dblclick)=\"onNodeDoubleClick(node.name)\"\r\n >\r\n <!--\r\n Lo Start non ha ingresso: e' il punto di partenza (\u00A73.4).\r\n `fConnectorConnectableSide` e' cio' che fa entrare l'arco dall'alto: senza, foblex\r\n calcola il lato e un arco che scende dal node sopra potrebbe agganciarsi di fianco.\r\n -->\r\n @if (!node.isStart) {\r\n <div\r\n fConnector\r\n fConnectorType=\"target\"\r\n [fConnectorId]=\"targetIdOf(node)\"\r\n [fConnectorConnectableSide]=\"sides.TOP\"\r\n fConnectorMultiple=\"true\"\r\n class=\"fb-connector fb-connector--in\"\r\n title=\"Ingresso\"\r\n ></div>\r\n }\r\n\r\n <div class=\"fb-node__head\">\r\n <span class=\"fb-node__icon\" aria-hidden=\"true\">{{ node.icon }}</span>\r\n <div class=\"fb-node__text\">\r\n <span class=\"fb-node__title\" [title]=\"node.description || node.label\">{{ node.label }}</span>\r\n <span class=\"fb-node__sub\">\r\n {{ node.typeLabel }}\r\n @if (node.subtitle) {\r\n <span class=\"fb-node__sub-dot\">\u00B7</span>{{ node.subtitle }}\r\n }\r\n </span>\r\n </div>\r\n @if (node.hasAutomaticOutput) {\r\n <!-- L'elemento espone il proprio risultato sotto il proprio nome (\u00A74.5). -->\r\n <span class=\"fb-node__auto\" title=\"Espone un output automatico referenziabile come \u00AB{{ node.name }}\u00BB\">\r\n \u0192\r\n </span>\r\n }\r\n <!--\r\n `fDragBlocker` impedisce che il pointerdown sul bottone diventi un trascinamento\r\n del node: senza, aprire il dettaglio sposterebbe l'elemento di qualche pixel.\r\n -->\r\n <button\r\n type=\"button\"\r\n fDragBlocker\r\n class=\"fb-node__edit\"\r\n [attr.aria-label]=\"'Apri il dettaglio di ' + node.label\"\r\n title=\"Apri il dettaglio\"\r\n (click)=\"onEditClick($event, node.name)\"\r\n >\r\n \u270E\r\n </button>\r\n @if (!node.isStart && isEditable()) {\r\n <!--\r\n Duplica: sta accanto a \u270E e non solo nell'inspector perche' e' il gesto con cui si\r\n riusa un elemento gi\u00E0 configurato, e cercarlo dentro il form dell'elemento da\r\n copiare e' il posto in cui non lo si cerca. Compare come \u00AB\u00D7\u00BB, sul solo node\r\n selezionato: aggiunge un elemento al documento, quindi non deve trovarsi sotto il\r\n puntatore di chi sta solo attraversando il grafo.\r\n -->\r\n <button\r\n type=\"button\"\r\n fDragBlocker\r\n class=\"fb-node__duplicate\"\r\n [attr.aria-label]=\"'Duplica ' + node.label\"\r\n title=\"Duplica questo elemento (Ctrl+D)\"\r\n (click)=\"onDuplicateClick($event, node.name)\"\r\n >\r\n \u29C9\r\n </button>\r\n <!--\r\n Si mostra solo sul node **selezionato**, non al passaggio del mouse come \u270E:\r\n cancellare non e' reversibile con un altro clic, e un comando distruttivo che\r\n appare sotto il puntatore mentre si attraversa il grafo si preme per sbaglio.\r\n Lo Start non lo espone: un flow senza ingresso non esisterebbe.\r\n -->\r\n <button\r\n type=\"button\"\r\n fDragBlocker\r\n class=\"fb-node__remove\"\r\n [attr.aria-label]=\"'Elimina ' + node.label\"\r\n title=\"Elimina questo elemento (si annulla con \u21B6)\"\r\n (click)=\"onRemoveClick($event, node.name)\"\r\n >\r\n \u00D7\r\n </button>\r\n }\r\n </div>\r\n\r\n <div class=\"fb-node__meta\">\r\n @if (!node.isStart) {\r\n <span class=\"fb-node__name\" [title]=\"'Nome tecnico: ' + node.name\">{{ node.name }}</span>\r\n }\r\n @if (!node.isReachable) {\r\n <span class=\"fb-badge fb-badge--unreachable\" title=\"Nessun percorso raggiunge questo elemento dallo Start\">\r\n scollegato\r\n </span>\r\n }\r\n @if (node.issueCount > 0) {\r\n <span\r\n class=\"fb-badge\"\r\n [class.fb-badge--error]=\"node.severity === 'Error'\"\r\n [class.fb-badge--warning]=\"node.severity === 'Warning'\"\r\n [class.fb-badge--info]=\"node.severity === 'Info'\"\r\n [title]=\"node.issueCount + ' rilievi di validazione'\"\r\n >\r\n {{ node.issueCount }}\r\n </span>\r\n }\r\n @if (node.danglingOutlets.length > 0) {\r\n <span\r\n class=\"fb-badge fb-badge--dangling\"\r\n [title]=\"'Rami dichiarati senza destinazione: ' + danglingLabels(node)\"\r\n >\r\n ramo incompleto\r\n </span>\r\n }\r\n </div>\r\n\r\n <!--\r\n Le uscite stanno sul bordo **inferiore**, una per ramo, nell'ordine in cui il modello\r\n le dichiara: per una Decision e' l'ordine di valutazione delle regole, che e'\r\n semantico (\u00A75.4), e da sinistra a destra si legge come la lista nell'inspector.\r\n L'etichetta si mostra solo quando i rami sono piu' di uno: su un `next` unico\r\n direbbe soltanto \u00ABSuccessivo\u00BB.\r\n -->\r\n <div class=\"fb-node__outlets\" [class.fb-node__outlets--labelled]=\"node.showsOutletLabels\">\r\n @for (outlet of node.outlets; track outlet.key) {\r\n <div class=\"fb-outlet\">\r\n @if (node.showsOutletLabels) {\r\n <span class=\"fb-outlet__label\" [title]=\"outlet.label\">{{ outlet.label }}</span>\r\n }\r\n <div\r\n fConnector\r\n fConnectorType=\"source\"\r\n [fConnectorId]=\"connectorIdOf(node, outlet.key)\"\r\n [fConnectorConnectableSide]=\"sides.BOTTOM\"\r\n [class]=\"'fb-connector fb-connector--out fb-connector--' + outlet.kind\"\r\n [title]=\"outlet.label\"\r\n ></div>\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n }\r\n\r\n <!-- Anteprima dell'arco durante il trascinamento. -->\r\n <f-connection-for-create fBehavior=\"floating\" fType=\"bezier\" class=\"fb-edge fb-edge--creating\">\r\n <f-connection-marker-arrow [type]=\"markerEnd\" />\r\n </f-connection-for-create>\r\n\r\n <f-selection-area />\r\n </f-canvas>\r\n\r\n <!--\r\n Fuori da <f-canvas> come la minimappa: dentro, la trasformazione del canvas se lo\r\n porterebbe via insieme ai node. `fDragBlocker` perche' il pointerdown non inizi una\r\n panoramica invece di premere il bottone.\r\n -->\r\n <!--\r\n Sempre nel DOM, nascosto con una classe e non con `@if`: dentro una proiezione di contenuto\r\n un blocco di controllo e' una complicazione gratuita, e `visibility: hidden` lo toglie\r\n anche dall'ordine di tabulazione. Il costo e' un bottone in piu' nel DOM, non un rischio.\r\n -->\r\n <div class=\"fb-viewport\" fDragBlocker role=\"group\" aria-label=\"Vista\">\r\n <!--\r\n Lo zoom ha bisogno di **comandi e di un numero**: con la sola rotella si finisce all'8% su\r\n un flow grande e sembra che il canvas si sia svuotato, senza un modo evidente di tornare.\r\n Il numero e' un bottone: riporta al 100% sull'elemento corrente.\r\n -->\r\n <button type=\"button\" class=\"fb-btn fb-viewport__btn\" title=\"Riduci (rotella indietro)\" (click)=\"zoomOut()\">\r\n \u2212\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-viewport__level\"\r\n title=\"Torna al 100% sull\u2019elemento selezionato\"\r\n (click)=\"zoomToActual()\"\r\n >\r\n {{ zoomPercent() }}%\r\n </button>\r\n <button type=\"button\" class=\"fb-btn fb-viewport__btn\" title=\"Ingrandisci (rotella avanti)\" (click)=\"zoomIn()\">\r\n +\r\n </button>\r\n <!--\r\n La modalita\u2019 selezione. Esiste perche\u2019 i due gesti che la libreria offre di serie \u2014\r\n `Ctrl`+clic per aggiungere, `Shift`+trascina per il rettangolo \u2014 non si vedono: senza un\r\n interruttore, su un flow grande si finisce a spostare un elemento alla volta. Accesa, il\r\n trascinamento sul canvas vuoto **seleziona** e la vista si sposta col tasto centrale.\r\n -->\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-viewport__select\"\r\n [class.fb-viewport__select--on]=\"isSelecting()\"\r\n [attr.aria-pressed]=\"isSelecting()\"\r\n [title]=\"\r\n isSelecting()\r\n ? 'Modalita\u2019 selezione attiva: trascina sul canvas per selezionare piu\u2019 elementi. La vista si sposta col tasto centrale del mouse'\r\n : 'Attiva la selezione a rettangolo: trascinando sul canvas selezioni piu\u2019 elementi invece di spostare la vista (Shift+trascina fa lo stesso, sempre)'\r\n \"\r\n (click)=\"toggleSelectionMode()\"\r\n >\r\n <span class=\"fb-viewport__icon\" aria-hidden=\"true\">\u25A4</span>\r\n Seleziona\r\n </button>\r\n <!--\r\n Sempre nel DOM, nascosto con una classe e non con `@if`: dentro una proiezione di contenuto\r\n un blocco di controllo e' una complicazione gratuita, e `visibility: hidden` lo toglie\r\n anche dall'ordine di tabulazione. Il costo e' un bottone in piu' nel DOM, non un rischio.\r\n -->\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-viewport__fit\"\r\n [class.fb-viewport__fit--hidden]=\"!hasMovedViewport()\"\r\n title=\"Rimetti il flow in vista. Su un flow molto grande inquadra l\u2019inizio, perche\u2019 l\u2019intero non sarebbe leggibile\"\r\n (click)=\"resetViewport()\"\r\n >\r\n <span class=\"fb-viewport__icon\" aria-hidden=\"true\">\u2922</span>\r\n Inquadra\r\n </button>\r\n </div>\r\n\r\n @if (selectionCount() > 1) {\r\n <!--\r\n Il dato che rende utile la selezione: che si spostano **insieme**. Senza dirlo, dopo aver\r\n selezionato cinque elementi si continua a trascinarne uno per volta.\r\n -->\r\n <p class=\"fb-select-hint\" role=\"status\">\r\n {{ selectionCount() }} elementi selezionati: trascinane uno per spostarli tutti \u00B7 Ctrl+clic\r\n aggiunge o toglie \u00B7 Canc li elimina\r\n </p>\r\n } @else if (isSelecting()) {\r\n <p class=\"fb-select-hint\" role=\"status\">\r\n Trascina sul canvas per selezionare \u00B7 la vista si sposta col tasto centrale\r\n </p>\r\n }\r\n\r\n @if (insertionEdgeId()) {\r\n <!-- Il rilascio su un arco non e' un gesto che si indovina: mentre e' possibile, si dice. -->\r\n <p class=\"fb-insert-hint\" role=\"status\">Rilascia qui per inserire l\u2019elemento in questo percorso</p>\r\n }\r\n\r\n <f-minimap [fMinSize]=\"1200\" class=\"fb-minimap\" />\r\n</f-flow>\r\n", styles: [":host{display:block;position:relative;width:100%;height:100%;overflow:hidden;background:var(--fb-canvas-bg, #f4f5f7)}f-flow{display:block;width:100%;height:100%}.fb-minimap{position:absolute;right:14px;bottom:14px;width:150px;height:300px;max-height:40%;border:1px solid var(--fb-border, #e2e5eb);border-radius:var(--fb-radius, 10px);background:var(--fb-surface, #fff);box-shadow:var(--fb-shadow-md, 0 6px 18px rgb(16 24 40 / 10%));overflow:hidden}.fb-viewport{position:absolute;right:14px;bottom:322px;z-index:1;display:flex;align-items:center;gap:4px;flex-wrap:wrap;justify-content:flex-end;max-width:200px}.fb-viewport__btn,.fb-viewport__level,.fb-viewport__fit{border-radius:var(--fb-radius-xs, 6px);box-shadow:var(--fb-shadow-md, 0 6px 18px rgb(16 24 40 / 10%));font-size:11px}.fb-viewport__btn{width:24px;padding:2px 0;font-size:14px;line-height:1}.fb-viewport__level{min-width:52px;font-variant-numeric:tabular-nums}.fb-viewport__fit--hidden{opacity:0;visibility:hidden}.fb-viewport__fit{transition:opacity .15s ease,visibility .15s ease}.fb-viewport__icon{font-size:13px;line-height:1;color:var(--fb-text-muted, #667085)}.fb-insert-hint{position:absolute;bottom:18px;left:50%;transform:translate(-50%);z-index:1;margin:0;padding:6px 12px;border-radius:999px;background:var(--fb-accent, #2f6feb);box-shadow:var(--fb-shadow-md, 0 6px 18px rgb(16 24 40 / 10%));color:#fff;font-size:11px;font-weight:600;pointer-events:none}.fb-select-hint{position:absolute;bottom:18px;left:50%;transform:translate(-50%);z-index:1;margin:0;padding:6px 12px;border-radius:999px;border:1px solid var(--fb-border, #e2e5eb);background:var(--fb-surface, #fff);box-shadow:var(--fb-shadow-md, 0 6px 18px rgb(16 24 40 / 10%));color:var(--fb-text-muted, #667085);font-size:11px;font-weight:600;pointer-events:none}.fb-viewport__select{border-radius:var(--fb-radius-xs, 6px);box-shadow:var(--fb-shadow-md, 0 6px 18px rgb(16 24 40 / 10%));font-size:11px}.fb-viewport__select--on{border-color:var(--fb-accent, #2f6feb);background:var(--fb-accent, #2f6feb);color:#fff}.fb-node{position:absolute;display:flex;flex-direction:column;box-sizing:border-box;width:240px;padding:0;border:1px solid var(--fb-border, #e2e5eb);border-radius:var(--fb-radius-lg, 12px);background:var(--fb-surface, #fff);box-shadow:var(--fb-shadow-sm, 0 1px 2px rgb(16 24 40 / 6%));font:inherit;cursor:grab;-webkit-user-select:none;user-select:none;transition:box-shadow .12s ease,border-color .12s ease}.fb-node--outlets-3{width:304px}.fb-node--outlets-4{width:380px}.fb-node--outlets-5{width:456px}.fb-node--outlets-6{width:520px}.fb-node:hover{box-shadow:var(--fb-shadow-md, 0 6px 18px rgb(16 24 40 / 10%))}.fb-node--selected{border-color:var(--fb-accent, #4f6ef7);box-shadow:0 0 0 3px color-mix(in srgb,var(--fb-accent, #4f6ef7) 22%,transparent)}.fb-node--unreachable{border-style:dashed;opacity:.8}.fb-node--error{border-color:var(--fb-error, #c9372c)}.fb-node--warning{border-color:var(--fb-warning, #b7791f)}.fb-node__head{display:flex;align-items:center;gap:8px;padding:10px 10px 6px 12px}.fb-node__icon{display:grid;place-items:center;flex:0 0 auto;width:28px;height:28px;border-radius:var(--fb-radius-sm, 8px);background:var(--fb-node-accent, #667085);color:#fff;font-size:14px;line-height:1}.cat-start{--fb-node-accent: #22a06b}.cat-screen{--fb-node-accent: #3b82f6}.cat-logic{--fb-node-accent: #8b5cf6}.cat-data{--fb-node-accent: #06b6d4}.cat-action{--fb-node-accent: #f59e0b}.cat-flow{--fb-node-accent: #14b8a6}.cat-other{--fb-node-accent: #667085}.fb-node__text{flex:1;min-width:0}.fb-node__title{display:block;font-size:13px;font-weight:600;line-height:17px;color:var(--fb-text, #1a1c23);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fb-node__sub{display:block;margin-top:1px;font-size:11px;line-height:14px;color:var(--fb-text-muted, #6b7086);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fb-node__sub-dot{margin:0 4px;color:var(--fb-text-subtle, #98a2b3)}.fb-node__auto{flex:0 0 auto;font-size:12px;font-weight:700;color:var(--fb-accent, #4f6ef7);cursor:help}.fb-node__edit{display:grid;place-items:center;flex:0 0 auto;width:22px;height:22px;padding:0;border:0;border-radius:var(--fb-radius-xs, 6px);background:transparent;color:var(--fb-text-subtle, #98a2b3);font:inherit;font-size:12px;cursor:pointer;opacity:0;transition:opacity .12s ease,background .12s ease}.fb-node:hover .fb-node__edit,.fb-node--selected .fb-node__edit,.fb-node__edit:focus-visible{opacity:1}.fb-node__edit:hover{background:var(--fb-surface-alt, #f7f8fa);color:var(--fb-text, #1a1c23)}.fb-node__duplicate,.fb-node__remove{display:grid;place-items:center;flex:0 0 auto;width:22px;height:22px;padding:0;border:0;border-radius:var(--fb-radius-xs, 6px);background:transparent;color:var(--fb-text-subtle, #98a2b3);font:inherit;font-size:15px;line-height:1;cursor:pointer;opacity:0;transition:opacity .12s ease,background .12s ease,color .12s ease}.fb-node--selected .fb-node__duplicate,.fb-node--selected .fb-node__remove,.fb-node__duplicate:focus-visible,.fb-node__remove:focus-visible{opacity:1}.fb-node__remove:hover{background:color-mix(in srgb,var(--fb-error, #c9372c) 12%,transparent);color:var(--fb-error, #c9372c)}.fb-node__duplicate:hover{background:color-mix(in srgb,var(--fb-accent, #3b6ef2) 12%,transparent);color:var(--fb-accent, #3b6ef2)}.fb-node__meta{display:flex;flex-wrap:wrap;align-items:center;gap:4px;min-height:14px;padding:0 12px 6px}.fb-node__name{flex:0 1 auto;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:10px;color:var(--fb-text-subtle, #98a2b3);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fb-badge{padding:1px 6px;border-radius:10px;background:var(--fb-badge-bg, #eef0f4);font-size:10px;font-weight:600;color:var(--fb-text-muted, #6b7086);white-space:nowrap;cursor:help}.fb-badge--error{background:color-mix(in srgb,var(--fb-error, #c9372c) 14%,transparent);color:var(--fb-error, #c9372c)}.fb-badge--warning{background:color-mix(in srgb,var(--fb-warning, #b7791f) 16%,transparent);color:var(--fb-warning, #b7791f)}.fb-badge--info{background:color-mix(in srgb,var(--fb-accent, #4f6ef7) 12%,transparent);color:var(--fb-accent, #4f6ef7)}.fb-badge--unreachable,.fb-badge--dangling{background:color-mix(in srgb,var(--fb-warning, #b7791f) 12%,transparent);color:var(--fb-warning, #b7791f)}.fb-node__outlets{display:flex;align-items:flex-end;justify-content:space-evenly;gap:4px;padding:0 8px 4px}.fb-node__outlets--labelled{padding-top:5px;border-top:1px solid var(--fb-border-subtle, #eef0f4)}.fb-outlet{display:flex;flex:1 1 0;flex-direction:column;align-items:center;gap:2px;min-width:0}.fb-outlet__label{max-width:100%;font-size:10px;line-height:13px;color:var(--fb-text-muted, #6b7086);text-align:center;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fb-connector{box-sizing:border-box;width:12px;height:12px;flex:0 0 auto;border:2px solid var(--fb-surface, #fff);border-radius:50%;background:var(--fb-connector, #98a2b3);cursor:crosshair;transition:transform .12s ease,box-shadow .12s ease}f-flow .fb-connector--out{position:relative;inset:auto;margin-bottom:-12px}.fb-connector--out:hover{transform:scale(1.2)}f-flow .fb-connector--in{position:absolute;inset:-2px auto auto 50%;width:24px;height:4px;border:0;border-radius:2px;transform:translate(-50%);background:var(--fb-border-strong, #cfd4de)}.fb-connector--out{--ff-connector-connected-color: var(--fb-connector, #98a2b3)}.fb-connector--Next,.fb-connector--Start,.fb-connector--LoopNext{--fb-connector: var(--fb-edge-next, #98a2b3)}.fb-connector--Rule,.fb-connector--WaitEvent{--fb-connector: var(--fb-edge-rule, #8b5cf6)}.fb-connector--Default,.fb-connector--LoopEnd{--fb-connector: var(--fb-edge-default, #06b6d4)}.fb-connector--Fault{--fb-connector: var(--fb-edge-fault, #dc2626)}.fb-connector--Timeout,.fb-connector--ScheduledPath{--fb-connector: var(--fb-edge-timeout, #f59e0b)}.fb-connector.f-connector-connectable{box-shadow:0 0 0 4px color-mix(in srgb,var(--fb-accent, #4f6ef7) 28%,transparent)}.fb-group{position:absolute;box-sizing:border-box;border:1px solid var(--fb-group-border, #d6dae1);border-radius:var(--fb-radius, 10px);background:var(--fb-group-fill, rgb(148 163 184 / 10%));pointer-events:none;overflow:hidden}.fb-group--selected{border-color:var(--fb-accent, #2f6feb);box-shadow:0 0 0 2px color-mix(in srgb,var(--fb-accent, #2f6feb) 25%,transparent)}.fb-group__bar{display:flex;align-items:center;gap:6px;height:30px;padding:0 6px;border-bottom:1px solid var(--fb-group-border, #d6dae1);background:var(--fb-group-bar, rgb(148 163 184 / 22%));color:var(--fb-text, #1d2939);font-size:12px;font-weight:600;pointer-events:auto;cursor:move;-webkit-user-select:none;user-select:none}.fb-group--collapsed .fb-group__bar{border-bottom:none}.fb-group__fold,.fb-group__remove{flex:none;width:18px;height:18px;padding:0;border:none;border-radius:var(--fb-radius-xs, 6px);background:transparent;color:inherit;font:inherit;font-size:12px;line-height:1;cursor:pointer}.fb-group__fold:hover,.fb-group__remove:hover{background:#10182814}.fb-group__remove{margin-left:auto;font-size:14px}.fb-group__title{overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.fb-group__count{flex:none;padding:0 5px;border-radius:8px;background:#10182814;font-size:10px;font-weight:600;font-variant-numeric:tabular-nums}.fb-group__stale{flex:none;padding:0 5px;border-radius:8px;background:color-mix(in srgb,var(--fb-warning, #b7791f) 14%,transparent);color:var(--fb-warning, #b7791f);font-size:10px;font-weight:600}.fb-group__note{margin:6px 8px 0;color:var(--fb-text-muted, #667085);font-size:11px;line-height:1.35;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2;overflow:hidden}.fb-group__resize{position:absolute;right:0;bottom:0;width:14px;height:14px;border-right:2px solid var(--fb-group-border, #d6dae1);border-bottom:2px solid var(--fb-group-border, #d6dae1);border-bottom-right-radius:var(--fb-radius, 10px);pointer-events:auto;cursor:nwse-resize}.fb-group--giallo{--fb-group-border: #e0c26a;--fb-group-fill: rgb(234 179 8 / 10%);--fb-group-bar: rgb(234 179 8 / 24%)}.fb-group--verde{--fb-group-border: #86c79b;--fb-group-fill: rgb(34 160 89 / 10%);--fb-group-bar: rgb(34 160 89 / 22%)}.fb-group--blu{--fb-group-border: #93b4ea;--fb-group-fill: rgb(47 111 235 / 9%);--fb-group-bar: rgb(47 111 235 / 20%)}.fb-group--viola{--fb-group-border: #bda6e6;--fb-group-fill: rgb(139 92 246 / 10%);--fb-group-bar: rgb(139 92 246 / 22%)}.fb-group--rosso{--fb-group-border: #e6a3a3;--fb-group-fill: rgb(214 69 69 / 9%);--fb-group-bar: rgb(214 69 69 / 20%)}.fb-group--grigio,.fb-group--neutro{--fb-group-border: #d6dae1;--fb-group-fill: rgb(148 163 184 / 10%);--fb-group-bar: rgb(148 163 184 / 22%)}\n"] }]
|
|
6206
|
+
], template: "<!--\r\n Gerarchia obbligatoria: f-flow > f-canvas > fNode / f-connection.\r\n I `@for` sono direttamente dentro <f-canvas>: nessun wrapper, quindi non serve\r\n `ngProjectAs` (che sarebbe indispensabile con blocchi annidati).\r\n-->\r\n<f-flow\r\n fDraggable\r\n (fCreateConnection)=\"onCreateConnection($event)\"\r\n (fReassignConnection)=\"onReassignConnection($event)\"\r\n (fMoveNodes)=\"onMoveNodes($event)\"\r\n (fSelectionChange)=\"onSelectionChange($event)\"\r\n (fDeleteSelected)=\"onDeleteSelected($event)\"\r\n (fCreateNode)=\"onCreateNode($event)\"\r\n (fNodesRendered)=\"onNodesRendered()\"\r\n (fDragStarted)=\"onDragStarted($event)\"\r\n (fDragEnded)=\"onDragEnded()\"\r\n>\r\n <!--\r\n `#canvas` + `(fCanvasChange)`: il primo serve per chiamare `fitToScreen()`, il secondo per\r\n sapere che l'utente ha mosso la vista. `[debounceTime]` non si imposta: l'evento serve solo\r\n ad accendere un flag, non a ricalcolare niente.\r\n -->\r\n <f-canvas fZoom #canvas (fCanvasChange)=\"onCanvasChange()\">\r\n <f-background>\r\n <f-circle-pattern />\r\n </f-background>\r\n\r\n <!--\r\n \u00A73.6 \u2014 i riquadri di raggruppamento. Vanno **sotto** i node, e non c'e' niente da fare per\r\n ottenerlo: `[fGroup]` viene proiettato nel layer dei gruppi, che nell'ordine della libreria\r\n sta sotto connessioni e node.\r\n\r\n Sono commenti: nessun connettore, nessun arco, nessuna raggiungibilita'. Il corpo del\r\n riquadro ha `pointer-events: none` (nel CSS) e solo la barra del titolo risponde: senza,\r\n una cornice grande quanto mezzo canvas si mangerebbe i click destinati alla panoramica e\r\n alla selezione a rettangolo, e i node dentro non si potrebbero piu' prendere.\r\n -->\r\n @for (group of groups(); track group.name) {\r\n <div\r\n fGroup\r\n [fGroupId]=\"group.id\"\r\n [fGroupPosition]=\"group.position\"\r\n [fGroupSize]=\"group.size\"\r\n [fGroupDraggingDisabled]=\"!isEditable()\"\r\n [class]=\"'fb-group ' + group.colorClass\"\r\n [class.fb-group--selected]=\"isGroupSelected(group)\"\r\n (fGroupSizeChange)=\"onGroupResized(group.name, $event)\"\r\n >\r\n <!--\r\n `fDragHandle` sulla sola barra: il riquadro si sposta dal titolo, come una finestra.\r\n Trascinandolo da dentro si sposterebbe la cornice ogni volta che si prova a prendere\r\n un node che le sta sopra.\r\n -->\r\n <div\r\n class=\"fb-group__bar\"\r\n fDragHandle\r\n [title]=\"group.description || group.label\"\r\n (click)=\"onGroupClick($event, group.name)\"\r\n >\r\n <button\r\n type=\"button\"\r\n fDragBlocker\r\n class=\"fb-group__fold\"\r\n aria-label=\"Chiudi il riquadro\"\r\n title=\"Chiudi il riquadro: gli elementi si nascondono e gli archi che lo attraversano si attaccano alla pastiglia\"\r\n (click)=\"toggleGroupCollapsed($event, group.name)\"\r\n >\r\n \u25BE\r\n </button>\r\n <span class=\"fb-group__title\">{{ group.label }}</span>\r\n <span class=\"fb-group__count\" [title]=\"group.memberCount + ' elementi nel riquadro'\">\r\n {{ group.memberCount }}\r\n </span>\r\n @if (group.unknownMembers.length > 0) {\r\n <!--\r\n Membri che nominano un elemento che non c'e' piu'. Non e' un difetto (\u00A73.6): e' cio'\r\n che resta dopo una cancellazione, ed e' un avviso \u2014 l'attivazione non si blocca.\r\n -->\r\n <span\r\n class=\"fb-group__stale\"\r\n [title]=\"'Elementi non piu\u2019 esistenti: ' + group.unknownMembers.join(', ')\"\r\n >\r\n {{ group.unknownMembers.length }} da ripulire\r\n </span>\r\n }\r\n @if (isEditable()) {\r\n <button\r\n type=\"button\"\r\n fDragBlocker\r\n class=\"fb-group__remove\"\r\n [attr.aria-label]=\"'Elimina il riquadro ' + group.label\"\r\n title=\"Elimina il riquadro: gli elementi che contiene restano\"\r\n (click)=\"onGroupRemoveClick($event, group.name)\"\r\n >\r\n \u00D7\r\n </button>\r\n }\r\n </div>\r\n\r\n @if (group.description) {\r\n <p class=\"fb-group__note\">{{ group.description }}</p>\r\n }\r\n\r\n @if (isEditable()) {\r\n <!--\r\n Una sola maniglia, in basso a destra: quattro bersagli su una cornice che sta sotto\r\n i node si prendono per sbaglio. Ridimensionare **dichiara** la geometria, che e' il\r\n modo di uscire dal \u00ABcalcolala sui membri\u00BB (\u00A73.6).\r\n -->\r\n <div\r\n fResizeHandle\r\n [fResizeHandleType]=\"resizeHandles.RIGHT_BOTTOM\"\r\n class=\"fb-group__resize\"\r\n title=\"Ridimensiona il riquadro\"\r\n ></div>\r\n }\r\n </div>\r\n }\r\n\r\n <!--\r\n \u00A73.6 \u2014 un riquadro **chiuso**: una pastiglia al posto dei suoi elementi.\r\n\r\n \u00C8 un `fNode` e non un `fGroup` per una ragione sola, ed e' la regola 4 di @foblex/flow: per\r\n nascondere i node bisogna che gli archi che entravano e uscivano trovino un connettore\r\n **vero**, altrimenti hanno geometria 0\u00D70 e si attaccano all'angolo del canvas. La pastiglia e'\r\n quel connettore. Un solo connettore in uscita: dietro ci stanno elementi diversi, e il nome di\r\n quello che esce sta nell'etichetta dell'arco.\r\n -->\r\n @for (fold of folds(); track fold.id) {\r\n <div\r\n fNode\r\n fDragHandle\r\n [fNodeId]=\"fold.id\"\r\n [fNodePosition]=\"fold.position\"\r\n [class]=\"'fb-fold ' + fold.colorClass\"\r\n [class.fb-fold--selected]=\"isFoldSelected(fold)\"\r\n [class.fb-fold--error]=\"fold.severity === 'Error'\"\r\n [class.fb-fold--warning]=\"fold.severity === 'Warning'\"\r\n (dblclick)=\"toggleGroupCollapsed($event, fold.name)\"\r\n >\r\n <div\r\n fConnector\r\n fConnectorType=\"target\"\r\n [fConnectorId]=\"foldTargetIdOf(fold)\"\r\n [fConnectorConnectableSide]=\"sides.TOP\"\r\n fConnectorMultiple=\"true\"\r\n class=\"fb-connector fb-connector--in\"\r\n title=\"Ingresso del riquadro chiuso\"\r\n ></div>\r\n\r\n <div class=\"fb-fold__head\">\r\n <button\r\n type=\"button\"\r\n fDragBlocker\r\n class=\"fb-fold__open\"\r\n [attr.aria-label]=\"'Apri il riquadro ' + fold.label\"\r\n title=\"Apri il riquadro: gli elementi tornano visibili al loro posto\"\r\n (click)=\"toggleGroupCollapsed($event, fold.name)\"\r\n >\r\n \u25B8\r\n </button>\r\n <span class=\"fb-fold__title\" [title]=\"fold.description || fold.label\">{{ fold.label }}</span>\r\n @if (isEditable()) {\r\n <button\r\n type=\"button\"\r\n fDragBlocker\r\n class=\"fb-fold__remove\"\r\n [attr.aria-label]=\"'Elimina il riquadro ' + fold.label\"\r\n title=\"Elimina il riquadro: gli elementi che contiene restano\"\r\n (click)=\"onGroupRemoveClick($event, fold.name)\"\r\n >\r\n \u00D7\r\n </button>\r\n }\r\n </div>\r\n\r\n <div class=\"fb-fold__meta\">\r\n <span class=\"fb-fold__count\">\r\n {{ fold.memberCount }} {{ fold.memberCount === 1 ? 'elemento nascosto' : 'elementi nascosti' }}\r\n </span>\r\n @if (fold.issueCount > 0) {\r\n <!-- I rilievi di cio' che e' nascosto: chiudere un riquadro non nasconde un errore (\u00A77). -->\r\n <span\r\n class=\"fb-badge\"\r\n [class.fb-badge--error]=\"fold.severity === 'Error'\"\r\n [class.fb-badge--warning]=\"fold.severity === 'Warning'\"\r\n [class.fb-badge--info]=\"fold.severity === 'Info'\"\r\n [title]=\"fold.issueCount + ' rilievi negli elementi nascosti'\"\r\n >\r\n {{ fold.issueCount }}\r\n </span>\r\n }\r\n @if (fold.unknownMembers.length > 0) {\r\n <span\r\n class=\"fb-badge fb-badge--dangling\"\r\n [title]=\"'Elementi non piu\u2019 esistenti: ' + fold.unknownMembers.join(', ')\"\r\n >\r\n {{ fold.unknownMembers.length }} da ripulire\r\n </span>\r\n }\r\n </div>\r\n\r\n <div class=\"fb-fold__outlets\">\r\n <div\r\n fConnector\r\n fConnectorType=\"source\"\r\n [fConnectorId]=\"foldSourceIdOf(fold)\"\r\n [fConnectorConnectableSide]=\"sides.BOTTOM\"\r\n class=\"fb-connector fb-connector--out fb-connector--Next\"\r\n title=\"Uscite del riquadro chiuso\"\r\n ></div>\r\n </div>\r\n </div>\r\n }\r\n\r\n @for (edge of edges(); track edge.id) {\r\n <f-connection\r\n [fConnectionId]=\"edge.id\"\r\n [fSourceId]=\"edge.sourceId\"\r\n [fTargetId]=\"edge.targetId\"\r\n fBehavior=\"floating\"\r\n [fType]=\"edge.isGoTo ? 'segment' : 'bezier'\"\r\n [class]=\"'fb-edge fb-edge--' + edge.kind + (edge.isGoTo ? ' fb-edge--goto' : '')\"\r\n [class.fb-edge--insert]=\"edge.id === insertionEdgeId()\"\r\n [class.fb-edge--folded]=\"edge.isFolded\"\r\n >\r\n <f-connection-marker-arrow [type]=\"markerEnd\" />\r\n @if (edge.label) {\r\n <div fConnectionContent class=\"fb-edge-label\">{{ edge.label }}</div>\r\n }\r\n </f-connection>\r\n }\r\n\r\n @for (node of nodes(); track node.id) {\r\n <div\r\n fNode\r\n fDragHandle\r\n [fNodeId]=\"node.id\"\r\n [fNodePosition]=\"node.position\"\r\n [class]=\"'fb-node ' + categoryClass(node) + ' ' + node.widthClass\"\r\n [class.fb-node--start]=\"node.isStart\"\r\n [class.fb-node--selected]=\"isSelected(node)\"\r\n [class.fb-node--unreachable]=\"!node.isReachable\"\r\n [class.fb-node--error]=\"node.severity === 'Error'\"\r\n [class.fb-node--warning]=\"node.severity === 'Warning'\"\r\n (dblclick)=\"onNodeDoubleClick(node.name)\"\r\n >\r\n <!--\r\n Lo Start non ha ingresso: e' il punto di partenza (\u00A73.4).\r\n `fConnectorConnectableSide` e' cio' che fa entrare l'arco dall'alto: senza, foblex\r\n calcola il lato e un arco che scende dal node sopra potrebbe agganciarsi di fianco.\r\n -->\r\n @if (!node.isStart) {\r\n <div\r\n fConnector\r\n fConnectorType=\"target\"\r\n [fConnectorId]=\"targetIdOf(node)\"\r\n [fConnectorConnectableSide]=\"sides.TOP\"\r\n fConnectorMultiple=\"true\"\r\n class=\"fb-connector fb-connector--in\"\r\n title=\"Ingresso\"\r\n ></div>\r\n }\r\n\r\n <div class=\"fb-node__head\">\r\n <span class=\"fb-node__icon\" aria-hidden=\"true\">{{ node.icon }}</span>\r\n <div class=\"fb-node__text\">\r\n <span class=\"fb-node__title\" [title]=\"node.description || node.label\">{{ node.label }}</span>\r\n <span class=\"fb-node__sub\">\r\n {{ node.typeLabel }}\r\n @if (node.subtitle) {\r\n <span class=\"fb-node__sub-dot\">\u00B7</span>{{ node.subtitle }}\r\n }\r\n </span>\r\n </div>\r\n @if (node.hasAutomaticOutput) {\r\n <!-- L'elemento espone il proprio risultato sotto il proprio nome (\u00A74.5). -->\r\n <span class=\"fb-node__auto\" title=\"Espone un output automatico referenziabile come \u00AB{{ node.name }}\u00BB\">\r\n \u0192\r\n </span>\r\n }\r\n <!--\r\n `fDragBlocker` impedisce che il pointerdown sul bottone diventi un trascinamento\r\n del node: senza, aprire il dettaglio sposterebbe l'elemento di qualche pixel.\r\n -->\r\n <button\r\n type=\"button\"\r\n fDragBlocker\r\n class=\"fb-node__edit\"\r\n [attr.aria-label]=\"'Apri il dettaglio di ' + node.label\"\r\n title=\"Apri il dettaglio\"\r\n (click)=\"onEditClick($event, node.name)\"\r\n >\r\n \u270E\r\n </button>\r\n @if (!node.isStart && isEditable()) {\r\n <!--\r\n Duplica: sta accanto a \u270E e non solo nell'inspector perche' e' il gesto con cui si\r\n riusa un elemento gi\u00E0 configurato, e cercarlo dentro il form dell'elemento da\r\n copiare e' il posto in cui non lo si cerca. Compare come \u00AB\u00D7\u00BB, sul solo node\r\n selezionato: aggiunge un elemento al documento, quindi non deve trovarsi sotto il\r\n puntatore di chi sta solo attraversando il grafo.\r\n -->\r\n <button\r\n type=\"button\"\r\n fDragBlocker\r\n class=\"fb-node__duplicate\"\r\n [attr.aria-label]=\"'Duplica ' + node.label\"\r\n title=\"Duplica questo elemento (Ctrl+D)\"\r\n (click)=\"onDuplicateClick($event, node.name)\"\r\n >\r\n \u29C9\r\n </button>\r\n <!--\r\n Si mostra solo sul node **selezionato**, non al passaggio del mouse come \u270E:\r\n cancellare non e' reversibile con un altro clic, e un comando distruttivo che\r\n appare sotto il puntatore mentre si attraversa il grafo si preme per sbaglio.\r\n Lo Start non lo espone: un flow senza ingresso non esisterebbe.\r\n -->\r\n <button\r\n type=\"button\"\r\n fDragBlocker\r\n class=\"fb-node__remove\"\r\n [attr.aria-label]=\"'Elimina ' + node.label\"\r\n title=\"Elimina questo elemento (si annulla con \u21B6)\"\r\n (click)=\"onRemoveClick($event, node.name)\"\r\n >\r\n \u00D7\r\n </button>\r\n }\r\n </div>\r\n\r\n <div class=\"fb-node__meta\">\r\n @if (!node.isStart) {\r\n <span class=\"fb-node__name\" [title]=\"'Nome tecnico: ' + node.name\">{{ node.name }}</span>\r\n }\r\n @if (!node.isReachable) {\r\n <span class=\"fb-badge fb-badge--unreachable\" title=\"Nessun percorso raggiunge questo elemento dallo Start\">\r\n scollegato\r\n </span>\r\n }\r\n @if (node.issueCount > 0) {\r\n <span\r\n class=\"fb-badge\"\r\n [class.fb-badge--error]=\"node.severity === 'Error'\"\r\n [class.fb-badge--warning]=\"node.severity === 'Warning'\"\r\n [class.fb-badge--info]=\"node.severity === 'Info'\"\r\n [title]=\"node.issueCount + ' rilievi di validazione'\"\r\n >\r\n {{ node.issueCount }}\r\n </span>\r\n }\r\n @if (node.danglingOutlets.length > 0) {\r\n <span\r\n class=\"fb-badge fb-badge--dangling\"\r\n [title]=\"'Rami dichiarati senza destinazione: ' + danglingLabels(node)\"\r\n >\r\n ramo incompleto\r\n </span>\r\n }\r\n </div>\r\n\r\n <!--\r\n Le uscite stanno sul bordo **inferiore**, una per ramo, nell'ordine in cui il modello\r\n le dichiara: per una Decision e' l'ordine di valutazione delle regole, che e'\r\n semantico (\u00A75.4), e da sinistra a destra si legge come la lista nell'inspector.\r\n L'etichetta si mostra solo quando i rami sono piu' di uno: su un `next` unico\r\n direbbe soltanto \u00ABSuccessivo\u00BB.\r\n -->\r\n <div class=\"fb-node__outlets\" [class.fb-node__outlets--labelled]=\"node.showsOutletLabels\">\r\n @for (outlet of node.outlets; track outlet.key) {\r\n <div class=\"fb-outlet\">\r\n @if (node.showsOutletLabels) {\r\n <span class=\"fb-outlet__label\" [title]=\"outlet.label\">{{ outlet.label }}</span>\r\n }\r\n <div\r\n fConnector\r\n fConnectorType=\"source\"\r\n [fConnectorId]=\"connectorIdOf(node, outlet.key)\"\r\n [fConnectorConnectableSide]=\"sides.BOTTOM\"\r\n [class]=\"'fb-connector fb-connector--out fb-connector--' + outlet.kind\"\r\n [title]=\"outlet.label\"\r\n ></div>\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n }\r\n\r\n <!-- Anteprima dell'arco durante il trascinamento. -->\r\n <f-connection-for-create fBehavior=\"floating\" fType=\"bezier\" class=\"fb-edge fb-edge--creating\">\r\n <f-connection-marker-arrow [type]=\"markerEnd\" />\r\n </f-connection-for-create>\r\n\r\n <f-selection-area />\r\n </f-canvas>\r\n\r\n <!--\r\n Fuori da <f-canvas> come la minimappa: dentro, la trasformazione del canvas se lo\r\n porterebbe via insieme ai node. `fDragBlocker` perche' il pointerdown non inizi una\r\n panoramica invece di premere il bottone.\r\n -->\r\n <!--\r\n Sempre nel DOM, nascosto con una classe e non con `@if`: dentro una proiezione di contenuto\r\n un blocco di controllo e' una complicazione gratuita, e `visibility: hidden` lo toglie\r\n anche dall'ordine di tabulazione. Il costo e' un bottone in piu' nel DOM, non un rischio.\r\n -->\r\n <div class=\"fb-viewport\" fDragBlocker role=\"group\" aria-label=\"Vista\">\r\n <!--\r\n Lo zoom ha bisogno di **comandi e di un numero**: con la sola rotella si finisce all'8% su\r\n un flow grande e sembra che il canvas si sia svuotato, senza un modo evidente di tornare.\r\n Il numero e' un bottone: riporta al 100% sull'elemento corrente.\r\n -->\r\n <button type=\"button\" class=\"fb-btn fb-viewport__btn\" title=\"Riduci (rotella indietro)\" (click)=\"zoomOut()\">\r\n \u2212\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-viewport__level\"\r\n title=\"Torna al 100% sull\u2019elemento selezionato\"\r\n (click)=\"zoomToActual()\"\r\n >\r\n {{ zoomPercent() }}%\r\n </button>\r\n <button type=\"button\" class=\"fb-btn fb-viewport__btn\" title=\"Ingrandisci (rotella avanti)\" (click)=\"zoomIn()\">\r\n +\r\n </button>\r\n <!--\r\n La modalita\u2019 selezione. Esiste perche\u2019 i due gesti che la libreria offre di serie \u2014\r\n `Ctrl`+clic per aggiungere, `Shift`+trascina per il rettangolo \u2014 non si vedono: senza un\r\n interruttore, su un flow grande si finisce a spostare un elemento alla volta. Accesa, il\r\n trascinamento sul canvas vuoto **seleziona** e la vista si sposta col tasto centrale.\r\n -->\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-viewport__select\"\r\n [class.fb-viewport__select--on]=\"isSelecting()\"\r\n [attr.aria-pressed]=\"isSelecting()\"\r\n [title]=\"\r\n isSelecting()\r\n ? 'Modalita\u2019 selezione attiva: trascina sul canvas per selezionare piu\u2019 elementi. La vista si sposta col tasto centrale del mouse'\r\n : 'Attiva la selezione a rettangolo: trascinando sul canvas selezioni piu\u2019 elementi invece di spostare la vista (Shift+trascina fa lo stesso, sempre)'\r\n \"\r\n (click)=\"toggleSelectionMode()\"\r\n >\r\n <span class=\"fb-viewport__icon\" aria-hidden=\"true\">\u25A4</span>\r\n Seleziona\r\n </button>\r\n <!--\r\n Sempre nel DOM, nascosto con una classe e non con `@if`: dentro una proiezione di contenuto\r\n un blocco di controllo e' una complicazione gratuita, e `visibility: hidden` lo toglie\r\n anche dall'ordine di tabulazione. Il costo e' un bottone in piu' nel DOM, non un rischio.\r\n -->\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-viewport__fit\"\r\n [class.fb-viewport__fit--hidden]=\"!hasMovedViewport()\"\r\n title=\"Rimetti il flow in vista. Su un flow molto grande inquadra l\u2019inizio, perche\u2019 l\u2019intero non sarebbe leggibile\"\r\n (click)=\"resetViewport()\"\r\n >\r\n <span class=\"fb-viewport__icon\" aria-hidden=\"true\">\u2922</span>\r\n Inquadra\r\n </button>\r\n </div>\r\n\r\n @if (selectionCount() > 1) {\r\n <!--\r\n Il dato che rende utile la selezione: che si spostano **insieme**. Senza dirlo, dopo aver\r\n selezionato cinque elementi si continua a trascinarne uno per volta.\r\n -->\r\n <p class=\"fb-select-hint\" role=\"status\">\r\n {{ selectionCount() }} elementi selezionati: trascinane uno per spostarli tutti \u00B7 Ctrl+clic\r\n aggiunge o toglie \u00B7 Canc li elimina\r\n </p>\r\n } @else if (isSelecting()) {\r\n <p class=\"fb-select-hint\" role=\"status\">\r\n Trascina sul canvas per selezionare \u00B7 la vista si sposta col tasto centrale\r\n </p>\r\n }\r\n\r\n @if (insertionEdgeId()) {\r\n <!-- Il rilascio su un arco non e' un gesto che si indovina: mentre e' possibile, si dice. -->\r\n <p class=\"fb-insert-hint\" role=\"status\">Rilascia qui per inserire l\u2019elemento in questo percorso</p>\r\n }\r\n\r\n <f-minimap [fMinSize]=\"1200\" class=\"fb-minimap\" />\r\n</f-flow>\r\n", styles: [":host{display:block;position:relative;width:100%;height:100%;overflow:hidden;background:var(--fb-canvas-bg, #f4f5f7)}f-flow{display:block;width:100%;height:100%}.fb-minimap{position:absolute;right:14px;bottom:14px;width:150px;height:300px;max-height:40%;border:1px solid var(--fb-border, #e2e5eb);border-radius:var(--fb-radius, 10px);background:var(--fb-surface, #fff);box-shadow:var(--fb-shadow-md, 0 6px 18px rgb(16 24 40 / 10%));overflow:hidden}.fb-viewport{position:absolute;right:14px;bottom:322px;z-index:1;display:flex;align-items:center;gap:4px;flex-wrap:wrap;justify-content:flex-end;max-width:200px}.fb-viewport__btn,.fb-viewport__level,.fb-viewport__fit{border-radius:var(--fb-radius-xs, 6px);box-shadow:var(--fb-shadow-md, 0 6px 18px rgb(16 24 40 / 10%));font-size:11px}.fb-viewport__btn{width:24px;padding:2px 0;font-size:14px;line-height:1}.fb-viewport__level{min-width:52px;font-variant-numeric:tabular-nums}.fb-viewport__fit--hidden{opacity:0;visibility:hidden}.fb-viewport__fit{transition:opacity .15s ease,visibility .15s ease}.fb-viewport__icon{font-size:13px;line-height:1;color:var(--fb-text-muted, #667085)}.fb-insert-hint{position:absolute;bottom:18px;left:50%;transform:translate(-50%);z-index:1;margin:0;padding:6px 12px;border-radius:999px;background:var(--fb-accent, #2f6feb);box-shadow:var(--fb-shadow-md, 0 6px 18px rgb(16 24 40 / 10%));color:#fff;font-size:11px;font-weight:600;pointer-events:none}.fb-select-hint{position:absolute;bottom:18px;left:50%;transform:translate(-50%);z-index:1;margin:0;padding:6px 12px;border-radius:999px;border:1px solid var(--fb-border, #e2e5eb);background:var(--fb-surface, #fff);box-shadow:var(--fb-shadow-md, 0 6px 18px rgb(16 24 40 / 10%));color:var(--fb-text-muted, #667085);font-size:11px;font-weight:600;pointer-events:none}.fb-viewport__select{border-radius:var(--fb-radius-xs, 6px);box-shadow:var(--fb-shadow-md, 0 6px 18px rgb(16 24 40 / 10%));font-size:11px}.fb-viewport__select--on{border-color:var(--fb-accent, #2f6feb);background:var(--fb-accent, #2f6feb);color:#fff}.fb-node{position:absolute;display:flex;flex-direction:column;box-sizing:border-box;width:240px;padding:0;border:1px solid var(--fb-border, #e2e5eb);border-radius:var(--fb-radius-lg, 12px);background:var(--fb-surface, #fff);box-shadow:var(--fb-shadow-sm, 0 1px 2px rgb(16 24 40 / 6%));font:inherit;cursor:grab;-webkit-user-select:none;user-select:none;transition:box-shadow .12s ease,border-color .12s ease}.fb-node--outlets-3{width:304px}.fb-node--outlets-4{width:380px}.fb-node--outlets-5{width:456px}.fb-node--outlets-6{width:520px}.fb-node:hover{box-shadow:var(--fb-shadow-md, 0 6px 18px rgb(16 24 40 / 10%))}.fb-node--selected{border-color:var(--fb-accent, #4f6ef7);box-shadow:0 0 0 3px color-mix(in srgb,var(--fb-accent, #4f6ef7) 22%,transparent)}.fb-node--unreachable{border-style:dashed;opacity:.8}.fb-node--error{border-color:var(--fb-error, #c9372c)}.fb-node--warning{border-color:var(--fb-warning, #b7791f)}.fb-node__head{display:flex;align-items:center;gap:8px;padding:10px 10px 6px 12px}.fb-node__icon{display:grid;place-items:center;flex:0 0 auto;width:28px;height:28px;border-radius:var(--fb-radius-sm, 8px);background:var(--fb-node-accent, #667085);color:#fff;font-size:14px;line-height:1}.cat-start{--fb-node-accent: #22a06b}.cat-screen{--fb-node-accent: #3b82f6}.cat-logic{--fb-node-accent: #8b5cf6}.cat-data{--fb-node-accent: #06b6d4}.cat-action{--fb-node-accent: #f59e0b}.cat-flow{--fb-node-accent: #14b8a6}.cat-other{--fb-node-accent: #667085}.fb-node__text{flex:1;min-width:0}.fb-node__title{display:block;font-size:13px;font-weight:600;line-height:17px;color:var(--fb-text, #1a1c23);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fb-node__sub{display:block;margin-top:1px;font-size:11px;line-height:14px;color:var(--fb-text-muted, #6b7086);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fb-node__sub-dot{margin:0 4px;color:var(--fb-text-subtle, #98a2b3)}.fb-node__auto{flex:0 0 auto;font-size:12px;font-weight:700;color:var(--fb-accent, #4f6ef7);cursor:help}.fb-node__edit{display:grid;place-items:center;flex:0 0 auto;width:22px;height:22px;padding:0;border:0;border-radius:var(--fb-radius-xs, 6px);background:transparent;color:var(--fb-text-subtle, #98a2b3);font:inherit;font-size:12px;cursor:pointer;opacity:0;transition:opacity .12s ease,background .12s ease}.fb-node:hover .fb-node__edit,.fb-node--selected .fb-node__edit,.fb-node__edit:focus-visible{opacity:1}.fb-node__edit:hover{background:var(--fb-surface-alt, #f7f8fa);color:var(--fb-text, #1a1c23)}.fb-node__duplicate,.fb-node__remove{display:grid;place-items:center;flex:0 0 auto;width:22px;height:22px;padding:0;border:0;border-radius:var(--fb-radius-xs, 6px);background:transparent;color:var(--fb-text-subtle, #98a2b3);font:inherit;font-size:15px;line-height:1;cursor:pointer;opacity:0;transition:opacity .12s ease,background .12s ease,color .12s ease}.fb-node--selected .fb-node__duplicate,.fb-node--selected .fb-node__remove,.fb-node__duplicate:focus-visible,.fb-node__remove:focus-visible{opacity:1}.fb-node__remove:hover{background:color-mix(in srgb,var(--fb-error, #c9372c) 12%,transparent);color:var(--fb-error, #c9372c)}.fb-node__duplicate:hover{background:color-mix(in srgb,var(--fb-accent, #3b6ef2) 12%,transparent);color:var(--fb-accent, #3b6ef2)}.fb-node__meta{display:flex;flex-wrap:wrap;align-items:center;gap:4px;min-height:14px;padding:0 12px 6px}.fb-node__name{flex:0 1 auto;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:10px;color:var(--fb-text-subtle, #98a2b3);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fb-badge{padding:1px 6px;border-radius:10px;background:var(--fb-badge-bg, #eef0f4);font-size:10px;font-weight:600;color:var(--fb-text-muted, #6b7086);white-space:nowrap;cursor:help}.fb-badge--error{background:color-mix(in srgb,var(--fb-error, #c9372c) 14%,transparent);color:var(--fb-error, #c9372c)}.fb-badge--warning{background:color-mix(in srgb,var(--fb-warning, #b7791f) 16%,transparent);color:var(--fb-warning, #b7791f)}.fb-badge--info{background:color-mix(in srgb,var(--fb-accent, #4f6ef7) 12%,transparent);color:var(--fb-accent, #4f6ef7)}.fb-badge--unreachable,.fb-badge--dangling{background:color-mix(in srgb,var(--fb-warning, #b7791f) 12%,transparent);color:var(--fb-warning, #b7791f)}.fb-node__outlets{display:flex;align-items:flex-end;justify-content:space-evenly;gap:4px;padding:0 8px 4px}.fb-node__outlets--labelled{padding-top:5px;border-top:1px solid var(--fb-border-subtle, #eef0f4)}.fb-outlet{display:flex;flex:1 1 0;flex-direction:column;align-items:center;gap:2px;min-width:0}.fb-outlet__label{max-width:100%;font-size:10px;line-height:13px;color:var(--fb-text-muted, #6b7086);text-align:center;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fb-connector{box-sizing:border-box;width:12px;height:12px;flex:0 0 auto;border:2px solid var(--fb-surface, #fff);border-radius:50%;background:var(--fb-connector, #98a2b3);cursor:crosshair;transition:transform .12s ease,box-shadow .12s ease}f-flow .fb-connector--out{position:relative;inset:auto;margin-bottom:-12px}.fb-connector--out:hover{transform:scale(1.2)}f-flow .fb-connector--in{position:absolute;inset:-2px auto auto 50%;width:24px;height:4px;border:0;border-radius:2px;transform:translate(-50%);background:var(--fb-border-strong, #cfd4de)}.fb-connector--out{--ff-connector-connected-color: var(--fb-connector, #98a2b3)}.fb-connector--Next,.fb-connector--Start,.fb-connector--LoopNext{--fb-connector: var(--fb-edge-next, #98a2b3)}.fb-connector--Rule,.fb-connector--WaitEvent{--fb-connector: var(--fb-edge-rule, #8b5cf6)}.fb-connector--Default,.fb-connector--LoopEnd{--fb-connector: var(--fb-edge-default, #06b6d4)}.fb-connector--Fault{--fb-connector: var(--fb-edge-fault, #dc2626)}.fb-connector--Timeout,.fb-connector--ScheduledPath{--fb-connector: var(--fb-edge-timeout, #f59e0b)}.fb-connector.f-connector-connectable{box-shadow:0 0 0 4px color-mix(in srgb,var(--fb-accent, #4f6ef7) 28%,transparent)}.fb-group{position:absolute;box-sizing:border-box;border:1px solid var(--fb-group-border, #d6dae1);border-radius:var(--fb-radius, 10px);background:var(--fb-group-fill, rgb(148 163 184 / 10%));pointer-events:none;overflow:hidden}.fb-group--selected{border-color:var(--fb-accent, #2f6feb);box-shadow:0 0 0 2px color-mix(in srgb,var(--fb-accent, #2f6feb) 25%,transparent)}.fb-group__bar{display:flex;align-items:center;gap:6px;height:30px;padding:0 6px;border-bottom:1px solid var(--fb-group-border, #d6dae1);background:var(--fb-group-bar, rgb(148 163 184 / 22%));color:var(--fb-text, #1d2939);font-size:12px;font-weight:600;pointer-events:auto;cursor:move;-webkit-user-select:none;user-select:none}.fb-fold{position:absolute;box-sizing:border-box;display:flex;flex-direction:column;justify-content:center;width:260px;min-height:64px;padding:6px 8px;border:1px solid var(--fb-group-border, #d6dae1);border-left:4px solid var(--fb-group-border, #d6dae1);border-radius:var(--fb-radius-lg, 12px);background:var(--fb-group-bar, rgb(148 163 184 / 22%));box-shadow:var(--fb-shadow-sm, 0 1px 2px rgb(16 24 40 / 6%));color:var(--fb-text, #1d2939);font:inherit;font-size:12px;cursor:grab;-webkit-user-select:none;user-select:none}.fb-fold:hover{box-shadow:var(--fb-shadow-md, 0 6px 18px rgb(16 24 40 / 10%))}.fb-fold--selected{border-color:var(--fb-accent, #4f6ef7);box-shadow:0 0 0 3px color-mix(in srgb,var(--fb-accent, #4f6ef7) 22%,transparent)}.fb-fold--error{border-color:var(--fb-error, #c9372c)}.fb-fold--warning{border-color:var(--fb-warning, #b7791f)}.fb-fold__head{display:flex;align-items:center;gap:6px}.fb-fold__title{flex:1;min-width:0;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;font-weight:600}.fb-fold__open,.fb-fold__remove{flex:none;width:18px;height:18px;padding:0;border:none;border-radius:var(--fb-radius-xs, 6px);background:transparent;color:inherit;font:inherit;font-size:12px;line-height:1;cursor:pointer}.fb-fold__remove{font-size:14px}.fb-fold__open:hover,.fb-fold__remove:hover{background:#10182814}.fb-fold__meta{display:flex;align-items:center;gap:6px;margin-top:2px;color:var(--fb-text-muted, #667085);font-size:11px}.fb-fold__count{overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.fb-fold__outlets{display:flex;justify-content:center;margin-top:4px}.fb-group__fold,.fb-group__remove{flex:none;width:18px;height:18px;padding:0;border:none;border-radius:var(--fb-radius-xs, 6px);background:transparent;color:inherit;font:inherit;font-size:12px;line-height:1;cursor:pointer}.fb-group__fold:hover,.fb-group__remove:hover{background:#10182814}.fb-group__remove{margin-left:auto;font-size:14px}.fb-group__title{overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.fb-group__count{flex:none;padding:0 5px;border-radius:8px;background:#10182814;font-size:10px;font-weight:600;font-variant-numeric:tabular-nums}.fb-group__stale{flex:none;padding:0 5px;border-radius:8px;background:color-mix(in srgb,var(--fb-warning, #b7791f) 14%,transparent);color:var(--fb-warning, #b7791f);font-size:10px;font-weight:600}.fb-group__note{margin:6px 8px 0;color:var(--fb-text-muted, #667085);font-size:11px;line-height:1.35;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2;overflow:hidden}.fb-group__resize{position:absolute;right:0;bottom:0;width:14px;height:14px;border-right:2px solid var(--fb-group-border, #d6dae1);border-bottom:2px solid var(--fb-group-border, #d6dae1);border-bottom-right-radius:var(--fb-radius, 10px);pointer-events:auto;cursor:nwse-resize}.fb-group--giallo{--fb-group-border: #e0c26a;--fb-group-fill: rgb(234 179 8 / 10%);--fb-group-bar: rgb(234 179 8 / 24%)}.fb-group--verde{--fb-group-border: #86c79b;--fb-group-fill: rgb(34 160 89 / 10%);--fb-group-bar: rgb(34 160 89 / 22%)}.fb-group--blu{--fb-group-border: #93b4ea;--fb-group-fill: rgb(47 111 235 / 9%);--fb-group-bar: rgb(47 111 235 / 20%)}.fb-group--viola{--fb-group-border: #bda6e6;--fb-group-fill: rgb(139 92 246 / 10%);--fb-group-bar: rgb(139 92 246 / 22%)}.fb-group--rosso{--fb-group-border: #e6a3a3;--fb-group-fill: rgb(214 69 69 / 9%);--fb-group-bar: rgb(214 69 69 / 20%)}.fb-group--grigio,.fb-group--neutro{--fb-group-border: #d6dae1;--fb-group-fill: rgb(148 163 184 / 10%);--fb-group-bar: rgb(148 163 184 / 22%)}\n"] }]
|
|
5904
6207
|
}], propDecorators: { selectedName: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectedName", required: false }] }], selectedGroupName: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectedGroupName", required: false }] }], selectedNames: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectedNames", required: false }] }], outline: [{ type: i0.Input, args: [{ isSignal: true, alias: "outline", required: false }] }], isEditable: [{ type: i0.Input, args: [{ isSignal: true, alias: "isEditable", required: false }] }], selectionChange: [{ type: i0.Output, args: ["selectionChange"] }], nodeOpened: [{ type: i0.Output, args: ["nodeOpened"] }], nodeRemoveRequested: [{ type: i0.Output, args: ["nodeRemoveRequested"] }], nodeDuplicateRequested: [{ type: i0.Output, args: ["nodeDuplicateRequested"] }], groupSelected: [{ type: i0.Output, args: ["groupSelected"] }], groupRemoveRequested: [{ type: i0.Output, args: ["groupRemoveRequested"] }], elementDropped: [{ type: i0.Output, args: ["elementDropped"] }], canvas: [{ type: i0.ViewChild, args: [i0.forwardRef(() => FCanvasComponent), { isSignal: true }] }] } });
|
|
5905
6208
|
|
|
5906
6209
|
/**
|
|
@@ -12938,80 +13241,63 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.28", ngImpo
|
|
|
12938
13241
|
* l'input `aggregationValues`; `Sum` anche `aggregationField`. Il form li gestisce come
|
|
12939
13242
|
* campi propri, invece di lasciare all'utente il compito di indovinare i nomi dei parametri.
|
|
12940
13243
|
*
|
|
13244
|
+
* **Non e' un form: e' una mappatura**, a tre pannelli come il compositore dello screen dinamico
|
|
13245
|
+
* (§5.2) e come il Transform delle ultime versioni di Salesforce. A sinistra le **sorgenti**
|
|
13246
|
+
* (i riferimenti del flow, navigabili dentro record e classi), al centro la **destinazione** —
|
|
13247
|
+
* una riga per ogni campo o membro che si puo' scrivere, mappato o no — a destra il **dettaglio**
|
|
13248
|
+
* della riga scelta. Si mappa trascinando una sorgente su una riga, oppure selezionando la riga e
|
|
13249
|
+
* cliccando la sorgente.
|
|
13250
|
+
*
|
|
13251
|
+
* Perche' l'elenco delle righe non e' l'elenco delle azioni: un elenco di azioni dice cio' che
|
|
13252
|
+
* **e' già** mappato e tace su cio' che manca, e su una classe di dieci membri «cosa resta da
|
|
13253
|
+
* riempire» era una domanda a cui si rispondeva contando a mano. Le righe vengono quindi dal
|
|
13254
|
+
* **catalogo della destinazione** (i membri di una classe, §4.7.1; i campi di un'entita', §4.4) e
|
|
13255
|
+
* l'azione e' cio' che una riga porta, non cio' che la fa esistere. Dove il catalogo non c'e' —
|
|
13256
|
+
* destinazione scalare, oppure una classe che non dichiara i membri — le righe restano quelle
|
|
13257
|
+
* delle azioni: si digita il nome, come prima, e nessuno accusa nulla.
|
|
13258
|
+
*
|
|
12941
13259
|
* Il target puo' anche essere una **classe** (`dataType: 'Structure'`): in quel caso
|
|
12942
13260
|
* `outputFieldApiName` porta il nome del **membro** invece del nome del campo (§4.7), ed e' il
|
|
12943
|
-
* modo di comporre un'istanza intera in un elemento solo invece di un Assignment per membro.
|
|
12944
|
-
*
|
|
12945
|
-
* ma assegnarlo e' `TARGET_NOT_WRITABLE`.
|
|
13261
|
+
* modo di comporre un'istanza intera in un elemento solo invece di un Assignment per membro. Un
|
|
13262
|
+
* membro calcolato si legge ma assegnarlo e' `TARGET_NOT_WRITABLE`: qui non compare fra le righe.
|
|
12946
13263
|
*
|
|
12947
13264
|
* Nota: `connector` qui e' un **array** — la mappa delle uscite lo nasconde al resto.
|
|
12948
13265
|
*/
|
|
12949
13266
|
/** I nomi dei parametri che le aggregazioni richiedono, fissati dal modello. */
|
|
12950
13267
|
const AGGREGATION_VALUES = 'aggregationValues';
|
|
12951
13268
|
const AGGREGATION_FIELD = 'aggregationField';
|
|
13269
|
+
/** Il glifo di un'operazione. È presentazione: l'elenco autoritativo resta il dizionario. */
|
|
13270
|
+
const OPERATION_ICONS = {
|
|
13271
|
+
Map: '→',
|
|
13272
|
+
Sum: 'Σ',
|
|
13273
|
+
Count: '#',
|
|
13274
|
+
};
|
|
12952
13275
|
class TransformInspectorComponent extends NodeInspectorBase {
|
|
13276
|
+
api = inject(FlowBuilderApi);
|
|
12953
13277
|
catalog = inject(FlowCatalogStore);
|
|
13278
|
+
document = inject(DOCUMENT);
|
|
12954
13279
|
elementType = 'Transform';
|
|
12955
13280
|
transform = computed(() => this.node(), ...(ngDevMode ? [{ debugName: "transform" }] : []));
|
|
12956
13281
|
transformTypes = computed(() => this.dictionaries.transformTypes(), ...(ngDevMode ? [{ debugName: "transformTypes" }] : []));
|
|
12957
13282
|
dataTypes = computed(() => this.dictionaries.dataTypes(), ...(ngDevMode ? [{ debugName: "dataTypes" }] : []));
|
|
12958
13283
|
enumTypes = signal([], ...(ngDevMode ? [{ debugName: "enumTypes" }] : []));
|
|
12959
|
-
/**
|
|
12960
|
-
* §4.7 — i membri della classe di destinazione: servono a sapere **il tipo** di cio' che ogni
|
|
12961
|
-
* `Map` scrive, che e' quello che guida l'editor del valore. Su un membro `Enum` e' la sola
|
|
12962
|
-
* strada per proporre i valori del tipo invece di farli digitare (§4.6).
|
|
12963
|
-
*/
|
|
12964
|
-
members = signal([], ...(ngDevMode ? [{ debugName: "members" }] : []));
|
|
12965
13284
|
constructor() {
|
|
12966
13285
|
super();
|
|
12967
13286
|
void this.catalog.listEnumTypes().then((list) => this.enumTypes.set(list ?? []));
|
|
12968
|
-
|
|
12969
|
-
|
|
12970
|
-
if (!className) {
|
|
12971
|
-
if (untracked(this.members).length) {
|
|
12972
|
-
this.members.set([]);
|
|
12973
|
-
}
|
|
12974
|
-
return;
|
|
12975
|
-
}
|
|
12976
|
-
void this.catalog
|
|
12977
|
-
.listStructureMembers(className)
|
|
12978
|
-
.then((list) => this.members.set(list ?? []))
|
|
12979
|
-
// Catalogo assente: il tipo del membro resta ignoto e il valore resta generico.
|
|
12980
|
-
.catch(() => this.members.set([]));
|
|
12981
|
-
});
|
|
12982
|
-
}
|
|
12983
|
-
/**
|
|
12984
|
-
* Il tipo di cio' che una `Map` scrive: il **membro** su un target `Structure`, il target stesso
|
|
12985
|
-
* altrove. Membro non in catalogo — o percorso annidato — il tipo resta ignoto: si digita.
|
|
12986
|
-
*/
|
|
12987
|
-
memberOf(action) {
|
|
12988
|
-
const name = action.outputFieldApiName;
|
|
12989
|
-
return name ? this.members().find((member) => member.name === name) : undefined;
|
|
12990
|
-
}
|
|
12991
|
-
mapDataType(action) {
|
|
12992
|
-
if (this.isStructureTarget()) {
|
|
12993
|
-
return this.memberOf(action)?.dataType ?? undefined;
|
|
12994
|
-
}
|
|
12995
|
-
return this.transform().dataType ?? undefined;
|
|
12996
|
-
}
|
|
12997
|
-
mapObjectType(action) {
|
|
12998
|
-
if (this.isStructureTarget()) {
|
|
12999
|
-
return this.memberOf(action)?.objectType ?? undefined;
|
|
13000
|
-
}
|
|
13001
|
-
return this.transform().objectType ?? undefined;
|
|
13287
|
+
this.loadReferences();
|
|
13288
|
+
this.loadTargetLevel();
|
|
13002
13289
|
}
|
|
13290
|
+
// ------------------------------------------------------------------ il risultato
|
|
13003
13291
|
enumOptions = computed(() => this.enumTypes(), ...(ngDevMode ? [{ debugName: "enumOptions" }] : []));
|
|
13004
13292
|
requiresObjectType = computed(() => this.dictionaries.requiresObjectType(this.transform().dataType), ...(ngDevMode ? [{ debugName: "requiresObjectType" }] : []));
|
|
13005
13293
|
/** §4.7 — con un target `Structure` la destinazione di ogni azione e' un **membro**. */
|
|
13006
13294
|
isStructureTarget = computed(() => this.dictionaries.isStructure(this.transform().dataType), ...(ngDevMode ? [{ debugName: "isStructureTarget" }] : []));
|
|
13007
13295
|
/** Su una `Structure` la classe mancante e' un errore, non un avviso (§4.7). */
|
|
13008
13296
|
missingObjectType = computed(() => this.isStructureTarget() && !this.transform().objectType, ...(ngDevMode ? [{ debugName: "missingObjectType" }] : []));
|
|
13009
|
-
/** Il modello prevede una lista di liste; l'editor lavora sulla prima, che e' il caso d'uso. */
|
|
13010
|
-
actions = computed(() => this.transform().transformValues?.[0]?.transformValueActions ?? [], ...(ngDevMode ? [{ debugName: "actions" }] : []));
|
|
13011
13297
|
setDataType(dataType) {
|
|
13012
13298
|
this.patch((node) => {
|
|
13013
13299
|
const transform = node;
|
|
13014
|
-
transform.dataType = dataType || undefined;
|
|
13300
|
+
transform.dataType = (dataType || undefined);
|
|
13015
13301
|
if (!this.dictionaries.requiresObjectType(dataType)) {
|
|
13016
13302
|
delete transform.objectType;
|
|
13017
13303
|
}
|
|
@@ -13031,7 +13317,356 @@ class TransformInspectorComponent extends NodeInspectorBase {
|
|
|
13031
13317
|
}
|
|
13032
13318
|
});
|
|
13033
13319
|
}
|
|
13320
|
+
// ------------------------------------------------------------------ le sorgenti
|
|
13321
|
+
/**
|
|
13322
|
+
* I riferimenti del flow: la stessa fonte del reference picker (§6.4). Serve **sia** per la
|
|
13323
|
+
* colonna delle sorgenti **sia** per riconoscere la radice di un percorso quando si entra in un
|
|
13324
|
+
* record o in una classe.
|
|
13325
|
+
*/
|
|
13326
|
+
references = signal([], ...(ngDevMode ? [{ debugName: "references" }] : []));
|
|
13327
|
+
referencesPending = signal(false, ...(ngDevMode ? [{ debugName: "referencesPending" }] : []));
|
|
13328
|
+
referencesError = signal(null, ...(ngDevMode ? [{ debugName: "referencesError" }] : []));
|
|
13329
|
+
/** Dove si sta guardando: vuoto = le radici, altrimenti il percorso in cui si e' entrati. */
|
|
13330
|
+
sourcePath = signal('', ...(ngDevMode ? [{ debugName: "sourcePath" }] : []));
|
|
13331
|
+
sourceQuery = signal('', ...(ngDevMode ? [{ debugName: "sourceQuery" }] : []));
|
|
13332
|
+
/**
|
|
13333
|
+
* Il punto in cui navigare, scritto **col punto in fondo**: e' la stessa convenzione del
|
|
13334
|
+
* reference picker — un percorso che finisce col punto ha come ultimo segmento il vuoto, e la
|
|
13335
|
+
* navigazione risponde con cio' che c'e' **dentro** invece di verificare un nome.
|
|
13336
|
+
*/
|
|
13337
|
+
sourceRoot = computed(() => {
|
|
13338
|
+
const path = this.sourcePath();
|
|
13339
|
+
if (!path) {
|
|
13340
|
+
return undefined;
|
|
13341
|
+
}
|
|
13342
|
+
return navigableRootOf(this.references(), `${path}.`, (dataType) => this.dictionaries.isStructure(dataType), {
|
|
13343
|
+
// Gli output automatici si **propongono** e non si giudicano (§4.5): senza questo, entrare in
|
|
13344
|
+
// `Leggi_Ordine` sarebbe impossibile pur essendo `Leggi_Ordine.Numero` un riferimento valido.
|
|
13345
|
+
proposeOnly: true,
|
|
13346
|
+
});
|
|
13347
|
+
}, ...(ngDevMode ? [{ debugName: "sourceRoot" }] : []));
|
|
13348
|
+
sourceNavigation = navigatePath(this.catalog, () => {
|
|
13349
|
+
const root = this.sourceRoot();
|
|
13350
|
+
return root ? { root: root.container, path: root.path } : null;
|
|
13351
|
+
});
|
|
13352
|
+
sourcePending = computed(() => this.referencesPending() || this.sourceNavigation.isPending(), ...(ngDevMode ? [{ debugName: "sourcePending" }] : []));
|
|
13353
|
+
sourceError = computed(() => this.referencesError(), ...(ngDevMode ? [{ debugName: "sourceError" }] : []));
|
|
13354
|
+
/** Le briciole di pane: «Tutte» piu' un livello per segmento del percorso in cui si e' entrati. */
|
|
13355
|
+
sourceCrumbs = computed(() => {
|
|
13356
|
+
const path = this.sourcePath();
|
|
13357
|
+
if (!path) {
|
|
13358
|
+
return [];
|
|
13359
|
+
}
|
|
13360
|
+
const segments = path.split('.');
|
|
13361
|
+
return segments.map((segment, index) => ({
|
|
13362
|
+
label: segment,
|
|
13363
|
+
path: segments.slice(0, index + 1).join('.'),
|
|
13364
|
+
}));
|
|
13365
|
+
}, ...(ngDevMode ? [{ debugName: "sourceCrumbs" }] : []));
|
|
13366
|
+
/** Cio' che si sta guardando: le radici, oppure i campi o i membri della tappa. */
|
|
13367
|
+
sourceItems = computed(() => {
|
|
13368
|
+
const path = this.sourcePath();
|
|
13369
|
+
if (!path) {
|
|
13370
|
+
return this.references().map((reference) => ({
|
|
13371
|
+
path: reference.name,
|
|
13372
|
+
label: reference.label || reference.name,
|
|
13373
|
+
detail: this.describeReference(reference),
|
|
13374
|
+
isCollection: reference.isCollection === true,
|
|
13375
|
+
canEnter: !!navigableRootOf(this.references(), reference.name, (dataType) => this.dictionaries.isStructure(dataType), { proposeOnly: true }),
|
|
13376
|
+
}));
|
|
13377
|
+
}
|
|
13378
|
+
const tail = this.sourceNavigation.resolution()?.tail;
|
|
13379
|
+
if (!tail) {
|
|
13380
|
+
return [];
|
|
13381
|
+
}
|
|
13382
|
+
return tail.level.entries.map((entry) => ({
|
|
13383
|
+
path: `${path}.${entry.name}`,
|
|
13384
|
+
label: entry.label || entry.name,
|
|
13385
|
+
detail: this.describeEntry(entry),
|
|
13386
|
+
isCollection: entry.isCollection === true,
|
|
13387
|
+
// Dentro una collection non si naviga: il percorso designa l'insieme, non un elemento (§4.4).
|
|
13388
|
+
canEnter: !!entry.next && !entry.isCollection,
|
|
13389
|
+
}));
|
|
13390
|
+
}, ...(ngDevMode ? [{ debugName: "sourceItems" }] : []));
|
|
13391
|
+
/** Il filtro e' locale: la ricerca serve su venti variabili, non su tre. */
|
|
13392
|
+
sources = computed(() => {
|
|
13393
|
+
const query = this.sourceQuery().trim().toLowerCase();
|
|
13394
|
+
const items = this.sourceItems();
|
|
13395
|
+
if (!query) {
|
|
13396
|
+
return items;
|
|
13397
|
+
}
|
|
13398
|
+
return items.filter((item) => item.path.toLowerCase().includes(query) || item.label.toLowerCase().includes(query));
|
|
13399
|
+
}, ...(ngDevMode ? [{ debugName: "sources" }] : []));
|
|
13400
|
+
/**
|
|
13401
|
+
* La tappa in cui si e' entrati non dichiara campi ne' membri: e' un "non lo so" del catalogo
|
|
13402
|
+
* (§4.7.1), non «qui non c'e' niente», e i due meritano parole diverse.
|
|
13403
|
+
*/
|
|
13404
|
+
sourceUndeclared = computed(() => {
|
|
13405
|
+
const status = this.sourceNavigation.resolution()?.tail?.level.status;
|
|
13406
|
+
return !!this.sourcePath() && !!status && status !== 'declared';
|
|
13407
|
+
}, ...(ngDevMode ? [{ debugName: "sourceUndeclared" }] : []));
|
|
13408
|
+
enterSource(path) {
|
|
13409
|
+
this.sourcePath.set(path);
|
|
13410
|
+
this.sourceQuery.set('');
|
|
13411
|
+
}
|
|
13412
|
+
leaveSource() {
|
|
13413
|
+
const segments = this.sourcePath().split('.');
|
|
13414
|
+
segments.pop();
|
|
13415
|
+
this.enterSource(segments.join('.'));
|
|
13416
|
+
}
|
|
13417
|
+
describeReference(reference) {
|
|
13418
|
+
const parts = [];
|
|
13419
|
+
if (reference.dataType) {
|
|
13420
|
+
parts.push(reference.isCollection ? `${reference.dataType}[]` : reference.dataType);
|
|
13421
|
+
}
|
|
13422
|
+
if (reference.objectType) {
|
|
13423
|
+
parts.push(reference.objectType);
|
|
13424
|
+
}
|
|
13425
|
+
return parts.join(' · ');
|
|
13426
|
+
}
|
|
13427
|
+
describeEntry(entry) {
|
|
13428
|
+
const parts = [];
|
|
13429
|
+
if (entry.dataType) {
|
|
13430
|
+
parts.push(entry.isCollection ? `${entry.dataType}[]` : entry.dataType);
|
|
13431
|
+
}
|
|
13432
|
+
if (entry.objectType) {
|
|
13433
|
+
parts.push(entry.objectType);
|
|
13434
|
+
}
|
|
13435
|
+
return parts.join(' · ');
|
|
13436
|
+
}
|
|
13437
|
+
loadReferences() {
|
|
13438
|
+
let sequence = 0;
|
|
13439
|
+
effect(() => {
|
|
13440
|
+
const definition = this.store.document();
|
|
13441
|
+
const token = ++sequence;
|
|
13442
|
+
this.referencesPending.set(true);
|
|
13443
|
+
this.referencesError.set(null);
|
|
13444
|
+
void this.api
|
|
13445
|
+
.getReferences(definition)
|
|
13446
|
+
.then((list) => {
|
|
13447
|
+
if (token !== sequence) {
|
|
13448
|
+
return;
|
|
13449
|
+
}
|
|
13450
|
+
this.references.set(list ?? []);
|
|
13451
|
+
this.referencesPending.set(false);
|
|
13452
|
+
})
|
|
13453
|
+
.catch(() => {
|
|
13454
|
+
if (token !== sequence) {
|
|
13455
|
+
return;
|
|
13456
|
+
}
|
|
13457
|
+
// Senza l'elenco si scrive a mano nel dettaglio: la colonna lo dice invece di restare vuota.
|
|
13458
|
+
this.references.set([]);
|
|
13459
|
+
this.referencesError.set('I riferimenti del flow non sono disponibili.');
|
|
13460
|
+
this.referencesPending.set(false);
|
|
13461
|
+
});
|
|
13462
|
+
});
|
|
13463
|
+
}
|
|
13464
|
+
// ------------------------------------------------------------------ la destinazione
|
|
13465
|
+
/**
|
|
13466
|
+
* Il catalogo della destinazione: i membri di una classe o i campi di un'entita', a seconda del
|
|
13467
|
+
* tipo del risultato. `loadPathLevel` copre entrambi i casi e porta con se' lo **stato** — solo
|
|
13468
|
+
* `declared` autorizza a dire che un nome non esiste (§4.7.1).
|
|
13469
|
+
*/
|
|
13470
|
+
targetLevel = signal(null, ...(ngDevMode ? [{ debugName: "targetLevel" }] : []));
|
|
13471
|
+
targetContainer = computed(() => {
|
|
13472
|
+
const transform = this.transform();
|
|
13473
|
+
if (!transform.objectType) {
|
|
13474
|
+
return null;
|
|
13475
|
+
}
|
|
13476
|
+
if (this.isStructureTarget()) {
|
|
13477
|
+
return { kind: 'structure', name: transform.objectType };
|
|
13478
|
+
}
|
|
13479
|
+
return transform.dataType === 'Object' ? { kind: 'object', name: transform.objectType } : null;
|
|
13480
|
+
}, ...(ngDevMode ? [{ debugName: "targetContainer" }] : []));
|
|
13481
|
+
loadTargetLevel() {
|
|
13482
|
+
let sequence = 0;
|
|
13483
|
+
effect(() => {
|
|
13484
|
+
const container = this.targetContainer();
|
|
13485
|
+
const token = ++sequence;
|
|
13486
|
+
if (!container) {
|
|
13487
|
+
this.targetLevel.set(null);
|
|
13488
|
+
return;
|
|
13489
|
+
}
|
|
13490
|
+
void loadPathLevel(this.catalog, container).then((level) => {
|
|
13491
|
+
if (token === sequence) {
|
|
13492
|
+
this.targetLevel.set(level);
|
|
13493
|
+
}
|
|
13494
|
+
});
|
|
13495
|
+
});
|
|
13496
|
+
}
|
|
13497
|
+
/**
|
|
13498
|
+
* L'entita' del risultato, quando il risultato e' un record: le destinazioni sono i suoi campi,
|
|
13499
|
+
* ed e' cio' che rende la casella del nome un elenco.
|
|
13500
|
+
*/
|
|
13501
|
+
targetObject = computed(() => this.transform().dataType === 'Object' ? this.transform().objectType : undefined, ...(ngDevMode ? [{ debugName: "targetObject" }] : []));
|
|
13502
|
+
/**
|
|
13503
|
+
* La destinazione ha una forma **dichiarata**: c'e' un catalogo da cui prendere le righe. Il caso
|
|
13504
|
+
* opposto non e' un difetto — un risultato scalare non ha campi — ed e' per questo che le due
|
|
13505
|
+
* domande sono separate da {@link targetUndeclared}.
|
|
13506
|
+
*/
|
|
13507
|
+
hasTargetCatalog = computed(() => !!this.targetContainer(), ...(ngDevMode ? [{ debugName: "hasTargetCatalog" }] : []));
|
|
13508
|
+
/**
|
|
13509
|
+
* Tappa valida di cui campi o membri non sono dichiarati (§4.7.1): si **digita** il nome, e non
|
|
13510
|
+
* si accusa nessuno. Dirlo evita di cercare un difetto dove c'e' un catalogo incompleto.
|
|
13511
|
+
*/
|
|
13512
|
+
targetUndeclared = computed(() => this.hasTargetCatalog() && !this.targetIsDeclared(), ...(ngDevMode ? [{ debugName: "targetUndeclared" }] : []));
|
|
13513
|
+
/** Il catalogo e' autorevole: solo allora un nome fuori elenco e' un nome sbagliato. */
|
|
13514
|
+
targetIsDeclared = computed(() => this.targetLevel()?.status === 'declared', ...(ngDevMode ? [{ debugName: "targetIsDeclared" }] : []));
|
|
13515
|
+
/**
|
|
13516
|
+
* Le destinazioni proponibili. Un membro calcolato non c'e': assegnarlo e' `TARGET_NOT_WRITABLE`
|
|
13517
|
+
* (§4.7). Su un'entita' `isWritable` e' già `createable || updateable` (§5.8).
|
|
13518
|
+
*/
|
|
13519
|
+
targetSlots = computed(() => (this.targetLevel()?.entries ?? []).filter((entry) => entry.isWritable), ...(ngDevMode ? [{ debugName: "targetSlots" }] : []));
|
|
13520
|
+
/** Il modello prevede una lista di liste; l'editor lavora sulla prima, che e' il caso d'uso. */
|
|
13521
|
+
actions = computed(() => this.transform().transformValues?.[0]?.transformValueActions ?? [], ...(ngDevMode ? [{ debugName: "actions" }] : []));
|
|
13522
|
+
/**
|
|
13523
|
+
* Le righe della destinazione: prima il catalogo — mappato o no — poi le azioni che nominano
|
|
13524
|
+
* qualcosa che il catalogo non ha. L'ordine e' quello, e non quello delle azioni, perche' la
|
|
13525
|
+
* colonna dice **la forma del risultato**: un elenco che si riordina a ogni mappatura non si
|
|
13526
|
+
* legge.
|
|
13527
|
+
*/
|
|
13528
|
+
targets = computed(() => {
|
|
13529
|
+
const actions = this.actions();
|
|
13530
|
+
const taken = new Set();
|
|
13531
|
+
const rows = [];
|
|
13532
|
+
for (const slot of this.targetSlots()) {
|
|
13533
|
+
const index = actions.findIndex((action, position) => !taken.has(position) && action.outputFieldApiName === slot.name);
|
|
13534
|
+
if (index >= 0) {
|
|
13535
|
+
taken.add(index);
|
|
13536
|
+
}
|
|
13537
|
+
rows.push({
|
|
13538
|
+
key: `slot:${slot.name}`,
|
|
13539
|
+
field: slot.name,
|
|
13540
|
+
label: slot.label || slot.name,
|
|
13541
|
+
dataType: slot.dataType,
|
|
13542
|
+
objectType: slot.objectType ?? undefined,
|
|
13543
|
+
isCollection: slot.isCollection,
|
|
13544
|
+
index,
|
|
13545
|
+
action: index >= 0 ? actions[index] : undefined,
|
|
13546
|
+
isUnknown: false,
|
|
13547
|
+
});
|
|
13548
|
+
}
|
|
13549
|
+
actions.forEach((action, index) => {
|
|
13550
|
+
if (taken.has(index)) {
|
|
13551
|
+
return;
|
|
13552
|
+
}
|
|
13553
|
+
const transform = this.transform();
|
|
13554
|
+
rows.push({
|
|
13555
|
+
key: `action:${index}`,
|
|
13556
|
+
field: action.outputFieldApiName ?? '',
|
|
13557
|
+
label: action.outputFieldApiName || '(destinazione da indicare)',
|
|
13558
|
+
// Senza catalogo il tipo di cio' che si scrive e' quello del risultato: e' quanto si sa.
|
|
13559
|
+
dataType: this.targetContainer() ? undefined : transform.dataType,
|
|
13560
|
+
objectType: this.targetContainer() ? undefined : transform.objectType,
|
|
13561
|
+
isCollection: this.targetContainer() ? undefined : transform.isCollection,
|
|
13562
|
+
index,
|
|
13563
|
+
action,
|
|
13564
|
+
isUnknown: this.targetIsDeclared() && !!action.outputFieldApiName,
|
|
13565
|
+
});
|
|
13566
|
+
});
|
|
13567
|
+
return rows;
|
|
13568
|
+
}, ...(ngDevMode ? [{ debugName: "targets" }] : []));
|
|
13569
|
+
mappedCount = computed(() => this.targets().filter((row) => row.index >= 0).length, ...(ngDevMode ? [{ debugName: "mappedCount" }] : []));
|
|
13570
|
+
/** Quante destinazioni il catalogo dichiara: senza catalogo non c'e' un totale da mostrare. */
|
|
13571
|
+
slotCount = computed(() => (this.targetIsDeclared() ? this.targetSlots().length : 0), ...(ngDevMode ? [{ debugName: "slotCount" }] : []));
|
|
13572
|
+
selectedKey = signal(null, ...(ngDevMode ? [{ debugName: "selectedKey" }] : []));
|
|
13573
|
+
/**
|
|
13574
|
+
* La riga aperta nel dettaglio. Il ripiego sulla prima non e' cosmetico: la colonna di destra
|
|
13575
|
+
* altrimenti resta vuota all'apertura, e il primo gesto sarebbe un click che non modifica niente.
|
|
13576
|
+
*/
|
|
13577
|
+
selected = computed(() => {
|
|
13578
|
+
const rows = this.targets();
|
|
13579
|
+
const key = this.selectedKey();
|
|
13580
|
+
return rows.find((row) => row.key === key) ?? rows[0] ?? null;
|
|
13581
|
+
}, ...(ngDevMode ? [{ debugName: "selected" }] : []));
|
|
13582
|
+
select(key) {
|
|
13583
|
+
this.selectedKey.set(key);
|
|
13584
|
+
}
|
|
13585
|
+
isSelected(row) {
|
|
13586
|
+
return this.selected()?.key === row.key;
|
|
13587
|
+
}
|
|
13588
|
+
/** I rilievi ancorati all'azione di una riga (§7): la riga li mostra, il dettaglio li spiega. */
|
|
13589
|
+
issuesOf(row) {
|
|
13590
|
+
return row.index >= 0 ? this.issuesAt(`transformValues[0].transformValueActions[${row.index}]`) : [];
|
|
13591
|
+
}
|
|
13592
|
+
// ------------------------------------------------------------------ le azioni
|
|
13593
|
+
operationOf(row) {
|
|
13594
|
+
return row.action?.transformType ?? 'Map';
|
|
13595
|
+
}
|
|
13596
|
+
operationIcon(row) {
|
|
13597
|
+
const type = this.operationOf(row);
|
|
13598
|
+
return OPERATION_ICONS[type] ?? '·';
|
|
13599
|
+
}
|
|
13600
|
+
operationLabel(type) {
|
|
13601
|
+
return this.transformTypes().find((entry) => entry.value === type)?.label ?? type;
|
|
13602
|
+
}
|
|
13603
|
+
/**
|
|
13604
|
+
* Cosa la riga scrive, in una riga di testo. È il solo modo di leggere una mappatura senza
|
|
13605
|
+
* aprirla: la colonna centrale mostra questo, il dettaglio i controlli.
|
|
13606
|
+
*/
|
|
13607
|
+
sourceSummary(row) {
|
|
13608
|
+
const action = row.action;
|
|
13609
|
+
if (!action) {
|
|
13610
|
+
return '';
|
|
13611
|
+
}
|
|
13612
|
+
const type = action.transformType ?? 'Map';
|
|
13613
|
+
if (type === 'Map') {
|
|
13614
|
+
return this.describeValue(action.value);
|
|
13615
|
+
}
|
|
13616
|
+
const collection = this.aggregationCollection(action) ?? '';
|
|
13617
|
+
if (type === 'Sum') {
|
|
13618
|
+
const field = this.aggregationField(action);
|
|
13619
|
+
return collection && field ? `${collection} · ${field}` : collection;
|
|
13620
|
+
}
|
|
13621
|
+
return collection;
|
|
13622
|
+
}
|
|
13623
|
+
describeValue(value) {
|
|
13624
|
+
const field = valuedFieldOf(value);
|
|
13625
|
+
if (!value || !field) {
|
|
13626
|
+
return '';
|
|
13627
|
+
}
|
|
13628
|
+
if (field === 'formulaExpression') {
|
|
13629
|
+
return `ƒ ${value.formulaExpression}`;
|
|
13630
|
+
}
|
|
13631
|
+
if (field === 'elementReference') {
|
|
13632
|
+
return value.elementReference ?? '';
|
|
13633
|
+
}
|
|
13634
|
+
return String(value[field]);
|
|
13635
|
+
}
|
|
13636
|
+
mutateAction(index, mutate) {
|
|
13637
|
+
this.patch((node) => {
|
|
13638
|
+
const action = node.transformValues?.[0]?.transformValueActions?.[index];
|
|
13639
|
+
if (action) {
|
|
13640
|
+
mutate(action);
|
|
13641
|
+
}
|
|
13642
|
+
});
|
|
13643
|
+
}
|
|
13644
|
+
/**
|
|
13645
|
+
* L'indice dell'azione della riga, creandola se non c'e'. È la conseguenza del «le righe vengono
|
|
13646
|
+
* dal catalogo»: una riga esiste anche prima di essere mappata, e la prima modifica e' cio' che
|
|
13647
|
+
* la fa diventare un'azione del documento.
|
|
13648
|
+
*/
|
|
13649
|
+
ensureAction(row) {
|
|
13650
|
+
if (row.index >= 0) {
|
|
13651
|
+
return row.index;
|
|
13652
|
+
}
|
|
13653
|
+
const index = this.actions().length;
|
|
13654
|
+
this.patch((node) => {
|
|
13655
|
+
const transform = node;
|
|
13656
|
+
transform.transformValues ??= [{}];
|
|
13657
|
+
const first = transform.transformValues[0];
|
|
13658
|
+
first.transformValueActions ??= [];
|
|
13659
|
+
first.transformValueActions.push({
|
|
13660
|
+
transformType: 'Map',
|
|
13661
|
+
outputFieldApiName: row.field || undefined,
|
|
13662
|
+
});
|
|
13663
|
+
});
|
|
13664
|
+
this.selectedKey.set(row.key);
|
|
13665
|
+
return index;
|
|
13666
|
+
}
|
|
13667
|
+
/** Una destinazione fuori catalogo: l'unico modo di scrivere un nome che il catalogo non ha. */
|
|
13034
13668
|
addAction() {
|
|
13669
|
+
const index = this.actions().length;
|
|
13035
13670
|
this.patch((node) => {
|
|
13036
13671
|
const transform = node;
|
|
13037
13672
|
transform.transformValues ??= [{}];
|
|
@@ -13039,26 +13674,26 @@ class TransformInspectorComponent extends NodeInspectorBase {
|
|
|
13039
13674
|
first.transformValueActions ??= [];
|
|
13040
13675
|
first.transformValueActions.push({ transformType: 'Map' });
|
|
13041
13676
|
});
|
|
13677
|
+
this.selectedKey.set(`action:${index}`);
|
|
13042
13678
|
}
|
|
13043
|
-
removeAction(
|
|
13679
|
+
removeAction(row) {
|
|
13680
|
+
if (row.index < 0) {
|
|
13681
|
+
return;
|
|
13682
|
+
}
|
|
13044
13683
|
this.patch((node) => {
|
|
13045
13684
|
const transform = node;
|
|
13046
13685
|
const actions = transform.transformValues?.[0]?.transformValueActions;
|
|
13047
|
-
actions?.splice(index, 1);
|
|
13686
|
+
actions?.splice(row.index, 1);
|
|
13048
13687
|
if (actions?.length === 0) {
|
|
13049
13688
|
delete transform.transformValues;
|
|
13050
13689
|
}
|
|
13051
13690
|
});
|
|
13691
|
+
// La riga resta (viene dal catalogo) o sparisce (era un'azione): in entrambi i casi la
|
|
13692
|
+
// selezione per chiave ritrova cio' che c'e', o ripiega sulla prima.
|
|
13693
|
+
this.selectedKey.set(row.key);
|
|
13052
13694
|
}
|
|
13053
|
-
|
|
13054
|
-
this.
|
|
13055
|
-
const action = node.transformValues?.[0]?.transformValueActions?.[index];
|
|
13056
|
-
if (action) {
|
|
13057
|
-
mutate(action);
|
|
13058
|
-
}
|
|
13059
|
-
});
|
|
13060
|
-
}
|
|
13061
|
-
setActionType(index, transformType) {
|
|
13695
|
+
setOperation(row, transformType) {
|
|
13696
|
+
const index = this.ensureAction(row);
|
|
13062
13697
|
this.mutateAction(index, (action) => {
|
|
13063
13698
|
action.transformType = transformType;
|
|
13064
13699
|
// Map usa `value`, Sum e Count usano `inputParameters`: tenere entrambi renderebbe
|
|
@@ -13078,24 +13713,38 @@ class TransformInspectorComponent extends NodeInspectorBase {
|
|
|
13078
13713
|
}
|
|
13079
13714
|
});
|
|
13080
13715
|
}
|
|
13081
|
-
setOutputField(
|
|
13716
|
+
setOutputField(row, field) {
|
|
13717
|
+
const index = this.ensureAction(row);
|
|
13082
13718
|
this.mutateAction(index, (action) => {
|
|
13083
13719
|
action.outputFieldApiName = field || undefined;
|
|
13084
13720
|
});
|
|
13721
|
+
/**
|
|
13722
|
+
* La chiave di una riga contiene il nome della destinazione, quindi cambiando nome la riga
|
|
13723
|
+
* selezionata **e' un'altra**: la si ritrova per indice dell'azione. Ricomporre la chiave a
|
|
13724
|
+
* mano (`slot:<nome>`) sbagliava proprio nel caso interessante — un nome fuori catalogo non ha
|
|
13725
|
+
* nessuno `slot:`, la selezione ripiegava sulla prima riga e la casella perdeva il focus a
|
|
13726
|
+
* ogni battuta.
|
|
13727
|
+
*/
|
|
13728
|
+
const moved = this.targets().find((candidate) => candidate.index === index);
|
|
13729
|
+
if (moved) {
|
|
13730
|
+
this.selectedKey.set(moved.key);
|
|
13731
|
+
}
|
|
13085
13732
|
}
|
|
13086
|
-
setMapValue(
|
|
13733
|
+
setMapValue(row, value) {
|
|
13734
|
+
const index = this.ensureAction(row);
|
|
13087
13735
|
this.mutateAction(index, (action) => {
|
|
13088
13736
|
action.value = value;
|
|
13089
13737
|
});
|
|
13090
13738
|
}
|
|
13091
13739
|
aggregationCollection(action) {
|
|
13092
|
-
return action
|
|
13740
|
+
return action?.inputParameters?.find((parameter) => parameter.name === AGGREGATION_VALUES)?.value
|
|
13093
13741
|
?.elementReference;
|
|
13094
13742
|
}
|
|
13095
13743
|
aggregationField(action) {
|
|
13096
|
-
return (action
|
|
13744
|
+
return (action?.inputParameters?.find((parameter) => parameter.name === AGGREGATION_FIELD)?.value?.stringValue ?? '');
|
|
13097
13745
|
}
|
|
13098
|
-
setAggregationCollection(
|
|
13746
|
+
setAggregationCollection(row, reference) {
|
|
13747
|
+
const index = this.ensureAction(row);
|
|
13099
13748
|
this.mutateAction(index, (action) => {
|
|
13100
13749
|
action.inputParameters ??= [];
|
|
13101
13750
|
const existing = action.inputParameters.find((parameter) => parameter.name === AGGREGATION_VALUES);
|
|
@@ -13110,7 +13759,8 @@ class TransformInspectorComponent extends NodeInspectorBase {
|
|
|
13110
13759
|
}
|
|
13111
13760
|
});
|
|
13112
13761
|
}
|
|
13113
|
-
setAggregationField(
|
|
13762
|
+
setAggregationField(row, field) {
|
|
13763
|
+
const index = this.ensureAction(row);
|
|
13114
13764
|
this.mutateAction(index, (action) => {
|
|
13115
13765
|
action.inputParameters ??= [];
|
|
13116
13766
|
const existing = action.inputParameters.find((parameter) => parameter.name === AGGREGATION_FIELD);
|
|
@@ -13125,29 +13775,136 @@ class TransformInspectorComponent extends NodeInspectorBase {
|
|
|
13125
13775
|
}
|
|
13126
13776
|
});
|
|
13127
13777
|
}
|
|
13128
|
-
isMap(
|
|
13129
|
-
return (
|
|
13778
|
+
isMap(row) {
|
|
13779
|
+
return this.operationOf(row) === 'Map';
|
|
13130
13780
|
}
|
|
13131
|
-
needsAggregationField(
|
|
13132
|
-
return
|
|
13781
|
+
needsAggregationField(row) {
|
|
13782
|
+
return this.operationOf(row) === 'Sum';
|
|
13133
13783
|
}
|
|
13134
|
-
needsAggregationValues(
|
|
13135
|
-
|
|
13784
|
+
needsAggregationValues(row) {
|
|
13785
|
+
const type = this.operationOf(row);
|
|
13786
|
+
return type === 'Sum' || type === 'Count';
|
|
13787
|
+
}
|
|
13788
|
+
// ------------------------------------------------------------------ tipi dedotti
|
|
13789
|
+
/**
|
|
13790
|
+
* Il tipo dell'elemento della collection aggregata: e' cio' che rende `aggregationField` un
|
|
13791
|
+
* elenco di campi invece di una casella di testo. Il valore nel documento resta il **nome** del
|
|
13792
|
+
* campo (`stringValue`), come vuole la §5.13.
|
|
13793
|
+
*/
|
|
13794
|
+
aggregationTypes = referenceTypes(this.catalog, (dataType) => this.dictionaries.isStructure(dataType), () => ({
|
|
13795
|
+
references: this.references(),
|
|
13796
|
+
values: this.actions().map((action) => this.aggregationCollection(action)),
|
|
13797
|
+
}));
|
|
13798
|
+
/** L'entita' degli elementi della collection aggregata, se e' una collection di record. */
|
|
13799
|
+
aggregationObject(row) {
|
|
13800
|
+
const path = this.aggregationCollection(row.action);
|
|
13801
|
+
const type = this.aggregationTypes.typeOf(path);
|
|
13802
|
+
return type?.dataType === 'Object' ? (type.objectType ?? undefined) : undefined;
|
|
13803
|
+
}
|
|
13804
|
+
/** La classe degli elementi della collection aggregata, se e' una collection di istanze (§4.7). */
|
|
13805
|
+
aggregationClass(row) {
|
|
13806
|
+
const path = this.aggregationCollection(row.action);
|
|
13807
|
+
const type = this.aggregationTypes.typeOf(path);
|
|
13808
|
+
return this.dictionaries.isStructure(type?.dataType) ? (type?.objectType ?? undefined) : undefined;
|
|
13809
|
+
}
|
|
13810
|
+
/**
|
|
13811
|
+
* Il tipo di cio' che una `Map` scrive: il campo o il membro quando il catalogo lo dichiara, il
|
|
13812
|
+
* risultato stesso altrove. Su un `Enum` e' la sola strada per proporre i valori del tipo invece
|
|
13813
|
+
* di farli digitare (§4.6).
|
|
13814
|
+
*/
|
|
13815
|
+
mapDataType(row) {
|
|
13816
|
+
return row.dataType ?? undefined;
|
|
13817
|
+
}
|
|
13818
|
+
mapObjectType(row) {
|
|
13819
|
+
return row.objectType ?? undefined;
|
|
13820
|
+
}
|
|
13821
|
+
// ------------------------------------------------------------------ mappatura a gesti
|
|
13822
|
+
/** La riga sotto il puntatore durante il trascinamento: solo evidenziazione, niente geometria. */
|
|
13823
|
+
dropKey = signal(null, ...(ngDevMode ? [{ debugName: "dropKey" }] : []));
|
|
13824
|
+
draggingPath = signal(null, ...(ngDevMode ? [{ debugName: "draggingPath" }] : []));
|
|
13825
|
+
onSourceDragStarted(source) {
|
|
13826
|
+
this.draggingPath.set(source.path);
|
|
13827
|
+
}
|
|
13828
|
+
onSourceDragMoved(event) {
|
|
13829
|
+
this.dropKey.set(this.hitTest(event.pointerPosition.x, event.pointerPosition.y));
|
|
13830
|
+
}
|
|
13831
|
+
onSourceDragEnded(event, source) {
|
|
13832
|
+
// Un trascinamento non deve valere anche come click: la stessa sorgente porta i due gesti, e il
|
|
13833
|
+
// click — che mappa sulla riga **selezionata** — arriverebbe dopo il rilascio, scrivendo due
|
|
13834
|
+
// righe con un solo gesto. Il flag lo esclude fino al task successivo.
|
|
13835
|
+
this.dragged = true;
|
|
13836
|
+
setTimeout(() => (this.dragged = false));
|
|
13837
|
+
const key = this.dropKey();
|
|
13838
|
+
this.dropKey.set(null);
|
|
13839
|
+
this.draggingPath.set(null);
|
|
13840
|
+
// A spostare qualcosa e' il documento: senza `reset()` resterebbe la trasformazione del
|
|
13841
|
+
// trascinamento sulla riga della palette, che Angular riusa per un'altra sorgente.
|
|
13842
|
+
event.source.reset();
|
|
13843
|
+
const row = this.targets().find((candidate) => candidate.key === key);
|
|
13844
|
+
if (!row) {
|
|
13845
|
+
return;
|
|
13846
|
+
}
|
|
13847
|
+
// Il documento si muta **dopo** il gesto: mutarlo qui ridisegna le due colonne mentre il CDK
|
|
13848
|
+
// sta ancora chiudendo il trascinamento sull'elemento che sta rilasciando.
|
|
13849
|
+
setTimeout(() => this.applySource(row, source));
|
|
13850
|
+
}
|
|
13851
|
+
/**
|
|
13852
|
+
* Fuori dai signal di proposito: e' la coda di un gesto, non uno stato da cui ridisegnare.
|
|
13853
|
+
*/
|
|
13854
|
+
dragged = false;
|
|
13855
|
+
/** Il gesto alternativo: riga selezionata, click sulla sorgente. Un click e' sempre riproducibile. */
|
|
13856
|
+
mapToSelected(source) {
|
|
13857
|
+
if (this.dragged) {
|
|
13858
|
+
return;
|
|
13859
|
+
}
|
|
13860
|
+
const row = this.selected();
|
|
13861
|
+
if (row) {
|
|
13862
|
+
this.applySource(row, source);
|
|
13863
|
+
}
|
|
13864
|
+
}
|
|
13865
|
+
/**
|
|
13866
|
+
* Dove finisce la sorgente rilasciata: nel valore di una `Map`, nella collection di un `Sum` o di
|
|
13867
|
+
* un `Count`. Non si cambia l'operazione da soli: chi ha scelto `Sum` non si aspetta che
|
|
13868
|
+
* trascinare una collection lo riporti a `Map`.
|
|
13869
|
+
*/
|
|
13870
|
+
applySource(row, source) {
|
|
13871
|
+
this.selectedKey.set(row.key);
|
|
13872
|
+
if (this.needsAggregationValues(row)) {
|
|
13873
|
+
this.setAggregationCollection(row, source.path);
|
|
13874
|
+
return;
|
|
13875
|
+
}
|
|
13876
|
+
this.setMapValue(row, { elementReference: source.path });
|
|
13877
|
+
}
|
|
13878
|
+
/**
|
|
13879
|
+
* La riga bersaglio, dal **DOM**. Si scorre tutto lo stack e non il solo primo elemento perche'
|
|
13880
|
+
* sopra al puntatore c'e' l'anteprima del trascinamento, che e' cio' che `elementFromPoint`
|
|
13881
|
+
* trova per prima.
|
|
13882
|
+
*/
|
|
13883
|
+
hitTest(x, y) {
|
|
13884
|
+
for (const element of this.document.elementsFromPoint(x, y)) {
|
|
13885
|
+
const row = element.closest?.('[data-fb-slot]');
|
|
13886
|
+
if (row) {
|
|
13887
|
+
return row.getAttribute('data-fb-slot');
|
|
13888
|
+
}
|
|
13889
|
+
}
|
|
13890
|
+
return null;
|
|
13136
13891
|
}
|
|
13137
13892
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.28", ngImport: i0, type: TransformInspectorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
13138
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.28", type: TransformInspectorComponent, isStandalone: true, selector: "fb-transform-inspector", usesInheritance: true, ngImport: i0, template: "<div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Tipo del risultato</label>\r\n <select class=\"fb-select\" [fbValue]=\"transform().dataType || ''\" (change)=\"setDataType($any($event.target).value)\">\r\n <option value=\"\">\u2014 scegli \u2014</option>\r\n @for (type of dataTypes(); track type.value) {\r\n <option [value]=\"type.value\">{{ type.label }}</option>\r\n }\r\n </select>\r\n @if (!transform().dataType) {\r\n <p class=\"fb-field__error\">Obbligatorio (TRANSFORM_DATA_TYPE_MISSING).</p>\r\n }\r\n</div>\r\n\r\n@if (requiresObjectType()) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">\r\n {{ isStructureTarget() ? 'Classe del risultato' : 'Tipo dell\u2019oggetto' }}\r\n </label>\r\n @if (isStructureTarget()) {\r\n <!-- \u00A74.7: una classe del backend. Le destinazioni delle azioni sono i suoi membri. -->\r\n <fb-structure-picker\r\n [value]=\"transform().objectType\"\r\n label=\"Classe del risultato\"\r\n (valueChange)=\"setObjectType($event ?? '')\"\r\n />\r\n @if (missingObjectType()) {\r\n <p class=\"fb-field__error\">\r\n La classe e\u2019 obbligatoria: senza, l\u2019attivazione e\u2019 bloccata (OBJECT_TYPE_MISSING).\r\n </p>\r\n } @else {\r\n <p class=\"fb-field__hint\">\r\n Componi l\u2019istanza intera qui: ogni trasformazione scrive un <strong>membro</strong> della classe,\r\n invece di un Assignment per membro.\r\n </p>\r\n }\r\n } @else if (transform().dataType === 'Enum') {\r\n <!-- Le enumerazioni sono un dizionario chiuso: non c'e' scrittura libera da concedere. -->\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"transform().objectType || ''\"\r\n (change)=\"setObjectType($any($event.target).value)\"\r\n >\r\n <option value=\"\">\u2014 scegli \u2014</option>\r\n @for (entry of enumOptions(); track entry.name) {\r\n <option [value]=\"entry.name\">{{ entry.label || entry.name }}</option>\r\n }\r\n </select>\r\n } @else {\r\n <fb-object-picker\r\n [value]=\"transform().objectType\"\r\n label=\"Tipo dell\u2019oggetto\"\r\n placeholder=\"Scrivi o scegli un oggetto\"\r\n (valueChange)=\"setObjectType($event ?? '')\"\r\n />\r\n }\r\n </div>\r\n}\r\n\r\n<label class=\"fb-check\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"transform().isCollection === true\"\r\n (change)=\"setIsCollection($any($event.target).checked)\"\r\n />\r\n Il risultato e\u2019 una collection\r\n</label>\r\n\r\n<p class=\"fb-callout\">\r\n Il risultato e\u2019 l\u2019<strong>output automatico</strong> dell\u2019elemento: si referenzia con\r\n <code>{{ name() }}</code>.\r\n</p>\r\n\r\n<fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Trasformazioni</legend>\r\n\r\n <div class=\"fb-list\">\r\n @for (action of actions(); track $index) {\r\n <div class=\"fb-list__item\">\r\n <div class=\"fb-list__header\">\r\n <span class=\"fb-list__index\">{{ $index + 1 }}</span>\r\n <span class=\"fb-list__spacer\"></span>\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-btn--ghost fb-btn--icon\"\r\n aria-label=\"Rimuovi\"\r\n (click)=\"removeAction($index)\"\r\n >\r\n \u00D7\r\n </button>\r\n </div>\r\n\r\n <!-- Operazione, destinazione e sorgente sono una frase sola: si leggono in riga. -->\r\n <div class=\"fb-fields-row\">\r\n <div class=\"fb-field fb-field--compact\">\r\n <label class=\"fb-field__label\">Operazione</label>\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"action.transformType || 'Map'\"\r\n (change)=\"setActionType($index, $any($event.target).value)\"\r\n >\r\n @for (type of transformTypes(); track type.value) {\r\n <option [value]=\"type.value\">{{ type.label }}</option>\r\n }\r\n </select>\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">\r\n {{ isStructureTarget() ? 'Membro di destinazione' : 'Campo di destinazione' }}\r\n </label>\r\n @if (isStructureTarget()) {\r\n <!-- Solo i membri scrivibili: un membro calcolato e' TARGET_NOT_WRITABLE (\u00A74.7). -->\r\n <fb-structure-member-picker\r\n [value]=\"action.outputFieldApiName\"\r\n [className]=\"transform().objectType\"\r\n usage=\"writable\"\r\n label=\"Membro di destinazione\"\r\n (valueChange)=\"setOutputField($index, $event ?? '')\"\r\n />\r\n } @else {\r\n <input\r\n class=\"fb-input\"\r\n [value]=\"action.outputFieldApiName || ''\"\r\n placeholder=\"Totale\"\r\n (input)=\"setOutputField($index, $any($event.target).value)\"\r\n />\r\n }\r\n </div>\r\n\r\n @if (isMap(action)) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Valore</label>\r\n <!--\r\n Il tipo di cio' che si scrive guida il controllo: su un membro Enum i valori\r\n proponibili sono quelli del suo tipo (\u00A74.6).\r\n -->\r\n <fb-value-editor\r\n [value]=\"action.value\"\r\n label=\"Valore\"\r\n [dataType]=\"$any(mapDataType(action))\"\r\n [objectType]=\"mapObjectType(action)\"\r\n (valueChange)=\"setMapValue($index, $event)\"\r\n />\r\n </div>\r\n }\r\n\r\n @if (needsAggregationValues(action)) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Collection su cui aggregare</label>\r\n <fb-reference-picker\r\n [value]=\"aggregationCollection(action)\"\r\n [isCollection]=\"true\"\r\n placeholder=\"Scegli una collection\"\r\n (valueChange)=\"setAggregationCollection($index, $event)\"\r\n />\r\n </div>\r\n }\r\n\r\n @if (needsAggregationField(action)) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Campo da sommare</label>\r\n <input\r\n class=\"fb-input\"\r\n [value]=\"aggregationField(action)\"\r\n placeholder=\"Importo\"\r\n (input)=\"setAggregationField($index, $any($event.target).value)\"\r\n />\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n } @empty {\r\n <p class=\"fb-empty\">Nessuna trasformazione: l\u2019elemento non produce nulla (TRANSFORM_WITHOUT_VALUES).</p>\r\n }\r\n </div>\r\n\r\n <button type=\"button\" class=\"fb-btn\" (click)=\"addAction()\">Aggiungi trasformazione</button>\r\n</fieldset>\r\n\r\n<fb-connector-editor\r\n [nodeName]=\"name()\"\r\n [node]=\"node()\"\r\n [outlets]=\"outlets()\"\r\n (connectorChanged)=\"onConnectorChanged($event)\"\r\n/>\r\n", dependencies: [{ kind: "component", type: ConnectorEditorComponent, selector: "fb-connector-editor", inputs: ["nodeName", "node", "outlets", "title"], outputs: ["connectorChanged"] }, { kind: "component", type: ObjectPickerComponent, selector: "fb-object-picker", inputs: ["value", "label", "placeholder", "disabled"], outputs: ["valueChange"] }, { kind: "component", type: ReferencePickerComponent, selector: "fb-reference-picker", inputs: ["value", "label", "placeholder", "disabled", "dataType", "isCollection", "objectType", "writableOnly", "elementsOnly", "extraReferences"], outputs: ["valueChange"] }, { kind: "component", type: StructureMemberPickerComponent, selector: "fb-structure-member-picker", inputs: ["value", "className", "usage", "label", "placeholder", "disabled"], outputs: ["valueChange"] }, { kind: "component", type: StructurePickerComponent, selector: "fb-structure-picker", inputs: ["value", "label", "placeholder", "disabled"], outputs: ["valueChange"] }, { kind: "component", type: ValueEditorComponent, selector: "fb-value-editor", inputs: ["value", "label", "dataType", "objectType", "isCollection", "valueSet", "disabled", "allowFormula"], outputs: ["valueChange"] }, { kind: "directive", type: SelectValueDirective, selector: "select[fbValue]", inputs: ["fbValue"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
13893
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.28", type: TransformInspectorComponent, isStandalone: true, selector: "fb-transform-inspector", usesInheritance: true, ngImport: i0, template: "<div class=\"fb-tr\">\r\n <!-- ============================================================ sorgenti -->\r\n <aside class=\"fb-tr__panel\" aria-label=\"Sorgenti\">\r\n <h3 class=\"fb-tr__panel-title\">Sorgenti</h3>\r\n\r\n <input\r\n class=\"fb-input fb-tr__search\"\r\n type=\"search\"\r\n placeholder=\"Cerca una sorgente\"\r\n aria-label=\"Cerca una sorgente\"\r\n [value]=\"sourceQuery()\"\r\n (input)=\"sourceQuery.set($any($event.target).value)\"\r\n />\r\n\r\n @if (sourceCrumbs().length) {\r\n <nav class=\"fb-tr__crumbs\" aria-label=\"Percorso della sorgente\">\r\n <button type=\"button\" class=\"fb-tr__crumb\" (click)=\"enterSource('')\">Tutte</button>\r\n @for (crumb of sourceCrumbs(); track crumb.path) {\r\n <span class=\"fb-tr__crumb-sep\" aria-hidden=\"true\">\u203A</span>\r\n <button type=\"button\" class=\"fb-tr__crumb\" (click)=\"enterSource(crumb.path)\">{{ crumb.label }}</button>\r\n }\r\n </nav>\r\n }\r\n\r\n <p class=\"fb-tr__hint\">\r\n Trascina su una destinazione, oppure clicca per mapparla su quella selezionata.\r\n </p>\r\n\r\n @if (sourceError()) {\r\n <p class=\"fb-callout fb-callout--warn\">\r\n {{ sourceError() }} Le sorgenti si scrivono a mano nel dettaglio: l\u2019elenco non c\u2019e\u2019, i\r\n riferimenti s\u00EC.\r\n </p>\r\n }\r\n\r\n <ul class=\"fb-tr__list\">\r\n @for (source of sources(); track source.path) {\r\n <li class=\"fb-tr__source-row\">\r\n <button\r\n type=\"button\"\r\n class=\"fb-tr__source\"\r\n cdkDrag\r\n [class.fb-tr__source--dragging]=\"draggingPath() === source.path\"\r\n [title]=\"source.path + (source.detail ? ' \u00B7 ' + source.detail : '')\"\r\n (cdkDragStarted)=\"onSourceDragStarted(source)\"\r\n (cdkDragMoved)=\"onSourceDragMoved($event)\"\r\n (cdkDragEnded)=\"onSourceDragEnded($event, source)\"\r\n (click)=\"mapToSelected(source)\"\r\n >\r\n <span class=\"fb-tr__source-name\">\r\n {{ source.label }}\r\n @if (source.isCollection) {\r\n <span class=\"fb-tr__badge\" aria-label=\"collection\">[ ]</span>\r\n }\r\n </span>\r\n @if (source.detail) {\r\n <span class=\"fb-tr__source-detail\">{{ source.detail }}</span>\r\n }\r\n </button>\r\n @if (source.canEnter) {\r\n <!-- Entrare e mappare sono due gesti diversi: un record intero e' una sorgente valida. -->\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-btn--ghost fb-btn--icon fb-tr__enter\"\r\n [attr.aria-label]=\"'Entra in ' + source.label\"\r\n (click)=\"enterSource(source.path)\"\r\n >\r\n \u203A\r\n </button>\r\n }\r\n </li>\r\n } @empty {\r\n @if (sourcePending()) {\r\n <li class=\"fb-tr__empty\">Caricamento\u2026</li>\r\n } @else if (sourceUndeclared()) {\r\n <li class=\"fb-tr__empty\">\r\n Il catalogo non dichiara cosa c\u2019e\u2019 qui dentro: non significa che non ci sia niente. Il\r\n percorso si scrive a mano nel dettaglio.\r\n </li>\r\n } @else {\r\n <li class=\"fb-tr__empty\">Nessuna sorgente con questo nome.</li>\r\n }\r\n }\r\n </ul>\r\n </aside>\r\n\r\n <!-- ========================================================= destinazione -->\r\n <section class=\"fb-tr__panel fb-tr__map\" aria-label=\"Destinazione\">\r\n <h3 class=\"fb-tr__panel-title\">\r\n Destinazione\r\n @if (slotCount()) {\r\n <span class=\"fb-tr__count\">{{ mappedCount() }} di {{ slotCount() }} mappate</span>\r\n } @else if (mappedCount()) {\r\n <span class=\"fb-tr__count\">{{ mappedCount() }} mappate</span>\r\n }\r\n </h3>\r\n\r\n <!-- Il tipo del risultato e' la forma della colonna: sta in cima, non in fondo. -->\r\n <div class=\"fb-tr__result\">\r\n <div class=\"fb-fields-row\">\r\n <div class=\"fb-field fb-field--compact\">\r\n <label class=\"fb-field__label fb-field__label--required\">Tipo del risultato</label>\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"transform().dataType || ''\"\r\n (change)=\"setDataType($any($event.target).value)\"\r\n >\r\n <option value=\"\">\u2014 scegli \u2014</option>\r\n @for (type of dataTypes(); track type.value) {\r\n <option [value]=\"type.value\">{{ type.label }}</option>\r\n }\r\n </select>\r\n </div>\r\n\r\n @if (requiresObjectType()) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">\r\n {{ isStructureTarget() ? 'Classe del risultato' : 'Tipo dell\u2019oggetto' }}\r\n </label>\r\n @if (isStructureTarget()) {\r\n <!-- \u00A74.7: una classe del backend. Le destinazioni delle azioni sono i suoi membri. -->\r\n <fb-structure-picker\r\n [value]=\"transform().objectType\"\r\n label=\"Classe del risultato\"\r\n (valueChange)=\"setObjectType($event ?? '')\"\r\n />\r\n } @else if (transform().dataType === 'Enum') {\r\n <!-- Le enumerazioni sono un dizionario chiuso: non c'e' scrittura libera da concedere. -->\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"transform().objectType || ''\"\r\n (change)=\"setObjectType($any($event.target).value)\"\r\n >\r\n <option value=\"\">\u2014 scegli \u2014</option>\r\n @for (entry of enumOptions(); track entry.name) {\r\n <option [value]=\"entry.name\">{{ entry.label || entry.name }}</option>\r\n }\r\n </select>\r\n } @else {\r\n <fb-object-picker\r\n [value]=\"transform().objectType\"\r\n label=\"Tipo dell\u2019oggetto\"\r\n placeholder=\"Scrivi o scegli un oggetto\"\r\n (valueChange)=\"setObjectType($event ?? '')\"\r\n />\r\n }\r\n </div>\r\n }\r\n\r\n <label class=\"fb-check fb-tr__collection\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"transform().isCollection === true\"\r\n (change)=\"setIsCollection($any($event.target).checked)\"\r\n />\r\n Collection\r\n </label>\r\n </div>\r\n\r\n @if (!transform().dataType) {\r\n <p class=\"fb-field__error\">Obbligatorio (TRANSFORM_DATA_TYPE_MISSING).</p>\r\n }\r\n @if (missingObjectType()) {\r\n <p class=\"fb-field__error\">\r\n La classe e\u2019 obbligatoria: senza, l\u2019attivazione e\u2019 bloccata (OBJECT_TYPE_MISSING).\r\n </p>\r\n }\r\n @if (targetUndeclared()) {\r\n <p class=\"fb-callout fb-callout--warn\">\r\n Il catalogo non dichiara i {{ isStructureTarget() ? 'membri' : 'campi' }} di\r\n <code>{{ transform().objectType }}</code>: le destinazioni si scrivono a mano, e nessuna di\r\n esse viene accusata di non esistere.\r\n </p>\r\n }\r\n </div>\r\n\r\n <ul class=\"fb-tr__list fb-tr__rows\">\r\n @for (row of targets(); track row.key) {\r\n <!--\r\n `data-fb-slot` e' cio' che il rilascio cerca: il bersaglio lo calcola `hitTest` dal DOM,\r\n non una drop list del CDK.\r\n -->\r\n <li\r\n class=\"fb-tr__row\"\r\n [attr.data-fb-slot]=\"row.key\"\r\n [class.fb-tr__row--selected]=\"isSelected(row)\"\r\n [class.fb-tr__row--drop]=\"dropKey() === row.key\"\r\n [class.fb-tr__row--empty]=\"row.index < 0\"\r\n >\r\n <button type=\"button\" class=\"fb-tr__row-main\" (click)=\"select(row.key)\">\r\n <span class=\"fb-tr__op\" [attr.title]=\"operationLabel(operationOf(row))\" aria-hidden=\"true\">\r\n {{ operationIcon(row) }}\r\n </span>\r\n <span class=\"fb-tr__row-text\">\r\n <span class=\"fb-tr__row-name\">\r\n {{ row.label }}\r\n @if (row.isCollection) {\r\n <span class=\"fb-tr__badge\" aria-label=\"collection\">[ ]</span>\r\n }\r\n @if (row.isUnknown) {\r\n <span class=\"fb-tr__badge fb-tr__badge--warn\">fuori catalogo</span>\r\n }\r\n </span>\r\n @if (row.index < 0) {\r\n <span class=\"fb-tr__row-src fb-tr__row-src--empty\">non mappata</span>\r\n } @else if (sourceSummary(row)) {\r\n <span class=\"fb-tr__row-src\">\u2190 {{ sourceSummary(row) }}</span>\r\n } @else {\r\n <span class=\"fb-tr__row-src fb-tr__row-src--empty\">sorgente da indicare</span>\r\n }\r\n </span>\r\n @if (row.dataType) {\r\n <span class=\"fb-tr__row-type\">{{ row.dataType }}</span>\r\n }\r\n @if (issuesOf(row).length) {\r\n <span class=\"fb-tr__row-issue\" [attr.title]=\"issuesOf(row)[0].message\">!</span>\r\n }\r\n </button>\r\n @if (row.index >= 0) {\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-btn--ghost fb-btn--icon\"\r\n [attr.aria-label]=\"'Togli la mappatura di ' + row.label\"\r\n (click)=\"removeAction(row)\"\r\n >\r\n \u00D7\r\n </button>\r\n }\r\n </li>\r\n } @empty {\r\n <li class=\"fb-empty\">\r\n Nessuna trasformazione: l\u2019elemento non produce nulla (TRANSFORM_WITHOUT_VALUES).\r\n </li>\r\n }\r\n </ul>\r\n\r\n <button type=\"button\" class=\"fb-btn\" (click)=\"addAction()\">\r\n {{ hasTargetCatalog() ? 'Aggiungi una destinazione fuori catalogo' : 'Aggiungi trasformazione' }}\r\n </button>\r\n\r\n <p class=\"fb-callout\">\r\n Il risultato e\u2019 l\u2019<strong>output automatico</strong> dell\u2019elemento: si referenzia con\r\n <code>{{ name() }}</code>.\r\n </p>\r\n </section>\r\n\r\n <!-- ============================================================ dettaglio -->\r\n <aside class=\"fb-tr__panel fb-tr__detail\" aria-label=\"Dettaglio della mappatura\">\r\n @if (selected(); as row) {\r\n <h3 class=\"fb-tr__panel-title\">{{ row.label }}</h3>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Operazione</label>\r\n <div class=\"fb-tr__modes\" role=\"group\" aria-label=\"Operazione\">\r\n @for (type of transformTypes(); track type.value) {\r\n <button\r\n type=\"button\"\r\n class=\"fb-tr__mode\"\r\n [class.fb-tr__mode--active]=\"operationOf(row) === type.value\"\r\n (click)=\"setOperation(row, type.value)\"\r\n >\r\n {{ type.label }}\r\n </button>\r\n }\r\n </div>\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">\r\n {{ isStructureTarget() ? 'Membro di destinazione' : 'Campo di destinazione' }}\r\n </label>\r\n @if (isStructureTarget()) {\r\n <!-- Solo i membri scrivibili: un membro calcolato e' TARGET_NOT_WRITABLE (\u00A74.7). -->\r\n <fb-structure-member-picker\r\n [value]=\"row.field\"\r\n [className]=\"transform().objectType\"\r\n usage=\"writable\"\r\n label=\"Membro di destinazione\"\r\n (valueChange)=\"setOutputField(row, $event ?? '')\"\r\n />\r\n } @else if (targetObject()) {\r\n <!-- Il risultato e' un record: le destinazioni sono i campi della sua entita' (\u00A74.4). -->\r\n <fb-field-picker\r\n [value]=\"row.field\"\r\n [object]=\"targetObject()\"\r\n label=\"Campo di destinazione\"\r\n (valueChange)=\"setOutputField(row, $event ?? '')\"\r\n />\r\n } @else {\r\n <input\r\n class=\"fb-input\"\r\n [value]=\"row.field\"\r\n placeholder=\"Totale\"\r\n (input)=\"setOutputField(row, $any($event.target).value)\"\r\n />\r\n }\r\n </div>\r\n\r\n @if (isMap(row)) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Valore</label>\r\n <!--\r\n Il tipo di cio' che si scrive guida il controllo: su un campo o un membro Enum i valori\r\n proponibili sono quelli del suo tipo (\u00A74.6).\r\n -->\r\n <fb-value-editor\r\n [value]=\"row.action?.value ?? undefined\"\r\n label=\"Valore\"\r\n [dataType]=\"mapDataType(row)\"\r\n [objectType]=\"mapObjectType(row)\"\r\n (valueChange)=\"setMapValue(row, $event)\"\r\n />\r\n </div>\r\n }\r\n\r\n @if (needsAggregationValues(row)) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Collection su cui aggregare</label>\r\n <fb-reference-picker\r\n [value]=\"aggregationCollection(row.action)\"\r\n [isCollection]=\"true\"\r\n placeholder=\"Scegli una collection\"\r\n (valueChange)=\"setAggregationCollection(row, $event)\"\r\n />\r\n </div>\r\n }\r\n\r\n @if (needsAggregationField(row)) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Campo da sommare</label>\r\n <!--\r\n Nel documento va il **nome** del campo (`stringValue`, \u00A75.13); dove il tipo della\r\n collection e' noto, il nome lo si sceglie da un elenco invece di ricordarlo.\r\n -->\r\n @if (aggregationObject(row); as object) {\r\n <fb-field-picker\r\n [value]=\"aggregationField(row.action)\"\r\n [object]=\"object\"\r\n label=\"Campo da sommare\"\r\n (valueChange)=\"setAggregationField(row, $event ?? '')\"\r\n />\r\n } @else if (aggregationClass(row); as className) {\r\n <fb-structure-member-picker\r\n [value]=\"aggregationField(row.action)\"\r\n [className]=\"className\"\r\n label=\"Membro da sommare\"\r\n (valueChange)=\"setAggregationField(row, $event ?? '')\"\r\n />\r\n } @else {\r\n <input\r\n class=\"fb-input\"\r\n [value]=\"aggregationField(row.action)\"\r\n placeholder=\"Importo\"\r\n (input)=\"setAggregationField(row, $any($event.target).value)\"\r\n />\r\n }\r\n </div>\r\n }\r\n\r\n @for (issue of issuesOf(row); track $index) {\r\n <p class=\"fb-callout fb-callout--error\">{{ issue.message }} ({{ issue.code }})</p>\r\n }\r\n } @else {\r\n <p class=\"fb-tr__hint\">\r\n Scegli il tipo del risultato: le destinazioni compaiono da l\u00EC, e la mappatura si fa\r\n trascinando una sorgente su una di esse.\r\n </p>\r\n }\r\n </aside>\r\n</div>\r\n\r\n<fb-connector-editor\r\n [nodeName]=\"name()\"\r\n [node]=\"node()\"\r\n [outlets]=\"outlets()\"\r\n (connectorChanged)=\"onConnectorChanged($event)\"\r\n/>\r\n", styles: [":host{display:block;container-type:inline-size}.fb-tr{display:grid;grid-template-columns:240px minmax(0,1fr) 320px;gap:12px;align-items:start;margin-bottom:14px}@container (max-width: 1040px){.fb-tr{grid-template-columns:minmax(0,1fr)}}.fb-tr__panel{min-width:0;padding:10px;border:1px solid var(--fb-border, #e2e5eb);border-radius:var(--fb-radius, 10px);background:var(--fb-surface-alt, #f7f8fa)}.fb-tr__map{background:var(--fb-surface, #fff)}.fb-tr__panel-title{display:flex;align-items:baseline;justify-content:space-between;gap:8px;margin:0 0 8px;font-size:11px;font-weight:700;letter-spacing:.05em;text-transform:uppercase;color:var(--fb-text-subtle, #98a2b3);overflow-wrap:anywhere}.fb-tr__count{font-size:10px;font-weight:600;letter-spacing:0;text-transform:none;color:var(--fb-text-muted, #6b7086)}.fb-tr__hint{margin:6px 0;font-size:11px;line-height:1.45;color:var(--fb-text-muted, #6b7086)}.fb-tr__search{margin-bottom:6px}.fb-tr__crumbs{display:flex;flex-wrap:wrap;align-items:center;gap:2px;margin-bottom:6px}.fb-tr__crumb{padding:2px 4px;border:0;border-radius:var(--fb-radius-xs, 6px);background:transparent;color:var(--fb-accent, #4f6ef7);font:inherit;font-size:11px;cursor:pointer}.fb-tr__crumb:hover{background:color-mix(in srgb,var(--fb-accent, #4f6ef7) 10%,transparent)}.fb-tr__crumb-sep{color:var(--fb-text-subtle, #98a2b3);font-size:11px}.fb-tr__list{display:flex;flex-direction:column;gap:4px;margin:0;padding:0;max-height:340px;overflow-y:auto;overscroll-behavior:contain;list-style:none}.fb-tr__empty{padding:8px;border:1px dashed var(--fb-border, #d6dae1);border-radius:var(--fb-radius-xs, 6px);font-size:11px;line-height:1.45;color:var(--fb-text-muted, #667085)}.fb-tr__source-row{display:flex;align-items:stretch;gap:2px}.fb-tr__source{display:flex;flex:1;flex-direction:column;gap:1px;min-width:0;padding:5px 8px;border:1px solid var(--fb-border, #e2e5eb);border-radius:var(--fb-radius-xs, 6px);background:var(--fb-surface, #fff);color:var(--fb-text, #1a1c23);font:inherit;font-size:12px;text-align:left;cursor:grab}.fb-tr__source:hover{border-color:var(--fb-accent, #4f6ef7)}.fb-tr__source--dragging{opacity:.6}.fb-tr__source-name,.fb-tr__source-detail{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fb-tr__source-detail{font-size:10px;color:var(--fb-text-subtle, #98a2b3)}.fb-tr__enter{flex:none}.fb-tr__badge{margin-left:4px;padding:0 4px;border-radius:4px;background:var(--fb-surface-sunken, #eef0f4);font-size:9px;font-weight:700;color:var(--fb-text-muted, #6b7086)}.fb-tr__badge--warn{background:color-mix(in srgb,var(--fb-warning, #b7791f) 16%,transparent);color:var(--fb-warning, #b7791f)}.fb-tr__result{margin-bottom:10px;padding-bottom:8px;border-bottom:1px solid var(--fb-border-subtle, #eef0f4)}.fb-tr__collection{align-self:end;padding-bottom:6px;white-space:nowrap}.fb-tr__rows{max-height:420px;margin-bottom:8px}.fb-tr__row{display:flex;align-items:stretch;gap:2px;border:1px solid var(--fb-border, #e2e5eb);border-radius:var(--fb-radius-xs, 6px);background:var(--fb-surface, #fff)}.fb-tr__row--empty{border-style:dashed;background:var(--fb-surface-alt, #f7f8fa)}.fb-tr__row--selected{border-color:var(--fb-accent, #4f6ef7);box-shadow:0 0 0 1px var(--fb-accent, #4f6ef7) inset}.fb-tr__row--drop{border-color:var(--fb-accent, #4f6ef7);border-style:solid;background:color-mix(in srgb,var(--fb-accent, #4f6ef7) 12%,transparent)}.fb-tr__row-main{display:flex;flex:1;align-items:center;gap:8px;min-width:0;padding:6px 8px;border:0;border-radius:var(--fb-radius-xs, 6px);background:transparent;color:var(--fb-text, #1a1c23);font:inherit;text-align:left;cursor:pointer}.fb-tr__op{display:grid;flex:none;place-items:center;width:20px;height:20px;border-radius:50%;background:var(--fb-surface-sunken, #eef0f4);font-size:11px;font-weight:700;color:var(--fb-text-muted, #6b7086)}.fb-tr__row-text{display:flex;flex:1;flex-direction:column;gap:1px;min-width:0}.fb-tr__row-name{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:12px;font-weight:600}.fb-tr__row-src{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:10px;color:var(--fb-text-muted, #6b7086)}.fb-tr__row-src--empty{color:var(--fb-text-subtle, #98a2b3);font-style:italic}.fb-tr__row-type{flex:none;font-size:10px;color:var(--fb-text-subtle, #98a2b3)}.fb-tr__row-issue{display:grid;flex:none;place-items:center;width:16px;height:16px;border-radius:50%;background:color-mix(in srgb,var(--fb-error, #dc2626) 14%,transparent);font-size:10px;font-weight:700;color:var(--fb-error, #dc2626)}.fb-tr__modes{margin-top:2px}\n"], dependencies: [{ kind: "directive", type: CdkDrag, selector: "[cdkDrag]", inputs: ["cdkDragData", "cdkDragLockAxis", "cdkDragRootElement", "cdkDragBoundary", "cdkDragStartDelay", "cdkDragFreeDragPosition", "cdkDragDisabled", "cdkDragConstrainPosition", "cdkDragPreviewClass", "cdkDragPreviewContainer", "cdkDragScale"], outputs: ["cdkDragStarted", "cdkDragReleased", "cdkDragEnded", "cdkDragEntered", "cdkDragExited", "cdkDragDropped", "cdkDragMoved"], exportAs: ["cdkDrag"] }, { kind: "component", type: ConnectorEditorComponent, selector: "fb-connector-editor", inputs: ["nodeName", "node", "outlets", "title"], outputs: ["connectorChanged"] }, { kind: "component", type: FieldPickerComponent, selector: "fb-field-picker", inputs: ["value", "object", "usage", "label", "placeholder", "disabled"], outputs: ["valueChange"] }, { kind: "component", type: ObjectPickerComponent, selector: "fb-object-picker", inputs: ["value", "label", "placeholder", "disabled"], outputs: ["valueChange"] }, { kind: "component", type: ReferencePickerComponent, selector: "fb-reference-picker", inputs: ["value", "label", "placeholder", "disabled", "dataType", "isCollection", "objectType", "writableOnly", "elementsOnly", "extraReferences"], outputs: ["valueChange"] }, { kind: "component", type: StructureMemberPickerComponent, selector: "fb-structure-member-picker", inputs: ["value", "className", "usage", "label", "placeholder", "disabled"], outputs: ["valueChange"] }, { kind: "component", type: StructurePickerComponent, selector: "fb-structure-picker", inputs: ["value", "label", "placeholder", "disabled"], outputs: ["valueChange"] }, { kind: "component", type: ValueEditorComponent, selector: "fb-value-editor", inputs: ["value", "label", "dataType", "objectType", "isCollection", "valueSet", "disabled", "allowFormula"], outputs: ["valueChange"] }, { kind: "directive", type: SelectValueDirective, selector: "select[fbValue]", inputs: ["fbValue"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
13139
13894
|
}
|
|
13140
13895
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.28", ngImport: i0, type: TransformInspectorComponent, decorators: [{
|
|
13141
13896
|
type: Component,
|
|
13142
13897
|
args: [{ selector: 'fb-transform-inspector', standalone: true, imports: [
|
|
13898
|
+
CdkDrag,
|
|
13143
13899
|
ConnectorEditorComponent,
|
|
13900
|
+
FieldPickerComponent,
|
|
13144
13901
|
ObjectPickerComponent,
|
|
13145
13902
|
ReferencePickerComponent,
|
|
13146
13903
|
StructureMemberPickerComponent,
|
|
13147
13904
|
StructurePickerComponent,
|
|
13148
13905
|
ValueEditorComponent,
|
|
13149
13906
|
SelectValueDirective,
|
|
13150
|
-
], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Tipo del risultato</label>\r\n <select class=\"fb-select\" [fbValue]=\"transform().dataType || ''\" (change)=\"setDataType($any($event.target).value)\">\r\n <option value=\"\">\u2014 scegli \u2014</option>\r\n @for (type of dataTypes(); track type.value) {\r\n <option [value]=\"type.value\">{{ type.label }}</option>\r\n }\r\n </select>\r\n @if (!transform().dataType) {\r\n <p class=\"fb-field__error\">Obbligatorio (TRANSFORM_DATA_TYPE_MISSING).</p>\r\n }\r\n</div>\r\n\r\n@if (requiresObjectType()) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">\r\n {{ isStructureTarget() ? 'Classe del risultato' : 'Tipo dell\u2019oggetto' }}\r\n </label>\r\n @if (isStructureTarget()) {\r\n <!-- \u00A74.7: una classe del backend. Le destinazioni delle azioni sono i suoi membri. -->\r\n <fb-structure-picker\r\n [value]=\"transform().objectType\"\r\n label=\"Classe del risultato\"\r\n (valueChange)=\"setObjectType($event ?? '')\"\r\n />\r\n @if (missingObjectType()) {\r\n <p class=\"fb-field__error\">\r\n La classe e\u2019 obbligatoria: senza, l\u2019attivazione e\u2019 bloccata (OBJECT_TYPE_MISSING).\r\n </p>\r\n } @else {\r\n <p class=\"fb-field__hint\">\r\n Componi l\u2019istanza intera qui: ogni trasformazione scrive un <strong>membro</strong> della classe,\r\n invece di un Assignment per membro.\r\n </p>\r\n }\r\n } @else if (transform().dataType === 'Enum') {\r\n <!-- Le enumerazioni sono un dizionario chiuso: non c'e' scrittura libera da concedere. -->\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"transform().objectType || ''\"\r\n (change)=\"setObjectType($any($event.target).value)\"\r\n >\r\n <option value=\"\">\u2014 scegli \u2014</option>\r\n @for (entry of enumOptions(); track entry.name) {\r\n <option [value]=\"entry.name\">{{ entry.label || entry.name }}</option>\r\n }\r\n </select>\r\n } @else {\r\n <fb-object-picker\r\n [value]=\"transform().objectType\"\r\n label=\"Tipo dell\u2019oggetto\"\r\n placeholder=\"Scrivi o scegli un oggetto\"\r\n (valueChange)=\"setObjectType($event ?? '')\"\r\n />\r\n }\r\n </div>\r\n}\r\n\r\n<label class=\"fb-check\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"transform().isCollection === true\"\r\n (change)=\"setIsCollection($any($event.target).checked)\"\r\n />\r\n Il risultato e\u2019 una collection\r\n</label>\r\n\r\n<p class=\"fb-callout\">\r\n Il risultato e\u2019 l\u2019<strong>output automatico</strong> dell\u2019elemento: si referenzia con\r\n <code>{{ name() }}</code>.\r\n</p>\r\n\r\n<fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Trasformazioni</legend>\r\n\r\n <div class=\"fb-list\">\r\n @for (action of actions(); track $index) {\r\n <div class=\"fb-list__item\">\r\n <div class=\"fb-list__header\">\r\n <span class=\"fb-list__index\">{{ $index + 1 }}</span>\r\n <span class=\"fb-list__spacer\"></span>\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-btn--ghost fb-btn--icon\"\r\n aria-label=\"Rimuovi\"\r\n (click)=\"removeAction($index)\"\r\n >\r\n \u00D7\r\n </button>\r\n </div>\r\n\r\n <!-- Operazione, destinazione e sorgente sono una frase sola: si leggono in riga. -->\r\n <div class=\"fb-fields-row\">\r\n <div class=\"fb-field fb-field--compact\">\r\n <label class=\"fb-field__label\">Operazione</label>\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"action.transformType || 'Map'\"\r\n (change)=\"setActionType($index, $any($event.target).value)\"\r\n >\r\n @for (type of transformTypes(); track type.value) {\r\n <option [value]=\"type.value\">{{ type.label }}</option>\r\n }\r\n </select>\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">\r\n {{ isStructureTarget() ? 'Membro di destinazione' : 'Campo di destinazione' }}\r\n </label>\r\n @if (isStructureTarget()) {\r\n <!-- Solo i membri scrivibili: un membro calcolato e' TARGET_NOT_WRITABLE (\u00A74.7). -->\r\n <fb-structure-member-picker\r\n [value]=\"action.outputFieldApiName\"\r\n [className]=\"transform().objectType\"\r\n usage=\"writable\"\r\n label=\"Membro di destinazione\"\r\n (valueChange)=\"setOutputField($index, $event ?? '')\"\r\n />\r\n } @else {\r\n <input\r\n class=\"fb-input\"\r\n [value]=\"action.outputFieldApiName || ''\"\r\n placeholder=\"Totale\"\r\n (input)=\"setOutputField($index, $any($event.target).value)\"\r\n />\r\n }\r\n </div>\r\n\r\n @if (isMap(action)) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Valore</label>\r\n <!--\r\n Il tipo di cio' che si scrive guida il controllo: su un membro Enum i valori\r\n proponibili sono quelli del suo tipo (\u00A74.6).\r\n -->\r\n <fb-value-editor\r\n [value]=\"action.value\"\r\n label=\"Valore\"\r\n [dataType]=\"$any(mapDataType(action))\"\r\n [objectType]=\"mapObjectType(action)\"\r\n (valueChange)=\"setMapValue($index, $event)\"\r\n />\r\n </div>\r\n }\r\n\r\n @if (needsAggregationValues(action)) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Collection su cui aggregare</label>\r\n <fb-reference-picker\r\n [value]=\"aggregationCollection(action)\"\r\n [isCollection]=\"true\"\r\n placeholder=\"Scegli una collection\"\r\n (valueChange)=\"setAggregationCollection($index, $event)\"\r\n />\r\n </div>\r\n }\r\n\r\n @if (needsAggregationField(action)) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Campo da sommare</label>\r\n <input\r\n class=\"fb-input\"\r\n [value]=\"aggregationField(action)\"\r\n placeholder=\"Importo\"\r\n (input)=\"setAggregationField($index, $any($event.target).value)\"\r\n />\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n } @empty {\r\n <p class=\"fb-empty\">Nessuna trasformazione: l\u2019elemento non produce nulla (TRANSFORM_WITHOUT_VALUES).</p>\r\n }\r\n </div>\r\n\r\n <button type=\"button\" class=\"fb-btn\" (click)=\"addAction()\">Aggiungi trasformazione</button>\r\n</fieldset>\r\n\r\n<fb-connector-editor\r\n [nodeName]=\"name()\"\r\n [node]=\"node()\"\r\n [outlets]=\"outlets()\"\r\n (connectorChanged)=\"onConnectorChanged($event)\"\r\n/>\r\n" }]
|
|
13907
|
+
], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"fb-tr\">\r\n <!-- ============================================================ sorgenti -->\r\n <aside class=\"fb-tr__panel\" aria-label=\"Sorgenti\">\r\n <h3 class=\"fb-tr__panel-title\">Sorgenti</h3>\r\n\r\n <input\r\n class=\"fb-input fb-tr__search\"\r\n type=\"search\"\r\n placeholder=\"Cerca una sorgente\"\r\n aria-label=\"Cerca una sorgente\"\r\n [value]=\"sourceQuery()\"\r\n (input)=\"sourceQuery.set($any($event.target).value)\"\r\n />\r\n\r\n @if (sourceCrumbs().length) {\r\n <nav class=\"fb-tr__crumbs\" aria-label=\"Percorso della sorgente\">\r\n <button type=\"button\" class=\"fb-tr__crumb\" (click)=\"enterSource('')\">Tutte</button>\r\n @for (crumb of sourceCrumbs(); track crumb.path) {\r\n <span class=\"fb-tr__crumb-sep\" aria-hidden=\"true\">\u203A</span>\r\n <button type=\"button\" class=\"fb-tr__crumb\" (click)=\"enterSource(crumb.path)\">{{ crumb.label }}</button>\r\n }\r\n </nav>\r\n }\r\n\r\n <p class=\"fb-tr__hint\">\r\n Trascina su una destinazione, oppure clicca per mapparla su quella selezionata.\r\n </p>\r\n\r\n @if (sourceError()) {\r\n <p class=\"fb-callout fb-callout--warn\">\r\n {{ sourceError() }} Le sorgenti si scrivono a mano nel dettaglio: l\u2019elenco non c\u2019e\u2019, i\r\n riferimenti s\u00EC.\r\n </p>\r\n }\r\n\r\n <ul class=\"fb-tr__list\">\r\n @for (source of sources(); track source.path) {\r\n <li class=\"fb-tr__source-row\">\r\n <button\r\n type=\"button\"\r\n class=\"fb-tr__source\"\r\n cdkDrag\r\n [class.fb-tr__source--dragging]=\"draggingPath() === source.path\"\r\n [title]=\"source.path + (source.detail ? ' \u00B7 ' + source.detail : '')\"\r\n (cdkDragStarted)=\"onSourceDragStarted(source)\"\r\n (cdkDragMoved)=\"onSourceDragMoved($event)\"\r\n (cdkDragEnded)=\"onSourceDragEnded($event, source)\"\r\n (click)=\"mapToSelected(source)\"\r\n >\r\n <span class=\"fb-tr__source-name\">\r\n {{ source.label }}\r\n @if (source.isCollection) {\r\n <span class=\"fb-tr__badge\" aria-label=\"collection\">[ ]</span>\r\n }\r\n </span>\r\n @if (source.detail) {\r\n <span class=\"fb-tr__source-detail\">{{ source.detail }}</span>\r\n }\r\n </button>\r\n @if (source.canEnter) {\r\n <!-- Entrare e mappare sono due gesti diversi: un record intero e' una sorgente valida. -->\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-btn--ghost fb-btn--icon fb-tr__enter\"\r\n [attr.aria-label]=\"'Entra in ' + source.label\"\r\n (click)=\"enterSource(source.path)\"\r\n >\r\n \u203A\r\n </button>\r\n }\r\n </li>\r\n } @empty {\r\n @if (sourcePending()) {\r\n <li class=\"fb-tr__empty\">Caricamento\u2026</li>\r\n } @else if (sourceUndeclared()) {\r\n <li class=\"fb-tr__empty\">\r\n Il catalogo non dichiara cosa c\u2019e\u2019 qui dentro: non significa che non ci sia niente. Il\r\n percorso si scrive a mano nel dettaglio.\r\n </li>\r\n } @else {\r\n <li class=\"fb-tr__empty\">Nessuna sorgente con questo nome.</li>\r\n }\r\n }\r\n </ul>\r\n </aside>\r\n\r\n <!-- ========================================================= destinazione -->\r\n <section class=\"fb-tr__panel fb-tr__map\" aria-label=\"Destinazione\">\r\n <h3 class=\"fb-tr__panel-title\">\r\n Destinazione\r\n @if (slotCount()) {\r\n <span class=\"fb-tr__count\">{{ mappedCount() }} di {{ slotCount() }} mappate</span>\r\n } @else if (mappedCount()) {\r\n <span class=\"fb-tr__count\">{{ mappedCount() }} mappate</span>\r\n }\r\n </h3>\r\n\r\n <!-- Il tipo del risultato e' la forma della colonna: sta in cima, non in fondo. -->\r\n <div class=\"fb-tr__result\">\r\n <div class=\"fb-fields-row\">\r\n <div class=\"fb-field fb-field--compact\">\r\n <label class=\"fb-field__label fb-field__label--required\">Tipo del risultato</label>\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"transform().dataType || ''\"\r\n (change)=\"setDataType($any($event.target).value)\"\r\n >\r\n <option value=\"\">\u2014 scegli \u2014</option>\r\n @for (type of dataTypes(); track type.value) {\r\n <option [value]=\"type.value\">{{ type.label }}</option>\r\n }\r\n </select>\r\n </div>\r\n\r\n @if (requiresObjectType()) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">\r\n {{ isStructureTarget() ? 'Classe del risultato' : 'Tipo dell\u2019oggetto' }}\r\n </label>\r\n @if (isStructureTarget()) {\r\n <!-- \u00A74.7: una classe del backend. Le destinazioni delle azioni sono i suoi membri. -->\r\n <fb-structure-picker\r\n [value]=\"transform().objectType\"\r\n label=\"Classe del risultato\"\r\n (valueChange)=\"setObjectType($event ?? '')\"\r\n />\r\n } @else if (transform().dataType === 'Enum') {\r\n <!-- Le enumerazioni sono un dizionario chiuso: non c'e' scrittura libera da concedere. -->\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"transform().objectType || ''\"\r\n (change)=\"setObjectType($any($event.target).value)\"\r\n >\r\n <option value=\"\">\u2014 scegli \u2014</option>\r\n @for (entry of enumOptions(); track entry.name) {\r\n <option [value]=\"entry.name\">{{ entry.label || entry.name }}</option>\r\n }\r\n </select>\r\n } @else {\r\n <fb-object-picker\r\n [value]=\"transform().objectType\"\r\n label=\"Tipo dell\u2019oggetto\"\r\n placeholder=\"Scrivi o scegli un oggetto\"\r\n (valueChange)=\"setObjectType($event ?? '')\"\r\n />\r\n }\r\n </div>\r\n }\r\n\r\n <label class=\"fb-check fb-tr__collection\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"transform().isCollection === true\"\r\n (change)=\"setIsCollection($any($event.target).checked)\"\r\n />\r\n Collection\r\n </label>\r\n </div>\r\n\r\n @if (!transform().dataType) {\r\n <p class=\"fb-field__error\">Obbligatorio (TRANSFORM_DATA_TYPE_MISSING).</p>\r\n }\r\n @if (missingObjectType()) {\r\n <p class=\"fb-field__error\">\r\n La classe e\u2019 obbligatoria: senza, l\u2019attivazione e\u2019 bloccata (OBJECT_TYPE_MISSING).\r\n </p>\r\n }\r\n @if (targetUndeclared()) {\r\n <p class=\"fb-callout fb-callout--warn\">\r\n Il catalogo non dichiara i {{ isStructureTarget() ? 'membri' : 'campi' }} di\r\n <code>{{ transform().objectType }}</code>: le destinazioni si scrivono a mano, e nessuna di\r\n esse viene accusata di non esistere.\r\n </p>\r\n }\r\n </div>\r\n\r\n <ul class=\"fb-tr__list fb-tr__rows\">\r\n @for (row of targets(); track row.key) {\r\n <!--\r\n `data-fb-slot` e' cio' che il rilascio cerca: il bersaglio lo calcola `hitTest` dal DOM,\r\n non una drop list del CDK.\r\n -->\r\n <li\r\n class=\"fb-tr__row\"\r\n [attr.data-fb-slot]=\"row.key\"\r\n [class.fb-tr__row--selected]=\"isSelected(row)\"\r\n [class.fb-tr__row--drop]=\"dropKey() === row.key\"\r\n [class.fb-tr__row--empty]=\"row.index < 0\"\r\n >\r\n <button type=\"button\" class=\"fb-tr__row-main\" (click)=\"select(row.key)\">\r\n <span class=\"fb-tr__op\" [attr.title]=\"operationLabel(operationOf(row))\" aria-hidden=\"true\">\r\n {{ operationIcon(row) }}\r\n </span>\r\n <span class=\"fb-tr__row-text\">\r\n <span class=\"fb-tr__row-name\">\r\n {{ row.label }}\r\n @if (row.isCollection) {\r\n <span class=\"fb-tr__badge\" aria-label=\"collection\">[ ]</span>\r\n }\r\n @if (row.isUnknown) {\r\n <span class=\"fb-tr__badge fb-tr__badge--warn\">fuori catalogo</span>\r\n }\r\n </span>\r\n @if (row.index < 0) {\r\n <span class=\"fb-tr__row-src fb-tr__row-src--empty\">non mappata</span>\r\n } @else if (sourceSummary(row)) {\r\n <span class=\"fb-tr__row-src\">\u2190 {{ sourceSummary(row) }}</span>\r\n } @else {\r\n <span class=\"fb-tr__row-src fb-tr__row-src--empty\">sorgente da indicare</span>\r\n }\r\n </span>\r\n @if (row.dataType) {\r\n <span class=\"fb-tr__row-type\">{{ row.dataType }}</span>\r\n }\r\n @if (issuesOf(row).length) {\r\n <span class=\"fb-tr__row-issue\" [attr.title]=\"issuesOf(row)[0].message\">!</span>\r\n }\r\n </button>\r\n @if (row.index >= 0) {\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-btn--ghost fb-btn--icon\"\r\n [attr.aria-label]=\"'Togli la mappatura di ' + row.label\"\r\n (click)=\"removeAction(row)\"\r\n >\r\n \u00D7\r\n </button>\r\n }\r\n </li>\r\n } @empty {\r\n <li class=\"fb-empty\">\r\n Nessuna trasformazione: l\u2019elemento non produce nulla (TRANSFORM_WITHOUT_VALUES).\r\n </li>\r\n }\r\n </ul>\r\n\r\n <button type=\"button\" class=\"fb-btn\" (click)=\"addAction()\">\r\n {{ hasTargetCatalog() ? 'Aggiungi una destinazione fuori catalogo' : 'Aggiungi trasformazione' }}\r\n </button>\r\n\r\n <p class=\"fb-callout\">\r\n Il risultato e\u2019 l\u2019<strong>output automatico</strong> dell\u2019elemento: si referenzia con\r\n <code>{{ name() }}</code>.\r\n </p>\r\n </section>\r\n\r\n <!-- ============================================================ dettaglio -->\r\n <aside class=\"fb-tr__panel fb-tr__detail\" aria-label=\"Dettaglio della mappatura\">\r\n @if (selected(); as row) {\r\n <h3 class=\"fb-tr__panel-title\">{{ row.label }}</h3>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Operazione</label>\r\n <div class=\"fb-tr__modes\" role=\"group\" aria-label=\"Operazione\">\r\n @for (type of transformTypes(); track type.value) {\r\n <button\r\n type=\"button\"\r\n class=\"fb-tr__mode\"\r\n [class.fb-tr__mode--active]=\"operationOf(row) === type.value\"\r\n (click)=\"setOperation(row, type.value)\"\r\n >\r\n {{ type.label }}\r\n </button>\r\n }\r\n </div>\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">\r\n {{ isStructureTarget() ? 'Membro di destinazione' : 'Campo di destinazione' }}\r\n </label>\r\n @if (isStructureTarget()) {\r\n <!-- Solo i membri scrivibili: un membro calcolato e' TARGET_NOT_WRITABLE (\u00A74.7). -->\r\n <fb-structure-member-picker\r\n [value]=\"row.field\"\r\n [className]=\"transform().objectType\"\r\n usage=\"writable\"\r\n label=\"Membro di destinazione\"\r\n (valueChange)=\"setOutputField(row, $event ?? '')\"\r\n />\r\n } @else if (targetObject()) {\r\n <!-- Il risultato e' un record: le destinazioni sono i campi della sua entita' (\u00A74.4). -->\r\n <fb-field-picker\r\n [value]=\"row.field\"\r\n [object]=\"targetObject()\"\r\n label=\"Campo di destinazione\"\r\n (valueChange)=\"setOutputField(row, $event ?? '')\"\r\n />\r\n } @else {\r\n <input\r\n class=\"fb-input\"\r\n [value]=\"row.field\"\r\n placeholder=\"Totale\"\r\n (input)=\"setOutputField(row, $any($event.target).value)\"\r\n />\r\n }\r\n </div>\r\n\r\n @if (isMap(row)) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Valore</label>\r\n <!--\r\n Il tipo di cio' che si scrive guida il controllo: su un campo o un membro Enum i valori\r\n proponibili sono quelli del suo tipo (\u00A74.6).\r\n -->\r\n <fb-value-editor\r\n [value]=\"row.action?.value ?? undefined\"\r\n label=\"Valore\"\r\n [dataType]=\"mapDataType(row)\"\r\n [objectType]=\"mapObjectType(row)\"\r\n (valueChange)=\"setMapValue(row, $event)\"\r\n />\r\n </div>\r\n }\r\n\r\n @if (needsAggregationValues(row)) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Collection su cui aggregare</label>\r\n <fb-reference-picker\r\n [value]=\"aggregationCollection(row.action)\"\r\n [isCollection]=\"true\"\r\n placeholder=\"Scegli una collection\"\r\n (valueChange)=\"setAggregationCollection(row, $event)\"\r\n />\r\n </div>\r\n }\r\n\r\n @if (needsAggregationField(row)) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Campo da sommare</label>\r\n <!--\r\n Nel documento va il **nome** del campo (`stringValue`, \u00A75.13); dove il tipo della\r\n collection e' noto, il nome lo si sceglie da un elenco invece di ricordarlo.\r\n -->\r\n @if (aggregationObject(row); as object) {\r\n <fb-field-picker\r\n [value]=\"aggregationField(row.action)\"\r\n [object]=\"object\"\r\n label=\"Campo da sommare\"\r\n (valueChange)=\"setAggregationField(row, $event ?? '')\"\r\n />\r\n } @else if (aggregationClass(row); as className) {\r\n <fb-structure-member-picker\r\n [value]=\"aggregationField(row.action)\"\r\n [className]=\"className\"\r\n label=\"Membro da sommare\"\r\n (valueChange)=\"setAggregationField(row, $event ?? '')\"\r\n />\r\n } @else {\r\n <input\r\n class=\"fb-input\"\r\n [value]=\"aggregationField(row.action)\"\r\n placeholder=\"Importo\"\r\n (input)=\"setAggregationField(row, $any($event.target).value)\"\r\n />\r\n }\r\n </div>\r\n }\r\n\r\n @for (issue of issuesOf(row); track $index) {\r\n <p class=\"fb-callout fb-callout--error\">{{ issue.message }} ({{ issue.code }})</p>\r\n }\r\n } @else {\r\n <p class=\"fb-tr__hint\">\r\n Scegli il tipo del risultato: le destinazioni compaiono da l\u00EC, e la mappatura si fa\r\n trascinando una sorgente su una di esse.\r\n </p>\r\n }\r\n </aside>\r\n</div>\r\n\r\n<fb-connector-editor\r\n [nodeName]=\"name()\"\r\n [node]=\"node()\"\r\n [outlets]=\"outlets()\"\r\n (connectorChanged)=\"onConnectorChanged($event)\"\r\n/>\r\n", styles: [":host{display:block;container-type:inline-size}.fb-tr{display:grid;grid-template-columns:240px minmax(0,1fr) 320px;gap:12px;align-items:start;margin-bottom:14px}@container (max-width: 1040px){.fb-tr{grid-template-columns:minmax(0,1fr)}}.fb-tr__panel{min-width:0;padding:10px;border:1px solid var(--fb-border, #e2e5eb);border-radius:var(--fb-radius, 10px);background:var(--fb-surface-alt, #f7f8fa)}.fb-tr__map{background:var(--fb-surface, #fff)}.fb-tr__panel-title{display:flex;align-items:baseline;justify-content:space-between;gap:8px;margin:0 0 8px;font-size:11px;font-weight:700;letter-spacing:.05em;text-transform:uppercase;color:var(--fb-text-subtle, #98a2b3);overflow-wrap:anywhere}.fb-tr__count{font-size:10px;font-weight:600;letter-spacing:0;text-transform:none;color:var(--fb-text-muted, #6b7086)}.fb-tr__hint{margin:6px 0;font-size:11px;line-height:1.45;color:var(--fb-text-muted, #6b7086)}.fb-tr__search{margin-bottom:6px}.fb-tr__crumbs{display:flex;flex-wrap:wrap;align-items:center;gap:2px;margin-bottom:6px}.fb-tr__crumb{padding:2px 4px;border:0;border-radius:var(--fb-radius-xs, 6px);background:transparent;color:var(--fb-accent, #4f6ef7);font:inherit;font-size:11px;cursor:pointer}.fb-tr__crumb:hover{background:color-mix(in srgb,var(--fb-accent, #4f6ef7) 10%,transparent)}.fb-tr__crumb-sep{color:var(--fb-text-subtle, #98a2b3);font-size:11px}.fb-tr__list{display:flex;flex-direction:column;gap:4px;margin:0;padding:0;max-height:340px;overflow-y:auto;overscroll-behavior:contain;list-style:none}.fb-tr__empty{padding:8px;border:1px dashed var(--fb-border, #d6dae1);border-radius:var(--fb-radius-xs, 6px);font-size:11px;line-height:1.45;color:var(--fb-text-muted, #667085)}.fb-tr__source-row{display:flex;align-items:stretch;gap:2px}.fb-tr__source{display:flex;flex:1;flex-direction:column;gap:1px;min-width:0;padding:5px 8px;border:1px solid var(--fb-border, #e2e5eb);border-radius:var(--fb-radius-xs, 6px);background:var(--fb-surface, #fff);color:var(--fb-text, #1a1c23);font:inherit;font-size:12px;text-align:left;cursor:grab}.fb-tr__source:hover{border-color:var(--fb-accent, #4f6ef7)}.fb-tr__source--dragging{opacity:.6}.fb-tr__source-name,.fb-tr__source-detail{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fb-tr__source-detail{font-size:10px;color:var(--fb-text-subtle, #98a2b3)}.fb-tr__enter{flex:none}.fb-tr__badge{margin-left:4px;padding:0 4px;border-radius:4px;background:var(--fb-surface-sunken, #eef0f4);font-size:9px;font-weight:700;color:var(--fb-text-muted, #6b7086)}.fb-tr__badge--warn{background:color-mix(in srgb,var(--fb-warning, #b7791f) 16%,transparent);color:var(--fb-warning, #b7791f)}.fb-tr__result{margin-bottom:10px;padding-bottom:8px;border-bottom:1px solid var(--fb-border-subtle, #eef0f4)}.fb-tr__collection{align-self:end;padding-bottom:6px;white-space:nowrap}.fb-tr__rows{max-height:420px;margin-bottom:8px}.fb-tr__row{display:flex;align-items:stretch;gap:2px;border:1px solid var(--fb-border, #e2e5eb);border-radius:var(--fb-radius-xs, 6px);background:var(--fb-surface, #fff)}.fb-tr__row--empty{border-style:dashed;background:var(--fb-surface-alt, #f7f8fa)}.fb-tr__row--selected{border-color:var(--fb-accent, #4f6ef7);box-shadow:0 0 0 1px var(--fb-accent, #4f6ef7) inset}.fb-tr__row--drop{border-color:var(--fb-accent, #4f6ef7);border-style:solid;background:color-mix(in srgb,var(--fb-accent, #4f6ef7) 12%,transparent)}.fb-tr__row-main{display:flex;flex:1;align-items:center;gap:8px;min-width:0;padding:6px 8px;border:0;border-radius:var(--fb-radius-xs, 6px);background:transparent;color:var(--fb-text, #1a1c23);font:inherit;text-align:left;cursor:pointer}.fb-tr__op{display:grid;flex:none;place-items:center;width:20px;height:20px;border-radius:50%;background:var(--fb-surface-sunken, #eef0f4);font-size:11px;font-weight:700;color:var(--fb-text-muted, #6b7086)}.fb-tr__row-text{display:flex;flex:1;flex-direction:column;gap:1px;min-width:0}.fb-tr__row-name{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:12px;font-weight:600}.fb-tr__row-src{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:10px;color:var(--fb-text-muted, #6b7086)}.fb-tr__row-src--empty{color:var(--fb-text-subtle, #98a2b3);font-style:italic}.fb-tr__row-type{flex:none;font-size:10px;color:var(--fb-text-subtle, #98a2b3)}.fb-tr__row-issue{display:grid;flex:none;place-items:center;width:16px;height:16px;border-radius:50%;background:color-mix(in srgb,var(--fb-error, #dc2626) 14%,transparent);font-size:10px;font-weight:700;color:var(--fb-error, #dc2626)}.fb-tr__modes{margin-top:2px}\n"] }]
|
|
13151
13908
|
}], ctorParameters: () => [] });
|
|
13152
13909
|
|
|
13153
13910
|
/**
|
|
@@ -14346,11 +15103,11 @@ class GroupPanelComponent {
|
|
|
14346
15103
|
this.createRequested.emit();
|
|
14347
15104
|
}
|
|
14348
15105
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.28", ngImport: i0, type: GroupPanelComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
14349
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.28", type: GroupPanelComponent, isStandalone: true, selector: "fb-group-panel", inputs: { isEditable: { classPropertyName: "isEditable", publicName: "isEditable", isSignal: true, isRequired: false, transformFunction: null }, selectedNodeNames: { classPropertyName: "selectedNodeNames", publicName: "selectedNodeNames", isSignal: true, isRequired: false, transformFunction: null }, target: { classPropertyName: "target", publicName: "target", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { closed: "closed", createRequested: "createRequested", memberFocused: "memberFocused" }, ngImport: i0, template: "<header class=\"fb-grp__header\">\r\n <h2 class=\"fb-grp__title\">Riquadri</h2>\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-btn--icon\"\r\n [disabled]=\"!isEditable()\"\r\n title=\"Crea un riquadro attorno agli elementi selezionati\"\r\n (click)=\"create()\"\r\n >\r\n Nuovo\r\n </button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--ghost fb-btn--icon\" aria-label=\"Chiudi\" (click)=\"close()\">\u00D7</button>\r\n</header>\r\n\r\n<div class=\"fb-grp__body\">\r\n <p class=\"fb-section__note\">\r\n Un riquadro documenta un pezzo di flow e non ne cambia il comportamento: il runtime non lo\r\n legge mai, non ha collegamenti e i suoi rilievi sono tutti avvisi. Il nome sta in uno spazio\r\n dei nomi a parte, quindi puo\u2019 coincidere con quello di una variabile.\r\n </p>\r\n\r\n @if (!groups().length) {\r\n <p class=\"fb-empty\">\r\n Nessun riquadro. Seleziona gli elementi sul canvas e premi \u00ABNuovo\u00BB: il riquadro nasce\r\n attorno a loro.\r\n </p>\r\n }\r\n\r\n <div class=\"fb-list\">\r\n @for (item of groups(); track $index) {\r\n <div class=\"fb-list__item\">\r\n <div class=\"fb-list__header\">\r\n <button type=\"button\" class=\"fb-grp__name\" (click)=\"toggleOpen(item.name)\">\r\n <span class=\"fb-list__title\">{{ item.group.label || item.name || '(senza nome)' }}</span>\r\n <span class=\"fb-grp__meta\">\r\n {{ membersOf($index).length }}\r\n {{ membersOf($index).length === 1 ? 'elemento' : 'elementi' }}\r\n @if (item.group.isCollapsed) {\r\n \u00B7 chiuso\r\n }\r\n @if (isAutoSized($index)) {\r\n \u00B7 misura sui membri\r\n }\r\n </span>\r\n </button>\r\n <span class=\"fb-list__spacer\"></span>\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-btn--ghost fb-btn--icon\"\r\n [disabled]=\"!isEditable()\"\r\n aria-label=\"Elimina il riquadro\"\r\n title=\"Elimina il riquadro: gli elementi che contiene restano\"\r\n (click)=\"remove($index)\"\r\n >\r\n \u00D7\r\n </button>\r\n </div>\r\n\r\n @if (nameProblem($index)) {\r\n <!-- Avviso, non errore: nessun rilievo su un riquadro blocca l\u2019attivazione (\u00A73.6). -->\r\n <p class=\"fb-field__hint fb-field__hint--warn\">{{ nameProblem($index) }}</p>\r\n }\r\n\r\n @if (isOpen(item.name)) {\r\n <div class=\"fb-fields-row\">\r\n <label class=\"fb-field\">\r\n <span class=\"fb-field__label\">Intestazione</span>\r\n <input\r\n [value]=\"item.group.label || ''\"\r\n placeholder=\"Preparazione dell\u2019ordine\"\r\n [disabled]=\"!isEditable()\"\r\n (input)=\"setLabel($index, $any($event.target).value)\"\r\n />\r\n </label>\r\n <label class=\"fb-field\">\r\n <span class=\"fb-field__label\">Nome tecnico</span>\r\n <input\r\n [value]=\"item.name\"\r\n placeholder=\"Riquadro_Preparazione\"\r\n [disabled]=\"!isEditable()\"\r\n (input)=\"setName($index, $any($event.target).value)\"\r\n />\r\n </label>\r\n </div>\r\n\r\n <label class=\"fb-field\">\r\n <span class=\"fb-field__label\">Commento</span>\r\n <textarea\r\n rows=\"3\"\r\n [value]=\"item.group.description || ''\"\r\n placeholder=\"Cosa fa questo pezzo di flow, e perche\u2019.\"\r\n [disabled]=\"!isEditable()\"\r\n (input)=\"setDescription($index, $any($event.target).value)\"\r\n ></textarea>\r\n <span class=\"fb-field__hint\">\r\n Sul canvas ne compaiono le prime righe; per intero sta qui e nel titolo della barra.\r\n </span>\r\n </label>\r\n\r\n <div class=\"fb-fields-row\">\r\n <label class=\"fb-field fb-field--compact\">\r\n <span class=\"fb-field__label\">Colore</span>\r\n <select\r\n [fbValue]=\"colorOf($index)\"\r\n [disabled]=\"!isEditable()\"\r\n (change)=\"setColor($index, $any($event.target).value)\"\r\n >\r\n <option value=\"\">(nessuno)</option>\r\n @for (color of colors; track color.value) {\r\n <option [value]=\"color.value\">{{ color.label }}</option>\r\n }\r\n <!--\r\n `color` e\u2019 un token concordato fra editor e backend (\u00A73.6): un valore che non\r\n proponiamo non e\u2019 sbagliato, e senza questa opzione la select resterebbe bianca\r\n su un riquadro scritto altrove.\r\n -->\r\n @if (isColorOutOfList($index)) {\r\n <option [value]=\"colorOf($index)\">{{ colorOf($index) }} (fuori elenco)</option>\r\n }\r\n </select>\r\n </label>\r\n <label class=\"fb-field fb-field--compact fb-grp__check\">\r\n <span class=\"fb-field__label\">Chiuso</span>\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"!!item.group.isCollapsed\"\r\n [disabled]=\"!isEditable()\"\r\n (change)=\"setCollapsed($index, $any($event.target).checked)\"\r\n />\r\n </label>\r\n </div>\r\n\r\n <div class=\"fb-grp__members\">\r\n <div class=\"fb-list__header\">\r\n <span class=\"fb-field__label\">Elementi nel riquadro</span>\r\n <span class=\"fb-list__spacer\"></span>\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-btn--icon\"\r\n [disabled]=\"!isEditable() || !canAddSelection()\"\r\n [title]=\"\r\n canAddSelection()\r\n ? 'Aggiungi gli elementi selezionati sul canvas'\r\n : 'Seleziona degli elementi sul canvas, poi aggiungili qui'\r\n \"\r\n (click)=\"addSelection($index)\"\r\n >\r\n Aggiungi la selezione\r\n </button>\r\n </div>\r\n\r\n @if (!membersOf($index).length) {\r\n <p class=\"fb-empty\">\r\n Vuoto. Trascina un elemento dentro la cornice sul canvas, oppure selezionalo e usa\r\n \u00ABAggiungi la selezione\u00BB.\r\n </p>\r\n }\r\n\r\n @for (member of membersOf($index); track $index) {\r\n <div class=\"fb-grp__member\">\r\n <button\r\n type=\"button\"\r\n class=\"fb-grp__member-name\"\r\n [class.fb-grp__member-name--stale]=\"!isKnownMember(member)\"\r\n [title]=\"isKnownMember(member) ? 'Porta la vista su questo elemento' : 'Questo elemento non esiste piu\u2019'\"\r\n [disabled]=\"!isKnownMember(member)\"\r\n (click)=\"memberFocused.emit(member)\"\r\n >\r\n {{ member }}\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-btn--ghost fb-btn--icon\"\r\n [disabled]=\"!isEditable()\"\r\n aria-label=\"Togli dal riquadro\"\r\n title=\"Togli dal riquadro: l\u2019elemento resta nel flow\"\r\n (click)=\"removeMember($index, member)\"\r\n >\r\n \u00D7\r\n </button>\r\n </div>\r\n }\r\n\r\n @if (staleMembersOf($index).length) {\r\n <p class=\"fb-callout fb-callout--warn\">\r\n <span>\r\n {{ staleMembersOf($index).length }}\r\n {{ staleMembersOf($index).length === 1 ? 'membro nomina un elemento' : 'membri nominano elementi' }}\r\n che non esiste piu\u2019 (GROUP_MEMBER_UNKNOWN). E\u2019 un avviso: non blocca\r\n l\u2019attivazione, e la ripulitura la fa l\u2019editor.\r\n </span>\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-btn--icon\"\r\n [disabled]=\"!isEditable()\"\r\n (click)=\"cleanStale($index)\"\r\n >\r\n Ripulisci\r\n </button>\r\n </p>\r\n }\r\n </div>\r\n }\r\n </div>\r\n }\r\n </div>\r\n</div>\r\n", styles: [":host{display:flex;flex-direction:column;height:100%;min-height:0;background:var(--fb-surface, #fff)}.fb-grp__header{display:flex;align-items:center;gap:6px;padding:10px 12px;border-bottom:1px solid var(--fb-border-subtle, #e6e9ee)}.fb-grp__title{margin:0;flex:1;font-size:14px;font-weight:600;color:var(--fb-text, #1d2939)}.fb-grp__body{flex:1;min-height:0;padding:10px 12px 16px;overflow-y:auto}.fb-grp__name{display:flex;flex-direction:column;gap:2px;padding:0;border:none;background:transparent;font:inherit;text-align:left;cursor:pointer}.fb-grp__meta{font-size:10px;color:var(--fb-text-muted, #667085)}.fb-grp__check{flex-direction:row;align-items:center;gap:6px}.fb-grp__members{margin-top:4px;padding-top:6px;border-top:1px dashed var(--fb-border, #d6dae1)}.fb-grp__member{display:flex;align-items:center;gap:4px}.fb-grp__member-name{flex:1;min-width:0;padding:3px 4px;border:none;border-radius:var(--fb-radius-xs, 6px);background:transparent;color:var(--fb-text, #1d2939);font:inherit;font-size:12px;text-align:left;overflow-wrap:anywhere;cursor:pointer}.fb-grp__member-name:hover:not(:disabled){background:var(--fb-surface-alt, #f7f8fa)}.fb-grp__member-name--stale{color:var(--fb-warning, #b7791f);text-decoration:line-through;cursor:default}\n"], dependencies: [{ kind: "directive", type: SelectValueDirective, selector: "select[fbValue]", inputs: ["fbValue"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
15106
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.28", type: GroupPanelComponent, isStandalone: true, selector: "fb-group-panel", inputs: { isEditable: { classPropertyName: "isEditable", publicName: "isEditable", isSignal: true, isRequired: false, transformFunction: null }, selectedNodeNames: { classPropertyName: "selectedNodeNames", publicName: "selectedNodeNames", isSignal: true, isRequired: false, transformFunction: null }, target: { classPropertyName: "target", publicName: "target", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { closed: "closed", createRequested: "createRequested", memberFocused: "memberFocused" }, ngImport: i0, template: "<header class=\"fb-grp__header\">\r\n <h2 class=\"fb-grp__title\">Riquadri</h2>\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-btn--icon\"\r\n [disabled]=\"!isEditable()\"\r\n title=\"Crea un riquadro attorno agli elementi selezionati\"\r\n (click)=\"create()\"\r\n >\r\n Nuovo\r\n </button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--ghost fb-btn--icon\" aria-label=\"Chiudi\" (click)=\"close()\">\u00D7</button>\r\n</header>\r\n\r\n<div class=\"fb-grp__body\">\r\n <p class=\"fb-section__note\">\r\n Un riquadro documenta un pezzo di flow e non ne cambia il comportamento: il runtime non lo\r\n legge mai, non ha collegamenti e i suoi rilievi sono tutti avvisi. Il nome sta in uno spazio\r\n dei nomi a parte, quindi puo\u2019 coincidere con quello di una variabile.\r\n </p>\r\n\r\n @if (!groups().length) {\r\n <p class=\"fb-empty\">\r\n Nessun riquadro. Seleziona gli elementi sul canvas e premi \u00ABNuovo\u00BB: il riquadro nasce\r\n attorno a loro.\r\n </p>\r\n }\r\n\r\n <div class=\"fb-list\">\r\n @for (item of groups(); track $index) {\r\n <div class=\"fb-list__item\">\r\n <div class=\"fb-list__header\">\r\n <button type=\"button\" class=\"fb-grp__name\" (click)=\"toggleOpen(item.name)\">\r\n <span class=\"fb-list__title\">{{ item.group.label || item.name || '(senza nome)' }}</span>\r\n <span class=\"fb-grp__meta\">\r\n {{ membersOf($index).length }}\r\n {{ membersOf($index).length === 1 ? 'elemento' : 'elementi' }}\r\n @if (item.group.isCollapsed) {\r\n \u00B7 chiuso: nascosti sul canvas\r\n }\r\n @if (isAutoSized($index)) {\r\n \u00B7 misura sui membri\r\n }\r\n </span>\r\n </button>\r\n <span class=\"fb-list__spacer\"></span>\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-btn--ghost fb-btn--icon\"\r\n [disabled]=\"!isEditable()\"\r\n aria-label=\"Elimina il riquadro\"\r\n title=\"Elimina il riquadro: gli elementi che contiene restano\"\r\n (click)=\"remove($index)\"\r\n >\r\n \u00D7\r\n </button>\r\n </div>\r\n\r\n @if (nameProblem($index)) {\r\n <!-- Avviso, non errore: nessun rilievo su un riquadro blocca l\u2019attivazione (\u00A73.6). -->\r\n <p class=\"fb-field__hint fb-field__hint--warn\">{{ nameProblem($index) }}</p>\r\n }\r\n\r\n @if (isOpen(item.name)) {\r\n <div class=\"fb-fields-row\">\r\n <label class=\"fb-field\">\r\n <span class=\"fb-field__label\">Intestazione</span>\r\n <input\r\n [value]=\"item.group.label || ''\"\r\n placeholder=\"Preparazione dell\u2019ordine\"\r\n [disabled]=\"!isEditable()\"\r\n (input)=\"setLabel($index, $any($event.target).value)\"\r\n />\r\n </label>\r\n <label class=\"fb-field\">\r\n <span class=\"fb-field__label\">Nome tecnico</span>\r\n <input\r\n [value]=\"item.name\"\r\n placeholder=\"Riquadro_Preparazione\"\r\n [disabled]=\"!isEditable()\"\r\n (input)=\"setName($index, $any($event.target).value)\"\r\n />\r\n </label>\r\n </div>\r\n\r\n <label class=\"fb-field\">\r\n <span class=\"fb-field__label\">Commento</span>\r\n <textarea\r\n rows=\"3\"\r\n [value]=\"item.group.description || ''\"\r\n placeholder=\"Cosa fa questo pezzo di flow, e perche\u2019.\"\r\n [disabled]=\"!isEditable()\"\r\n (input)=\"setDescription($index, $any($event.target).value)\"\r\n ></textarea>\r\n <span class=\"fb-field__hint\">\r\n Sul canvas ne compaiono le prime righe; per intero sta qui e nel titolo della barra.\r\n </span>\r\n </label>\r\n\r\n <div class=\"fb-fields-row\">\r\n <label class=\"fb-field fb-field--compact\">\r\n <span class=\"fb-field__label\">Colore</span>\r\n <select\r\n [fbValue]=\"colorOf($index)\"\r\n [disabled]=\"!isEditable()\"\r\n (change)=\"setColor($index, $any($event.target).value)\"\r\n >\r\n <option value=\"\">(nessuno)</option>\r\n @for (color of colors; track color.value) {\r\n <option [value]=\"color.value\">{{ color.label }}</option>\r\n }\r\n <!--\r\n `color` e\u2019 un token concordato fra editor e backend (\u00A73.6): un valore che non\r\n proponiamo non e\u2019 sbagliato, e senza questa opzione la select resterebbe bianca\r\n su un riquadro scritto altrove.\r\n -->\r\n @if (isColorOutOfList($index)) {\r\n <option [value]=\"colorOf($index)\">{{ colorOf($index) }} (fuori elenco)</option>\r\n }\r\n </select>\r\n </label>\r\n <!--\r\n Chiuso non e' un dettaglio grafico: sul canvas gli elementi del riquadro **non si\r\n disegnano piu'** e al loro posto compare una pastiglia, a cui si attaccano gli archi\r\n che entrano nel riquadro o ne escono. Il documento non cambia: e' un modo di guardarlo.\r\n -->\r\n <label class=\"fb-field fb-field--compact fb-grp__check\">\r\n <span class=\"fb-field__label\" title=\"Nasconde gli elementi del riquadro dietro una pastiglia\">\r\n Chiuso\r\n </span>\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"!!item.group.isCollapsed\"\r\n [disabled]=\"!isEditable()\"\r\n (change)=\"setCollapsed($index, $any($event.target).checked)\"\r\n />\r\n </label>\r\n </div>\r\n\r\n <div class=\"fb-grp__members\">\r\n <div class=\"fb-list__header\">\r\n <span class=\"fb-field__label\">Elementi nel riquadro</span>\r\n <span class=\"fb-list__spacer\"></span>\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-btn--icon\"\r\n [disabled]=\"!isEditable() || !canAddSelection()\"\r\n [title]=\"\r\n canAddSelection()\r\n ? 'Aggiungi gli elementi selezionati sul canvas'\r\n : 'Seleziona degli elementi sul canvas, poi aggiungili qui'\r\n \"\r\n (click)=\"addSelection($index)\"\r\n >\r\n Aggiungi la selezione\r\n </button>\r\n </div>\r\n\r\n @if (!membersOf($index).length) {\r\n <p class=\"fb-empty\">\r\n Vuoto. Trascina un elemento dentro la cornice sul canvas, oppure selezionalo e usa\r\n \u00ABAggiungi la selezione\u00BB.\r\n </p>\r\n }\r\n\r\n @for (member of membersOf($index); track $index) {\r\n <div class=\"fb-grp__member\">\r\n <button\r\n type=\"button\"\r\n class=\"fb-grp__member-name\"\r\n [class.fb-grp__member-name--stale]=\"!isKnownMember(member)\"\r\n [title]=\"isKnownMember(member) ? 'Porta la vista su questo elemento' : 'Questo elemento non esiste piu\u2019'\"\r\n [disabled]=\"!isKnownMember(member)\"\r\n (click)=\"memberFocused.emit(member)\"\r\n >\r\n {{ member }}\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-btn--ghost fb-btn--icon\"\r\n [disabled]=\"!isEditable()\"\r\n aria-label=\"Togli dal riquadro\"\r\n title=\"Togli dal riquadro: l\u2019elemento resta nel flow\"\r\n (click)=\"removeMember($index, member)\"\r\n >\r\n \u00D7\r\n </button>\r\n </div>\r\n }\r\n\r\n @if (staleMembersOf($index).length) {\r\n <p class=\"fb-callout fb-callout--warn\">\r\n <span>\r\n {{ staleMembersOf($index).length }}\r\n {{ staleMembersOf($index).length === 1 ? 'membro nomina un elemento' : 'membri nominano elementi' }}\r\n che non esiste piu\u2019 (GROUP_MEMBER_UNKNOWN). E\u2019 un avviso: non blocca\r\n l\u2019attivazione, e la ripulitura la fa l\u2019editor.\r\n </span>\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-btn--icon\"\r\n [disabled]=\"!isEditable()\"\r\n (click)=\"cleanStale($index)\"\r\n >\r\n Ripulisci\r\n </button>\r\n </p>\r\n }\r\n </div>\r\n }\r\n </div>\r\n }\r\n </div>\r\n</div>\r\n", styles: [":host{display:flex;flex-direction:column;height:100%;min-height:0;background:var(--fb-surface, #fff)}.fb-grp__header{display:flex;align-items:center;gap:6px;padding:10px 12px;border-bottom:1px solid var(--fb-border-subtle, #e6e9ee)}.fb-grp__title{margin:0;flex:1;font-size:14px;font-weight:600;color:var(--fb-text, #1d2939)}.fb-grp__body{flex:1;min-height:0;padding:10px 12px 16px;overflow-y:auto}.fb-grp__name{display:flex;flex-direction:column;gap:2px;padding:0;border:none;background:transparent;font:inherit;text-align:left;cursor:pointer}.fb-grp__meta{font-size:10px;color:var(--fb-text-muted, #667085)}.fb-grp__check{flex-direction:row;align-items:center;gap:6px}.fb-grp__members{margin-top:4px;padding-top:6px;border-top:1px dashed var(--fb-border, #d6dae1)}.fb-grp__member{display:flex;align-items:center;gap:4px}.fb-grp__member-name{flex:1;min-width:0;padding:3px 4px;border:none;border-radius:var(--fb-radius-xs, 6px);background:transparent;color:var(--fb-text, #1d2939);font:inherit;font-size:12px;text-align:left;overflow-wrap:anywhere;cursor:pointer}.fb-grp__member-name:hover:not(:disabled){background:var(--fb-surface-alt, #f7f8fa)}.fb-grp__member-name--stale{color:var(--fb-warning, #b7791f);text-decoration:line-through;cursor:default}\n"], dependencies: [{ kind: "directive", type: SelectValueDirective, selector: "select[fbValue]", inputs: ["fbValue"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
14350
15107
|
}
|
|
14351
15108
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.28", ngImport: i0, type: GroupPanelComponent, decorators: [{
|
|
14352
15109
|
type: Component,
|
|
14353
|
-
args: [{ selector: 'fb-group-panel', standalone: true, imports: [SelectValueDirective], changeDetection: ChangeDetectionStrategy.OnPush, template: "<header class=\"fb-grp__header\">\r\n <h2 class=\"fb-grp__title\">Riquadri</h2>\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-btn--icon\"\r\n [disabled]=\"!isEditable()\"\r\n title=\"Crea un riquadro attorno agli elementi selezionati\"\r\n (click)=\"create()\"\r\n >\r\n Nuovo\r\n </button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--ghost fb-btn--icon\" aria-label=\"Chiudi\" (click)=\"close()\">\u00D7</button>\r\n</header>\r\n\r\n<div class=\"fb-grp__body\">\r\n <p class=\"fb-section__note\">\r\n Un riquadro documenta un pezzo di flow e non ne cambia il comportamento: il runtime non lo\r\n legge mai, non ha collegamenti e i suoi rilievi sono tutti avvisi. Il nome sta in uno spazio\r\n dei nomi a parte, quindi puo\u2019 coincidere con quello di una variabile.\r\n </p>\r\n\r\n @if (!groups().length) {\r\n <p class=\"fb-empty\">\r\n Nessun riquadro. Seleziona gli elementi sul canvas e premi \u00ABNuovo\u00BB: il riquadro nasce\r\n attorno a loro.\r\n </p>\r\n }\r\n\r\n <div class=\"fb-list\">\r\n @for (item of groups(); track $index) {\r\n <div class=\"fb-list__item\">\r\n <div class=\"fb-list__header\">\r\n <button type=\"button\" class=\"fb-grp__name\" (click)=\"toggleOpen(item.name)\">\r\n <span class=\"fb-list__title\">{{ item.group.label || item.name || '(senza nome)' }}</span>\r\n <span class=\"fb-grp__meta\">\r\n {{ membersOf($index).length }}\r\n {{ membersOf($index).length === 1 ? 'elemento' : 'elementi' }}\r\n @if (item.group.isCollapsed) {\r\n \u00B7 chiuso\r\n }\r\n @if (isAutoSized($index)) {\r\n \u00B7 misura sui membri\r\n }\r\n </span>\r\n </button>\r\n <span class=\"fb-list__spacer\"></span>\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-btn--ghost fb-btn--icon\"\r\n [disabled]=\"!isEditable()\"\r\n aria-label=\"Elimina il riquadro\"\r\n title=\"Elimina il riquadro: gli elementi che contiene restano\"\r\n (click)=\"remove($index)\"\r\n >\r\n \u00D7\r\n </button>\r\n </div>\r\n\r\n @if (nameProblem($index)) {\r\n <!-- Avviso, non errore: nessun rilievo su un riquadro blocca l\u2019attivazione (\u00A73.6). -->\r\n <p class=\"fb-field__hint fb-field__hint--warn\">{{ nameProblem($index) }}</p>\r\n }\r\n\r\n @if (isOpen(item.name)) {\r\n <div class=\"fb-fields-row\">\r\n <label class=\"fb-field\">\r\n <span class=\"fb-field__label\">Intestazione</span>\r\n <input\r\n [value]=\"item.group.label || ''\"\r\n placeholder=\"Preparazione dell\u2019ordine\"\r\n [disabled]=\"!isEditable()\"\r\n (input)=\"setLabel($index, $any($event.target).value)\"\r\n />\r\n </label>\r\n <label class=\"fb-field\">\r\n <span class=\"fb-field__label\">Nome tecnico</span>\r\n <input\r\n [value]=\"item.name\"\r\n placeholder=\"Riquadro_Preparazione\"\r\n [disabled]=\"!isEditable()\"\r\n (input)=\"setName($index, $any($event.target).value)\"\r\n />\r\n </label>\r\n </div>\r\n\r\n <label class=\"fb-field\">\r\n <span class=\"fb-field__label\">Commento</span>\r\n <textarea\r\n rows=\"3\"\r\n [value]=\"item.group.description || ''\"\r\n placeholder=\"Cosa fa questo pezzo di flow, e perche\u2019.\"\r\n [disabled]=\"!isEditable()\"\r\n (input)=\"setDescription($index, $any($event.target).value)\"\r\n ></textarea>\r\n <span class=\"fb-field__hint\">\r\n Sul canvas ne compaiono le prime righe; per intero sta qui e nel titolo della barra.\r\n </span>\r\n </label>\r\n\r\n <div class=\"fb-fields-row\">\r\n <label class=\"fb-field fb-field--compact\">\r\n <span class=\"fb-field__label\">Colore</span>\r\n <select\r\n [fbValue]=\"colorOf($index)\"\r\n [disabled]=\"!isEditable()\"\r\n (change)=\"setColor($index, $any($event.target).value)\"\r\n >\r\n <option value=\"\">(nessuno)</option>\r\n @for (color of colors; track color.value) {\r\n <option [value]=\"color.value\">{{ color.label }}</option>\r\n }\r\n <!--\r\n `color` e\u2019 un token concordato fra editor e backend (\u00A73.6): un valore che non\r\n proponiamo non e\u2019 sbagliato, e senza questa opzione la select resterebbe bianca\r\n su un riquadro scritto altrove.\r\n -->\r\n @if (isColorOutOfList($index)) {\r\n <option [value]=\"colorOf($index)\">{{ colorOf($index) }} (fuori elenco)</option>\r\n }\r\n </select>\r\n </label>\r\n <label class=\"fb-field fb-field--compact fb-grp__check\">\r\n <span class=\"fb-field__label\">Chiuso</span>\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"!!item.group.isCollapsed\"\r\n [disabled]=\"!isEditable()\"\r\n (change)=\"setCollapsed($index, $any($event.target).checked)\"\r\n />\r\n </label>\r\n </div>\r\n\r\n <div class=\"fb-grp__members\">\r\n <div class=\"fb-list__header\">\r\n <span class=\"fb-field__label\">Elementi nel riquadro</span>\r\n <span class=\"fb-list__spacer\"></span>\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-btn--icon\"\r\n [disabled]=\"!isEditable() || !canAddSelection()\"\r\n [title]=\"\r\n canAddSelection()\r\n ? 'Aggiungi gli elementi selezionati sul canvas'\r\n : 'Seleziona degli elementi sul canvas, poi aggiungili qui'\r\n \"\r\n (click)=\"addSelection($index)\"\r\n >\r\n Aggiungi la selezione\r\n </button>\r\n </div>\r\n\r\n @if (!membersOf($index).length) {\r\n <p class=\"fb-empty\">\r\n Vuoto. Trascina un elemento dentro la cornice sul canvas, oppure selezionalo e usa\r\n \u00ABAggiungi la selezione\u00BB.\r\n </p>\r\n }\r\n\r\n @for (member of membersOf($index); track $index) {\r\n <div class=\"fb-grp__member\">\r\n <button\r\n type=\"button\"\r\n class=\"fb-grp__member-name\"\r\n [class.fb-grp__member-name--stale]=\"!isKnownMember(member)\"\r\n [title]=\"isKnownMember(member) ? 'Porta la vista su questo elemento' : 'Questo elemento non esiste piu\u2019'\"\r\n [disabled]=\"!isKnownMember(member)\"\r\n (click)=\"memberFocused.emit(member)\"\r\n >\r\n {{ member }}\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-btn--ghost fb-btn--icon\"\r\n [disabled]=\"!isEditable()\"\r\n aria-label=\"Togli dal riquadro\"\r\n title=\"Togli dal riquadro: l\u2019elemento resta nel flow\"\r\n (click)=\"removeMember($index, member)\"\r\n >\r\n \u00D7\r\n </button>\r\n </div>\r\n }\r\n\r\n @if (staleMembersOf($index).length) {\r\n <p class=\"fb-callout fb-callout--warn\">\r\n <span>\r\n {{ staleMembersOf($index).length }}\r\n {{ staleMembersOf($index).length === 1 ? 'membro nomina un elemento' : 'membri nominano elementi' }}\r\n che non esiste piu\u2019 (GROUP_MEMBER_UNKNOWN). E\u2019 un avviso: non blocca\r\n l\u2019attivazione, e la ripulitura la fa l\u2019editor.\r\n </span>\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-btn--icon\"\r\n [disabled]=\"!isEditable()\"\r\n (click)=\"cleanStale($index)\"\r\n >\r\n Ripulisci\r\n </button>\r\n </p>\r\n }\r\n </div>\r\n }\r\n </div>\r\n }\r\n </div>\r\n</div>\r\n", styles: [":host{display:flex;flex-direction:column;height:100%;min-height:0;background:var(--fb-surface, #fff)}.fb-grp__header{display:flex;align-items:center;gap:6px;padding:10px 12px;border-bottom:1px solid var(--fb-border-subtle, #e6e9ee)}.fb-grp__title{margin:0;flex:1;font-size:14px;font-weight:600;color:var(--fb-text, #1d2939)}.fb-grp__body{flex:1;min-height:0;padding:10px 12px 16px;overflow-y:auto}.fb-grp__name{display:flex;flex-direction:column;gap:2px;padding:0;border:none;background:transparent;font:inherit;text-align:left;cursor:pointer}.fb-grp__meta{font-size:10px;color:var(--fb-text-muted, #667085)}.fb-grp__check{flex-direction:row;align-items:center;gap:6px}.fb-grp__members{margin-top:4px;padding-top:6px;border-top:1px dashed var(--fb-border, #d6dae1)}.fb-grp__member{display:flex;align-items:center;gap:4px}.fb-grp__member-name{flex:1;min-width:0;padding:3px 4px;border:none;border-radius:var(--fb-radius-xs, 6px);background:transparent;color:var(--fb-text, #1d2939);font:inherit;font-size:12px;text-align:left;overflow-wrap:anywhere;cursor:pointer}.fb-grp__member-name:hover:not(:disabled){background:var(--fb-surface-alt, #f7f8fa)}.fb-grp__member-name--stale{color:var(--fb-warning, #b7791f);text-decoration:line-through;cursor:default}\n"] }]
|
|
15110
|
+
args: [{ selector: 'fb-group-panel', standalone: true, imports: [SelectValueDirective], changeDetection: ChangeDetectionStrategy.OnPush, template: "<header class=\"fb-grp__header\">\r\n <h2 class=\"fb-grp__title\">Riquadri</h2>\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-btn--icon\"\r\n [disabled]=\"!isEditable()\"\r\n title=\"Crea un riquadro attorno agli elementi selezionati\"\r\n (click)=\"create()\"\r\n >\r\n Nuovo\r\n </button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--ghost fb-btn--icon\" aria-label=\"Chiudi\" (click)=\"close()\">\u00D7</button>\r\n</header>\r\n\r\n<div class=\"fb-grp__body\">\r\n <p class=\"fb-section__note\">\r\n Un riquadro documenta un pezzo di flow e non ne cambia il comportamento: il runtime non lo\r\n legge mai, non ha collegamenti e i suoi rilievi sono tutti avvisi. Il nome sta in uno spazio\r\n dei nomi a parte, quindi puo\u2019 coincidere con quello di una variabile.\r\n </p>\r\n\r\n @if (!groups().length) {\r\n <p class=\"fb-empty\">\r\n Nessun riquadro. Seleziona gli elementi sul canvas e premi \u00ABNuovo\u00BB: il riquadro nasce\r\n attorno a loro.\r\n </p>\r\n }\r\n\r\n <div class=\"fb-list\">\r\n @for (item of groups(); track $index) {\r\n <div class=\"fb-list__item\">\r\n <div class=\"fb-list__header\">\r\n <button type=\"button\" class=\"fb-grp__name\" (click)=\"toggleOpen(item.name)\">\r\n <span class=\"fb-list__title\">{{ item.group.label || item.name || '(senza nome)' }}</span>\r\n <span class=\"fb-grp__meta\">\r\n {{ membersOf($index).length }}\r\n {{ membersOf($index).length === 1 ? 'elemento' : 'elementi' }}\r\n @if (item.group.isCollapsed) {\r\n \u00B7 chiuso: nascosti sul canvas\r\n }\r\n @if (isAutoSized($index)) {\r\n \u00B7 misura sui membri\r\n }\r\n </span>\r\n </button>\r\n <span class=\"fb-list__spacer\"></span>\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-btn--ghost fb-btn--icon\"\r\n [disabled]=\"!isEditable()\"\r\n aria-label=\"Elimina il riquadro\"\r\n title=\"Elimina il riquadro: gli elementi che contiene restano\"\r\n (click)=\"remove($index)\"\r\n >\r\n \u00D7\r\n </button>\r\n </div>\r\n\r\n @if (nameProblem($index)) {\r\n <!-- Avviso, non errore: nessun rilievo su un riquadro blocca l\u2019attivazione (\u00A73.6). -->\r\n <p class=\"fb-field__hint fb-field__hint--warn\">{{ nameProblem($index) }}</p>\r\n }\r\n\r\n @if (isOpen(item.name)) {\r\n <div class=\"fb-fields-row\">\r\n <label class=\"fb-field\">\r\n <span class=\"fb-field__label\">Intestazione</span>\r\n <input\r\n [value]=\"item.group.label || ''\"\r\n placeholder=\"Preparazione dell\u2019ordine\"\r\n [disabled]=\"!isEditable()\"\r\n (input)=\"setLabel($index, $any($event.target).value)\"\r\n />\r\n </label>\r\n <label class=\"fb-field\">\r\n <span class=\"fb-field__label\">Nome tecnico</span>\r\n <input\r\n [value]=\"item.name\"\r\n placeholder=\"Riquadro_Preparazione\"\r\n [disabled]=\"!isEditable()\"\r\n (input)=\"setName($index, $any($event.target).value)\"\r\n />\r\n </label>\r\n </div>\r\n\r\n <label class=\"fb-field\">\r\n <span class=\"fb-field__label\">Commento</span>\r\n <textarea\r\n rows=\"3\"\r\n [value]=\"item.group.description || ''\"\r\n placeholder=\"Cosa fa questo pezzo di flow, e perche\u2019.\"\r\n [disabled]=\"!isEditable()\"\r\n (input)=\"setDescription($index, $any($event.target).value)\"\r\n ></textarea>\r\n <span class=\"fb-field__hint\">\r\n Sul canvas ne compaiono le prime righe; per intero sta qui e nel titolo della barra.\r\n </span>\r\n </label>\r\n\r\n <div class=\"fb-fields-row\">\r\n <label class=\"fb-field fb-field--compact\">\r\n <span class=\"fb-field__label\">Colore</span>\r\n <select\r\n [fbValue]=\"colorOf($index)\"\r\n [disabled]=\"!isEditable()\"\r\n (change)=\"setColor($index, $any($event.target).value)\"\r\n >\r\n <option value=\"\">(nessuno)</option>\r\n @for (color of colors; track color.value) {\r\n <option [value]=\"color.value\">{{ color.label }}</option>\r\n }\r\n <!--\r\n `color` e\u2019 un token concordato fra editor e backend (\u00A73.6): un valore che non\r\n proponiamo non e\u2019 sbagliato, e senza questa opzione la select resterebbe bianca\r\n su un riquadro scritto altrove.\r\n -->\r\n @if (isColorOutOfList($index)) {\r\n <option [value]=\"colorOf($index)\">{{ colorOf($index) }} (fuori elenco)</option>\r\n }\r\n </select>\r\n </label>\r\n <!--\r\n Chiuso non e' un dettaglio grafico: sul canvas gli elementi del riquadro **non si\r\n disegnano piu'** e al loro posto compare una pastiglia, a cui si attaccano gli archi\r\n che entrano nel riquadro o ne escono. Il documento non cambia: e' un modo di guardarlo.\r\n -->\r\n <label class=\"fb-field fb-field--compact fb-grp__check\">\r\n <span class=\"fb-field__label\" title=\"Nasconde gli elementi del riquadro dietro una pastiglia\">\r\n Chiuso\r\n </span>\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"!!item.group.isCollapsed\"\r\n [disabled]=\"!isEditable()\"\r\n (change)=\"setCollapsed($index, $any($event.target).checked)\"\r\n />\r\n </label>\r\n </div>\r\n\r\n <div class=\"fb-grp__members\">\r\n <div class=\"fb-list__header\">\r\n <span class=\"fb-field__label\">Elementi nel riquadro</span>\r\n <span class=\"fb-list__spacer\"></span>\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-btn--icon\"\r\n [disabled]=\"!isEditable() || !canAddSelection()\"\r\n [title]=\"\r\n canAddSelection()\r\n ? 'Aggiungi gli elementi selezionati sul canvas'\r\n : 'Seleziona degli elementi sul canvas, poi aggiungili qui'\r\n \"\r\n (click)=\"addSelection($index)\"\r\n >\r\n Aggiungi la selezione\r\n </button>\r\n </div>\r\n\r\n @if (!membersOf($index).length) {\r\n <p class=\"fb-empty\">\r\n Vuoto. Trascina un elemento dentro la cornice sul canvas, oppure selezionalo e usa\r\n \u00ABAggiungi la selezione\u00BB.\r\n </p>\r\n }\r\n\r\n @for (member of membersOf($index); track $index) {\r\n <div class=\"fb-grp__member\">\r\n <button\r\n type=\"button\"\r\n class=\"fb-grp__member-name\"\r\n [class.fb-grp__member-name--stale]=\"!isKnownMember(member)\"\r\n [title]=\"isKnownMember(member) ? 'Porta la vista su questo elemento' : 'Questo elemento non esiste piu\u2019'\"\r\n [disabled]=\"!isKnownMember(member)\"\r\n (click)=\"memberFocused.emit(member)\"\r\n >\r\n {{ member }}\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-btn--ghost fb-btn--icon\"\r\n [disabled]=\"!isEditable()\"\r\n aria-label=\"Togli dal riquadro\"\r\n title=\"Togli dal riquadro: l\u2019elemento resta nel flow\"\r\n (click)=\"removeMember($index, member)\"\r\n >\r\n \u00D7\r\n </button>\r\n </div>\r\n }\r\n\r\n @if (staleMembersOf($index).length) {\r\n <p class=\"fb-callout fb-callout--warn\">\r\n <span>\r\n {{ staleMembersOf($index).length }}\r\n {{ staleMembersOf($index).length === 1 ? 'membro nomina un elemento' : 'membri nominano elementi' }}\r\n che non esiste piu\u2019 (GROUP_MEMBER_UNKNOWN). E\u2019 un avviso: non blocca\r\n l\u2019attivazione, e la ripulitura la fa l\u2019editor.\r\n </span>\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-btn--icon\"\r\n [disabled]=\"!isEditable()\"\r\n (click)=\"cleanStale($index)\"\r\n >\r\n Ripulisci\r\n </button>\r\n </p>\r\n }\r\n </div>\r\n }\r\n </div>\r\n }\r\n </div>\r\n</div>\r\n", styles: [":host{display:flex;flex-direction:column;height:100%;min-height:0;background:var(--fb-surface, #fff)}.fb-grp__header{display:flex;align-items:center;gap:6px;padding:10px 12px;border-bottom:1px solid var(--fb-border-subtle, #e6e9ee)}.fb-grp__title{margin:0;flex:1;font-size:14px;font-weight:600;color:var(--fb-text, #1d2939)}.fb-grp__body{flex:1;min-height:0;padding:10px 12px 16px;overflow-y:auto}.fb-grp__name{display:flex;flex-direction:column;gap:2px;padding:0;border:none;background:transparent;font:inherit;text-align:left;cursor:pointer}.fb-grp__meta{font-size:10px;color:var(--fb-text-muted, #667085)}.fb-grp__check{flex-direction:row;align-items:center;gap:6px}.fb-grp__members{margin-top:4px;padding-top:6px;border-top:1px dashed var(--fb-border, #d6dae1)}.fb-grp__member{display:flex;align-items:center;gap:4px}.fb-grp__member-name{flex:1;min-width:0;padding:3px 4px;border:none;border-radius:var(--fb-radius-xs, 6px);background:transparent;color:var(--fb-text, #1d2939);font:inherit;font-size:12px;text-align:left;overflow-wrap:anywhere;cursor:pointer}.fb-grp__member-name:hover:not(:disabled){background:var(--fb-surface-alt, #f7f8fa)}.fb-grp__member-name--stale{color:var(--fb-warning, #b7791f);text-decoration:line-through;cursor:default}\n"] }]
|
|
14354
15111
|
}], ctorParameters: () => [], propDecorators: { isEditable: [{ type: i0.Input, args: [{ isSignal: true, alias: "isEditable", required: false }] }], selectedNodeNames: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectedNodeNames", required: false }] }], target: [{ type: i0.Input, args: [{ isSignal: true, alias: "target", required: false }] }], closed: [{ type: i0.Output, args: ["closed"] }], createRequested: [{ type: i0.Output, args: ["createRequested"] }], memberFocused: [{ type: i0.Output, args: ["memberFocused"] }] } });
|
|
14355
15112
|
|
|
14356
15113
|
/**
|
|
@@ -16506,5 +17263,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.28", ngImpo
|
|
|
16506
17263
|
* Generated bundle index. Do not edit.
|
|
16507
17264
|
*/
|
|
16508
17265
|
|
|
16509
|
-
export { ConditionEditorComponent, ConnectorEditorComponent, DebugPanelComponent, DynamicScreenInspectorComponent, ENUM_CHOICE_SET_DEFAULT_DISPLAY_FIELD, ENUM_CHOICE_SET_DEFAULT_VALUE_FIELD, ENUM_CHOICE_SET_FIELDS, ElementDialogComponent, ElementInspectorComponent, ElementPaletteComponent, EnumValuePickerComponent, FALLBACK_COLLECTION_BY_TYPE, FALLBACK_TYPE_LABEL, FLOW_BUILDER_HTTP_CONFIG, FLOW_CLIPBOARD_KIND, FLOW_CLIPBOARD_VERSION, FLOW_ELEMENT_ICONS, FLOW_ELEMENT_VARIANT_FIELDS, FLOW_ELEMENT_VARIANT_ICONS, FLOW_ERROR_FALLBACK_MESSAGE, FLOW_ERROR_HTTP_STATUS, FLOW_GROUP_COLORS, FLOW_NAME_PATTERN, FLOW_NODE_COLLECTIONS, FLOW_NODE_HEIGHT, FLOW_NODE_WIDTH, FLOW_REFERENCE_FIELDS, FLOW_RESOURCE_COLLECTIONS, FLOW_VALUE_FIELDS, FLOW_VALUE_LITERAL_FIELDS, FieldAssignmentEditorComponent, FieldPickerComponent, FieldValuePickerComponent, FlowApiError, FlowBuilderApi, FlowBuilderComponent, FlowCanvasComponent, FlowCatalogStore, FlowClipboardService, FlowDictionaryStore, FlowDocumentStore, FlowEditorSession, FlowLayoutService, FlowValidationStore, FormulaEditorComponent, FormulaValidationService, GROUP_HEADER_HEIGHT, GROUP_MIN_HEIGHT, GROUP_MIN_WIDTH, GROUP_PADDING, GroupPanelComponent, HttpFlowBuilderApi, NamePickerComponent, NodeInspectorBase, ORCHESTRATION_CONDITION_OUTPUT, ObjectPickerComponent, OrchestratedStageInspectorComponent, ParameterEditorComponent, PasteDialogComponent, ProblemsPanelComponent, RecordFilterEditorComponent, ReferencePickerComponent, ResourcePanelComponent, RunDialogComponent, SEVERITY_BUCKETS, SEVERITY_ICON, SEVERITY_LABEL, START_NODE_NAME, SelectValueDirective, StartInspectorComponent, StructureMemberPickerComponent, StructurePickerComponent, TYPES_WITH_AUTOMATIC_OUTPUT, TYPE_BY_COLLECTION, UNSUPPORTED_TYPES, ValueEditorComponent, VersionPanelComponent, allFieldsOf, applyPaste, areTypesComparable, boundsAround, buildClipboardPayload, canvasGroupId, canvasNodeId, checkConditionLogic, checkFlowName, choiceSetSourceOf, declaredChoiceSetSources, describeFieldPath, describePathEntry, duplicateField, elementIcon, emptyFlowDefinition, enumChoiceSetFieldType, fieldAt, filterReferences, flattenFields, flowNodeWidth, flowNodeWidthClass, groupColorClass, groupRect, hasAutoSize, innerNamesOf, insertField, isClipboardPayload, isCustomConditionLogic, isEmptyReferenceFilter, isFieldResource, isGlobalReference, isNumericType, isPathInside, isTypeCheckedOperator, isUnknownEnumChoiceSetField, isValidFlowName, isValued, loadPathLevel, matchesReferenceFilter, membersInside, moveCondition, moveField, navigatePath, otherSourceFieldsOf, outletByKey, outletsOf, parseCanvasGroupId, parseCanvasNodeId, parseFieldPath, parseInvariantNumber, parseSourceConnectorId, parseTargetConnectorId, pathAvailableNames, pathContainerLabel, pathKey, pathNotVerifiableMessage, planPaste, recomputeMembers, referenceRoot, referencedRootsOf, remapConditionLogic, removeCondition, removeField, resolvePath, rewriteReferences, sameMembers, samePath, screenActionNames, screenFieldNames, severityBucket, slugifyFlowName, sourceConnectorId, stageStepNames, stageStepOutputReferenced, stepsOf, targetConnectorId, typeOfCollection, uniqueFlowName, valuedFieldOf, valuedFieldsOf, variantFieldOf, variantOf, variantPresetOf };
|
|
17266
|
+
export { ConditionEditorComponent, ConnectorEditorComponent, DebugPanelComponent, DynamicScreenInspectorComponent, ENUM_CHOICE_SET_DEFAULT_DISPLAY_FIELD, ENUM_CHOICE_SET_DEFAULT_VALUE_FIELD, ENUM_CHOICE_SET_FIELDS, ElementDialogComponent, ElementInspectorComponent, ElementPaletteComponent, EnumValuePickerComponent, FALLBACK_COLLECTION_BY_TYPE, FALLBACK_TYPE_LABEL, FLOW_BUILDER_HTTP_CONFIG, FLOW_CLIPBOARD_KIND, FLOW_CLIPBOARD_VERSION, FLOW_ELEMENT_ICONS, FLOW_ELEMENT_VARIANT_FIELDS, FLOW_ELEMENT_VARIANT_ICONS, FLOW_ERROR_FALLBACK_MESSAGE, FLOW_ERROR_HTTP_STATUS, FLOW_FOLD_HEIGHT, FLOW_FOLD_WIDTH, FLOW_GROUP_COLORS, FLOW_NAME_PATTERN, FLOW_NODE_COLLECTIONS, FLOW_NODE_HEIGHT, FLOW_NODE_WIDTH, FLOW_REFERENCE_FIELDS, FLOW_RESOURCE_COLLECTIONS, FLOW_VALUE_FIELDS, FLOW_VALUE_LITERAL_FIELDS, FieldAssignmentEditorComponent, FieldPickerComponent, FieldValuePickerComponent, FlowApiError, FlowBuilderApi, FlowBuilderComponent, FlowCanvasComponent, FlowCatalogStore, FlowClipboardService, FlowDictionaryStore, FlowDocumentStore, FlowEditorSession, FlowLayoutService, FlowValidationStore, FormulaEditorComponent, FormulaValidationService, GROUP_HEADER_HEIGHT, GROUP_MIN_HEIGHT, GROUP_MIN_WIDTH, GROUP_PADDING, GroupPanelComponent, HttpFlowBuilderApi, NamePickerComponent, NodeInspectorBase, ORCHESTRATION_CONDITION_OUTPUT, ObjectPickerComponent, OrchestratedStageInspectorComponent, ParameterEditorComponent, PasteDialogComponent, ProblemsPanelComponent, RecordFilterEditorComponent, ReferencePickerComponent, ResourcePanelComponent, RunDialogComponent, SEVERITY_BUCKETS, SEVERITY_ICON, SEVERITY_LABEL, START_NODE_NAME, SelectValueDirective, StartInspectorComponent, StructureMemberPickerComponent, StructurePickerComponent, TYPES_WITH_AUTOMATIC_OUTPUT, TYPE_BY_COLLECTION, UNSUPPORTED_TYPES, ValueEditorComponent, VersionPanelComponent, allFieldsOf, applyPaste, areTypesComparable, boundsAround, buildClipboardPayload, canvasFoldId, canvasGroupId, canvasNodeId, checkConditionLogic, checkFlowName, choiceSetSourceOf, declaredChoiceSetSources, describeFieldPath, describePathEntry, duplicateField, elementIcon, emptyFlowDefinition, enumChoiceSetFieldType, fieldAt, filterReferences, flattenFields, flowNodeWidth, flowNodeWidthClass, foldSourceConnectorId, foldTargetConnectorId, groupColorClass, groupRect, hasAutoSize, innerNamesOf, insertField, isClipboardPayload, isCustomConditionLogic, isEmptyReferenceFilter, isFieldResource, isFoldReference, isGlobalReference, isNumericType, isPathInside, isTypeCheckedOperator, isUnknownEnumChoiceSetField, isValidFlowName, isValued, loadPathLevel, matchesReferenceFilter, membersInside, moveCondition, moveField, navigatePath, otherSourceFieldsOf, outletByKey, outletsOf, parseCanvasFoldId, parseCanvasGroupId, parseCanvasNodeId, parseFieldPath, parseInvariantNumber, parseSourceConnectorId, parseTargetConnectorId, pathAvailableNames, pathContainerLabel, pathKey, pathNotVerifiableMessage, planPaste, recomputeMembers, referenceRoot, referencedRootsOf, remapConditionLogic, removeCondition, removeField, resolvePath, rewriteReferences, sameMembers, samePath, screenActionNames, screenFieldNames, severityBucket, slugifyFlowName, sourceConnectorId, stageStepNames, stageStepOutputReferenced, stepsOf, targetConnectorId, typeOfCollection, uniqueFlowName, valuedFieldOf, valuedFieldsOf, variantFieldOf, variantOf, variantPresetOf };
|
|
16510
17267
|
//# sourceMappingURL=esfaenza-flow-builder.mjs.map
|