@esfaenza/flow-builder 20.3.56 → 20.3.57

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.
@@ -119,7 +119,7 @@ const FLOW_ERROR_FALLBACK_MESSAGE = {
119
119
  FlowNotFound: 'Il flow non esiste piu’.',
120
120
  VersionNotFound: 'La versione richiesta non esiste.',
121
121
  AlreadyExists: 'Esiste già un flow con questo nome.',
122
- NotEditable: 'Questa versione non e’ modificabile: creane una nuova.',
122
+ NotEditable: 'Questa e’ la versione attiva: per modificarla creane una nuova, per eliminarla va prima disattivata.',
123
123
  VersionConflict: 'Qualcun altro ha salvato questa versione nel frattempo.',
124
124
  MissingService: 'Funzione non configurata su questo ambiente.',
125
125
  RegistrationFailed: 'L’infrastruttura non ha accettato la presa in carico del flow: la versione non e’ attiva.',
@@ -174,7 +174,7 @@ class FlowBuilderApi {
174
174
  void request;
175
175
  return missing('cloneFlow');
176
176
  }
177
- /** `DELETE /flows/{name}/versions/{v}` — rifiutata sulla versione attiva. */
177
+ /** `DELETE /flows/{name}/versions/{v}` — rifiutata sulla versione attiva, con `NotEditable` (§10). */
178
178
  deleteVersion(flowName, version) {
179
179
  void flowName;
180
180
  void version;
@@ -4905,8 +4905,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.32", ngImpo
4905
4905
  * FRONTEND.md §6.2, §8, §9, §11.
4906
4906
  *
4907
4907
  * Tre responsabilita' che stanno bene insieme perche' condividono lo stesso stato:
4908
- * - il ciclo di vita: si modifica una bozza; su una versione non modificabile il comando
4909
- * e' "Nuova versione", non "Salva" (§8.1);
4908
+ * - il ciclo di vita: si modifica tutto tranne la versione attiva, e lì il comando e' "Nuova
4909
+ * versione", non "Salva" (§8, regola 1);
4910
4910
  * - la concorrenza ottimistica: `updatedAt` conservato al caricamento e rimandato come
4911
4911
  * `expectedUpdatedAt` al salvataggio (§9);
4912
4912
  * - la distinzione fra "salvato" e "sano": il salvataggio riesce anche con errori, e
@@ -5006,7 +5006,7 @@ class FlowEditorSession {
5006
5006
  }
5007
5007
  return this.currentVersionSummary()?.isEditable ?? false;
5008
5008
  }, ...(ngDevMode ? [{ debugName: "isEditable" }] : []));
5009
- /** Su una versione non modificabile il comando primario diventa "Nuova versione" (§8.1). */
5009
+ /** Sulla versione attiva il comando primario diventa "Nuova versione" (§8, regola 1). */
5010
5010
  primaryCommand = computed(() => {
5011
5011
  if (this.isNew()) {
5012
5012
  return 'create';
@@ -5014,6 +5014,16 @@ class FlowEditorSession {
5014
5014
  return this.isEditable() ? 'save' : 'newVersion';
5015
5015
  }, ...(ngDevMode ? [{ debugName: "primaryCommand" }] : []));
5016
5016
  isActiveVersion = computed(() => isVersionActive(this.currentVersionSummary()), ...(ngDevMode ? [{ debugName: "isActiveVersion" }] : []));
5017
+ /**
5018
+ * §8, regola 1 — la versione aperta e' una **superata**: si modifica, ma il primo salvataggio va
5019
+ * confermato. Un'esecuzione sospesa nata quando era attiva — una pausa, un'attesa, uno step, una
5020
+ * schermata parcheggiata — riprende sulla definizione modificata, e se il node su cui era ferma e'
5021
+ * stato tolto o rinominato fallisce. Il backend non puo' saperlo al posto nostro.
5022
+ *
5023
+ * Si legge lo `status` della riga, che e' del backend come `isEditable`: salvata, la versione torna
5024
+ * bozza e la domanda smette di porsi da se' al primo `refreshVersions`.
5025
+ */
5026
+ isObsoleteVersion = computed(() => this.currentVersionSummary()?.status === 'Obsolete', ...(ngDevMode ? [{ debugName: "isObsoleteVersion" }] : []));
5017
5027
  /** Attivare esige zero errori (§8.3): il comando resta disabilitato finche' ce ne sono. */
5018
5028
  canActivate = computed(() => !this.isNew() && !this.isActiveVersion() && this.validation.canActivate(), ...(ngDevMode ? [{ debugName: "canActivate" }] : []));
5019
5029
  setAuthor(author) {
@@ -5177,7 +5187,7 @@ class FlowEditorSession {
5177
5187
  // Il nome e' quello con cui abbiamo scritto; la versione la decide il backend — un
5178
5188
  // salvataggio puo' creare una versione nuova — quindi come ripiego vale quella caricata.
5179
5189
  const result = this.normalizeResult(response, { flowName: targetName });
5180
- this.applySaveResult(result);
5190
+ void this.applySaveResult(result);
5181
5191
  return result;
5182
5192
  }
5183
5193
  catch (error) {
@@ -5191,6 +5201,13 @@ class FlowEditorSession {
5191
5201
  // `activate: true` fallisce senza scrivere nulla se ci sono errori (§6.2).
5192
5202
  this.validation.adopt(error.validation);
5193
5203
  }
5204
+ // §10 — `NotEditable` vuol dire che la versione in mano e' **diventata** quella attiva: l'ha
5205
+ // attivata qualcun altro, perche' l'elenco su cui avevamo deciso «Salva» diceva il contrario.
5206
+ // Rileggerlo e' cio' che fa comparire «Nuova versione» al posto di un comando che fallira'
5207
+ // sempre.
5208
+ if (FlowApiError.is(error) && error.category === 'NotEditable') {
5209
+ await this.refreshVersions().catch(() => undefined);
5210
+ }
5194
5211
  // §6.2, §8 — se invece a fallire e' la **presa in carico**, la bozza **resta salvata** e non
5195
5212
  // attivata: non si perde il lavoro, ma nemmeno si puo' credere di essere fermi a prima. Si
5196
5213
  // rilegge l'elenco versioni, adottando il numero se il salvataggio era una creazione.
@@ -5235,6 +5252,11 @@ class FlowEditorSession {
5235
5252
  FlowEditorSession.warned.add(message);
5236
5253
  console.warn(`[flow-builder] ${message}`);
5237
5254
  }
5255
+ /**
5256
+ * Restituisce la rilettura dell'elenco versioni, che parte comunque: chi la aspetta e' chi ha
5257
+ * appena scritto una versione **nuova**, che nell'elenco vecchio non c'e' — e finche' non c'e'
5258
+ * `currentVersionSummary` e' vuoto, cioe' stato «Nuovo» e editor in sola lettura.
5259
+ */
5238
5260
  applySaveResult(result) {
5239
5261
  // Un nome vuoto non e' una rinomina: se la risposta non lo porta si tiene quello con cui
5240
5262
  // stiamo lavorando, altrimenti la sessione perde il flow su cui e' aperta.
@@ -5259,7 +5281,7 @@ class FlowEditorSession {
5259
5281
  }, { silent: true });
5260
5282
  this.documentStore.markSaved();
5261
5283
  this.validation.adopt(result.validation);
5262
- void this.refreshVersions({ adoptVersion: true });
5284
+ return this.refreshVersions({ adoptVersion: true }).catch(() => undefined);
5263
5285
  }
5264
5286
  /**
5265
5287
  * Duplica: scrive il flow **sotto un altro nome** e sposta la sessione sulla copia
@@ -5324,7 +5346,7 @@ class FlowEditorSession {
5324
5346
  definition.label = options.label;
5325
5347
  }, { silent: true });
5326
5348
  }
5327
- this.applySaveResult(result);
5349
+ void this.applySaveResult(result);
5328
5350
  return result;
5329
5351
  }
5330
5352
  catch (error) {
@@ -5400,7 +5422,7 @@ class FlowEditorSession {
5400
5422
  flowName,
5401
5423
  version,
5402
5424
  });
5403
- this.applySaveResult(result);
5425
+ void this.applySaveResult(result);
5404
5426
  return result;
5405
5427
  }
5406
5428
  catch (error) {
@@ -5474,12 +5496,20 @@ class FlowEditorSession {
5474
5496
  this._conflict.set(null);
5475
5497
  await this.open(flowName, version ?? undefined);
5476
5498
  }
5499
+ /** Risoluzione del conflitto, seconda opzione: le modifiche locali in una nuova versione (§9.3). */
5500
+ async saveAsNewVersionAfterConflict() {
5501
+ return this.saveAsNewVersion();
5502
+ }
5477
5503
  /**
5478
- * Risoluzione del conflitto, seconda opzione: salva come nuova versione, conservando
5479
- * le modifiche locali (§9.3). Il salvataggio senza `expectedUpdatedAt` e senza `version`
5480
- * lascia scegliere al backend, che crea una nuova versione se l'ultima non e' toccabile.
5504
+ * Il documento in mano, modifiche comprese, scritto in una versione **nuova**: lascia intatta
5505
+ * quella da cui si era partiti. Serve dove sovrascrivere e' la cosa sbagliata o rischiosa — un
5506
+ * conflitto (§9.3), o una superata su cui ci sono esecuzioni sospese (§8, regola 1).
5507
+ *
5508
+ * Sono due chiamate e non «salva senza `version`»: quella strada sceglie da se' e sovrascrive
5509
+ * l'ultima se e' una bozza (§8), cioe' proprio cio' che qui si vuole evitare. `createVersion`
5510
+ * copia una versione salvata, e il salvataggio subito dopo ci scrive sopra il documento.
5481
5511
  */
5482
- async saveAsNewVersionAfterConflict() {
5512
+ async saveAsNewVersion() {
5483
5513
  const flowName = this._flowName();
5484
5514
  if (!flowName) {
5485
5515
  throw new FlowApiError('InvalidRequest', 'Nessun flow aperto.');
@@ -5488,13 +5518,15 @@ class FlowEditorSession {
5488
5518
  this._conflict.set(null);
5489
5519
  try {
5490
5520
  const created = await this.api.createVersion(flowName, { author: this._author() });
5491
- const result = await this.api.saveFlow(flowName, {
5521
+ const response = await this.api.saveFlow(flowName, {
5492
5522
  definition: this.documentStore.document(),
5493
- version: created.version,
5494
- expectedUpdatedAt: created.updatedAt,
5523
+ version: created?.version,
5524
+ expectedUpdatedAt: created?.updatedAt,
5495
5525
  author: this._author(),
5496
5526
  });
5497
- this.applySaveResult(result);
5527
+ // Il ripiego e' la versione appena creata, non quella aperta: e' lì che il documento e' finito.
5528
+ const result = this.normalizeResult(response, { flowName, version: created?.version ?? null });
5529
+ await this.applySaveResult(result);
5498
5530
  return result;
5499
5531
  }
5500
5532
  finally {
@@ -7525,6 +7557,28 @@ class ReferencePickerComponent {
7525
7557
  * destinazione farebbe attivare un flow che poi fallisce davanti all'utente. L'eccezione sono i
7526
7558
  * campi del record che innesca, che si assegnano (§4.1).
7527
7559
  */
7560
+ /**
7561
+ * In un campo di destinazione, la radice del valore quando e' **nota ma non scrivibile**: sta fra
7562
+ * le radici di `getReferences` e non fra quelle di `getWritableReferences`. E' il caso di chi
7563
+ * scrive a mano `Leggi_Ordine.Stato` — l'output automatico si legge, ma solo le variabili si
7564
+ * assegnano (§4.6, §5.3) — e di una costante o una formula. Senza, finivano in `navigated`, che
7565
+ * dice «non verificato» di cio' che la validazione rifiuta.
7566
+ *
7567
+ * Le globali restano fuori: le giudica {@link isReadOnlyPath}, e i campi di `$Record` si
7568
+ * assegnano. E senza l'elenco scrivibile — in volo, fallito, o un host che risponde vuoto — non
7569
+ * si accusa nessuno: la regola e' quella del validatore, che senza nomi scrivibili tace.
7570
+ */
7571
+ readOnlyRoot = computed(() => {
7572
+ const writable = mergeReferences(this.available(), this.extraReferences());
7573
+ const root = referenceRoot((this.value() ?? '').trim()).toLowerCase();
7574
+ if (!this.writableOnly() || !writable.length || !root || isGlobalReference(root)) {
7575
+ return undefined;
7576
+ }
7577
+ if (writable.some((reference) => referenceRoot(reference.name).toLowerCase() === root)) {
7578
+ return undefined;
7579
+ }
7580
+ return this.roots().find((reference) => reference.name.toLowerCase() === root);
7581
+ }, ...(ngDevMode ? [{ debugName: "readOnlyRoot" }] : []));
7528
7582
  isReadOnlyPath = computed(() => {
7529
7583
  const reference = this.proposalRoot()?.reference;
7530
7584
  if (!reference) {
@@ -7704,6 +7758,9 @@ class ReferencePickerComponent {
7704
7758
  * classe, `FIELD_UNKNOWN` su un'entita', e a differenza di prima si sa per certo;
7705
7759
  * - `memberNotWritable`: il segmento esiste, non e' scrivibile, e questo campo vuole una
7706
7760
  * destinazione: `TARGET_NOT_WRITABLE`;
7761
+ * - `rootNotWritable`: la radice esiste ma non e' una variabile — un output automatico, una
7762
+ * costante, una formula — e questo campo vuole una destinazione: `TARGET_NOT_WRITABLE` sul
7763
+ * nome intero come sui suoi campi (§4.6, §5.3);
7707
7764
  * - `pathUnverified`: la catena si interrompe (`PATH_NOT_VERIFIABLE`): avviso, non errore;
7708
7765
  * - `globalPathUntyped`: percorso globale che porta un contenitore senza dichiararne il tipo:
7709
7766
  * `PATH_NOT_VERIFIABLE`, e la correzione sta nel catalogo dell'host, non nel flow (§4.1);
@@ -7768,6 +7825,9 @@ class ReferencePickerComponent {
7768
7825
  ? 'globalPathUntyped'
7769
7826
  : 'globalPathInvalid';
7770
7827
  }
7828
+ if (this.readOnlyRoot()) {
7829
+ return 'rootNotWritable';
7830
+ }
7771
7831
  // Fra **tutte** le radici: `Leggi_Ordine` non e' un `String`, ma `Leggi_Ordine.Numero` sì, e
7772
7832
  // accusarlo di non esistere sarebbe falso (§4.5).
7773
7833
  if (this.roots().some((reference) => reference.name === root)) {
@@ -7819,6 +7879,14 @@ class ReferencePickerComponent {
7819
7879
  return this.tailIsObject()
7820
7880
  ? 'Questo campo non si scrive ne’ creando ne’ aggiornando un record: assegnarlo e’ TARGET_NOT_WRITABLE.'
7821
7881
  : 'Questo membro e’ calcolato: si legge, ma assegnarlo e’ TARGET_NOT_WRITABLE.';
7882
+ case 'rootNotWritable': {
7883
+ const reference = this.readOnlyRoot();
7884
+ // Il rimedio non e' lo stesso: il risultato di un elemento si puo' mettere in una variabile,
7885
+ // una costante no.
7886
+ return reference?.kind === 'ElementOutput'
7887
+ ? `«${reference.name}» e’ l’output automatico di un elemento: si legge, ma non si assegna, e nemmeno i suoi campi (TARGET_NOT_WRITABLE). Per scriverci, salva il risultato dell’elemento in una variabile e assegna quella.`
7888
+ : `«${reference?.name}» non e’ una variabile: solo le variabili sono scrivibili (TARGET_NOT_WRITABLE).`;
7889
+ }
7822
7890
  case 'pathUnverified':
7823
7891
  return resolution ? pathNotVerifiableMessage(resolution) : null;
7824
7892
  case 'globalPathUntyped':
@@ -7859,7 +7927,7 @@ class ReferencePickerComponent {
7859
7927
  if (state === 'priorValuesMissing') {
7860
7928
  return this.priorValuesSeverity() === 'error';
7861
7929
  }
7862
- return ['memberUnknown', 'memberNotWritable', 'globalPathInvalid'].includes(state);
7930
+ return ['memberUnknown', 'memberNotWritable', 'rootNotWritable', 'globalPathInvalid'].includes(state);
7863
7931
  }, ...(ngDevMode ? [{ debugName: "isHintError" }] : []));
7864
7932
  errorMessage = computed(() => this.loadError(), ...(ngDevMode ? [{ debugName: "errorMessage" }] : []));
7865
7933
  open() {
@@ -13999,8 +14067,25 @@ class RecordLookupInspectorComponent extends NodeInspectorBase {
13999
14067
  * ordinamento, campi di output) li carica il picker che li mostra.
14000
14068
  */
14001
14069
  allFields = signal([], ...(ngDevMode ? [{ debugName: "allFields" }] : []));
14070
+ /**
14071
+ * La modalita' scelta col radio, finche' il documento non la rende evidente da se'.
14072
+ *
14073
+ * Vale solo per le due modalita' che possono restare senza dati: «in una variabile» appena
14074
+ * scelta non ha ancora la variabile, e «campo per campo» resta senza righe quando si toglie
14075
+ * l'ultima. Dedotte dal solo documento ricadevano su «scarta», e il radio tornava indietro
14076
+ * prima che si potesse scegliere la variabile. Scrivere `outputReference: ''` per
14077
+ * ricordarsene e' una chiave vuota nel documento (§2), e non bastava nemmeno: e' falsa.
14078
+ * Automatico e scarta il documento li esprime per intero, quindi qui non entrano — un
14079
+ * annulla deve poterli riportare indietro.
14080
+ */
14081
+ pendingOutputMode = signal(null, ...(ngDevMode ? [{ debugName: "pendingOutputMode" }] : []));
14002
14082
  constructor() {
14003
14083
  super();
14084
+ // Cambiare elemento azzera la scelta locale: e' di questo node, non del prossimo.
14085
+ effect(() => {
14086
+ this.name();
14087
+ this.pendingOutputMode.set(null);
14088
+ });
14004
14089
  effect(() => {
14005
14090
  const object = this.lookup().object;
14006
14091
  if (!object) {
@@ -14015,7 +14100,7 @@ class RecordLookupInspectorComponent extends NodeInspectorBase {
14015
14100
  }
14016
14101
  fieldOptions = computed(() => this.allFields(), ...(ngDevMode ? [{ debugName: "fieldOptions" }] : []));
14017
14102
  queriedFields = computed(() => this.lookup().queriedFields ?? [], ...(ngDevMode ? [{ debugName: "queriedFields" }] : []));
14018
- /** La modalita' di output dedotta dai campi valorizzati. */
14103
+ /** La modalita' di output dedotta dai campi valorizzati, poi dalla scelta ancora vuota. */
14019
14104
  outputMode = computed(() => {
14020
14105
  const lookup = this.lookup();
14021
14106
  if (lookup.storeOutputAutomatically) {
@@ -14027,7 +14112,7 @@ class RecordLookupInspectorComponent extends NodeInspectorBase {
14027
14112
  if (lookup.outputReference) {
14028
14113
  return 'variable';
14029
14114
  }
14030
- return 'discard';
14115
+ return this.pendingOutputMode() ?? 'discard';
14031
14116
  }, ...(ngDevMode ? [{ debugName: "outputMode" }] : []));
14032
14117
  /** Entrambi dichiarati: capita su documenti arrivati da altri editor. */
14033
14118
  hasOutputConflict = computed(() => {
@@ -14096,6 +14181,7 @@ class RecordLookupInspectorComponent extends NodeInspectorBase {
14096
14181
  }
14097
14182
  /** Il passaggio fra modalita' azzera i campi delle altre: il conflitto non e' scrivibile. */
14098
14183
  setOutputMode(mode) {
14184
+ this.pendingOutputMode.set(mode === 'variable' || mode === 'assignments' ? mode : null);
14099
14185
  this.patch((node) => {
14100
14186
  const lookup = node;
14101
14187
  delete lookup.storeOutputAutomatically;
@@ -14105,18 +14191,18 @@ class RecordLookupInspectorComponent extends NodeInspectorBase {
14105
14191
  case 'automatic':
14106
14192
  lookup.storeOutputAutomatically = true;
14107
14193
  break;
14108
- case 'variable':
14109
- lookup.outputReference = '';
14110
- break;
14111
14194
  case 'assignments':
14112
14195
  lookup.outputAssignments = [{}];
14113
14196
  break;
14197
+ case 'variable':
14114
14198
  case 'discard':
14115
14199
  break;
14116
14200
  }
14117
14201
  });
14118
14202
  }
14119
14203
  setOutputReference(reference) {
14204
+ // Svuotare il picker non e' scegliere «scarta»: il picker deve restare dov'e'.
14205
+ this.pendingOutputMode.set('variable');
14120
14206
  this.setField('outputReference', reference);
14121
14207
  }
14122
14208
  addOutputAssignment() {
@@ -14127,6 +14213,8 @@ class RecordLookupInspectorComponent extends NodeInspectorBase {
14127
14213
  });
14128
14214
  }
14129
14215
  removeOutputAssignment(index) {
14216
+ // Togliere l'ultima riga lascia la modalita' com'e', con «Aggiungi campo» a portata.
14217
+ this.pendingOutputMode.set('assignments');
14130
14218
  this.patch((node) => {
14131
14219
  const lookup = node;
14132
14220
  lookup.outputAssignments?.splice(index, 1);
@@ -14157,7 +14245,7 @@ class RecordLookupInspectorComponent extends NodeInspectorBase {
14157
14245
  /** `relatedRecords` e' modellato ma non tradotto in query: se c'e', si avvisa (§5.7). */
14158
14246
  hasRelatedRecords = computed(() => (this.lookup().relatedRecords?.length ?? 0) > 0, ...(ngDevMode ? [{ debugName: "hasRelatedRecords" }] : []));
14159
14247
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.32", ngImport: i0, type: RecordLookupInspectorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
14160
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.32", type: RecordLookupInspectorComponent, isStandalone: true, selector: "fb-record-lookup-inspector", usesInheritance: true, ngImport: i0, template: "<div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Oggetto</label>\r\n <fb-object-picker\r\n [value]=\"lookup().object\"\r\n placeholder=\"Scrivi o scegli un oggetto\"\r\n (valueChange)=\"setObject($event ?? '')\"\r\n />\r\n</div>\r\n\r\n<fb-record-filter-editor\r\n [holder]=\"lookup()\"\r\n [object]=\"lookup().object\"\r\n title=\"Quali record leggere\"\r\n usage=\"filterable\"\r\n [supportsLogic]=\"true\"\r\n [supportsFormula]=\"true\"\r\n emptyWarning=\"Senza filtri legge tutti i record dell\u2019oggetto.\"\r\n (changed)=\"onFiltersChanged($event)\"\r\n/>\r\n\r\n<fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Quanti e in che ordine</legend>\r\n\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"lookup().getFirstRecordOnly === true\"\r\n (change)=\"setFirstOnly($any($event.target).checked)\"\r\n />\r\n Solo il primo record\r\n </label>\r\n <p class=\"fb-field__hint\">\r\n @if (returnsCollection()) {\r\n Il risultato e\u2019 una <strong>collection</strong>: puo\u2019 essere iterata da un Loop.\r\n } @else {\r\n Il risultato e\u2019 un <strong>record singolo</strong>: non e\u2019 iterabile da un Loop.\r\n }\r\n </p>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Ordina per</label>\r\n <fb-field-picker\r\n [value]=\"lookup().sortField\"\r\n [object]=\"lookup().object\"\r\n usage=\"sortable\"\r\n label=\"Campo di ordinamento\"\r\n placeholder=\"Nessun ordinamento\"\r\n (valueChange)=\"setSortField($event ?? '')\"\r\n />\r\n </div>\r\n\r\n @if (lookup().sortField) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Direzione</label>\r\n <select class=\"fb-select\" [fbValue]=\"lookup().sortOrder || ''\" (change)=\"setSortOrder($any($event.target).value)\">\r\n @for (order of sortOrders(); track order.value) {\r\n <option [value]=\"order.value\">{{ order.label }}</option>\r\n }\r\n </select>\r\n </div>\r\n }\r\n\r\n @if (returnsCollection()) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Numero massimo di record</label>\r\n <input\r\n class=\"fb-input\"\r\n type=\"number\"\r\n min=\"1\"\r\n [value]=\"lookup().limit ?? ''\"\r\n (input)=\"setLimit($any($event.target).value)\"\r\n />\r\n </div>\r\n }\r\n</fieldset>\r\n\r\n@if (fieldOptions().length) {\r\n <fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Campi da leggere</legend>\r\n <p class=\"fb-section__note\">Nessuna selezione = tutti i campi disponibili.</p>\r\n <div class=\"fb-fields-grid\">\r\n @for (field of fieldOptions(); track field.name) {\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"isQueried(field.name)\"\r\n (change)=\"toggleQueriedField(field.name, $any($event.target).checked)\"\r\n />\r\n {{ field.label || field.name }}\r\n </label>\r\n }\r\n </div>\r\n </fieldset>\r\n}\r\n\r\n<fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Dove finisce il risultato</legend>\r\n\r\n @if (hasOutputConflict()) {\r\n <p class=\"fb-callout fb-callout--error\">\r\n Sono dichiarati insieme l\u2019output automatico e una destinazione esplicita: e\u2019 un conflitto\r\n (OUTPUT_CONFIGURATION_CONFLICT). Scegli una sola modalita\u2019 qui sotto.\r\n </p>\r\n }\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"radio\"\r\n name=\"output-mode\"\r\n [checked]=\"outputMode() === 'automatic'\"\r\n (change)=\"setOutputMode('automatic')\"\r\n />\r\n Output automatico <em>(consigliato)</em>\r\n </label>\r\n @if (outputMode() === 'automatic') {\r\n <p class=\"fb-field__hint\">\r\n Il risultato si referenzia con il nome dell\u2019elemento: <code>{{ name() }}</code>,\r\n <code>{{ name() }}.Campo</code>. Non serve dichiarare nessuna variabile.\r\n </p>\r\n }\r\n\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"radio\"\r\n name=\"output-mode\"\r\n [checked]=\"outputMode() === 'variable'\"\r\n (change)=\"setOutputMode('variable')\"\r\n />\r\n In una variabile\r\n </label>\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"radio\"\r\n name=\"output-mode\"\r\n [checked]=\"outputMode() === 'assignments'\"\r\n (change)=\"setOutputMode('assignments')\"\r\n />\r\n Campo per campo\r\n </label>\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"radio\"\r\n name=\"output-mode\"\r\n [checked]=\"outputMode() === 'discard'\"\r\n (change)=\"setOutputMode('discard')\"\r\n />\r\n Scarta il risultato\r\n </label>\r\n </div>\r\n\r\n @if (outputMode() === 'variable') {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Variabile di destinazione</label>\r\n <fb-reference-picker\r\n [value]=\"lookup().outputReference\"\r\n [writableOnly]=\"true\"\r\n [isCollection]=\"returnsCollection()\"\r\n [objectType]=\"lookup().object\"\r\n placeholder=\"Scegli una variabile\"\r\n (valueChange)=\"setOutputReference($event)\"\r\n />\r\n </div>\r\n }\r\n\r\n @if (outputMode() === 'assignments') {\r\n <div class=\"fb-list\">\r\n @for (assignment of outputAssignments(); 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)=\"removeOutputAssignment($index)\"\r\n >\r\n \u00D7\r\n </button>\r\n </div>\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Campo del record</label>\r\n <fb-field-picker\r\n [value]=\"assignment.field\"\r\n [object]=\"lookup().object\"\r\n usage=\"any\"\r\n placeholder=\"Scrivi o scegli un campo\"\r\n (valueChange)=\"setOutputAssignmentField($index, $event ?? '')\"\r\n />\r\n </div>\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Assegna a</label>\r\n <fb-reference-picker\r\n [value]=\"assignment.assignToReference\"\r\n [writableOnly]=\"true\"\r\n placeholder=\"Scegli una variabile\"\r\n (valueChange)=\"setOutputAssignmentTarget($index, $event)\"\r\n />\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n <button type=\"button\" class=\"fb-btn\" (click)=\"addOutputAssignment()\">Aggiungi campo</button>\r\n }\r\n\r\n @if (outputMode() === 'discard') {\r\n <p class=\"fb-callout fb-callout--warn\">\r\n L\u2019elemento interroga il database e butta via il risultato (LOOKUP_RESULT_DISCARDED).\r\n </p>\r\n }\r\n</fieldset>\r\n\r\n@if (hasRelatedRecords()) {\r\n <p class=\"fb-callout fb-callout--warn\">\r\n Questo elemento dichiara <code>relatedRecords</code>: il campo e\u2019 modellato ma non viene tradotto in\r\n query, quindi non ha effetto.\r\n </p>\r\n}\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: 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: RecordFilterEditorComponent, selector: "fb-record-filter-editor", inputs: ["holder", "object", "title", "usage", "fieldOptions", "supportsLogic", "supportsFormula", "emptyWarning", "emptyWarningSeverity"], outputs: ["changed"] }, { kind: "component", type: ReferencePickerComponent, selector: "fb-reference-picker", inputs: ["value", "label", "placeholder", "disabled", "dataType", "isCollection", "objectType", "writableOnly", "elementsOnly", "extraReferences", "preferredRoots"], outputs: ["valueChange"] }, { kind: "directive", type: SelectValueDirective, selector: "select[fbValue]", inputs: ["fbValue"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
14248
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.32", type: RecordLookupInspectorComponent, isStandalone: true, selector: "fb-record-lookup-inspector", usesInheritance: true, ngImport: i0, template: "<div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Oggetto</label>\r\n <fb-object-picker\r\n [value]=\"lookup().object\"\r\n placeholder=\"Scrivi o scegli un oggetto\"\r\n (valueChange)=\"setObject($event ?? '')\"\r\n />\r\n</div>\r\n\r\n<fb-record-filter-editor\r\n [holder]=\"lookup()\"\r\n [object]=\"lookup().object\"\r\n title=\"Quali record leggere\"\r\n usage=\"filterable\"\r\n [supportsLogic]=\"true\"\r\n [supportsFormula]=\"true\"\r\n emptyWarning=\"Senza filtri legge tutti i record dell\u2019oggetto.\"\r\n (changed)=\"onFiltersChanged($event)\"\r\n/>\r\n\r\n<fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Quanti e in che ordine</legend>\r\n\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"lookup().getFirstRecordOnly === true\"\r\n (change)=\"setFirstOnly($any($event.target).checked)\"\r\n />\r\n Solo il primo record\r\n </label>\r\n <p class=\"fb-field__hint\">\r\n @if (returnsCollection()) {\r\n Il risultato e\u2019 una <strong>collection</strong>: puo\u2019 essere iterata da un Loop.\r\n } @else {\r\n Il risultato e\u2019 un <strong>record singolo</strong>: non e\u2019 iterabile da un Loop.\r\n }\r\n </p>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Ordina per</label>\r\n <fb-field-picker\r\n [value]=\"lookup().sortField\"\r\n [object]=\"lookup().object\"\r\n usage=\"sortable\"\r\n label=\"Campo di ordinamento\"\r\n placeholder=\"Nessun ordinamento\"\r\n (valueChange)=\"setSortField($event ?? '')\"\r\n />\r\n </div>\r\n\r\n @if (lookup().sortField) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Direzione</label>\r\n <select class=\"fb-select\" [fbValue]=\"lookup().sortOrder || ''\" (change)=\"setSortOrder($any($event.target).value)\">\r\n @for (order of sortOrders(); track order.value) {\r\n <option [value]=\"order.value\">{{ order.label }}</option>\r\n }\r\n </select>\r\n </div>\r\n }\r\n\r\n @if (returnsCollection()) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Numero massimo di record</label>\r\n <input\r\n class=\"fb-input\"\r\n type=\"number\"\r\n min=\"1\"\r\n [value]=\"lookup().limit ?? ''\"\r\n (input)=\"setLimit($any($event.target).value)\"\r\n />\r\n </div>\r\n }\r\n</fieldset>\r\n\r\n@if (fieldOptions().length) {\r\n <fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Campi da leggere</legend>\r\n <p class=\"fb-section__note\">Nessuna selezione = tutti i campi disponibili.</p>\r\n <div class=\"fb-fields-grid\">\r\n @for (field of fieldOptions(); track field.name) {\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"isQueried(field.name)\"\r\n (change)=\"toggleQueriedField(field.name, $any($event.target).checked)\"\r\n />\r\n {{ field.label || field.name }}\r\n </label>\r\n }\r\n </div>\r\n </fieldset>\r\n}\r\n\r\n<fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Dove finisce il risultato</legend>\r\n\r\n @if (hasOutputConflict()) {\r\n <p class=\"fb-callout fb-callout--error\">\r\n Sono dichiarati insieme l\u2019output automatico e una destinazione esplicita: e\u2019 un conflitto\r\n (OUTPUT_CONFIGURATION_CONFLICT). Scegli una sola modalita\u2019 qui sotto.\r\n </p>\r\n }\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"radio\"\r\n name=\"output-mode\"\r\n [checked]=\"outputMode() === 'automatic'\"\r\n (change)=\"setOutputMode('automatic')\"\r\n />\r\n Output automatico <em>(consigliato)</em>\r\n </label>\r\n @if (outputMode() === 'automatic') {\r\n <p class=\"fb-field__hint\">\r\n Il risultato si referenzia con il nome dell\u2019elemento: <code>{{ name() }}</code>,\r\n <code>{{ name() }}.Campo</code>. Non serve dichiarare nessuna variabile.\r\n </p>\r\n }\r\n\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"radio\"\r\n name=\"output-mode\"\r\n [checked]=\"outputMode() === 'variable'\"\r\n (change)=\"setOutputMode('variable')\"\r\n />\r\n In una variabile\r\n </label>\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"radio\"\r\n name=\"output-mode\"\r\n [checked]=\"outputMode() === 'assignments'\"\r\n (change)=\"setOutputMode('assignments')\"\r\n />\r\n Campo per campo\r\n </label>\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"radio\"\r\n name=\"output-mode\"\r\n [checked]=\"outputMode() === 'discard'\"\r\n (change)=\"setOutputMode('discard')\"\r\n />\r\n Scarta il risultato\r\n </label>\r\n </div>\r\n\r\n @if (outputMode() === 'variable') {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Variabile di destinazione</label>\r\n <fb-reference-picker\r\n [value]=\"lookup().outputReference\"\r\n [writableOnly]=\"true\"\r\n dataType=\"Object\"\r\n [isCollection]=\"returnsCollection()\"\r\n [objectType]=\"lookup().object\"\r\n placeholder=\"Scegli una variabile\"\r\n (valueChange)=\"setOutputReference($event)\"\r\n />\r\n </div>\r\n }\r\n\r\n @if (outputMode() === 'assignments') {\r\n <div class=\"fb-list\">\r\n @for (assignment of outputAssignments(); 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)=\"removeOutputAssignment($index)\"\r\n >\r\n \u00D7\r\n </button>\r\n </div>\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Campo del record</label>\r\n <fb-field-picker\r\n [value]=\"assignment.field\"\r\n [object]=\"lookup().object\"\r\n usage=\"any\"\r\n placeholder=\"Scrivi o scegli un campo\"\r\n (valueChange)=\"setOutputAssignmentField($index, $event ?? '')\"\r\n />\r\n </div>\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Assegna a</label>\r\n <fb-reference-picker\r\n [value]=\"assignment.assignToReference\"\r\n [writableOnly]=\"true\"\r\n placeholder=\"Scegli una variabile\"\r\n (valueChange)=\"setOutputAssignmentTarget($index, $event)\"\r\n />\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n <button type=\"button\" class=\"fb-btn\" (click)=\"addOutputAssignment()\">Aggiungi campo</button>\r\n }\r\n\r\n @if (outputMode() === 'discard') {\r\n <p class=\"fb-callout fb-callout--warn\">\r\n L\u2019elemento interroga il database e butta via il risultato (LOOKUP_RESULT_DISCARDED).\r\n </p>\r\n }\r\n</fieldset>\r\n\r\n@if (hasRelatedRecords()) {\r\n <p class=\"fb-callout fb-callout--warn\">\r\n Questo elemento dichiara <code>relatedRecords</code>: il campo e\u2019 modellato ma non viene tradotto in\r\n query, quindi non ha effetto.\r\n </p>\r\n}\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: 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: RecordFilterEditorComponent, selector: "fb-record-filter-editor", inputs: ["holder", "object", "title", "usage", "fieldOptions", "supportsLogic", "supportsFormula", "emptyWarning", "emptyWarningSeverity"], outputs: ["changed"] }, { kind: "component", type: ReferencePickerComponent, selector: "fb-reference-picker", inputs: ["value", "label", "placeholder", "disabled", "dataType", "isCollection", "objectType", "writableOnly", "elementsOnly", "extraReferences", "preferredRoots"], outputs: ["valueChange"] }, { kind: "directive", type: SelectValueDirective, selector: "select[fbValue]", inputs: ["fbValue"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
14161
14249
  }
14162
14250
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.32", ngImport: i0, type: RecordLookupInspectorComponent, decorators: [{
14163
14251
  type: Component,
@@ -14168,7 +14256,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.32", ngImpo
14168
14256
  RecordFilterEditorComponent,
14169
14257
  ReferencePickerComponent,
14170
14258
  SelectValueDirective,
14171
- ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Oggetto</label>\r\n <fb-object-picker\r\n [value]=\"lookup().object\"\r\n placeholder=\"Scrivi o scegli un oggetto\"\r\n (valueChange)=\"setObject($event ?? '')\"\r\n />\r\n</div>\r\n\r\n<fb-record-filter-editor\r\n [holder]=\"lookup()\"\r\n [object]=\"lookup().object\"\r\n title=\"Quali record leggere\"\r\n usage=\"filterable\"\r\n [supportsLogic]=\"true\"\r\n [supportsFormula]=\"true\"\r\n emptyWarning=\"Senza filtri legge tutti i record dell\u2019oggetto.\"\r\n (changed)=\"onFiltersChanged($event)\"\r\n/>\r\n\r\n<fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Quanti e in che ordine</legend>\r\n\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"lookup().getFirstRecordOnly === true\"\r\n (change)=\"setFirstOnly($any($event.target).checked)\"\r\n />\r\n Solo il primo record\r\n </label>\r\n <p class=\"fb-field__hint\">\r\n @if (returnsCollection()) {\r\n Il risultato e\u2019 una <strong>collection</strong>: puo\u2019 essere iterata da un Loop.\r\n } @else {\r\n Il risultato e\u2019 un <strong>record singolo</strong>: non e\u2019 iterabile da un Loop.\r\n }\r\n </p>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Ordina per</label>\r\n <fb-field-picker\r\n [value]=\"lookup().sortField\"\r\n [object]=\"lookup().object\"\r\n usage=\"sortable\"\r\n label=\"Campo di ordinamento\"\r\n placeholder=\"Nessun ordinamento\"\r\n (valueChange)=\"setSortField($event ?? '')\"\r\n />\r\n </div>\r\n\r\n @if (lookup().sortField) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Direzione</label>\r\n <select class=\"fb-select\" [fbValue]=\"lookup().sortOrder || ''\" (change)=\"setSortOrder($any($event.target).value)\">\r\n @for (order of sortOrders(); track order.value) {\r\n <option [value]=\"order.value\">{{ order.label }}</option>\r\n }\r\n </select>\r\n </div>\r\n }\r\n\r\n @if (returnsCollection()) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Numero massimo di record</label>\r\n <input\r\n class=\"fb-input\"\r\n type=\"number\"\r\n min=\"1\"\r\n [value]=\"lookup().limit ?? ''\"\r\n (input)=\"setLimit($any($event.target).value)\"\r\n />\r\n </div>\r\n }\r\n</fieldset>\r\n\r\n@if (fieldOptions().length) {\r\n <fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Campi da leggere</legend>\r\n <p class=\"fb-section__note\">Nessuna selezione = tutti i campi disponibili.</p>\r\n <div class=\"fb-fields-grid\">\r\n @for (field of fieldOptions(); track field.name) {\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"isQueried(field.name)\"\r\n (change)=\"toggleQueriedField(field.name, $any($event.target).checked)\"\r\n />\r\n {{ field.label || field.name }}\r\n </label>\r\n }\r\n </div>\r\n </fieldset>\r\n}\r\n\r\n<fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Dove finisce il risultato</legend>\r\n\r\n @if (hasOutputConflict()) {\r\n <p class=\"fb-callout fb-callout--error\">\r\n Sono dichiarati insieme l\u2019output automatico e una destinazione esplicita: e\u2019 un conflitto\r\n (OUTPUT_CONFIGURATION_CONFLICT). Scegli una sola modalita\u2019 qui sotto.\r\n </p>\r\n }\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"radio\"\r\n name=\"output-mode\"\r\n [checked]=\"outputMode() === 'automatic'\"\r\n (change)=\"setOutputMode('automatic')\"\r\n />\r\n Output automatico <em>(consigliato)</em>\r\n </label>\r\n @if (outputMode() === 'automatic') {\r\n <p class=\"fb-field__hint\">\r\n Il risultato si referenzia con il nome dell\u2019elemento: <code>{{ name() }}</code>,\r\n <code>{{ name() }}.Campo</code>. Non serve dichiarare nessuna variabile.\r\n </p>\r\n }\r\n\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"radio\"\r\n name=\"output-mode\"\r\n [checked]=\"outputMode() === 'variable'\"\r\n (change)=\"setOutputMode('variable')\"\r\n />\r\n In una variabile\r\n </label>\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"radio\"\r\n name=\"output-mode\"\r\n [checked]=\"outputMode() === 'assignments'\"\r\n (change)=\"setOutputMode('assignments')\"\r\n />\r\n Campo per campo\r\n </label>\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"radio\"\r\n name=\"output-mode\"\r\n [checked]=\"outputMode() === 'discard'\"\r\n (change)=\"setOutputMode('discard')\"\r\n />\r\n Scarta il risultato\r\n </label>\r\n </div>\r\n\r\n @if (outputMode() === 'variable') {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Variabile di destinazione</label>\r\n <fb-reference-picker\r\n [value]=\"lookup().outputReference\"\r\n [writableOnly]=\"true\"\r\n [isCollection]=\"returnsCollection()\"\r\n [objectType]=\"lookup().object\"\r\n placeholder=\"Scegli una variabile\"\r\n (valueChange)=\"setOutputReference($event)\"\r\n />\r\n </div>\r\n }\r\n\r\n @if (outputMode() === 'assignments') {\r\n <div class=\"fb-list\">\r\n @for (assignment of outputAssignments(); 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)=\"removeOutputAssignment($index)\"\r\n >\r\n \u00D7\r\n </button>\r\n </div>\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Campo del record</label>\r\n <fb-field-picker\r\n [value]=\"assignment.field\"\r\n [object]=\"lookup().object\"\r\n usage=\"any\"\r\n placeholder=\"Scrivi o scegli un campo\"\r\n (valueChange)=\"setOutputAssignmentField($index, $event ?? '')\"\r\n />\r\n </div>\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Assegna a</label>\r\n <fb-reference-picker\r\n [value]=\"assignment.assignToReference\"\r\n [writableOnly]=\"true\"\r\n placeholder=\"Scegli una variabile\"\r\n (valueChange)=\"setOutputAssignmentTarget($index, $event)\"\r\n />\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n <button type=\"button\" class=\"fb-btn\" (click)=\"addOutputAssignment()\">Aggiungi campo</button>\r\n }\r\n\r\n @if (outputMode() === 'discard') {\r\n <p class=\"fb-callout fb-callout--warn\">\r\n L\u2019elemento interroga il database e butta via il risultato (LOOKUP_RESULT_DISCARDED).\r\n </p>\r\n }\r\n</fieldset>\r\n\r\n@if (hasRelatedRecords()) {\r\n <p class=\"fb-callout fb-callout--warn\">\r\n Questo elemento dichiara <code>relatedRecords</code>: il campo e\u2019 modellato ma non viene tradotto in\r\n query, quindi non ha effetto.\r\n </p>\r\n}\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" }]
14259
+ ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Oggetto</label>\r\n <fb-object-picker\r\n [value]=\"lookup().object\"\r\n placeholder=\"Scrivi o scegli un oggetto\"\r\n (valueChange)=\"setObject($event ?? '')\"\r\n />\r\n</div>\r\n\r\n<fb-record-filter-editor\r\n [holder]=\"lookup()\"\r\n [object]=\"lookup().object\"\r\n title=\"Quali record leggere\"\r\n usage=\"filterable\"\r\n [supportsLogic]=\"true\"\r\n [supportsFormula]=\"true\"\r\n emptyWarning=\"Senza filtri legge tutti i record dell\u2019oggetto.\"\r\n (changed)=\"onFiltersChanged($event)\"\r\n/>\r\n\r\n<fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Quanti e in che ordine</legend>\r\n\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"lookup().getFirstRecordOnly === true\"\r\n (change)=\"setFirstOnly($any($event.target).checked)\"\r\n />\r\n Solo il primo record\r\n </label>\r\n <p class=\"fb-field__hint\">\r\n @if (returnsCollection()) {\r\n Il risultato e\u2019 una <strong>collection</strong>: puo\u2019 essere iterata da un Loop.\r\n } @else {\r\n Il risultato e\u2019 un <strong>record singolo</strong>: non e\u2019 iterabile da un Loop.\r\n }\r\n </p>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Ordina per</label>\r\n <fb-field-picker\r\n [value]=\"lookup().sortField\"\r\n [object]=\"lookup().object\"\r\n usage=\"sortable\"\r\n label=\"Campo di ordinamento\"\r\n placeholder=\"Nessun ordinamento\"\r\n (valueChange)=\"setSortField($event ?? '')\"\r\n />\r\n </div>\r\n\r\n @if (lookup().sortField) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Direzione</label>\r\n <select class=\"fb-select\" [fbValue]=\"lookup().sortOrder || ''\" (change)=\"setSortOrder($any($event.target).value)\">\r\n @for (order of sortOrders(); track order.value) {\r\n <option [value]=\"order.value\">{{ order.label }}</option>\r\n }\r\n </select>\r\n </div>\r\n }\r\n\r\n @if (returnsCollection()) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Numero massimo di record</label>\r\n <input\r\n class=\"fb-input\"\r\n type=\"number\"\r\n min=\"1\"\r\n [value]=\"lookup().limit ?? ''\"\r\n (input)=\"setLimit($any($event.target).value)\"\r\n />\r\n </div>\r\n }\r\n</fieldset>\r\n\r\n@if (fieldOptions().length) {\r\n <fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Campi da leggere</legend>\r\n <p class=\"fb-section__note\">Nessuna selezione = tutti i campi disponibili.</p>\r\n <div class=\"fb-fields-grid\">\r\n @for (field of fieldOptions(); track field.name) {\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"isQueried(field.name)\"\r\n (change)=\"toggleQueriedField(field.name, $any($event.target).checked)\"\r\n />\r\n {{ field.label || field.name }}\r\n </label>\r\n }\r\n </div>\r\n </fieldset>\r\n}\r\n\r\n<fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Dove finisce il risultato</legend>\r\n\r\n @if (hasOutputConflict()) {\r\n <p class=\"fb-callout fb-callout--error\">\r\n Sono dichiarati insieme l\u2019output automatico e una destinazione esplicita: e\u2019 un conflitto\r\n (OUTPUT_CONFIGURATION_CONFLICT). Scegli una sola modalita\u2019 qui sotto.\r\n </p>\r\n }\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"radio\"\r\n name=\"output-mode\"\r\n [checked]=\"outputMode() === 'automatic'\"\r\n (change)=\"setOutputMode('automatic')\"\r\n />\r\n Output automatico <em>(consigliato)</em>\r\n </label>\r\n @if (outputMode() === 'automatic') {\r\n <p class=\"fb-field__hint\">\r\n Il risultato si referenzia con il nome dell\u2019elemento: <code>{{ name() }}</code>,\r\n <code>{{ name() }}.Campo</code>. Non serve dichiarare nessuna variabile.\r\n </p>\r\n }\r\n\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"radio\"\r\n name=\"output-mode\"\r\n [checked]=\"outputMode() === 'variable'\"\r\n (change)=\"setOutputMode('variable')\"\r\n />\r\n In una variabile\r\n </label>\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"radio\"\r\n name=\"output-mode\"\r\n [checked]=\"outputMode() === 'assignments'\"\r\n (change)=\"setOutputMode('assignments')\"\r\n />\r\n Campo per campo\r\n </label>\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"radio\"\r\n name=\"output-mode\"\r\n [checked]=\"outputMode() === 'discard'\"\r\n (change)=\"setOutputMode('discard')\"\r\n />\r\n Scarta il risultato\r\n </label>\r\n </div>\r\n\r\n @if (outputMode() === 'variable') {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Variabile di destinazione</label>\r\n <fb-reference-picker\r\n [value]=\"lookup().outputReference\"\r\n [writableOnly]=\"true\"\r\n dataType=\"Object\"\r\n [isCollection]=\"returnsCollection()\"\r\n [objectType]=\"lookup().object\"\r\n placeholder=\"Scegli una variabile\"\r\n (valueChange)=\"setOutputReference($event)\"\r\n />\r\n </div>\r\n }\r\n\r\n @if (outputMode() === 'assignments') {\r\n <div class=\"fb-list\">\r\n @for (assignment of outputAssignments(); 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)=\"removeOutputAssignment($index)\"\r\n >\r\n \u00D7\r\n </button>\r\n </div>\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Campo del record</label>\r\n <fb-field-picker\r\n [value]=\"assignment.field\"\r\n [object]=\"lookup().object\"\r\n usage=\"any\"\r\n placeholder=\"Scrivi o scegli un campo\"\r\n (valueChange)=\"setOutputAssignmentField($index, $event ?? '')\"\r\n />\r\n </div>\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Assegna a</label>\r\n <fb-reference-picker\r\n [value]=\"assignment.assignToReference\"\r\n [writableOnly]=\"true\"\r\n placeholder=\"Scegli una variabile\"\r\n (valueChange)=\"setOutputAssignmentTarget($index, $event)\"\r\n />\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n <button type=\"button\" class=\"fb-btn\" (click)=\"addOutputAssignment()\">Aggiungi campo</button>\r\n }\r\n\r\n @if (outputMode() === 'discard') {\r\n <p class=\"fb-callout fb-callout--warn\">\r\n L\u2019elemento interroga il database e butta via il risultato (LOOKUP_RESULT_DISCARDED).\r\n </p>\r\n }\r\n</fieldset>\r\n\r\n@if (hasRelatedRecords()) {\r\n <p class=\"fb-callout fb-callout--warn\">\r\n Questo elemento dichiara <code>relatedRecords</code>: il campo e\u2019 modellato ma non viene tradotto in\r\n query, quindi non ha effetto.\r\n </p>\r\n}\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" }]
14172
14260
  }], ctorParameters: () => [] });
14173
14261
 
14174
14262
  /**
@@ -14224,12 +14312,19 @@ class RecordWriteInspectorComponent extends NodeInspectorBase {
14224
14312
  * campi per oggetto restavano visibili.
14225
14313
  */
14226
14314
  chosenMode = signal(null, ...(ngDevMode ? [{ debugName: "chosenMode" }] : []));
14315
+ /**
14316
+ * Stessa ragione di {@link chosenMode}, sull'upsert: scelto «dall'id standard», il campo
14317
+ * non c'e' ancora, e il vuoto scritto per ricordarsene era falso — il radio tornava su
14318
+ * «id esterno».
14319
+ */
14320
+ chosenUpsertMode = signal(null, ...(ngDevMode ? [{ debugName: "chosenUpsertMode" }] : []));
14227
14321
  constructor() {
14228
14322
  super();
14229
14323
  // Cambiare elemento azzera le scelte locali al form: sono di questo node, non del prossimo.
14230
14324
  effect(() => {
14231
14325
  this.name();
14232
14326
  this.chosenMode.set(null);
14327
+ this.chosenUpsertMode.set(null);
14233
14328
  this.bulkUpdateConfirmed.set(false);
14234
14329
  });
14235
14330
  }
@@ -14268,7 +14363,13 @@ class RecordWriteInspectorComponent extends NodeInspectorBase {
14268
14363
  if (!record.doesUpsert) {
14269
14364
  return 'none';
14270
14365
  }
14271
- return record.upsertStandardIdField ? 'standard' : 'external';
14366
+ if (record.upsertStandardIdField) {
14367
+ return 'standard';
14368
+ }
14369
+ if (record.upsertExternalIdField) {
14370
+ return 'external';
14371
+ }
14372
+ return this.chosenUpsertMode() ?? 'external';
14272
14373
  }, ...(ngDevMode ? [{ debugName: "upsertMode" }] : []));
14273
14374
  /** Entrambi i campi di upsert insieme e' `UPSERT_CONFIGURATION_INVALID`. */
14274
14375
  hasUpsertConflict = computed(() => {
@@ -14330,24 +14431,25 @@ class RecordWriteInspectorComponent extends NodeInspectorBase {
14330
14431
  });
14331
14432
  }
14332
14433
  setUpsertMode(mode) {
14434
+ this.chosenUpsertMode.set(mode);
14333
14435
  this.patch((node) => {
14334
14436
  const record = node;
14335
14437
  record.doesUpsert = true;
14336
14438
  // Mai entrambi: scegliere l'uno cancella l'altro.
14337
14439
  if (mode === 'external') {
14338
14440
  delete record.upsertStandardIdField;
14339
- record.upsertExternalIdField ??= '';
14340
14441
  }
14341
14442
  else {
14342
14443
  delete record.upsertExternalIdField;
14343
- record.upsertStandardIdField ??= '';
14344
14444
  }
14345
14445
  });
14346
14446
  }
14347
14447
  setUpsertExternalField(value) {
14448
+ this.chosenUpsertMode.set('external');
14348
14449
  this.setField('upsertExternalIdField', value);
14349
14450
  }
14350
14451
  setUpsertStandardField(value) {
14452
+ this.chosenUpsertMode.set('standard');
14351
14453
  this.setField('upsertStandardIdField', value);
14352
14454
  }
14353
14455
  setStoreOutputAutomatically(value) {
@@ -17874,8 +17976,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.32", ngImpo
17874
17976
  * Versioni e ciclo di vita — FRONTEND.md §8, §9.
17875
17977
  *
17876
17978
  * Le quattro regole che l'interfaccia deve rispettare, tutte visibili qui:
17877
- * 1. **si modifica una bozza**: su una versione non modificabile il comando e' "Nuova
17878
- * versione", non "Salva" — `isEditable` arriva dal backend e non si ricalcola;
17979
+ * 1. **si modifica tutto tranne la versione attiva**: lì il comando e' "Nuova versione", non
17980
+ * "Salva" — `isEditable` arriva dal backend e non si ricalcola. Una superata si modifica, e
17981
+ * salvata torna bozza: la riga lo dice prima di aprirla, la conferma sta nel builder;
17879
17982
  * 2. una bozza si salva anche con errori, e finisce in `InvalidDraft`;
17880
17983
  * 3. **attivare esige zero errori**;
17881
17984
  * 4. una sola versione attiva: attivarne una rende `Obsolete` la precedente.
@@ -17988,11 +18091,11 @@ class VersionPanelComponent {
17988
18091
  this.closed.emit();
17989
18092
  }
17990
18093
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.32", ngImport: i0, type: VersionPanelComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
17991
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.32", type: VersionPanelComponent, isStandalone: true, selector: "fb-version-panel", outputs: { closed: "closed", versionOpened: "versionOpened", notice: "notice" }, ngImport: i0, template: "<header class=\"fb-ver__header\">\r\n <h2 class=\"fb-ver__title\">Versioni</h2>\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-ver__body\">\r\n <div class=\"fb-field__row\">\r\n <button type=\"button\" class=\"fb-btn\" [disabled]=\"isBusy()\" (click)=\"createNewVersion()\">\r\n Nuova versione dalla corrente\r\n </button>\r\n @if (hasActiveVersion()) {\r\n <button type=\"button\" class=\"fb-btn fb-btn--danger\" [disabled]=\"isBusy()\" (click)=\"deactivate()\">\r\n Disattiva\r\n </button>\r\n }\r\n </div>\r\n\r\n <ul class=\"fb-ver__list\">\r\n @for (version of versions(); track version.version) {\r\n <li class=\"fb-ver__item\" [class.fb-ver__item--current]=\"isCurrent(version)\">\r\n <div class=\"fb-ver__row\">\r\n <button type=\"button\" class=\"fb-ver__open\" (click)=\"open(version)\">\r\n <span class=\"fb-ver__number\">v{{ version.version }}</span>\r\n <span\r\n class=\"fb-ver__status\"\r\n [class.fb-ver__status--active]=\"isActive(version)\"\r\n [class.fb-ver__status--invalid]=\"version.status === 'InvalidDraft'\"\r\n >\r\n {{ statusLabel(version) }}\r\n </span>\r\n @if (isCurrent(version)) {\r\n <span class=\"fb-ver__badge\">aperta</span>\r\n }\r\n </button>\r\n </div>\r\n\r\n <p class=\"fb-ver__meta\">\r\n @if (version.updatedAt) {\r\n aggiornata {{ version.updatedAt }}\r\n }\r\n @if (version.updatedBy) {\r\n da {{ version.updatedBy }}\r\n }\r\n </p>\r\n\r\n <div class=\"fb-ver__actions\">\r\n @if (version.isEditable) {\r\n <span class=\"fb-ver__hint\">Modificabile</span>\r\n } @else {\r\n <!-- Salvare su una versione Active o Obsolete verrebbe rifiutato con NotEditable. -->\r\n <span class=\"fb-ver__hint\">Sola lettura: per modificarla, creane una nuova</span>\r\n }\r\n\r\n @if (!isActive(version) && isCurrent(version)) {\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-btn--primary fb-btn--icon\"\r\n [disabled]=\"isBusy() || !canActivate()\"\r\n [title]=\"canActivate() ? 'Attiva questa versione' : 'Ci sono errori da correggere prima di attivare'\"\r\n (click)=\"activate(version)\"\r\n >\r\n Attiva\r\n </button>\r\n }\r\n\r\n @if (!isActive(version)) {\r\n @if (pendingDelete() === version.version) {\r\n <span class=\"fb-ver__confirm\">\r\n Le esecuzioni sospese nate su questo numero riprenderebbero su una definizione diversa.\r\n <button type=\"button\" class=\"fb-btn fb-btn--danger fb-btn--icon\" (click)=\"confirmDelete(version.version)\">\r\n Elimina comunque\r\n </button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--ghost fb-btn--icon\" (click)=\"cancelDelete()\">\r\n Annulla\r\n </button>\r\n </span>\r\n } @else {\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-btn--ghost fb-btn--icon\"\r\n [disabled]=\"isBusy()\"\r\n (click)=\"requestDelete(version)\"\r\n >\r\n Elimina\r\n </button>\r\n }\r\n }\r\n </div>\r\n </li>\r\n } @empty {\r\n <li class=\"fb-empty\">Nessuna versione: il flow non e\u2019 ancora stato salvato.</li>\r\n }\r\n </ul>\r\n\r\n <p class=\"fb-ver__note\">\r\n Una sola versione e\u2019 attiva: attivarne una rende superata la precedente, senza doverla disattivare prima.\r\n Le versioni superate costano poco \u2014 lo stato \u00ABSuperata\u00BB esiste per questo.\r\n </p>\r\n</div>\r\n", styles: [":host{display:flex;flex-direction:column;height:100%;min-height:0;background:var(--fb-surface, #fff)}.fb-ver__header{display:flex;align-items:center;justify-content:space-between;padding:10px 12px;border-bottom:1px solid var(--fb-border-subtle, #e6e9ee)}.fb-ver__title{margin:0;font-size:14px;font-weight:600;color:var(--fb-text, #1d2939)}.fb-ver__body{flex:1;min-height:0;overflow-y:auto;padding:10px 12px}.fb-ver__list{margin:12px 0 0;padding:0;list-style:none}.fb-ver__item{margin-bottom:6px;padding:8px;border:1px solid var(--fb-border-subtle, #e6e9ee);border-radius:6px;background:var(--fb-surface-alt, #f8f9fb)}.fb-ver__item--current{border-color:var(--fb-accent, #2f6feb);background:color-mix(in srgb,var(--fb-accent, #2f6feb) 5%,transparent)}.fb-ver__open{display:flex;align-items:center;gap:6px;width:100%;padding:0;border:0;background:transparent;color:var(--fb-text, #1d2939);font:inherit;text-align:left;cursor:pointer}.fb-ver__number{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:13px;font-weight:700}.fb-ver__status{padding:1px 6px;border-radius:8px;background:var(--fb-border, #d6dae1);font-size:10px;font-weight:600;color:var(--fb-text, #1d2939)}.fb-ver__status--active{background:color-mix(in srgb,var(--fb-success, #3f8f5f) 18%,transparent);color:var(--fb-success, #3f8f5f)}.fb-ver__status--invalid{background:color-mix(in srgb,var(--fb-warning, #b7791f) 16%,transparent);color:var(--fb-warning, #b7791f)}.fb-ver__badge{font-size:9px;color:var(--fb-accent, #2f6feb);text-transform:uppercase;letter-spacing:.05em}.fb-ver__meta{margin:3px 0 0;font-size:10px;color:var(--fb-text-muted, #667085)}.fb-ver__actions{display:flex;flex-wrap:wrap;align-items:center;gap:6px;margin-top:6px}.fb-ver__hint{font-size:10px;color:var(--fb-text-subtle, #98a2b3)}.fb-ver__confirm{display:flex;flex-wrap:wrap;align-items:center;gap:4px;padding:5px 7px;border-radius:5px;background:color-mix(in srgb,var(--fb-error, #c9372c) 8%,transparent);font-size:10px;color:var(--fb-error, #c9372c)}.fb-ver__note{margin:12px 0 0;font-size:10px;line-height:1.45;color:var(--fb-text-subtle, #98a2b3)}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
18094
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.32", type: VersionPanelComponent, isStandalone: true, selector: "fb-version-panel", outputs: { closed: "closed", versionOpened: "versionOpened", notice: "notice" }, ngImport: i0, template: "<header class=\"fb-ver__header\">\r\n <h2 class=\"fb-ver__title\">Versioni</h2>\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-ver__body\">\r\n <div class=\"fb-field__row\">\r\n <button type=\"button\" class=\"fb-btn\" [disabled]=\"isBusy()\" (click)=\"createNewVersion()\">\r\n Nuova versione dalla corrente\r\n </button>\r\n @if (hasActiveVersion()) {\r\n <button type=\"button\" class=\"fb-btn fb-btn--danger\" [disabled]=\"isBusy()\" (click)=\"deactivate()\">\r\n Disattiva\r\n </button>\r\n }\r\n </div>\r\n\r\n <ul class=\"fb-ver__list\">\r\n @for (version of versions(); track version.version) {\r\n <li class=\"fb-ver__item\" [class.fb-ver__item--current]=\"isCurrent(version)\">\r\n <div class=\"fb-ver__row\">\r\n <button type=\"button\" class=\"fb-ver__open\" (click)=\"open(version)\">\r\n <span class=\"fb-ver__number\">v{{ version.version }}</span>\r\n <span\r\n class=\"fb-ver__status\"\r\n [class.fb-ver__status--active]=\"isActive(version)\"\r\n [class.fb-ver__status--invalid]=\"version.status === 'InvalidDraft'\"\r\n >\r\n {{ statusLabel(version) }}\r\n </span>\r\n @if (isCurrent(version)) {\r\n <span class=\"fb-ver__badge\">aperta</span>\r\n }\r\n </button>\r\n </div>\r\n\r\n <p class=\"fb-ver__meta\">\r\n @if (version.updatedAt) {\r\n aggiornata {{ version.updatedAt }}\r\n }\r\n @if (version.updatedBy) {\r\n da {{ version.updatedBy }}\r\n }\r\n </p>\r\n\r\n <div class=\"fb-ver__actions\">\r\n @if (!version.isEditable) {\r\n <!-- Salvare sulla versione attiva verrebbe rifiutato con NotEditable (\u00A78, regola 1). -->\r\n <span class=\"fb-ver__hint\">Sola lettura: per modificarla, creane una nuova</span>\r\n } @else if (version.status === 'Obsolete') {\r\n <!--\r\n Modificabile come una bozza, ma non e\u2019 la stessa cosa: salvata torna bozza, e le\r\n esecuzioni sospese nate su di lei riprendono con le modifiche. Detto qui perche\u2019 e\u2019 il\r\n posto in cui si sceglie quale versione aprire.\r\n -->\r\n <span class=\"fb-ver__hint\">Modificabile: salvata torna bozza, e va riattivata</span>\r\n } @else {\r\n <span class=\"fb-ver__hint\">Modificabile</span>\r\n }\r\n\r\n @if (!isActive(version) && isCurrent(version)) {\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-btn--primary fb-btn--icon\"\r\n [disabled]=\"isBusy() || !canActivate()\"\r\n [title]=\"canActivate() ? 'Attiva questa versione' : 'Ci sono errori da correggere prima di attivare'\"\r\n (click)=\"activate(version)\"\r\n >\r\n Attiva\r\n </button>\r\n }\r\n\r\n @if (!isActive(version)) {\r\n @if (pendingDelete() === version.version) {\r\n <span class=\"fb-ver__confirm\">\r\n Le esecuzioni sospese nate su questo numero riprenderebbero su una definizione diversa.\r\n <button type=\"button\" class=\"fb-btn fb-btn--danger fb-btn--icon\" (click)=\"confirmDelete(version.version)\">\r\n Elimina comunque\r\n </button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--ghost fb-btn--icon\" (click)=\"cancelDelete()\">\r\n Annulla\r\n </button>\r\n </span>\r\n } @else {\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-btn--ghost fb-btn--icon\"\r\n [disabled]=\"isBusy()\"\r\n (click)=\"requestDelete(version)\"\r\n >\r\n Elimina\r\n </button>\r\n }\r\n }\r\n </div>\r\n </li>\r\n } @empty {\r\n <li class=\"fb-empty\">Nessuna versione: il flow non e\u2019 ancora stato salvato.</li>\r\n }\r\n </ul>\r\n\r\n <p class=\"fb-ver__note\">\r\n Una sola versione e\u2019 attiva: attivarne una rende superata la precedente, senza doverla disattivare prima.\r\n Le versioni superate costano poco \u2014 lo stato \u00ABSuperata\u00BB esiste per questo \u2014 e restano modificabili:\r\n l\u2019unica che non si tocca e\u2019 quella attiva.\r\n </p>\r\n</div>\r\n", styles: [":host{display:flex;flex-direction:column;height:100%;min-height:0;background:var(--fb-surface, #fff)}.fb-ver__header{display:flex;align-items:center;justify-content:space-between;padding:10px 12px;border-bottom:1px solid var(--fb-border-subtle, #e6e9ee)}.fb-ver__title{margin:0;font-size:14px;font-weight:600;color:var(--fb-text, #1d2939)}.fb-ver__body{flex:1;min-height:0;overflow-y:auto;padding:10px 12px}.fb-ver__list{margin:12px 0 0;padding:0;list-style:none}.fb-ver__item{margin-bottom:6px;padding:8px;border:1px solid var(--fb-border-subtle, #e6e9ee);border-radius:6px;background:var(--fb-surface-alt, #f8f9fb)}.fb-ver__item--current{border-color:var(--fb-accent, #2f6feb);background:color-mix(in srgb,var(--fb-accent, #2f6feb) 5%,transparent)}.fb-ver__open{display:flex;align-items:center;gap:6px;width:100%;padding:0;border:0;background:transparent;color:var(--fb-text, #1d2939);font:inherit;text-align:left;cursor:pointer}.fb-ver__number{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:13px;font-weight:700}.fb-ver__status{padding:1px 6px;border-radius:8px;background:var(--fb-border, #d6dae1);font-size:10px;font-weight:600;color:var(--fb-text, #1d2939)}.fb-ver__status--active{background:color-mix(in srgb,var(--fb-success, #3f8f5f) 18%,transparent);color:var(--fb-success, #3f8f5f)}.fb-ver__status--invalid{background:color-mix(in srgb,var(--fb-warning, #b7791f) 16%,transparent);color:var(--fb-warning, #b7791f)}.fb-ver__badge{font-size:9px;color:var(--fb-accent, #2f6feb);text-transform:uppercase;letter-spacing:.05em}.fb-ver__meta{margin:3px 0 0;font-size:10px;color:var(--fb-text-muted, #667085)}.fb-ver__actions{display:flex;flex-wrap:wrap;align-items:center;gap:6px;margin-top:6px}.fb-ver__hint{font-size:10px;color:var(--fb-text-subtle, #98a2b3)}.fb-ver__confirm{display:flex;flex-wrap:wrap;align-items:center;gap:4px;padding:5px 7px;border-radius:5px;background:color-mix(in srgb,var(--fb-error, #c9372c) 8%,transparent);font-size:10px;color:var(--fb-error, #c9372c)}.fb-ver__note{margin:12px 0 0;font-size:10px;line-height:1.45;color:var(--fb-text-subtle, #98a2b3)}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
17992
18095
  }
17993
18096
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.32", ngImport: i0, type: VersionPanelComponent, decorators: [{
17994
18097
  type: Component,
17995
- args: [{ selector: 'fb-version-panel', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, template: "<header class=\"fb-ver__header\">\r\n <h2 class=\"fb-ver__title\">Versioni</h2>\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-ver__body\">\r\n <div class=\"fb-field__row\">\r\n <button type=\"button\" class=\"fb-btn\" [disabled]=\"isBusy()\" (click)=\"createNewVersion()\">\r\n Nuova versione dalla corrente\r\n </button>\r\n @if (hasActiveVersion()) {\r\n <button type=\"button\" class=\"fb-btn fb-btn--danger\" [disabled]=\"isBusy()\" (click)=\"deactivate()\">\r\n Disattiva\r\n </button>\r\n }\r\n </div>\r\n\r\n <ul class=\"fb-ver__list\">\r\n @for (version of versions(); track version.version) {\r\n <li class=\"fb-ver__item\" [class.fb-ver__item--current]=\"isCurrent(version)\">\r\n <div class=\"fb-ver__row\">\r\n <button type=\"button\" class=\"fb-ver__open\" (click)=\"open(version)\">\r\n <span class=\"fb-ver__number\">v{{ version.version }}</span>\r\n <span\r\n class=\"fb-ver__status\"\r\n [class.fb-ver__status--active]=\"isActive(version)\"\r\n [class.fb-ver__status--invalid]=\"version.status === 'InvalidDraft'\"\r\n >\r\n {{ statusLabel(version) }}\r\n </span>\r\n @if (isCurrent(version)) {\r\n <span class=\"fb-ver__badge\">aperta</span>\r\n }\r\n </button>\r\n </div>\r\n\r\n <p class=\"fb-ver__meta\">\r\n @if (version.updatedAt) {\r\n aggiornata {{ version.updatedAt }}\r\n }\r\n @if (version.updatedBy) {\r\n da {{ version.updatedBy }}\r\n }\r\n </p>\r\n\r\n <div class=\"fb-ver__actions\">\r\n @if (version.isEditable) {\r\n <span class=\"fb-ver__hint\">Modificabile</span>\r\n } @else {\r\n <!-- Salvare su una versione Active o Obsolete verrebbe rifiutato con NotEditable. -->\r\n <span class=\"fb-ver__hint\">Sola lettura: per modificarla, creane una nuova</span>\r\n }\r\n\r\n @if (!isActive(version) && isCurrent(version)) {\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-btn--primary fb-btn--icon\"\r\n [disabled]=\"isBusy() || !canActivate()\"\r\n [title]=\"canActivate() ? 'Attiva questa versione' : 'Ci sono errori da correggere prima di attivare'\"\r\n (click)=\"activate(version)\"\r\n >\r\n Attiva\r\n </button>\r\n }\r\n\r\n @if (!isActive(version)) {\r\n @if (pendingDelete() === version.version) {\r\n <span class=\"fb-ver__confirm\">\r\n Le esecuzioni sospese nate su questo numero riprenderebbero su una definizione diversa.\r\n <button type=\"button\" class=\"fb-btn fb-btn--danger fb-btn--icon\" (click)=\"confirmDelete(version.version)\">\r\n Elimina comunque\r\n </button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--ghost fb-btn--icon\" (click)=\"cancelDelete()\">\r\n Annulla\r\n </button>\r\n </span>\r\n } @else {\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-btn--ghost fb-btn--icon\"\r\n [disabled]=\"isBusy()\"\r\n (click)=\"requestDelete(version)\"\r\n >\r\n Elimina\r\n </button>\r\n }\r\n }\r\n </div>\r\n </li>\r\n } @empty {\r\n <li class=\"fb-empty\">Nessuna versione: il flow non e\u2019 ancora stato salvato.</li>\r\n }\r\n </ul>\r\n\r\n <p class=\"fb-ver__note\">\r\n Una sola versione e\u2019 attiva: attivarne una rende superata la precedente, senza doverla disattivare prima.\r\n Le versioni superate costano poco \u2014 lo stato \u00ABSuperata\u00BB esiste per questo.\r\n </p>\r\n</div>\r\n", styles: [":host{display:flex;flex-direction:column;height:100%;min-height:0;background:var(--fb-surface, #fff)}.fb-ver__header{display:flex;align-items:center;justify-content:space-between;padding:10px 12px;border-bottom:1px solid var(--fb-border-subtle, #e6e9ee)}.fb-ver__title{margin:0;font-size:14px;font-weight:600;color:var(--fb-text, #1d2939)}.fb-ver__body{flex:1;min-height:0;overflow-y:auto;padding:10px 12px}.fb-ver__list{margin:12px 0 0;padding:0;list-style:none}.fb-ver__item{margin-bottom:6px;padding:8px;border:1px solid var(--fb-border-subtle, #e6e9ee);border-radius:6px;background:var(--fb-surface-alt, #f8f9fb)}.fb-ver__item--current{border-color:var(--fb-accent, #2f6feb);background:color-mix(in srgb,var(--fb-accent, #2f6feb) 5%,transparent)}.fb-ver__open{display:flex;align-items:center;gap:6px;width:100%;padding:0;border:0;background:transparent;color:var(--fb-text, #1d2939);font:inherit;text-align:left;cursor:pointer}.fb-ver__number{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:13px;font-weight:700}.fb-ver__status{padding:1px 6px;border-radius:8px;background:var(--fb-border, #d6dae1);font-size:10px;font-weight:600;color:var(--fb-text, #1d2939)}.fb-ver__status--active{background:color-mix(in srgb,var(--fb-success, #3f8f5f) 18%,transparent);color:var(--fb-success, #3f8f5f)}.fb-ver__status--invalid{background:color-mix(in srgb,var(--fb-warning, #b7791f) 16%,transparent);color:var(--fb-warning, #b7791f)}.fb-ver__badge{font-size:9px;color:var(--fb-accent, #2f6feb);text-transform:uppercase;letter-spacing:.05em}.fb-ver__meta{margin:3px 0 0;font-size:10px;color:var(--fb-text-muted, #667085)}.fb-ver__actions{display:flex;flex-wrap:wrap;align-items:center;gap:6px;margin-top:6px}.fb-ver__hint{font-size:10px;color:var(--fb-text-subtle, #98a2b3)}.fb-ver__confirm{display:flex;flex-wrap:wrap;align-items:center;gap:4px;padding:5px 7px;border-radius:5px;background:color-mix(in srgb,var(--fb-error, #c9372c) 8%,transparent);font-size:10px;color:var(--fb-error, #c9372c)}.fb-ver__note{margin:12px 0 0;font-size:10px;line-height:1.45;color:var(--fb-text-subtle, #98a2b3)}\n"] }]
18098
+ args: [{ selector: 'fb-version-panel', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, template: "<header class=\"fb-ver__header\">\r\n <h2 class=\"fb-ver__title\">Versioni</h2>\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-ver__body\">\r\n <div class=\"fb-field__row\">\r\n <button type=\"button\" class=\"fb-btn\" [disabled]=\"isBusy()\" (click)=\"createNewVersion()\">\r\n Nuova versione dalla corrente\r\n </button>\r\n @if (hasActiveVersion()) {\r\n <button type=\"button\" class=\"fb-btn fb-btn--danger\" [disabled]=\"isBusy()\" (click)=\"deactivate()\">\r\n Disattiva\r\n </button>\r\n }\r\n </div>\r\n\r\n <ul class=\"fb-ver__list\">\r\n @for (version of versions(); track version.version) {\r\n <li class=\"fb-ver__item\" [class.fb-ver__item--current]=\"isCurrent(version)\">\r\n <div class=\"fb-ver__row\">\r\n <button type=\"button\" class=\"fb-ver__open\" (click)=\"open(version)\">\r\n <span class=\"fb-ver__number\">v{{ version.version }}</span>\r\n <span\r\n class=\"fb-ver__status\"\r\n [class.fb-ver__status--active]=\"isActive(version)\"\r\n [class.fb-ver__status--invalid]=\"version.status === 'InvalidDraft'\"\r\n >\r\n {{ statusLabel(version) }}\r\n </span>\r\n @if (isCurrent(version)) {\r\n <span class=\"fb-ver__badge\">aperta</span>\r\n }\r\n </button>\r\n </div>\r\n\r\n <p class=\"fb-ver__meta\">\r\n @if (version.updatedAt) {\r\n aggiornata {{ version.updatedAt }}\r\n }\r\n @if (version.updatedBy) {\r\n da {{ version.updatedBy }}\r\n }\r\n </p>\r\n\r\n <div class=\"fb-ver__actions\">\r\n @if (!version.isEditable) {\r\n <!-- Salvare sulla versione attiva verrebbe rifiutato con NotEditable (\u00A78, regola 1). -->\r\n <span class=\"fb-ver__hint\">Sola lettura: per modificarla, creane una nuova</span>\r\n } @else if (version.status === 'Obsolete') {\r\n <!--\r\n Modificabile come una bozza, ma non e\u2019 la stessa cosa: salvata torna bozza, e le\r\n esecuzioni sospese nate su di lei riprendono con le modifiche. Detto qui perche\u2019 e\u2019 il\r\n posto in cui si sceglie quale versione aprire.\r\n -->\r\n <span class=\"fb-ver__hint\">Modificabile: salvata torna bozza, e va riattivata</span>\r\n } @else {\r\n <span class=\"fb-ver__hint\">Modificabile</span>\r\n }\r\n\r\n @if (!isActive(version) && isCurrent(version)) {\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-btn--primary fb-btn--icon\"\r\n [disabled]=\"isBusy() || !canActivate()\"\r\n [title]=\"canActivate() ? 'Attiva questa versione' : 'Ci sono errori da correggere prima di attivare'\"\r\n (click)=\"activate(version)\"\r\n >\r\n Attiva\r\n </button>\r\n }\r\n\r\n @if (!isActive(version)) {\r\n @if (pendingDelete() === version.version) {\r\n <span class=\"fb-ver__confirm\">\r\n Le esecuzioni sospese nate su questo numero riprenderebbero su una definizione diversa.\r\n <button type=\"button\" class=\"fb-btn fb-btn--danger fb-btn--icon\" (click)=\"confirmDelete(version.version)\">\r\n Elimina comunque\r\n </button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--ghost fb-btn--icon\" (click)=\"cancelDelete()\">\r\n Annulla\r\n </button>\r\n </span>\r\n } @else {\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-btn--ghost fb-btn--icon\"\r\n [disabled]=\"isBusy()\"\r\n (click)=\"requestDelete(version)\"\r\n >\r\n Elimina\r\n </button>\r\n }\r\n }\r\n </div>\r\n </li>\r\n } @empty {\r\n <li class=\"fb-empty\">Nessuna versione: il flow non e\u2019 ancora stato salvato.</li>\r\n }\r\n </ul>\r\n\r\n <p class=\"fb-ver__note\">\r\n Una sola versione e\u2019 attiva: attivarne una rende superata la precedente, senza doverla disattivare prima.\r\n Le versioni superate costano poco \u2014 lo stato \u00ABSuperata\u00BB esiste per questo \u2014 e restano modificabili:\r\n l\u2019unica che non si tocca e\u2019 quella attiva.\r\n </p>\r\n</div>\r\n", styles: [":host{display:flex;flex-direction:column;height:100%;min-height:0;background:var(--fb-surface, #fff)}.fb-ver__header{display:flex;align-items:center;justify-content:space-between;padding:10px 12px;border-bottom:1px solid var(--fb-border-subtle, #e6e9ee)}.fb-ver__title{margin:0;font-size:14px;font-weight:600;color:var(--fb-text, #1d2939)}.fb-ver__body{flex:1;min-height:0;overflow-y:auto;padding:10px 12px}.fb-ver__list{margin:12px 0 0;padding:0;list-style:none}.fb-ver__item{margin-bottom:6px;padding:8px;border:1px solid var(--fb-border-subtle, #e6e9ee);border-radius:6px;background:var(--fb-surface-alt, #f8f9fb)}.fb-ver__item--current{border-color:var(--fb-accent, #2f6feb);background:color-mix(in srgb,var(--fb-accent, #2f6feb) 5%,transparent)}.fb-ver__open{display:flex;align-items:center;gap:6px;width:100%;padding:0;border:0;background:transparent;color:var(--fb-text, #1d2939);font:inherit;text-align:left;cursor:pointer}.fb-ver__number{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:13px;font-weight:700}.fb-ver__status{padding:1px 6px;border-radius:8px;background:var(--fb-border, #d6dae1);font-size:10px;font-weight:600;color:var(--fb-text, #1d2939)}.fb-ver__status--active{background:color-mix(in srgb,var(--fb-success, #3f8f5f) 18%,transparent);color:var(--fb-success, #3f8f5f)}.fb-ver__status--invalid{background:color-mix(in srgb,var(--fb-warning, #b7791f) 16%,transparent);color:var(--fb-warning, #b7791f)}.fb-ver__badge{font-size:9px;color:var(--fb-accent, #2f6feb);text-transform:uppercase;letter-spacing:.05em}.fb-ver__meta{margin:3px 0 0;font-size:10px;color:var(--fb-text-muted, #667085)}.fb-ver__actions{display:flex;flex-wrap:wrap;align-items:center;gap:6px;margin-top:6px}.fb-ver__hint{font-size:10px;color:var(--fb-text-subtle, #98a2b3)}.fb-ver__confirm{display:flex;flex-wrap:wrap;align-items:center;gap:4px;padding:5px 7px;border-radius:5px;background:color-mix(in srgb,var(--fb-error, #c9372c) 8%,transparent);font-size:10px;color:var(--fb-error, #c9372c)}.fb-ver__note{margin:12px 0 0;font-size:10px;line-height:1.45;color:var(--fb-text-subtle, #98a2b3)}\n"] }]
17996
18099
  }], propDecorators: { closed: [{ type: i0.Output, args: ["closed"] }], versionOpened: [{ type: i0.Output, args: ["versionOpened"] }], notice: [{ type: i0.Output, args: ["notice"] }] } });
17997
18100
 
17998
18101
  /**
@@ -18906,9 +19009,11 @@ class FlowBuilderComponent {
18906
19009
  }
18907
19010
  this.isLoading.set(true);
18908
19011
  this.notice.set(null);
18909
- // Si sta cambiando documento: una dialog aperta guarderebbe un elemento di un altro flow.
19012
+ // Si sta cambiando documento: una dialog aperta guarderebbe un elemento di un altro flow, e una
19013
+ // conferma aperta parlerebbe di una versione che non e' piu' quella in mano.
18910
19014
  this.isDialogOpen.set(false);
18911
19015
  this.editingResource.set(null);
19016
+ this.isObsoleteSaveOpen.set(false);
18912
19017
  try {
18913
19018
  if (flowName) {
18914
19019
  await this.session.open(flowName, version ?? undefined);
@@ -19646,12 +19751,66 @@ class FlowBuilderComponent {
19646
19751
  // -------------------------------------------------------------------------
19647
19752
  // Salvataggio e ciclo di vita
19648
19753
  // -------------------------------------------------------------------------
19754
+ /**
19755
+ * §8, regola 1 — il salvataggio su una versione **superata** aspetta una conferma. Si modifica come
19756
+ * una bozza, ma le esecuzioni sospese nate quando era attiva riprendono sulla definizione nuova, e
19757
+ * se il node su cui erano ferme non c'e' piu' falliscono: e' una domanda a cui il backend non puo'
19758
+ * rispondere al posto di chi salva. La barra resta finche' non si sceglie, come quella del duplicato,
19759
+ * e un secondo «Salva» non vale come conferma: la conferma e' un bottone che dice cosa accadra'.
19760
+ */
19761
+ isObsoleteSaveOpen = signal(false, ...(ngDevMode ? [{ debugName: "isObsoleteSaveOpen" }] : []));
19649
19762
  async save() {
19650
19763
  const definition = this.store.document();
19651
19764
  if (!definition.fullName) {
19652
19765
  this.notice.set({ kind: 'error', message: 'Dai un nome tecnico al flow prima di salvare.' });
19653
19766
  return;
19654
19767
  }
19768
+ if (this.session.isObsoleteVersion()) {
19769
+ this.isObsoleteSaveOpen.set(true);
19770
+ return;
19771
+ }
19772
+ await this.writeCurrentVersion();
19773
+ }
19774
+ /** «Salva comunque»: la superata si sovrascrive e torna bozza (§8). */
19775
+ async confirmObsoleteSave() {
19776
+ this.isObsoleteSaveOpen.set(false);
19777
+ const version = this.session.version();
19778
+ const saved = await this.writeCurrentVersion();
19779
+ if (saved) {
19780
+ // Detto qui perche' e' l'unico momento in cui e' una notizia: la versione non e' piu' una
19781
+ // superata, e non tornera' in produzione finche' qualcuno non la riattiva.
19782
+ const errors = saved.validation?.errorCount ?? 0;
19783
+ this.notice.set({
19784
+ kind: 'info',
19785
+ message: `Versione ${version} salvata: e’ tornata bozza${errors ? `, con ${errors} ${errors === 1 ? 'errore' : 'errori'} da correggere` : ''}. ` +
19786
+ 'Per rimetterla in produzione va riattivata.',
19787
+ });
19788
+ }
19789
+ }
19790
+ /** L'alternativa che non tocca le esecuzioni sospese: la superata resta com'era. */
19791
+ async saveObsoleteAsNewVersion() {
19792
+ this.isObsoleteSaveOpen.set(false);
19793
+ try {
19794
+ const result = await this.session.saveAsNewVersion();
19795
+ this.saved.emit(result);
19796
+ const version = this.session.version();
19797
+ this.notice.set({
19798
+ kind: 'info',
19799
+ message: version != null
19800
+ ? `Le modifiche sono nella nuova versione ${version}; la superata resta com’era.`
19801
+ : 'Le modifiche sono in una nuova versione; la superata resta com’era.',
19802
+ });
19803
+ }
19804
+ catch (error) {
19805
+ this.showError(error);
19806
+ }
19807
+ }
19808
+ cancelObsoleteSave() {
19809
+ this.isObsoleteSaveOpen.set(false);
19810
+ }
19811
+ /** Il salvataggio vero, con la notifica dell'esito; `undefined` se non e' riuscito. */
19812
+ async writeCurrentVersion() {
19813
+ const definition = this.store.document();
19655
19814
  try {
19656
19815
  const result = await this.session.save({ flowName: definition.fullName });
19657
19816
  this.saved.emit(result);
@@ -19668,9 +19827,11 @@ class FlowBuilderComponent {
19668
19827
  ? `${saved}, con ${errors} ${errors === 1 ? 'errore' : 'errori'} da correggere.`
19669
19828
  : `${saved}.`,
19670
19829
  });
19830
+ return result;
19671
19831
  }
19672
19832
  catch (error) {
19673
19833
  this.showError(error);
19834
+ return undefined;
19674
19835
  }
19675
19836
  }
19676
19837
  // ----------------------------------------------------------------- duplica
@@ -19958,6 +20119,7 @@ class FlowBuilderComponent {
19958
20119
  this.isLoading.set(true);
19959
20120
  this.isDialogOpen.set(false);
19960
20121
  this.editingResource.set(null);
20122
+ this.isObsoleteSaveOpen.set(false);
19961
20123
  try {
19962
20124
  await this.session.open(flowName, version);
19963
20125
  this.selectedName.set(START_NODE_NAME);
@@ -20005,7 +20167,7 @@ class FlowBuilderComponent {
20005
20167
  FormulaValidationService,
20006
20168
  FlowEditorSession,
20007
20169
  FlowLayoutService,
20008
- ], viewQueries: [{ propertyName: "canvas", first: true, predicate: FlowCanvasComponent, descendants: true, isSignal: true }, { propertyName: "search", first: true, predicate: FlowSearchComponent, descendants: true, isSignal: true }, { propertyName: "copyNameInput", first: true, predicate: ["copyNameInput"], descendants: true, isSignal: true }], ngImport: i0, template: "<div class=\"fb-builder\" [attr.data-fb-theme]=\"null\">\r\n <header class=\"fb-top\">\r\n <div class=\"fb-top__identity\">\r\n <input\r\n class=\"fb-top__label\"\r\n [value]=\"document().label || ''\"\r\n placeholder=\"Nome del flow\"\r\n aria-label=\"Nome del flow\"\r\n [disabled]=\"!isEditable()\"\r\n (input)=\"setLabel($any($event.target).value)\"\r\n />\r\n <div class=\"fb-top__meta\">\r\n <input\r\n class=\"fb-top__name\"\r\n [value]=\"document().fullName || ''\"\r\n placeholder=\"NomeTecnico\"\r\n aria-label=\"Nome tecnico del flow\"\r\n [disabled]=\"!isEditable()\"\r\n (input)=\"setFullName($any($event.target).value)\"\r\n />\r\n <!--\r\n Il tipo di flow si sceglie **alla creazione** e poi non si tocca pi\u00F9: decide quali\r\n globali esistono (\u00A74.1), quali elementi sono ammessi e come il motore esegue il flow,\r\n e cambiarlo su un documento gi\u00E0 scritto lo lascia pieno di riferimenti che non\r\n esistono. Su un flow salvato resta quindi un\u2019etichetta, non un comando.\r\n\r\n \u00A73.4 \u2014 cio\u2019 che si sceglie e\u2019 il **tipo di flow** (`flowTypes`), non il `processType`:\r\n quello si ricava, insieme al `triggerType`. Dove il dizionario non dichiara i tipi si\r\n torna alla vecchia tendina dei soli `processType`: e\u2019 un ripiego, non un difetto.\r\n -->\r\n @if (session.isNew() && flowTypes().length) {\r\n <select\r\n class=\"fb-top__process\"\r\n [fbValue]=\"flowType()?.value || ''\"\r\n aria-label=\"Tipo di flow\"\r\n [disabled]=\"!isEditable()\"\r\n (change)=\"setFlowType($any($event.target).value)\"\r\n >\r\n @if (!flowType()) {\r\n <option value=\"\" disabled>\u2014 tipo di flow \u2014</option>\r\n }\r\n @for (type of flowTypes(); track type.value) {\r\n <option [value]=\"type.value\" [title]=\"type.description || type.label\">{{ type.label }}</option>\r\n }\r\n </select>\r\n } @else if (session.isNew()) {\r\n <select\r\n class=\"fb-top__process\"\r\n [fbValue]=\"document().processType || ''\"\r\n aria-label=\"Tipo di flow\"\r\n [disabled]=\"!isEditable()\"\r\n (change)=\"setProcessType($any($event.target).value)\"\r\n >\r\n <!--\r\n Il segnaposto esiste perche' un `<select>` senza opzione corrispondente non e'\r\n \u00ABvuoto\u00BB: e' a `selectedIndex = -1`, e mostra una casella bianca. Su un flow nuovo\r\n (`processType` non ancora scelto) e' questa la riga che si vede, disabilitata\r\n perche' non e' un valore valido da salvare.\r\n -->\r\n @if (!document().processType) {\r\n <option value=\"\" disabled>\u2014 tipo di flow \u2014</option>\r\n }\r\n @for (type of processTypes(); track type.value) {\r\n <option [value]=\"type.value\">{{ type.label }}</option>\r\n }\r\n <!--\r\n Il tipo del documento che il dizionario non conosce: senza questa opzione la select\r\n resta bianca su un flow che il backend ha salvato con un `processType` fuori\r\n catalogo (o quando `processTypes` manca dalla risposta dei dizionari), e sembra che\r\n l'editor non abbia ricaricato il flow. Va mostrato **senza** riscrivere il\r\n documento: il valore e' del backend, non nostro da correggere.\r\n -->\r\n @if (isProcessTypeOutOfCatalog()) {\r\n <option [value]=\"document().processType\">{{ document().processType }} (fuori catalogo)</option>\r\n }\r\n </select>\r\n } @else {\r\n <span class=\"fb-top__process fb-top__process--fixed\" title=\"Il tipo di flow si sceglie alla creazione\">\r\n {{ flowTypeLabel() }}\r\n </span>\r\n }\r\n <span class=\"fb-top__status\">{{ statusLabel() }}</span>\r\n @if (session.version() != null) {\r\n <span class=\"fb-top__version\">v{{ session.version() }}</span>\r\n }\r\n @if (isDirty()) {\r\n <span class=\"fb-top__dirty\" title=\"Ci sono modifiche non salvate\">modificato</span>\r\n }\r\n </div>\r\n </div>\r\n\r\n <!--\r\n La ricerca **nel documento**: e\u2019 l\u2019unico modo di arrivare a un elemento su un flow che non\r\n sta in una schermata. Non e\u2019 la casella della palette, che filtra i tipi da aggiungere.\r\n -->\r\n <fb-flow-search (picked)=\"onSearchPicked($event)\" />\r\n\r\n <div class=\"fb-top__actions\">\r\n <button type=\"button\" class=\"fb-btn\" [disabled]=\"!canUndo()\" aria-label=\"Annulla\" (click)=\"undo()\">\u21B6</button>\r\n <button type=\"button\" class=\"fb-btn\" [disabled]=\"!canRedo()\" aria-label=\"Ripeti\" (click)=\"redo()\">\u21B7</button>\r\n <button type=\"button\" class=\"fb-btn\" title=\"Ricalcola le posizioni\" (click)=\"autoLayout()\">Riordina</button>\r\n <!--\r\n Un riquadro e\u2019 un commento sul canvas (\u00A73.6): non viene eseguito, quindi il comando sta\r\n qui accanto a \u00ABRiordina\u00BB e non nella palette degli elementi. Nasce attorno a cio\u2019 che e\u2019\r\n selezionato, che e\u2019 il gesto per cui serve.\r\n -->\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn\"\r\n [disabled]=\"!isEditable()\"\r\n [title]=\"\r\n canCopy()\r\n ? 'Racchiudi gli elementi selezionati in un riquadro di commento'\r\n : 'Crea un riquadro di commento vuoto: non cambia il comportamento del flow'\r\n \"\r\n aria-label=\"Nuovo riquadro di raggruppamento\"\r\n (click)=\"createGroup()\"\r\n >\r\n \u2B1A\r\n </button>\r\n\r\n <!--\r\n Copia e incolla stanno **anche** qui e non solo sui tasti: una scorciatoia che nessuno\r\n annuncia non esiste. Il titolo la dice, cos\u00EC si impara usandola una volta.\r\n L\u2019incolla e\u2019 acceso anche con la selezione vuota: cio\u2019 che si incolla sta negli appunti,\r\n e puo\u2019 venire da un altro flow.\r\n -->\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn\"\r\n [disabled]=\"!canCopy()\"\r\n title=\"Copia gli elementi selezionati, con le risorse che usano (Ctrl+C)\"\r\n aria-label=\"Copia gli elementi selezionati\"\r\n (click)=\"copySelection()\"\r\n >\r\n \u29C9\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn\"\r\n [disabled]=\"!canPaste() || !isEditable()\"\r\n title=\"Incolla gli elementi copiati, anche da un altro flow (Ctrl+V)\"\r\n aria-label=\"Incolla gli elementi copiati\"\r\n (click)=\"startPaste()\"\r\n >\r\n \u2398\r\n </button>\r\n @if (isDialogMode()) {\r\n <!-- Il doppio click sul node fa la stessa cosa, ma non si vede: questo comando s\u00EC. -->\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn\"\r\n [disabled]=\"!selectedName()\"\r\n title=\"Apri il dettaglio dell\u2019elemento selezionato\"\r\n (click)=\"openSelectedElement()\"\r\n >\r\n Dettaglio\r\n </button>\r\n }\r\n <button type=\"button\" class=\"fb-btn\" [disabled]=\"isBusy()\" (click)=\"validateNow()\">Valida</button>\r\n\r\n <!--\r\n Esegui e Debug non eseguono niente qui dentro: raccolgono i valori di ingresso e li\r\n consegnano all\u2019applicazione ospite. Si esegue la versione **salvata**, quindi su un flow\r\n mai scritto sono spenti e il titolo dice perche\u2019.\r\n -->\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn\"\r\n [disabled]=\"!canRun()\"\r\n [title]=\"canRun() ? 'Esegui il flow salvato' : 'Salva il flow prima di eseguirlo'\"\r\n (click)=\"openRun('run')\"\r\n >\r\n Esegui\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn\"\r\n [disabled]=\"!canRun()\"\r\n [title]=\"canRun() ? 'Esegui il flow salvato in debug' : 'Salva il flow prima di eseguirlo'\"\r\n (click)=\"openRun('debug')\"\r\n >\r\n Debug\r\n </button>\r\n\r\n @switch (primaryCommand()) {\r\n @case ('newVersion') {\r\n <!-- Su una versione non modificabile il comando primario e' \"Nuova versione\" (\u00A78.1). -->\r\n <button type=\"button\" class=\"fb-btn fb-btn--primary\" [disabled]=\"isBusy()\" (click)=\"createNewVersion()\">\r\n Nuova versione\r\n </button>\r\n }\r\n @case ('create') {\r\n <button type=\"button\" class=\"fb-btn fb-btn--primary\" [disabled]=\"isBusy()\" (click)=\"save()\">\r\n Crea\r\n </button>\r\n }\r\n @default {\r\n <button type=\"button\" class=\"fb-btn fb-btn--primary\" [disabled]=\"isBusy()\" (click)=\"save()\">\r\n Salva\r\n </button>\r\n }\r\n }\r\n\r\n @if (canDuplicate()) {\r\n <!-- \u00A76.2 \u00ABDuplica\u00BB: il flow sotto un altro nome, alla versione 1. -->\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn\"\r\n [disabled]=\"isBusy()\"\r\n title=\"Salva una copia con un altro nome\"\r\n (click)=\"startCopy()\"\r\n >\r\n Duplica\u2026\r\n </button>\r\n }\r\n\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn\"\r\n [disabled]=\"isBusy() || !canActivate()\"\r\n [title]=\"\r\n canActivate()\r\n ? 'Attiva questa versione'\r\n : 'L\u2019attivazione esige zero errori: correggili nel pannello dei problemi'\r\n \"\r\n (click)=\"activate()\"\r\n >\r\n Attiva\r\n </button>\r\n </div>\r\n </header>\r\n\r\n @if (conflict()) {\r\n <!-- \u00A79.3: qualcun altro ha salvato. Due strade, entrambe offerte. -->\r\n <div class=\"fb-banner fb-banner--warn\" role=\"alert\">\r\n <span>\r\n {{ conflict()?.message }}\r\n @if (conflict()?.conflictingAuthor) {\r\n Ha salvato {{ conflict()?.conflictingAuthor }}.\r\n }\r\n </span>\r\n <button type=\"button\" class=\"fb-btn fb-btn--icon\" (click)=\"reloadAfterConflict()\">Ricarica</button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--icon\" (click)=\"saveAsNewVersionAfterConflict()\">\r\n Salva come nuova versione\r\n </button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--ghost fb-btn--icon\" (click)=\"dismissConflict()\">\u00D7</button>\r\n </div>\r\n }\r\n\r\n @if (isCopyOpen()) {\r\n <!--\r\n Il nome si chiede prima di scrivere: e\u2019 l\u2019unico dato che il backend non puo\u2019 inventare, e\r\n un doppione lo rifiuta con AlreadyExists (\u00A76.2). Invio conferma, Esc annulla.\r\n -->\r\n <div class=\"fb-banner fb-copy\" role=\"group\" aria-label=\"Duplica il flow\">\r\n <label class=\"fb-copy__field\">\r\n <span class=\"fb-copy__caption\">Nome tecnico della copia</span>\r\n <input\r\n #copyNameInput\r\n class=\"fb-copy__input\"\r\n [value]=\"copyName()\"\r\n placeholder=\"NomeTecnico_Copia\"\r\n (input)=\"setCopyName($any($event.target).value)\"\r\n (keydown.enter)=\"confirmCopy()\"\r\n (keydown.escape)=\"cancelCopy()\"\r\n />\r\n </label>\r\n <label class=\"fb-copy__field\">\r\n <span class=\"fb-copy__caption\">Nome visibile</span>\r\n <input\r\n class=\"fb-copy__input\"\r\n [value]=\"copyLabel()\"\r\n placeholder=\"(facoltativo)\"\r\n (input)=\"setCopyLabel($any($event.target).value)\"\r\n (keydown.enter)=\"confirmCopy()\"\r\n (keydown.escape)=\"cancelCopy()\"\r\n />\r\n </label>\r\n <span class=\"fb-copy__hint\" [class.fb-copy__hint--error]=\"!!copyNameProblem()\">\r\n {{ copyNameProblem() || copyHint() }}\r\n </span>\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-btn--primary fb-btn--icon\"\r\n [disabled]=\"isBusy() || !!copyNameProblem()\"\r\n (click)=\"confirmCopy()\"\r\n >\r\n Duplica\r\n </button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--ghost fb-btn--icon\" (click)=\"cancelCopy()\">Annulla</button>\r\n </div>\r\n }\r\n\r\n @if (notice()) {\r\n <div\r\n class=\"fb-banner\"\r\n [class.fb-banner--error]=\"notice()?.kind === 'error'\"\r\n role=\"status\"\r\n >\r\n <span>{{ notice()?.message }}</span>\r\n <button type=\"button\" class=\"fb-btn fb-btn--ghost fb-btn--icon\" aria-label=\"Chiudi\" (click)=\"dismissNotice()\">\r\n \u00D7\r\n </button>\r\n </div>\r\n }\r\n\r\n @if (hasScreensInAutoLaunched()) {\r\n <div class=\"fb-banner fb-banner--error\">\r\n Un flow AutoLaunched che contiene screen e\u2019 un errore di validazione: non c\u2019e\u2019 nessuno a cui mostrarli.\r\n </div>\r\n }\r\n @if (isOrchestrationWithoutStages()) {\r\n <div class=\"fb-banner fb-banner--warn\">\r\n Un flow Orchestration senza stage di orchestrazione viene segnalato dalla validazione\r\n (ORCHESTRATION_WITHOUT_STAGES).\r\n </div>\r\n }\r\n @if (hasNoScreensInScreenFlow()) {\r\n <div class=\"fb-banner fb-banner--warn\">\r\n Un flow di tipo Screen senza nessuno screen viene segnalato dalla validazione.\r\n </div>\r\n }\r\n @if (!isEditable()) {\r\n <div class=\"fb-banner\">\r\n Questa versione e\u2019 in sola lettura: per modificarla creane una nuova.\r\n </div>\r\n }\r\n\r\n <div class=\"fb-main\">\r\n <aside class=\"fb-main__palette\">\r\n <fb-element-palette\r\n [processType]=\"document().processType\"\r\n [triggerType]=\"document().start?.[0]?.triggerType\"\r\n (elementPicked)=\"onElementPicked($event)\"\r\n />\r\n </aside>\r\n\r\n <div class=\"fb-main__center\">\r\n <fb-flow-canvas\r\n class=\"fb-main__canvas\"\r\n [selectedName]=\"selectedName()\"\r\n [selectedNames]=\"selectedNames()\"\r\n [selectedGroupName]=\"selectedGroupName()\"\r\n [outline]=\"outline()\"\r\n [isEditable]=\"isEditable()\"\r\n (selectionChange)=\"onSelectionChange($event)\"\r\n (nodeOpened)=\"onNodeOpened($event)\"\r\n (nodeRemoveRequested)=\"onRemoveNode($event)\"\r\n (nodeDuplicateRequested)=\"onDuplicateNode($event)\"\r\n (elementDropped)=\"onElementDropped($event)\"\r\n (groupSelected)=\"onGroupSelected($event)\"\r\n (groupRemoveRequested)=\"onGroupRemoved($event)\"\r\n />\r\n\r\n @if (showProblems()) {\r\n <fb-problems-panel\r\n class=\"fb-main__problems\"\r\n (elementFocused)=\"revealIssueElement($event)\"\r\n (elementOpened)=\"openIssueElement($event)\"\r\n (closed)=\"toggleProblems()\"\r\n />\r\n } @else {\r\n <button type=\"button\" class=\"fb-main__problems-toggle\" (click)=\"toggleProblems()\">\r\n Problemi\r\n @if (errorCount()) {\r\n <span class=\"fb-main__count fb-main__count--error\">{{ errorCount() }}</span>\r\n }\r\n @if (warningCount()) {\r\n <span class=\"fb-main__count fb-main__count--warn\">{{ warningCount() }}</span>\r\n }\r\n </button>\r\n }\r\n </div>\r\n\r\n <aside class=\"fb-main__side\">\r\n <nav class=\"fb-side__tabs\" aria-label=\"Pannelli\">\r\n @if (!isDialogMode()) {\r\n <button\r\n type=\"button\"\r\n class=\"fb-side__tab\"\r\n [class.fb-side__tab--active]=\"sidePanel() === 'inspector'\"\r\n (click)=\"setPanel('inspector')\"\r\n >\r\n Elemento\r\n </button>\r\n }\r\n <button\r\n type=\"button\"\r\n class=\"fb-side__tab\"\r\n [class.fb-side__tab--active]=\"sidePanel() === 'resources'\"\r\n (click)=\"setPanel('resources')\"\r\n >\r\n Risorse\r\n </button>\r\n <!--\r\n I riquadri (\u00A73.6). Il tab porta il conteggio perche\u2019 e\u2019 l\u2019unico modo di sapere che un\r\n flow ne ha, quando sono chiusi o fuori dalla vista.\r\n -->\r\n <button\r\n type=\"button\"\r\n class=\"fb-side__tab\"\r\n [class.fb-side__tab--active]=\"sidePanel() === 'groups'\"\r\n (click)=\"setPanel('groups')\"\r\n >\r\n Riquadri\r\n @if (store.groups().length) {\r\n <span class=\"fb-main__count\">{{ store.groups().length }}</span>\r\n }\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-side__tab\"\r\n [class.fb-side__tab--active]=\"sidePanel() === 'versions'\"\r\n (click)=\"setPanel('versions')\"\r\n >\r\n Versioni\r\n </button>\r\n <!--\r\n Il pannello dell\u2019ultima esecuzione. \u00C8 l\u2019unico posto in cui si vede la traccia di un\r\n flow **senza schermate**: l\u00EC il runtime non apre nessuna interfaccia, e senza questo\r\n tab non ci sarebbe niente da guardare.\r\n -->\r\n <button\r\n type=\"button\"\r\n class=\"fb-side__tab\"\r\n [class.fb-side__tab--active]=\"sidePanel() === 'debug'\"\r\n (click)=\"setPanel('debug')\"\r\n >\r\n Debug\r\n @if (isRunning()) {\r\n <span class=\"fb-side__tab-dot\" aria-label=\"esecuzione in corso\"></span>\r\n }\r\n </button>\r\n </nav>\r\n\r\n <div class=\"fb-side__content\">\r\n @switch (sidePanel()) {\r\n @case ('inspector') {\r\n <fb-element-inspector\r\n [selectedName]=\"selectedName()\"\r\n [initialFieldPath]=\"pendingFieldPath()\"\r\n (removeRequested)=\"onRemoveNode($event)\"\r\n (duplicateRequested)=\"onDuplicateNode($event)\"\r\n (renamed)=\"onNodeRenamed($event)\"\r\n (closed)=\"setPanel('resources')\"\r\n />\r\n }\r\n @case ('resources') {\r\n <fb-resource-panel\r\n [target]=\"resourceTarget()\"\r\n [editing]=\"editingResource()\"\r\n (editRequested)=\"onResourceEditRequested($event)\"\r\n (closed)=\"setPanel('inspector')\"\r\n />\r\n }\r\n @case ('groups') {\r\n <fb-group-panel\r\n [isEditable]=\"isEditable()\"\r\n [selectedNodeNames]=\"selectedNames()\"\r\n [target]=\"groupTarget()\"\r\n (createRequested)=\"createGroup()\"\r\n (memberFocused)=\"focusMember($event)\"\r\n (closed)=\"setPanel('resources')\"\r\n />\r\n }\r\n @case ('versions') {\r\n <fb-version-panel\r\n (versionOpened)=\"openVersion($event)\"\r\n (notice)=\"showNotice($event)\"\r\n (closed)=\"setPanel('inspector')\"\r\n />\r\n }\r\n @case ('debug') {\r\n <!-- Evidenzia sul canvas senza rubare il pannello: la traccia si sta leggendo. -->\r\n <fb-debug-panel\r\n [outcome]=\"shownOutcome()\"\r\n [wasDebug]=\"wasRunInDebug()\"\r\n [isRunning]=\"isRunning()\"\r\n (elementFocused)=\"highlightElement($event)\"\r\n (cleared)=\"clearRunOutcome()\"\r\n (closed)=\"setPanel('inspector')\"\r\n />\r\n }\r\n }\r\n </div>\r\n\r\n <footer class=\"fb-side__footer\">\r\n <label class=\"fb-btn fb-btn--icon\">\r\n Importa JSON\r\n <input type=\"file\" accept=\"application/json,.json\" hidden (change)=\"onFileSelected($event)\" />\r\n </label>\r\n </footer>\r\n </aside>\r\n </div>\r\n\r\n @if (pastePreview(); as preview) {\r\n <!--\r\n L\u2019incolla passa da una conferma perche\u2019 non e\u2019 mai una copia identica: nomi gi\u00E0 presi,\r\n risorse che qui non esistono, riferimenti che restano orfani. Il piano e\u2019 gi\u00E0 calcolato,\r\n la finestra lo mostra e basta.\r\n -->\r\n <fb-paste-dialog\r\n [payload]=\"preview.payload\"\r\n [plan]=\"preview.plan\"\r\n (confirmed)=\"confirmPaste($event)\"\r\n (cancelled)=\"cancelPaste()\"\r\n />\r\n }\r\n\r\n @if (runMode(); as mode) {\r\n <!--\r\n La finestra degli ingressi. Non avvia niente e non chiama niente: raccoglie i valori e li\r\n annuncia con `runRequested`, perche\u2019 l\u2019unico a sapere dove gira il motore e\u2019 l\u2019ospite.\r\n -->\r\n <fb-run-dialog\r\n [mode]=\"mode\"\r\n [flowName]=\"session.flowName()\"\r\n [version]=\"session.version()\"\r\n [isDirty]=\"isDirty()\"\r\n [isBusy]=\"isRunning()\"\r\n (confirmed)=\"confirmRun($event)\"\r\n (cancelled)=\"closeRun()\"\r\n />\r\n }\r\n\r\n @if (editingResource(); as edit) {\r\n <!--\r\n La finestra di una risorsa. Sta qui e non dentro il pannello per una ragione sola: nella\r\n colonna laterale la ritaglierebbe lo scorrimento.\r\n -->\r\n <fb-resource-dialog [edit]=\"edit\" (closed)=\"onResourceEditRequested(null)\" />\r\n }\r\n\r\n @if (isDialogMode() && isDialogOpen() && selectedName()) {\r\n <!-- La dialog sta dentro il builder, non nel body: la libreria e\u2019 innestabile. -->\r\n <fb-element-dialog\r\n [selectedName]=\"selectedName()\"\r\n [initialFieldPath]=\"pendingFieldPath()\"\r\n (closed)=\"closeDialog()\"\r\n (removeRequested)=\"onRemoveNode($event)\"\r\n (duplicateRequested)=\"onDuplicateNode($event)\"\r\n (renamed)=\"onNodeRenamed($event)\"\r\n />\r\n }\r\n</div>\r\n", styles: [":host{display:block;width:100%;height:100%;min-height:0;font:inherit;color:var(--fb-text, #1d2939)}.fb-builder{position:relative;display:flex;flex-direction:column;width:100%;height:100%;max-height:100vh;min-height:0;background:var(--fb-surface, #fff)}.fb-top{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:9px 14px;border-bottom:1px solid var(--fb-border, #e2e5eb);background:var(--fb-surface, #fff)}.fb-top__identity{min-width:0}.fb-top__label{width:100%;max-width:420px;padding:2px 4px;border:1px solid transparent;border-radius:4px;background:transparent;color:var(--fb-text, #1d2939);font:inherit;font-size:15px;font-weight:600}.fb-top__label:hover:not(:disabled),.fb-top__label:focus-visible{border-color:var(--fb-border, #d6dae1);background:var(--fb-surface, #fff)}.fb-top__meta{display:flex;flex-wrap:wrap;align-items:center;gap:6px;margin-top:2px}.fb-top__name{width:180px;padding:1px 4px;border:1px solid transparent;border-radius:4px;background:transparent;color:var(--fb-text-muted, #667085);font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px}.fb-top__name:hover:not(:disabled),.fb-top__name:focus-visible{border-color:var(--fb-border, #d6dae1)}.fb-top__process{padding:1px 4px;border:1px solid var(--fb-border-subtle, #e6e9ee);border-radius:4px;background:transparent;color:var(--fb-text-muted, #667085);font:inherit;font-size:11px}.fb-top__process--fixed{border-color:transparent;background:var(--fb-surface-sunken, #eef0f4);border-radius:999px;padding:2px 7px;font-weight:600}.fb-top__status,.fb-top__version,.fb-top__dirty{padding:2px 7px;border-radius:999px;background:var(--fb-surface-sunken, #eef0f4);color:var(--fb-text-muted, #6b7086);font-size:10px;font-weight:600}.fb-top__version{font-family:ui-monospace,SFMono-Regular,Menlo,monospace}.fb-top__dirty{background:color-mix(in srgb,var(--fb-warning, #b7791f) 16%,transparent);color:var(--fb-warning, #b7791f)}.fb-top__actions{display:flex;flex-wrap:wrap;gap:4px}.fb-banner{display:flex;flex-wrap:wrap;align-items:center;gap:8px;padding:6px 12px;border-bottom:1px solid var(--fb-border-subtle, #e6e9ee);background:color-mix(in srgb,var(--fb-accent, #2f6feb) 7%,transparent);font-size:11px}.fb-banner--warn{background:color-mix(in srgb,var(--fb-warning, #b7791f) 12%,transparent);color:var(--fb-warning, #b7791f)}.fb-banner--error{background:color-mix(in srgb,var(--fb-error, #c9372c) 10%,transparent);color:var(--fb-error, #c9372c)}.fb-banner>span{flex:1;min-width:200px}.fb-copy__field{display:flex;align-items:center;gap:6px}.fb-copy__caption{color:var(--fb-text-muted, #667085);white-space:nowrap}.fb-copy__input{width:180px;padding:2px 5px;border:1px solid var(--fb-border, #d6dae1);border-radius:4px;background:var(--fb-surface, #fff);color:var(--fb-text, #1d2939);font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px}.fb-copy__hint{flex:1;min-width:180px;color:var(--fb-text-muted, #667085)}.fb-copy__hint--error{color:var(--fb-error, #c9372c)}.fb-main{display:flex;flex:1;min-height:0}.fb-main__palette{flex:0 0 190px;min-width:0}.fb-main__center{display:flex;flex:1;flex-direction:column;min-width:0;min-height:0}.fb-main__canvas{flex:1;min-height:0}.fb-main__problems{flex:0 0 auto;height:220px}.fb-main__problems-toggle{display:flex;align-items:center;gap:6px;padding:4px 12px;border:0;border-top:1px solid var(--fb-border, #d6dae1);background:var(--fb-surface-alt, #f8f9fb);color:var(--fb-text-muted, #667085);font:inherit;font-size:11px;cursor:pointer}.fb-main__count{padding:0 5px;border-radius:8px;background:var(--fb-border, #d6dae1);font-size:9px;font-weight:700}.fb-main__count--error{background:color-mix(in srgb,var(--fb-error, #c9372c) 16%,transparent);color:var(--fb-error, #c9372c)}.fb-main__count--warn{background:color-mix(in srgb,var(--fb-warning, #b7791f) 16%,transparent);color:var(--fb-warning, #b7791f)}.fb-main__side{display:flex;flex-direction:column;flex:0 0 340px;min-width:0;min-height:0;border-left:1px solid var(--fb-border, #d6dae1);background:var(--fb-surface, #fff)}.fb-side__tabs{display:flex;gap:2px;margin:8px 10px;padding:3px;border-radius:var(--fb-radius, 10px);background:var(--fb-surface-sunken, #eef0f4)}.fb-side__tab{flex:1;padding:5px 8px;border:0;border-radius:var(--fb-radius-xs, 6px);background:transparent;color:var(--fb-text-muted, #6b7086);font:inherit;font-size:11px;cursor:pointer;transition:background .12s ease,color .12s ease}.fb-side__tab-dot{display:inline-block;width:6px;height:6px;margin-left:4px;border-radius:50%;background:var(--fb-accent, #2f6feb);vertical-align:middle}.fb-side__tab:hover:not(.fb-side__tab--active){color:var(--fb-text, #1a1c23)}.fb-side__tab--active{background:var(--fb-surface, #fff);box-shadow:var(--fb-shadow-sm, 0 1px 2px rgb(16 24 40 / 6%));color:var(--fb-text, #1a1c23);font-weight:600}.fb-side__content{flex:1;min-height:0;border-top:1px solid var(--fb-border-subtle, #eef0f4)}.fb-side__content>*{height:100%}.fb-side__footer{display:flex;gap:6px;padding:6px 8px;border-top:1px solid var(--fb-border-subtle, #e6e9ee)}@media(max-width:1200px){.fb-main__palette{flex-basis:150px}.fb-main__side{flex-basis:290px}}\n"], dependencies: [{ kind: "component", type: DebugPanelComponent, selector: "fb-debug-panel", inputs: ["outcome", "wasDebug", "isRunning"], outputs: ["closed", "elementFocused", "cleared"] }, { kind: "component", type: ElementDialogComponent, selector: "fb-element-dialog", inputs: ["selectedName", "initialFieldPath"], outputs: ["closed", "removeRequested", "duplicateRequested", "renamed"] }, { kind: "component", type: ElementInspectorComponent, selector: "fb-element-inspector", inputs: ["selectedName", "showHeader", "initialFieldPath"], outputs: ["closed", "removeRequested", "duplicateRequested", "renamed"] }, { kind: "component", type: ElementPaletteComponent, selector: "fb-element-palette", inputs: ["processType", "triggerType"], outputs: ["elementPicked"] }, { kind: "component", type: FlowCanvasComponent, selector: "fb-flow-canvas", inputs: ["selectedName", "selectedGroupName", "selectedNames", "outline", "isEditable"], outputs: ["selectionChange", "nodeOpened", "nodeRemoveRequested", "nodeDuplicateRequested", "groupSelected", "groupRemoveRequested", "elementDropped"] }, { kind: "component", type: FlowSearchComponent, selector: "fb-flow-search", outputs: ["picked"] }, { kind: "component", type: GroupPanelComponent, selector: "fb-group-panel", inputs: ["isEditable", "selectedNodeNames", "target"], outputs: ["closed", "createRequested", "memberFocused"] }, { kind: "component", type: PasteDialogComponent, selector: "fb-paste-dialog", inputs: ["payload", "plan"], outputs: ["confirmed", "cancelled"] }, { kind: "component", type: ProblemsPanelComponent, selector: "fb-problems-panel", outputs: ["elementFocused", "elementOpened", "closed"] }, { kind: "component", type: ResourceDialogComponent, selector: "fb-resource-dialog", inputs: ["edit"], outputs: ["closed"] }, { kind: "component", type: ResourcePanelComponent, selector: "fb-resource-panel", inputs: ["target", "editing"], outputs: ["closed", "editRequested"] }, { kind: "component", type: RunDialogComponent, selector: "fb-run-dialog", inputs: ["mode", "flowName", "version", "isDirty", "isBusy"], outputs: ["confirmed", "cancelled"] }, { kind: "component", type: VersionPanelComponent, selector: "fb-version-panel", outputs: ["closed", "versionOpened", "notice"] }, { kind: "directive", type: SelectValueDirective, selector: "select[fbValue]", inputs: ["fbValue"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
20170
+ ], viewQueries: [{ propertyName: "canvas", first: true, predicate: FlowCanvasComponent, descendants: true, isSignal: true }, { propertyName: "search", first: true, predicate: FlowSearchComponent, descendants: true, isSignal: true }, { propertyName: "copyNameInput", first: true, predicate: ["copyNameInput"], descendants: true, isSignal: true }], ngImport: i0, template: "<div class=\"fb-builder\" [attr.data-fb-theme]=\"null\">\r\n <header class=\"fb-top\">\r\n <div class=\"fb-top__identity\">\r\n <input\r\n class=\"fb-top__label\"\r\n [value]=\"document().label || ''\"\r\n placeholder=\"Nome del flow\"\r\n aria-label=\"Nome del flow\"\r\n [disabled]=\"!isEditable()\"\r\n (input)=\"setLabel($any($event.target).value)\"\r\n />\r\n <div class=\"fb-top__meta\">\r\n <input\r\n class=\"fb-top__name\"\r\n [value]=\"document().fullName || ''\"\r\n placeholder=\"NomeTecnico\"\r\n aria-label=\"Nome tecnico del flow\"\r\n [disabled]=\"!isEditable()\"\r\n (input)=\"setFullName($any($event.target).value)\"\r\n />\r\n <!--\r\n Il tipo di flow si sceglie **alla creazione** e poi non si tocca pi\u00F9: decide quali\r\n globali esistono (\u00A74.1), quali elementi sono ammessi e come il motore esegue il flow,\r\n e cambiarlo su un documento gi\u00E0 scritto lo lascia pieno di riferimenti che non\r\n esistono. Su un flow salvato resta quindi un\u2019etichetta, non un comando.\r\n\r\n \u00A73.4 \u2014 cio\u2019 che si sceglie e\u2019 il **tipo di flow** (`flowTypes`), non il `processType`:\r\n quello si ricava, insieme al `triggerType`. Dove il dizionario non dichiara i tipi si\r\n torna alla vecchia tendina dei soli `processType`: e\u2019 un ripiego, non un difetto.\r\n -->\r\n @if (session.isNew() && flowTypes().length) {\r\n <select\r\n class=\"fb-top__process\"\r\n [fbValue]=\"flowType()?.value || ''\"\r\n aria-label=\"Tipo di flow\"\r\n [disabled]=\"!isEditable()\"\r\n (change)=\"setFlowType($any($event.target).value)\"\r\n >\r\n @if (!flowType()) {\r\n <option value=\"\" disabled>\u2014 tipo di flow \u2014</option>\r\n }\r\n @for (type of flowTypes(); track type.value) {\r\n <option [value]=\"type.value\" [title]=\"type.description || type.label\">{{ type.label }}</option>\r\n }\r\n </select>\r\n } @else if (session.isNew()) {\r\n <select\r\n class=\"fb-top__process\"\r\n [fbValue]=\"document().processType || ''\"\r\n aria-label=\"Tipo di flow\"\r\n [disabled]=\"!isEditable()\"\r\n (change)=\"setProcessType($any($event.target).value)\"\r\n >\r\n <!--\r\n Il segnaposto esiste perche' un `<select>` senza opzione corrispondente non e'\r\n \u00ABvuoto\u00BB: e' a `selectedIndex = -1`, e mostra una casella bianca. Su un flow nuovo\r\n (`processType` non ancora scelto) e' questa la riga che si vede, disabilitata\r\n perche' non e' un valore valido da salvare.\r\n -->\r\n @if (!document().processType) {\r\n <option value=\"\" disabled>\u2014 tipo di flow \u2014</option>\r\n }\r\n @for (type of processTypes(); track type.value) {\r\n <option [value]=\"type.value\">{{ type.label }}</option>\r\n }\r\n <!--\r\n Il tipo del documento che il dizionario non conosce: senza questa opzione la select\r\n resta bianca su un flow che il backend ha salvato con un `processType` fuori\r\n catalogo (o quando `processTypes` manca dalla risposta dei dizionari), e sembra che\r\n l'editor non abbia ricaricato il flow. Va mostrato **senza** riscrivere il\r\n documento: il valore e' del backend, non nostro da correggere.\r\n -->\r\n @if (isProcessTypeOutOfCatalog()) {\r\n <option [value]=\"document().processType\">{{ document().processType }} (fuori catalogo)</option>\r\n }\r\n </select>\r\n } @else {\r\n <span class=\"fb-top__process fb-top__process--fixed\" title=\"Il tipo di flow si sceglie alla creazione\">\r\n {{ flowTypeLabel() }}\r\n </span>\r\n }\r\n <span class=\"fb-top__status\">{{ statusLabel() }}</span>\r\n @if (session.version() != null) {\r\n <span class=\"fb-top__version\">v{{ session.version() }}</span>\r\n }\r\n @if (isDirty()) {\r\n <span class=\"fb-top__dirty\" title=\"Ci sono modifiche non salvate\">modificato</span>\r\n }\r\n </div>\r\n </div>\r\n\r\n <!--\r\n La ricerca **nel documento**: e\u2019 l\u2019unico modo di arrivare a un elemento su un flow che non\r\n sta in una schermata. Non e\u2019 la casella della palette, che filtra i tipi da aggiungere.\r\n -->\r\n <fb-flow-search (picked)=\"onSearchPicked($event)\" />\r\n\r\n <div class=\"fb-top__actions\">\r\n <button type=\"button\" class=\"fb-btn\" [disabled]=\"!canUndo()\" aria-label=\"Annulla\" (click)=\"undo()\">\u21B6</button>\r\n <button type=\"button\" class=\"fb-btn\" [disabled]=\"!canRedo()\" aria-label=\"Ripeti\" (click)=\"redo()\">\u21B7</button>\r\n <button type=\"button\" class=\"fb-btn\" title=\"Ricalcola le posizioni\" (click)=\"autoLayout()\">Riordina</button>\r\n <!--\r\n Un riquadro e\u2019 un commento sul canvas (\u00A73.6): non viene eseguito, quindi il comando sta\r\n qui accanto a \u00ABRiordina\u00BB e non nella palette degli elementi. Nasce attorno a cio\u2019 che e\u2019\r\n selezionato, che e\u2019 il gesto per cui serve.\r\n -->\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn\"\r\n [disabled]=\"!isEditable()\"\r\n [title]=\"\r\n canCopy()\r\n ? 'Racchiudi gli elementi selezionati in un riquadro di commento'\r\n : 'Crea un riquadro di commento vuoto: non cambia il comportamento del flow'\r\n \"\r\n aria-label=\"Nuovo riquadro di raggruppamento\"\r\n (click)=\"createGroup()\"\r\n >\r\n \u2B1A\r\n </button>\r\n\r\n <!--\r\n Copia e incolla stanno **anche** qui e non solo sui tasti: una scorciatoia che nessuno\r\n annuncia non esiste. Il titolo la dice, cos\u00EC si impara usandola una volta.\r\n L\u2019incolla e\u2019 acceso anche con la selezione vuota: cio\u2019 che si incolla sta negli appunti,\r\n e puo\u2019 venire da un altro flow.\r\n -->\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn\"\r\n [disabled]=\"!canCopy()\"\r\n title=\"Copia gli elementi selezionati, con le risorse che usano (Ctrl+C)\"\r\n aria-label=\"Copia gli elementi selezionati\"\r\n (click)=\"copySelection()\"\r\n >\r\n \u29C9\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn\"\r\n [disabled]=\"!canPaste() || !isEditable()\"\r\n title=\"Incolla gli elementi copiati, anche da un altro flow (Ctrl+V)\"\r\n aria-label=\"Incolla gli elementi copiati\"\r\n (click)=\"startPaste()\"\r\n >\r\n \u2398\r\n </button>\r\n @if (isDialogMode()) {\r\n <!-- Il doppio click sul node fa la stessa cosa, ma non si vede: questo comando s\u00EC. -->\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn\"\r\n [disabled]=\"!selectedName()\"\r\n title=\"Apri il dettaglio dell\u2019elemento selezionato\"\r\n (click)=\"openSelectedElement()\"\r\n >\r\n Dettaglio\r\n </button>\r\n }\r\n <button type=\"button\" class=\"fb-btn\" [disabled]=\"isBusy()\" (click)=\"validateNow()\">Valida</button>\r\n\r\n <!--\r\n Esegui e Debug non eseguono niente qui dentro: raccolgono i valori di ingresso e li\r\n consegnano all\u2019applicazione ospite. Si esegue la versione **salvata**, quindi su un flow\r\n mai scritto sono spenti e il titolo dice perche\u2019.\r\n -->\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn\"\r\n [disabled]=\"!canRun()\"\r\n [title]=\"canRun() ? 'Esegui il flow salvato' : 'Salva il flow prima di eseguirlo'\"\r\n (click)=\"openRun('run')\"\r\n >\r\n Esegui\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn\"\r\n [disabled]=\"!canRun()\"\r\n [title]=\"canRun() ? 'Esegui il flow salvato in debug' : 'Salva il flow prima di eseguirlo'\"\r\n (click)=\"openRun('debug')\"\r\n >\r\n Debug\r\n </button>\r\n\r\n @switch (primaryCommand()) {\r\n @case ('newVersion') {\r\n <!-- Sulla versione attiva il comando primario e' \"Nuova versione\" (\u00A78, regola 1). -->\r\n <button type=\"button\" class=\"fb-btn fb-btn--primary\" [disabled]=\"isBusy()\" (click)=\"createNewVersion()\">\r\n Nuova versione\r\n </button>\r\n }\r\n @case ('create') {\r\n <button type=\"button\" class=\"fb-btn fb-btn--primary\" [disabled]=\"isBusy()\" (click)=\"save()\">\r\n Crea\r\n </button>\r\n }\r\n @default {\r\n <button type=\"button\" class=\"fb-btn fb-btn--primary\" [disabled]=\"isBusy()\" (click)=\"save()\">\r\n Salva\r\n </button>\r\n }\r\n }\r\n\r\n @if (canDuplicate()) {\r\n <!-- \u00A76.2 \u00ABDuplica\u00BB: il flow sotto un altro nome, alla versione 1. -->\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn\"\r\n [disabled]=\"isBusy()\"\r\n title=\"Salva una copia con un altro nome\"\r\n (click)=\"startCopy()\"\r\n >\r\n Duplica\u2026\r\n </button>\r\n }\r\n\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn\"\r\n [disabled]=\"isBusy() || !canActivate()\"\r\n [title]=\"\r\n canActivate()\r\n ? 'Attiva questa versione'\r\n : 'L\u2019attivazione esige zero errori: correggili nel pannello dei problemi'\r\n \"\r\n (click)=\"activate()\"\r\n >\r\n Attiva\r\n </button>\r\n </div>\r\n </header>\r\n\r\n @if (conflict()) {\r\n <!-- \u00A79.3: qualcun altro ha salvato. Due strade, entrambe offerte. -->\r\n <div class=\"fb-banner fb-banner--warn\" role=\"alert\">\r\n <span>\r\n {{ conflict()?.message }}\r\n @if (conflict()?.conflictingAuthor) {\r\n Ha salvato {{ conflict()?.conflictingAuthor }}.\r\n }\r\n </span>\r\n <button type=\"button\" class=\"fb-btn fb-btn--icon\" (click)=\"reloadAfterConflict()\">Ricarica</button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--icon\" (click)=\"saveAsNewVersionAfterConflict()\">\r\n Salva come nuova versione\r\n </button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--ghost fb-btn--icon\" (click)=\"dismissConflict()\">\u00D7</button>\r\n </div>\r\n }\r\n\r\n @if (isObsoleteSaveOpen() && session.isObsoleteVersion()) {\r\n <!--\r\n \u00A78, regola 1 \u2014 una superata si salva, ma il primo salvataggio si conferma: le esecuzioni\r\n sospese nate quando era attiva riprendono con le modifiche. La terza strada lascia la\r\n superata com\u2019era e mette le modifiche in una versione nuova.\r\n -->\r\n <div class=\"fb-banner fb-banner--warn\" role=\"alertdialog\" aria-label=\"Salvataggio su una versione superata\">\r\n <span>\r\n Questa versione e\u2019 superata. Le esecuzioni ancora in corso su questa versione riprenderanno con\r\n le modifiche, e se il punto in cui sono ferme non c\u2019e\u2019 piu\u2019 falliranno. Salvata, torna bozza:\r\n per rimetterla in produzione andra\u2019 riattivata.\r\n </span>\r\n <button type=\"button\" class=\"fb-btn fb-btn--primary fb-btn--icon\" [disabled]=\"isBusy()\" (click)=\"confirmObsoleteSave()\">\r\n Salva comunque\r\n </button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--icon\" [disabled]=\"isBusy()\" (click)=\"saveObsoleteAsNewVersion()\">\r\n Salva come nuova versione\r\n </button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--ghost fb-btn--icon\" (click)=\"cancelObsoleteSave()\">Annulla</button>\r\n </div>\r\n }\r\n\r\n @if (isCopyOpen()) {\r\n <!--\r\n Il nome si chiede prima di scrivere: e\u2019 l\u2019unico dato che il backend non puo\u2019 inventare, e\r\n un doppione lo rifiuta con AlreadyExists (\u00A76.2). Invio conferma, Esc annulla.\r\n -->\r\n <div class=\"fb-banner fb-copy\" role=\"group\" aria-label=\"Duplica il flow\">\r\n <label class=\"fb-copy__field\">\r\n <span class=\"fb-copy__caption\">Nome tecnico della copia</span>\r\n <input\r\n #copyNameInput\r\n class=\"fb-copy__input\"\r\n [value]=\"copyName()\"\r\n placeholder=\"NomeTecnico_Copia\"\r\n (input)=\"setCopyName($any($event.target).value)\"\r\n (keydown.enter)=\"confirmCopy()\"\r\n (keydown.escape)=\"cancelCopy()\"\r\n />\r\n </label>\r\n <label class=\"fb-copy__field\">\r\n <span class=\"fb-copy__caption\">Nome visibile</span>\r\n <input\r\n class=\"fb-copy__input\"\r\n [value]=\"copyLabel()\"\r\n placeholder=\"(facoltativo)\"\r\n (input)=\"setCopyLabel($any($event.target).value)\"\r\n (keydown.enter)=\"confirmCopy()\"\r\n (keydown.escape)=\"cancelCopy()\"\r\n />\r\n </label>\r\n <span class=\"fb-copy__hint\" [class.fb-copy__hint--error]=\"!!copyNameProblem()\">\r\n {{ copyNameProblem() || copyHint() }}\r\n </span>\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-btn--primary fb-btn--icon\"\r\n [disabled]=\"isBusy() || !!copyNameProblem()\"\r\n (click)=\"confirmCopy()\"\r\n >\r\n Duplica\r\n </button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--ghost fb-btn--icon\" (click)=\"cancelCopy()\">Annulla</button>\r\n </div>\r\n }\r\n\r\n @if (notice()) {\r\n <div\r\n class=\"fb-banner\"\r\n [class.fb-banner--error]=\"notice()?.kind === 'error'\"\r\n role=\"status\"\r\n >\r\n <span>{{ notice()?.message }}</span>\r\n <button type=\"button\" class=\"fb-btn fb-btn--ghost fb-btn--icon\" aria-label=\"Chiudi\" (click)=\"dismissNotice()\">\r\n \u00D7\r\n </button>\r\n </div>\r\n }\r\n\r\n @if (hasScreensInAutoLaunched()) {\r\n <div class=\"fb-banner fb-banner--error\">\r\n Un flow AutoLaunched che contiene screen e\u2019 un errore di validazione: non c\u2019e\u2019 nessuno a cui mostrarli.\r\n </div>\r\n }\r\n @if (isOrchestrationWithoutStages()) {\r\n <div class=\"fb-banner fb-banner--warn\">\r\n Un flow Orchestration senza stage di orchestrazione viene segnalato dalla validazione\r\n (ORCHESTRATION_WITHOUT_STAGES).\r\n </div>\r\n }\r\n @if (hasNoScreensInScreenFlow()) {\r\n <div class=\"fb-banner fb-banner--warn\">\r\n Un flow di tipo Screen senza nessuno screen viene segnalato dalla validazione.\r\n </div>\r\n }\r\n @if (!isEditable()) {\r\n <!-- \u00A78, regola 1: la sola versione che non si modifica e\u2019 quella da cui partono le esecuzioni. -->\r\n <div class=\"fb-banner\">\r\n Questa e\u2019 la versione attiva, ed e\u2019 in sola lettura: per modificarla creane una nuova.\r\n </div>\r\n }\r\n\r\n <div class=\"fb-main\">\r\n <aside class=\"fb-main__palette\">\r\n <fb-element-palette\r\n [processType]=\"document().processType\"\r\n [triggerType]=\"document().start?.[0]?.triggerType\"\r\n (elementPicked)=\"onElementPicked($event)\"\r\n />\r\n </aside>\r\n\r\n <div class=\"fb-main__center\">\r\n <fb-flow-canvas\r\n class=\"fb-main__canvas\"\r\n [selectedName]=\"selectedName()\"\r\n [selectedNames]=\"selectedNames()\"\r\n [selectedGroupName]=\"selectedGroupName()\"\r\n [outline]=\"outline()\"\r\n [isEditable]=\"isEditable()\"\r\n (selectionChange)=\"onSelectionChange($event)\"\r\n (nodeOpened)=\"onNodeOpened($event)\"\r\n (nodeRemoveRequested)=\"onRemoveNode($event)\"\r\n (nodeDuplicateRequested)=\"onDuplicateNode($event)\"\r\n (elementDropped)=\"onElementDropped($event)\"\r\n (groupSelected)=\"onGroupSelected($event)\"\r\n (groupRemoveRequested)=\"onGroupRemoved($event)\"\r\n />\r\n\r\n @if (showProblems()) {\r\n <fb-problems-panel\r\n class=\"fb-main__problems\"\r\n (elementFocused)=\"revealIssueElement($event)\"\r\n (elementOpened)=\"openIssueElement($event)\"\r\n (closed)=\"toggleProblems()\"\r\n />\r\n } @else {\r\n <button type=\"button\" class=\"fb-main__problems-toggle\" (click)=\"toggleProblems()\">\r\n Problemi\r\n @if (errorCount()) {\r\n <span class=\"fb-main__count fb-main__count--error\">{{ errorCount() }}</span>\r\n }\r\n @if (warningCount()) {\r\n <span class=\"fb-main__count fb-main__count--warn\">{{ warningCount() }}</span>\r\n }\r\n </button>\r\n }\r\n </div>\r\n\r\n <aside class=\"fb-main__side\">\r\n <nav class=\"fb-side__tabs\" aria-label=\"Pannelli\">\r\n @if (!isDialogMode()) {\r\n <button\r\n type=\"button\"\r\n class=\"fb-side__tab\"\r\n [class.fb-side__tab--active]=\"sidePanel() === 'inspector'\"\r\n (click)=\"setPanel('inspector')\"\r\n >\r\n Elemento\r\n </button>\r\n }\r\n <button\r\n type=\"button\"\r\n class=\"fb-side__tab\"\r\n [class.fb-side__tab--active]=\"sidePanel() === 'resources'\"\r\n (click)=\"setPanel('resources')\"\r\n >\r\n Risorse\r\n </button>\r\n <!--\r\n I riquadri (\u00A73.6). Il tab porta il conteggio perche\u2019 e\u2019 l\u2019unico modo di sapere che un\r\n flow ne ha, quando sono chiusi o fuori dalla vista.\r\n -->\r\n <button\r\n type=\"button\"\r\n class=\"fb-side__tab\"\r\n [class.fb-side__tab--active]=\"sidePanel() === 'groups'\"\r\n (click)=\"setPanel('groups')\"\r\n >\r\n Riquadri\r\n @if (store.groups().length) {\r\n <span class=\"fb-main__count\">{{ store.groups().length }}</span>\r\n }\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-side__tab\"\r\n [class.fb-side__tab--active]=\"sidePanel() === 'versions'\"\r\n (click)=\"setPanel('versions')\"\r\n >\r\n Versioni\r\n </button>\r\n <!--\r\n Il pannello dell\u2019ultima esecuzione. \u00C8 l\u2019unico posto in cui si vede la traccia di un\r\n flow **senza schermate**: l\u00EC il runtime non apre nessuna interfaccia, e senza questo\r\n tab non ci sarebbe niente da guardare.\r\n -->\r\n <button\r\n type=\"button\"\r\n class=\"fb-side__tab\"\r\n [class.fb-side__tab--active]=\"sidePanel() === 'debug'\"\r\n (click)=\"setPanel('debug')\"\r\n >\r\n Debug\r\n @if (isRunning()) {\r\n <span class=\"fb-side__tab-dot\" aria-label=\"esecuzione in corso\"></span>\r\n }\r\n </button>\r\n </nav>\r\n\r\n <div class=\"fb-side__content\">\r\n @switch (sidePanel()) {\r\n @case ('inspector') {\r\n <fb-element-inspector\r\n [selectedName]=\"selectedName()\"\r\n [initialFieldPath]=\"pendingFieldPath()\"\r\n (removeRequested)=\"onRemoveNode($event)\"\r\n (duplicateRequested)=\"onDuplicateNode($event)\"\r\n (renamed)=\"onNodeRenamed($event)\"\r\n (closed)=\"setPanel('resources')\"\r\n />\r\n }\r\n @case ('resources') {\r\n <fb-resource-panel\r\n [target]=\"resourceTarget()\"\r\n [editing]=\"editingResource()\"\r\n (editRequested)=\"onResourceEditRequested($event)\"\r\n (closed)=\"setPanel('inspector')\"\r\n />\r\n }\r\n @case ('groups') {\r\n <fb-group-panel\r\n [isEditable]=\"isEditable()\"\r\n [selectedNodeNames]=\"selectedNames()\"\r\n [target]=\"groupTarget()\"\r\n (createRequested)=\"createGroup()\"\r\n (memberFocused)=\"focusMember($event)\"\r\n (closed)=\"setPanel('resources')\"\r\n />\r\n }\r\n @case ('versions') {\r\n <fb-version-panel\r\n (versionOpened)=\"openVersion($event)\"\r\n (notice)=\"showNotice($event)\"\r\n (closed)=\"setPanel('inspector')\"\r\n />\r\n }\r\n @case ('debug') {\r\n <!-- Evidenzia sul canvas senza rubare il pannello: la traccia si sta leggendo. -->\r\n <fb-debug-panel\r\n [outcome]=\"shownOutcome()\"\r\n [wasDebug]=\"wasRunInDebug()\"\r\n [isRunning]=\"isRunning()\"\r\n (elementFocused)=\"highlightElement($event)\"\r\n (cleared)=\"clearRunOutcome()\"\r\n (closed)=\"setPanel('inspector')\"\r\n />\r\n }\r\n }\r\n </div>\r\n\r\n <footer class=\"fb-side__footer\">\r\n <label class=\"fb-btn fb-btn--icon\">\r\n Importa JSON\r\n <input type=\"file\" accept=\"application/json,.json\" hidden (change)=\"onFileSelected($event)\" />\r\n </label>\r\n </footer>\r\n </aside>\r\n </div>\r\n\r\n @if (pastePreview(); as preview) {\r\n <!--\r\n L\u2019incolla passa da una conferma perche\u2019 non e\u2019 mai una copia identica: nomi gi\u00E0 presi,\r\n risorse che qui non esistono, riferimenti che restano orfani. Il piano e\u2019 gi\u00E0 calcolato,\r\n la finestra lo mostra e basta.\r\n -->\r\n <fb-paste-dialog\r\n [payload]=\"preview.payload\"\r\n [plan]=\"preview.plan\"\r\n (confirmed)=\"confirmPaste($event)\"\r\n (cancelled)=\"cancelPaste()\"\r\n />\r\n }\r\n\r\n @if (runMode(); as mode) {\r\n <!--\r\n La finestra degli ingressi. Non avvia niente e non chiama niente: raccoglie i valori e li\r\n annuncia con `runRequested`, perche\u2019 l\u2019unico a sapere dove gira il motore e\u2019 l\u2019ospite.\r\n -->\r\n <fb-run-dialog\r\n [mode]=\"mode\"\r\n [flowName]=\"session.flowName()\"\r\n [version]=\"session.version()\"\r\n [isDirty]=\"isDirty()\"\r\n [isBusy]=\"isRunning()\"\r\n (confirmed)=\"confirmRun($event)\"\r\n (cancelled)=\"closeRun()\"\r\n />\r\n }\r\n\r\n @if (editingResource(); as edit) {\r\n <!--\r\n La finestra di una risorsa. Sta qui e non dentro il pannello per una ragione sola: nella\r\n colonna laterale la ritaglierebbe lo scorrimento.\r\n -->\r\n <fb-resource-dialog [edit]=\"edit\" (closed)=\"onResourceEditRequested(null)\" />\r\n }\r\n\r\n @if (isDialogMode() && isDialogOpen() && selectedName()) {\r\n <!-- La dialog sta dentro il builder, non nel body: la libreria e\u2019 innestabile. -->\r\n <fb-element-dialog\r\n [selectedName]=\"selectedName()\"\r\n [initialFieldPath]=\"pendingFieldPath()\"\r\n (closed)=\"closeDialog()\"\r\n (removeRequested)=\"onRemoveNode($event)\"\r\n (duplicateRequested)=\"onDuplicateNode($event)\"\r\n (renamed)=\"onNodeRenamed($event)\"\r\n />\r\n }\r\n</div>\r\n", styles: [":host{display:block;width:100%;height:100%;min-height:0;font:inherit;color:var(--fb-text, #1d2939)}.fb-builder{position:relative;display:flex;flex-direction:column;width:100%;height:100%;max-height:100vh;min-height:0;background:var(--fb-surface, #fff)}.fb-top{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:9px 14px;border-bottom:1px solid var(--fb-border, #e2e5eb);background:var(--fb-surface, #fff)}.fb-top__identity{min-width:0}.fb-top__label{width:100%;max-width:420px;padding:2px 4px;border:1px solid transparent;border-radius:4px;background:transparent;color:var(--fb-text, #1d2939);font:inherit;font-size:15px;font-weight:600}.fb-top__label:hover:not(:disabled),.fb-top__label:focus-visible{border-color:var(--fb-border, #d6dae1);background:var(--fb-surface, #fff)}.fb-top__meta{display:flex;flex-wrap:wrap;align-items:center;gap:6px;margin-top:2px}.fb-top__name{width:180px;padding:1px 4px;border:1px solid transparent;border-radius:4px;background:transparent;color:var(--fb-text-muted, #667085);font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px}.fb-top__name:hover:not(:disabled),.fb-top__name:focus-visible{border-color:var(--fb-border, #d6dae1)}.fb-top__process{padding:1px 4px;border:1px solid var(--fb-border-subtle, #e6e9ee);border-radius:4px;background:transparent;color:var(--fb-text-muted, #667085);font:inherit;font-size:11px}.fb-top__process--fixed{border-color:transparent;background:var(--fb-surface-sunken, #eef0f4);border-radius:999px;padding:2px 7px;font-weight:600}.fb-top__status,.fb-top__version,.fb-top__dirty{padding:2px 7px;border-radius:999px;background:var(--fb-surface-sunken, #eef0f4);color:var(--fb-text-muted, #6b7086);font-size:10px;font-weight:600}.fb-top__version{font-family:ui-monospace,SFMono-Regular,Menlo,monospace}.fb-top__dirty{background:color-mix(in srgb,var(--fb-warning, #b7791f) 16%,transparent);color:var(--fb-warning, #b7791f)}.fb-top__actions{display:flex;flex-wrap:wrap;gap:4px}.fb-banner{display:flex;flex-wrap:wrap;align-items:center;gap:8px;padding:6px 12px;border-bottom:1px solid var(--fb-border-subtle, #e6e9ee);background:color-mix(in srgb,var(--fb-accent, #2f6feb) 7%,transparent);font-size:11px}.fb-banner--warn{background:color-mix(in srgb,var(--fb-warning, #b7791f) 12%,transparent);color:var(--fb-warning, #b7791f)}.fb-banner--error{background:color-mix(in srgb,var(--fb-error, #c9372c) 10%,transparent);color:var(--fb-error, #c9372c)}.fb-banner>span{flex:1;min-width:200px}.fb-copy__field{display:flex;align-items:center;gap:6px}.fb-copy__caption{color:var(--fb-text-muted, #667085);white-space:nowrap}.fb-copy__input{width:180px;padding:2px 5px;border:1px solid var(--fb-border, #d6dae1);border-radius:4px;background:var(--fb-surface, #fff);color:var(--fb-text, #1d2939);font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px}.fb-copy__hint{flex:1;min-width:180px;color:var(--fb-text-muted, #667085)}.fb-copy__hint--error{color:var(--fb-error, #c9372c)}.fb-main{display:flex;flex:1;min-height:0}.fb-main__palette{flex:0 0 190px;min-width:0}.fb-main__center{display:flex;flex:1;flex-direction:column;min-width:0;min-height:0}.fb-main__canvas{flex:1;min-height:0}.fb-main__problems{flex:0 0 auto;height:220px}.fb-main__problems-toggle{display:flex;align-items:center;gap:6px;padding:4px 12px;border:0;border-top:1px solid var(--fb-border, #d6dae1);background:var(--fb-surface-alt, #f8f9fb);color:var(--fb-text-muted, #667085);font:inherit;font-size:11px;cursor:pointer}.fb-main__count{padding:0 5px;border-radius:8px;background:var(--fb-border, #d6dae1);font-size:9px;font-weight:700}.fb-main__count--error{background:color-mix(in srgb,var(--fb-error, #c9372c) 16%,transparent);color:var(--fb-error, #c9372c)}.fb-main__count--warn{background:color-mix(in srgb,var(--fb-warning, #b7791f) 16%,transparent);color:var(--fb-warning, #b7791f)}.fb-main__side{display:flex;flex-direction:column;flex:0 0 340px;min-width:0;min-height:0;border-left:1px solid var(--fb-border, #d6dae1);background:var(--fb-surface, #fff)}.fb-side__tabs{display:flex;gap:2px;margin:8px 10px;padding:3px;border-radius:var(--fb-radius, 10px);background:var(--fb-surface-sunken, #eef0f4)}.fb-side__tab{flex:1;padding:5px 8px;border:0;border-radius:var(--fb-radius-xs, 6px);background:transparent;color:var(--fb-text-muted, #6b7086);font:inherit;font-size:11px;cursor:pointer;transition:background .12s ease,color .12s ease}.fb-side__tab-dot{display:inline-block;width:6px;height:6px;margin-left:4px;border-radius:50%;background:var(--fb-accent, #2f6feb);vertical-align:middle}.fb-side__tab:hover:not(.fb-side__tab--active){color:var(--fb-text, #1a1c23)}.fb-side__tab--active{background:var(--fb-surface, #fff);box-shadow:var(--fb-shadow-sm, 0 1px 2px rgb(16 24 40 / 6%));color:var(--fb-text, #1a1c23);font-weight:600}.fb-side__content{flex:1;min-height:0;border-top:1px solid var(--fb-border-subtle, #eef0f4)}.fb-side__content>*{height:100%}.fb-side__footer{display:flex;gap:6px;padding:6px 8px;border-top:1px solid var(--fb-border-subtle, #e6e9ee)}@media(max-width:1200px){.fb-main__palette{flex-basis:150px}.fb-main__side{flex-basis:290px}}\n"], dependencies: [{ kind: "component", type: DebugPanelComponent, selector: "fb-debug-panel", inputs: ["outcome", "wasDebug", "isRunning"], outputs: ["closed", "elementFocused", "cleared"] }, { kind: "component", type: ElementDialogComponent, selector: "fb-element-dialog", inputs: ["selectedName", "initialFieldPath"], outputs: ["closed", "removeRequested", "duplicateRequested", "renamed"] }, { kind: "component", type: ElementInspectorComponent, selector: "fb-element-inspector", inputs: ["selectedName", "showHeader", "initialFieldPath"], outputs: ["closed", "removeRequested", "duplicateRequested", "renamed"] }, { kind: "component", type: ElementPaletteComponent, selector: "fb-element-palette", inputs: ["processType", "triggerType"], outputs: ["elementPicked"] }, { kind: "component", type: FlowCanvasComponent, selector: "fb-flow-canvas", inputs: ["selectedName", "selectedGroupName", "selectedNames", "outline", "isEditable"], outputs: ["selectionChange", "nodeOpened", "nodeRemoveRequested", "nodeDuplicateRequested", "groupSelected", "groupRemoveRequested", "elementDropped"] }, { kind: "component", type: FlowSearchComponent, selector: "fb-flow-search", outputs: ["picked"] }, { kind: "component", type: GroupPanelComponent, selector: "fb-group-panel", inputs: ["isEditable", "selectedNodeNames", "target"], outputs: ["closed", "createRequested", "memberFocused"] }, { kind: "component", type: PasteDialogComponent, selector: "fb-paste-dialog", inputs: ["payload", "plan"], outputs: ["confirmed", "cancelled"] }, { kind: "component", type: ProblemsPanelComponent, selector: "fb-problems-panel", outputs: ["elementFocused", "elementOpened", "closed"] }, { kind: "component", type: ResourceDialogComponent, selector: "fb-resource-dialog", inputs: ["edit"], outputs: ["closed"] }, { kind: "component", type: ResourcePanelComponent, selector: "fb-resource-panel", inputs: ["target", "editing"], outputs: ["closed", "editRequested"] }, { kind: "component", type: RunDialogComponent, selector: "fb-run-dialog", inputs: ["mode", "flowName", "version", "isDirty", "isBusy"], outputs: ["confirmed", "cancelled"] }, { kind: "component", type: VersionPanelComponent, selector: "fb-version-panel", outputs: ["closed", "versionOpened", "notice"] }, { kind: "directive", type: SelectValueDirective, selector: "select[fbValue]", inputs: ["fbValue"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
20009
20171
  }
20010
20172
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.32", ngImport: i0, type: FlowBuilderComponent, decorators: [{
20011
20173
  type: Component,
@@ -20031,7 +20193,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.32", ngImpo
20031
20193
  FormulaValidationService,
20032
20194
  FlowEditorSession,
20033
20195
  FlowLayoutService,
20034
- ], changeDetection: ChangeDetectionStrategy.OnPush, host: { '(keydown)': 'onKeyDown($event)' }, template: "<div class=\"fb-builder\" [attr.data-fb-theme]=\"null\">\r\n <header class=\"fb-top\">\r\n <div class=\"fb-top__identity\">\r\n <input\r\n class=\"fb-top__label\"\r\n [value]=\"document().label || ''\"\r\n placeholder=\"Nome del flow\"\r\n aria-label=\"Nome del flow\"\r\n [disabled]=\"!isEditable()\"\r\n (input)=\"setLabel($any($event.target).value)\"\r\n />\r\n <div class=\"fb-top__meta\">\r\n <input\r\n class=\"fb-top__name\"\r\n [value]=\"document().fullName || ''\"\r\n placeholder=\"NomeTecnico\"\r\n aria-label=\"Nome tecnico del flow\"\r\n [disabled]=\"!isEditable()\"\r\n (input)=\"setFullName($any($event.target).value)\"\r\n />\r\n <!--\r\n Il tipo di flow si sceglie **alla creazione** e poi non si tocca pi\u00F9: decide quali\r\n globali esistono (\u00A74.1), quali elementi sono ammessi e come il motore esegue il flow,\r\n e cambiarlo su un documento gi\u00E0 scritto lo lascia pieno di riferimenti che non\r\n esistono. Su un flow salvato resta quindi un\u2019etichetta, non un comando.\r\n\r\n \u00A73.4 \u2014 cio\u2019 che si sceglie e\u2019 il **tipo di flow** (`flowTypes`), non il `processType`:\r\n quello si ricava, insieme al `triggerType`. Dove il dizionario non dichiara i tipi si\r\n torna alla vecchia tendina dei soli `processType`: e\u2019 un ripiego, non un difetto.\r\n -->\r\n @if (session.isNew() && flowTypes().length) {\r\n <select\r\n class=\"fb-top__process\"\r\n [fbValue]=\"flowType()?.value || ''\"\r\n aria-label=\"Tipo di flow\"\r\n [disabled]=\"!isEditable()\"\r\n (change)=\"setFlowType($any($event.target).value)\"\r\n >\r\n @if (!flowType()) {\r\n <option value=\"\" disabled>\u2014 tipo di flow \u2014</option>\r\n }\r\n @for (type of flowTypes(); track type.value) {\r\n <option [value]=\"type.value\" [title]=\"type.description || type.label\">{{ type.label }}</option>\r\n }\r\n </select>\r\n } @else if (session.isNew()) {\r\n <select\r\n class=\"fb-top__process\"\r\n [fbValue]=\"document().processType || ''\"\r\n aria-label=\"Tipo di flow\"\r\n [disabled]=\"!isEditable()\"\r\n (change)=\"setProcessType($any($event.target).value)\"\r\n >\r\n <!--\r\n Il segnaposto esiste perche' un `<select>` senza opzione corrispondente non e'\r\n \u00ABvuoto\u00BB: e' a `selectedIndex = -1`, e mostra una casella bianca. Su un flow nuovo\r\n (`processType` non ancora scelto) e' questa la riga che si vede, disabilitata\r\n perche' non e' un valore valido da salvare.\r\n -->\r\n @if (!document().processType) {\r\n <option value=\"\" disabled>\u2014 tipo di flow \u2014</option>\r\n }\r\n @for (type of processTypes(); track type.value) {\r\n <option [value]=\"type.value\">{{ type.label }}</option>\r\n }\r\n <!--\r\n Il tipo del documento che il dizionario non conosce: senza questa opzione la select\r\n resta bianca su un flow che il backend ha salvato con un `processType` fuori\r\n catalogo (o quando `processTypes` manca dalla risposta dei dizionari), e sembra che\r\n l'editor non abbia ricaricato il flow. Va mostrato **senza** riscrivere il\r\n documento: il valore e' del backend, non nostro da correggere.\r\n -->\r\n @if (isProcessTypeOutOfCatalog()) {\r\n <option [value]=\"document().processType\">{{ document().processType }} (fuori catalogo)</option>\r\n }\r\n </select>\r\n } @else {\r\n <span class=\"fb-top__process fb-top__process--fixed\" title=\"Il tipo di flow si sceglie alla creazione\">\r\n {{ flowTypeLabel() }}\r\n </span>\r\n }\r\n <span class=\"fb-top__status\">{{ statusLabel() }}</span>\r\n @if (session.version() != null) {\r\n <span class=\"fb-top__version\">v{{ session.version() }}</span>\r\n }\r\n @if (isDirty()) {\r\n <span class=\"fb-top__dirty\" title=\"Ci sono modifiche non salvate\">modificato</span>\r\n }\r\n </div>\r\n </div>\r\n\r\n <!--\r\n La ricerca **nel documento**: e\u2019 l\u2019unico modo di arrivare a un elemento su un flow che non\r\n sta in una schermata. Non e\u2019 la casella della palette, che filtra i tipi da aggiungere.\r\n -->\r\n <fb-flow-search (picked)=\"onSearchPicked($event)\" />\r\n\r\n <div class=\"fb-top__actions\">\r\n <button type=\"button\" class=\"fb-btn\" [disabled]=\"!canUndo()\" aria-label=\"Annulla\" (click)=\"undo()\">\u21B6</button>\r\n <button type=\"button\" class=\"fb-btn\" [disabled]=\"!canRedo()\" aria-label=\"Ripeti\" (click)=\"redo()\">\u21B7</button>\r\n <button type=\"button\" class=\"fb-btn\" title=\"Ricalcola le posizioni\" (click)=\"autoLayout()\">Riordina</button>\r\n <!--\r\n Un riquadro e\u2019 un commento sul canvas (\u00A73.6): non viene eseguito, quindi il comando sta\r\n qui accanto a \u00ABRiordina\u00BB e non nella palette degli elementi. Nasce attorno a cio\u2019 che e\u2019\r\n selezionato, che e\u2019 il gesto per cui serve.\r\n -->\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn\"\r\n [disabled]=\"!isEditable()\"\r\n [title]=\"\r\n canCopy()\r\n ? 'Racchiudi gli elementi selezionati in un riquadro di commento'\r\n : 'Crea un riquadro di commento vuoto: non cambia il comportamento del flow'\r\n \"\r\n aria-label=\"Nuovo riquadro di raggruppamento\"\r\n (click)=\"createGroup()\"\r\n >\r\n \u2B1A\r\n </button>\r\n\r\n <!--\r\n Copia e incolla stanno **anche** qui e non solo sui tasti: una scorciatoia che nessuno\r\n annuncia non esiste. Il titolo la dice, cos\u00EC si impara usandola una volta.\r\n L\u2019incolla e\u2019 acceso anche con la selezione vuota: cio\u2019 che si incolla sta negli appunti,\r\n e puo\u2019 venire da un altro flow.\r\n -->\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn\"\r\n [disabled]=\"!canCopy()\"\r\n title=\"Copia gli elementi selezionati, con le risorse che usano (Ctrl+C)\"\r\n aria-label=\"Copia gli elementi selezionati\"\r\n (click)=\"copySelection()\"\r\n >\r\n \u29C9\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn\"\r\n [disabled]=\"!canPaste() || !isEditable()\"\r\n title=\"Incolla gli elementi copiati, anche da un altro flow (Ctrl+V)\"\r\n aria-label=\"Incolla gli elementi copiati\"\r\n (click)=\"startPaste()\"\r\n >\r\n \u2398\r\n </button>\r\n @if (isDialogMode()) {\r\n <!-- Il doppio click sul node fa la stessa cosa, ma non si vede: questo comando s\u00EC. -->\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn\"\r\n [disabled]=\"!selectedName()\"\r\n title=\"Apri il dettaglio dell\u2019elemento selezionato\"\r\n (click)=\"openSelectedElement()\"\r\n >\r\n Dettaglio\r\n </button>\r\n }\r\n <button type=\"button\" class=\"fb-btn\" [disabled]=\"isBusy()\" (click)=\"validateNow()\">Valida</button>\r\n\r\n <!--\r\n Esegui e Debug non eseguono niente qui dentro: raccolgono i valori di ingresso e li\r\n consegnano all\u2019applicazione ospite. Si esegue la versione **salvata**, quindi su un flow\r\n mai scritto sono spenti e il titolo dice perche\u2019.\r\n -->\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn\"\r\n [disabled]=\"!canRun()\"\r\n [title]=\"canRun() ? 'Esegui il flow salvato' : 'Salva il flow prima di eseguirlo'\"\r\n (click)=\"openRun('run')\"\r\n >\r\n Esegui\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn\"\r\n [disabled]=\"!canRun()\"\r\n [title]=\"canRun() ? 'Esegui il flow salvato in debug' : 'Salva il flow prima di eseguirlo'\"\r\n (click)=\"openRun('debug')\"\r\n >\r\n Debug\r\n </button>\r\n\r\n @switch (primaryCommand()) {\r\n @case ('newVersion') {\r\n <!-- Su una versione non modificabile il comando primario e' \"Nuova versione\" (\u00A78.1). -->\r\n <button type=\"button\" class=\"fb-btn fb-btn--primary\" [disabled]=\"isBusy()\" (click)=\"createNewVersion()\">\r\n Nuova versione\r\n </button>\r\n }\r\n @case ('create') {\r\n <button type=\"button\" class=\"fb-btn fb-btn--primary\" [disabled]=\"isBusy()\" (click)=\"save()\">\r\n Crea\r\n </button>\r\n }\r\n @default {\r\n <button type=\"button\" class=\"fb-btn fb-btn--primary\" [disabled]=\"isBusy()\" (click)=\"save()\">\r\n Salva\r\n </button>\r\n }\r\n }\r\n\r\n @if (canDuplicate()) {\r\n <!-- \u00A76.2 \u00ABDuplica\u00BB: il flow sotto un altro nome, alla versione 1. -->\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn\"\r\n [disabled]=\"isBusy()\"\r\n title=\"Salva una copia con un altro nome\"\r\n (click)=\"startCopy()\"\r\n >\r\n Duplica\u2026\r\n </button>\r\n }\r\n\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn\"\r\n [disabled]=\"isBusy() || !canActivate()\"\r\n [title]=\"\r\n canActivate()\r\n ? 'Attiva questa versione'\r\n : 'L\u2019attivazione esige zero errori: correggili nel pannello dei problemi'\r\n \"\r\n (click)=\"activate()\"\r\n >\r\n Attiva\r\n </button>\r\n </div>\r\n </header>\r\n\r\n @if (conflict()) {\r\n <!-- \u00A79.3: qualcun altro ha salvato. Due strade, entrambe offerte. -->\r\n <div class=\"fb-banner fb-banner--warn\" role=\"alert\">\r\n <span>\r\n {{ conflict()?.message }}\r\n @if (conflict()?.conflictingAuthor) {\r\n Ha salvato {{ conflict()?.conflictingAuthor }}.\r\n }\r\n </span>\r\n <button type=\"button\" class=\"fb-btn fb-btn--icon\" (click)=\"reloadAfterConflict()\">Ricarica</button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--icon\" (click)=\"saveAsNewVersionAfterConflict()\">\r\n Salva come nuova versione\r\n </button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--ghost fb-btn--icon\" (click)=\"dismissConflict()\">\u00D7</button>\r\n </div>\r\n }\r\n\r\n @if (isCopyOpen()) {\r\n <!--\r\n Il nome si chiede prima di scrivere: e\u2019 l\u2019unico dato che il backend non puo\u2019 inventare, e\r\n un doppione lo rifiuta con AlreadyExists (\u00A76.2). Invio conferma, Esc annulla.\r\n -->\r\n <div class=\"fb-banner fb-copy\" role=\"group\" aria-label=\"Duplica il flow\">\r\n <label class=\"fb-copy__field\">\r\n <span class=\"fb-copy__caption\">Nome tecnico della copia</span>\r\n <input\r\n #copyNameInput\r\n class=\"fb-copy__input\"\r\n [value]=\"copyName()\"\r\n placeholder=\"NomeTecnico_Copia\"\r\n (input)=\"setCopyName($any($event.target).value)\"\r\n (keydown.enter)=\"confirmCopy()\"\r\n (keydown.escape)=\"cancelCopy()\"\r\n />\r\n </label>\r\n <label class=\"fb-copy__field\">\r\n <span class=\"fb-copy__caption\">Nome visibile</span>\r\n <input\r\n class=\"fb-copy__input\"\r\n [value]=\"copyLabel()\"\r\n placeholder=\"(facoltativo)\"\r\n (input)=\"setCopyLabel($any($event.target).value)\"\r\n (keydown.enter)=\"confirmCopy()\"\r\n (keydown.escape)=\"cancelCopy()\"\r\n />\r\n </label>\r\n <span class=\"fb-copy__hint\" [class.fb-copy__hint--error]=\"!!copyNameProblem()\">\r\n {{ copyNameProblem() || copyHint() }}\r\n </span>\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-btn--primary fb-btn--icon\"\r\n [disabled]=\"isBusy() || !!copyNameProblem()\"\r\n (click)=\"confirmCopy()\"\r\n >\r\n Duplica\r\n </button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--ghost fb-btn--icon\" (click)=\"cancelCopy()\">Annulla</button>\r\n </div>\r\n }\r\n\r\n @if (notice()) {\r\n <div\r\n class=\"fb-banner\"\r\n [class.fb-banner--error]=\"notice()?.kind === 'error'\"\r\n role=\"status\"\r\n >\r\n <span>{{ notice()?.message }}</span>\r\n <button type=\"button\" class=\"fb-btn fb-btn--ghost fb-btn--icon\" aria-label=\"Chiudi\" (click)=\"dismissNotice()\">\r\n \u00D7\r\n </button>\r\n </div>\r\n }\r\n\r\n @if (hasScreensInAutoLaunched()) {\r\n <div class=\"fb-banner fb-banner--error\">\r\n Un flow AutoLaunched che contiene screen e\u2019 un errore di validazione: non c\u2019e\u2019 nessuno a cui mostrarli.\r\n </div>\r\n }\r\n @if (isOrchestrationWithoutStages()) {\r\n <div class=\"fb-banner fb-banner--warn\">\r\n Un flow Orchestration senza stage di orchestrazione viene segnalato dalla validazione\r\n (ORCHESTRATION_WITHOUT_STAGES).\r\n </div>\r\n }\r\n @if (hasNoScreensInScreenFlow()) {\r\n <div class=\"fb-banner fb-banner--warn\">\r\n Un flow di tipo Screen senza nessuno screen viene segnalato dalla validazione.\r\n </div>\r\n }\r\n @if (!isEditable()) {\r\n <div class=\"fb-banner\">\r\n Questa versione e\u2019 in sola lettura: per modificarla creane una nuova.\r\n </div>\r\n }\r\n\r\n <div class=\"fb-main\">\r\n <aside class=\"fb-main__palette\">\r\n <fb-element-palette\r\n [processType]=\"document().processType\"\r\n [triggerType]=\"document().start?.[0]?.triggerType\"\r\n (elementPicked)=\"onElementPicked($event)\"\r\n />\r\n </aside>\r\n\r\n <div class=\"fb-main__center\">\r\n <fb-flow-canvas\r\n class=\"fb-main__canvas\"\r\n [selectedName]=\"selectedName()\"\r\n [selectedNames]=\"selectedNames()\"\r\n [selectedGroupName]=\"selectedGroupName()\"\r\n [outline]=\"outline()\"\r\n [isEditable]=\"isEditable()\"\r\n (selectionChange)=\"onSelectionChange($event)\"\r\n (nodeOpened)=\"onNodeOpened($event)\"\r\n (nodeRemoveRequested)=\"onRemoveNode($event)\"\r\n (nodeDuplicateRequested)=\"onDuplicateNode($event)\"\r\n (elementDropped)=\"onElementDropped($event)\"\r\n (groupSelected)=\"onGroupSelected($event)\"\r\n (groupRemoveRequested)=\"onGroupRemoved($event)\"\r\n />\r\n\r\n @if (showProblems()) {\r\n <fb-problems-panel\r\n class=\"fb-main__problems\"\r\n (elementFocused)=\"revealIssueElement($event)\"\r\n (elementOpened)=\"openIssueElement($event)\"\r\n (closed)=\"toggleProblems()\"\r\n />\r\n } @else {\r\n <button type=\"button\" class=\"fb-main__problems-toggle\" (click)=\"toggleProblems()\">\r\n Problemi\r\n @if (errorCount()) {\r\n <span class=\"fb-main__count fb-main__count--error\">{{ errorCount() }}</span>\r\n }\r\n @if (warningCount()) {\r\n <span class=\"fb-main__count fb-main__count--warn\">{{ warningCount() }}</span>\r\n }\r\n </button>\r\n }\r\n </div>\r\n\r\n <aside class=\"fb-main__side\">\r\n <nav class=\"fb-side__tabs\" aria-label=\"Pannelli\">\r\n @if (!isDialogMode()) {\r\n <button\r\n type=\"button\"\r\n class=\"fb-side__tab\"\r\n [class.fb-side__tab--active]=\"sidePanel() === 'inspector'\"\r\n (click)=\"setPanel('inspector')\"\r\n >\r\n Elemento\r\n </button>\r\n }\r\n <button\r\n type=\"button\"\r\n class=\"fb-side__tab\"\r\n [class.fb-side__tab--active]=\"sidePanel() === 'resources'\"\r\n (click)=\"setPanel('resources')\"\r\n >\r\n Risorse\r\n </button>\r\n <!--\r\n I riquadri (\u00A73.6). Il tab porta il conteggio perche\u2019 e\u2019 l\u2019unico modo di sapere che un\r\n flow ne ha, quando sono chiusi o fuori dalla vista.\r\n -->\r\n <button\r\n type=\"button\"\r\n class=\"fb-side__tab\"\r\n [class.fb-side__tab--active]=\"sidePanel() === 'groups'\"\r\n (click)=\"setPanel('groups')\"\r\n >\r\n Riquadri\r\n @if (store.groups().length) {\r\n <span class=\"fb-main__count\">{{ store.groups().length }}</span>\r\n }\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-side__tab\"\r\n [class.fb-side__tab--active]=\"sidePanel() === 'versions'\"\r\n (click)=\"setPanel('versions')\"\r\n >\r\n Versioni\r\n </button>\r\n <!--\r\n Il pannello dell\u2019ultima esecuzione. \u00C8 l\u2019unico posto in cui si vede la traccia di un\r\n flow **senza schermate**: l\u00EC il runtime non apre nessuna interfaccia, e senza questo\r\n tab non ci sarebbe niente da guardare.\r\n -->\r\n <button\r\n type=\"button\"\r\n class=\"fb-side__tab\"\r\n [class.fb-side__tab--active]=\"sidePanel() === 'debug'\"\r\n (click)=\"setPanel('debug')\"\r\n >\r\n Debug\r\n @if (isRunning()) {\r\n <span class=\"fb-side__tab-dot\" aria-label=\"esecuzione in corso\"></span>\r\n }\r\n </button>\r\n </nav>\r\n\r\n <div class=\"fb-side__content\">\r\n @switch (sidePanel()) {\r\n @case ('inspector') {\r\n <fb-element-inspector\r\n [selectedName]=\"selectedName()\"\r\n [initialFieldPath]=\"pendingFieldPath()\"\r\n (removeRequested)=\"onRemoveNode($event)\"\r\n (duplicateRequested)=\"onDuplicateNode($event)\"\r\n (renamed)=\"onNodeRenamed($event)\"\r\n (closed)=\"setPanel('resources')\"\r\n />\r\n }\r\n @case ('resources') {\r\n <fb-resource-panel\r\n [target]=\"resourceTarget()\"\r\n [editing]=\"editingResource()\"\r\n (editRequested)=\"onResourceEditRequested($event)\"\r\n (closed)=\"setPanel('inspector')\"\r\n />\r\n }\r\n @case ('groups') {\r\n <fb-group-panel\r\n [isEditable]=\"isEditable()\"\r\n [selectedNodeNames]=\"selectedNames()\"\r\n [target]=\"groupTarget()\"\r\n (createRequested)=\"createGroup()\"\r\n (memberFocused)=\"focusMember($event)\"\r\n (closed)=\"setPanel('resources')\"\r\n />\r\n }\r\n @case ('versions') {\r\n <fb-version-panel\r\n (versionOpened)=\"openVersion($event)\"\r\n (notice)=\"showNotice($event)\"\r\n (closed)=\"setPanel('inspector')\"\r\n />\r\n }\r\n @case ('debug') {\r\n <!-- Evidenzia sul canvas senza rubare il pannello: la traccia si sta leggendo. -->\r\n <fb-debug-panel\r\n [outcome]=\"shownOutcome()\"\r\n [wasDebug]=\"wasRunInDebug()\"\r\n [isRunning]=\"isRunning()\"\r\n (elementFocused)=\"highlightElement($event)\"\r\n (cleared)=\"clearRunOutcome()\"\r\n (closed)=\"setPanel('inspector')\"\r\n />\r\n }\r\n }\r\n </div>\r\n\r\n <footer class=\"fb-side__footer\">\r\n <label class=\"fb-btn fb-btn--icon\">\r\n Importa JSON\r\n <input type=\"file\" accept=\"application/json,.json\" hidden (change)=\"onFileSelected($event)\" />\r\n </label>\r\n </footer>\r\n </aside>\r\n </div>\r\n\r\n @if (pastePreview(); as preview) {\r\n <!--\r\n L\u2019incolla passa da una conferma perche\u2019 non e\u2019 mai una copia identica: nomi gi\u00E0 presi,\r\n risorse che qui non esistono, riferimenti che restano orfani. Il piano e\u2019 gi\u00E0 calcolato,\r\n la finestra lo mostra e basta.\r\n -->\r\n <fb-paste-dialog\r\n [payload]=\"preview.payload\"\r\n [plan]=\"preview.plan\"\r\n (confirmed)=\"confirmPaste($event)\"\r\n (cancelled)=\"cancelPaste()\"\r\n />\r\n }\r\n\r\n @if (runMode(); as mode) {\r\n <!--\r\n La finestra degli ingressi. Non avvia niente e non chiama niente: raccoglie i valori e li\r\n annuncia con `runRequested`, perche\u2019 l\u2019unico a sapere dove gira il motore e\u2019 l\u2019ospite.\r\n -->\r\n <fb-run-dialog\r\n [mode]=\"mode\"\r\n [flowName]=\"session.flowName()\"\r\n [version]=\"session.version()\"\r\n [isDirty]=\"isDirty()\"\r\n [isBusy]=\"isRunning()\"\r\n (confirmed)=\"confirmRun($event)\"\r\n (cancelled)=\"closeRun()\"\r\n />\r\n }\r\n\r\n @if (editingResource(); as edit) {\r\n <!--\r\n La finestra di una risorsa. Sta qui e non dentro il pannello per una ragione sola: nella\r\n colonna laterale la ritaglierebbe lo scorrimento.\r\n -->\r\n <fb-resource-dialog [edit]=\"edit\" (closed)=\"onResourceEditRequested(null)\" />\r\n }\r\n\r\n @if (isDialogMode() && isDialogOpen() && selectedName()) {\r\n <!-- La dialog sta dentro il builder, non nel body: la libreria e\u2019 innestabile. -->\r\n <fb-element-dialog\r\n [selectedName]=\"selectedName()\"\r\n [initialFieldPath]=\"pendingFieldPath()\"\r\n (closed)=\"closeDialog()\"\r\n (removeRequested)=\"onRemoveNode($event)\"\r\n (duplicateRequested)=\"onDuplicateNode($event)\"\r\n (renamed)=\"onNodeRenamed($event)\"\r\n />\r\n }\r\n</div>\r\n", styles: [":host{display:block;width:100%;height:100%;min-height:0;font:inherit;color:var(--fb-text, #1d2939)}.fb-builder{position:relative;display:flex;flex-direction:column;width:100%;height:100%;max-height:100vh;min-height:0;background:var(--fb-surface, #fff)}.fb-top{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:9px 14px;border-bottom:1px solid var(--fb-border, #e2e5eb);background:var(--fb-surface, #fff)}.fb-top__identity{min-width:0}.fb-top__label{width:100%;max-width:420px;padding:2px 4px;border:1px solid transparent;border-radius:4px;background:transparent;color:var(--fb-text, #1d2939);font:inherit;font-size:15px;font-weight:600}.fb-top__label:hover:not(:disabled),.fb-top__label:focus-visible{border-color:var(--fb-border, #d6dae1);background:var(--fb-surface, #fff)}.fb-top__meta{display:flex;flex-wrap:wrap;align-items:center;gap:6px;margin-top:2px}.fb-top__name{width:180px;padding:1px 4px;border:1px solid transparent;border-radius:4px;background:transparent;color:var(--fb-text-muted, #667085);font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px}.fb-top__name:hover:not(:disabled),.fb-top__name:focus-visible{border-color:var(--fb-border, #d6dae1)}.fb-top__process{padding:1px 4px;border:1px solid var(--fb-border-subtle, #e6e9ee);border-radius:4px;background:transparent;color:var(--fb-text-muted, #667085);font:inherit;font-size:11px}.fb-top__process--fixed{border-color:transparent;background:var(--fb-surface-sunken, #eef0f4);border-radius:999px;padding:2px 7px;font-weight:600}.fb-top__status,.fb-top__version,.fb-top__dirty{padding:2px 7px;border-radius:999px;background:var(--fb-surface-sunken, #eef0f4);color:var(--fb-text-muted, #6b7086);font-size:10px;font-weight:600}.fb-top__version{font-family:ui-monospace,SFMono-Regular,Menlo,monospace}.fb-top__dirty{background:color-mix(in srgb,var(--fb-warning, #b7791f) 16%,transparent);color:var(--fb-warning, #b7791f)}.fb-top__actions{display:flex;flex-wrap:wrap;gap:4px}.fb-banner{display:flex;flex-wrap:wrap;align-items:center;gap:8px;padding:6px 12px;border-bottom:1px solid var(--fb-border-subtle, #e6e9ee);background:color-mix(in srgb,var(--fb-accent, #2f6feb) 7%,transparent);font-size:11px}.fb-banner--warn{background:color-mix(in srgb,var(--fb-warning, #b7791f) 12%,transparent);color:var(--fb-warning, #b7791f)}.fb-banner--error{background:color-mix(in srgb,var(--fb-error, #c9372c) 10%,transparent);color:var(--fb-error, #c9372c)}.fb-banner>span{flex:1;min-width:200px}.fb-copy__field{display:flex;align-items:center;gap:6px}.fb-copy__caption{color:var(--fb-text-muted, #667085);white-space:nowrap}.fb-copy__input{width:180px;padding:2px 5px;border:1px solid var(--fb-border, #d6dae1);border-radius:4px;background:var(--fb-surface, #fff);color:var(--fb-text, #1d2939);font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px}.fb-copy__hint{flex:1;min-width:180px;color:var(--fb-text-muted, #667085)}.fb-copy__hint--error{color:var(--fb-error, #c9372c)}.fb-main{display:flex;flex:1;min-height:0}.fb-main__palette{flex:0 0 190px;min-width:0}.fb-main__center{display:flex;flex:1;flex-direction:column;min-width:0;min-height:0}.fb-main__canvas{flex:1;min-height:0}.fb-main__problems{flex:0 0 auto;height:220px}.fb-main__problems-toggle{display:flex;align-items:center;gap:6px;padding:4px 12px;border:0;border-top:1px solid var(--fb-border, #d6dae1);background:var(--fb-surface-alt, #f8f9fb);color:var(--fb-text-muted, #667085);font:inherit;font-size:11px;cursor:pointer}.fb-main__count{padding:0 5px;border-radius:8px;background:var(--fb-border, #d6dae1);font-size:9px;font-weight:700}.fb-main__count--error{background:color-mix(in srgb,var(--fb-error, #c9372c) 16%,transparent);color:var(--fb-error, #c9372c)}.fb-main__count--warn{background:color-mix(in srgb,var(--fb-warning, #b7791f) 16%,transparent);color:var(--fb-warning, #b7791f)}.fb-main__side{display:flex;flex-direction:column;flex:0 0 340px;min-width:0;min-height:0;border-left:1px solid var(--fb-border, #d6dae1);background:var(--fb-surface, #fff)}.fb-side__tabs{display:flex;gap:2px;margin:8px 10px;padding:3px;border-radius:var(--fb-radius, 10px);background:var(--fb-surface-sunken, #eef0f4)}.fb-side__tab{flex:1;padding:5px 8px;border:0;border-radius:var(--fb-radius-xs, 6px);background:transparent;color:var(--fb-text-muted, #6b7086);font:inherit;font-size:11px;cursor:pointer;transition:background .12s ease,color .12s ease}.fb-side__tab-dot{display:inline-block;width:6px;height:6px;margin-left:4px;border-radius:50%;background:var(--fb-accent, #2f6feb);vertical-align:middle}.fb-side__tab:hover:not(.fb-side__tab--active){color:var(--fb-text, #1a1c23)}.fb-side__tab--active{background:var(--fb-surface, #fff);box-shadow:var(--fb-shadow-sm, 0 1px 2px rgb(16 24 40 / 6%));color:var(--fb-text, #1a1c23);font-weight:600}.fb-side__content{flex:1;min-height:0;border-top:1px solid var(--fb-border-subtle, #eef0f4)}.fb-side__content>*{height:100%}.fb-side__footer{display:flex;gap:6px;padding:6px 8px;border-top:1px solid var(--fb-border-subtle, #e6e9ee)}@media(max-width:1200px){.fb-main__palette{flex-basis:150px}.fb-main__side{flex-basis:290px}}\n"] }]
20196
+ ], changeDetection: ChangeDetectionStrategy.OnPush, host: { '(keydown)': 'onKeyDown($event)' }, template: "<div class=\"fb-builder\" [attr.data-fb-theme]=\"null\">\r\n <header class=\"fb-top\">\r\n <div class=\"fb-top__identity\">\r\n <input\r\n class=\"fb-top__label\"\r\n [value]=\"document().label || ''\"\r\n placeholder=\"Nome del flow\"\r\n aria-label=\"Nome del flow\"\r\n [disabled]=\"!isEditable()\"\r\n (input)=\"setLabel($any($event.target).value)\"\r\n />\r\n <div class=\"fb-top__meta\">\r\n <input\r\n class=\"fb-top__name\"\r\n [value]=\"document().fullName || ''\"\r\n placeholder=\"NomeTecnico\"\r\n aria-label=\"Nome tecnico del flow\"\r\n [disabled]=\"!isEditable()\"\r\n (input)=\"setFullName($any($event.target).value)\"\r\n />\r\n <!--\r\n Il tipo di flow si sceglie **alla creazione** e poi non si tocca pi\u00F9: decide quali\r\n globali esistono (\u00A74.1), quali elementi sono ammessi e come il motore esegue il flow,\r\n e cambiarlo su un documento gi\u00E0 scritto lo lascia pieno di riferimenti che non\r\n esistono. Su un flow salvato resta quindi un\u2019etichetta, non un comando.\r\n\r\n \u00A73.4 \u2014 cio\u2019 che si sceglie e\u2019 il **tipo di flow** (`flowTypes`), non il `processType`:\r\n quello si ricava, insieme al `triggerType`. Dove il dizionario non dichiara i tipi si\r\n torna alla vecchia tendina dei soli `processType`: e\u2019 un ripiego, non un difetto.\r\n -->\r\n @if (session.isNew() && flowTypes().length) {\r\n <select\r\n class=\"fb-top__process\"\r\n [fbValue]=\"flowType()?.value || ''\"\r\n aria-label=\"Tipo di flow\"\r\n [disabled]=\"!isEditable()\"\r\n (change)=\"setFlowType($any($event.target).value)\"\r\n >\r\n @if (!flowType()) {\r\n <option value=\"\" disabled>\u2014 tipo di flow \u2014</option>\r\n }\r\n @for (type of flowTypes(); track type.value) {\r\n <option [value]=\"type.value\" [title]=\"type.description || type.label\">{{ type.label }}</option>\r\n }\r\n </select>\r\n } @else if (session.isNew()) {\r\n <select\r\n class=\"fb-top__process\"\r\n [fbValue]=\"document().processType || ''\"\r\n aria-label=\"Tipo di flow\"\r\n [disabled]=\"!isEditable()\"\r\n (change)=\"setProcessType($any($event.target).value)\"\r\n >\r\n <!--\r\n Il segnaposto esiste perche' un `<select>` senza opzione corrispondente non e'\r\n \u00ABvuoto\u00BB: e' a `selectedIndex = -1`, e mostra una casella bianca. Su un flow nuovo\r\n (`processType` non ancora scelto) e' questa la riga che si vede, disabilitata\r\n perche' non e' un valore valido da salvare.\r\n -->\r\n @if (!document().processType) {\r\n <option value=\"\" disabled>\u2014 tipo di flow \u2014</option>\r\n }\r\n @for (type of processTypes(); track type.value) {\r\n <option [value]=\"type.value\">{{ type.label }}</option>\r\n }\r\n <!--\r\n Il tipo del documento che il dizionario non conosce: senza questa opzione la select\r\n resta bianca su un flow che il backend ha salvato con un `processType` fuori\r\n catalogo (o quando `processTypes` manca dalla risposta dei dizionari), e sembra che\r\n l'editor non abbia ricaricato il flow. Va mostrato **senza** riscrivere il\r\n documento: il valore e' del backend, non nostro da correggere.\r\n -->\r\n @if (isProcessTypeOutOfCatalog()) {\r\n <option [value]=\"document().processType\">{{ document().processType }} (fuori catalogo)</option>\r\n }\r\n </select>\r\n } @else {\r\n <span class=\"fb-top__process fb-top__process--fixed\" title=\"Il tipo di flow si sceglie alla creazione\">\r\n {{ flowTypeLabel() }}\r\n </span>\r\n }\r\n <span class=\"fb-top__status\">{{ statusLabel() }}</span>\r\n @if (session.version() != null) {\r\n <span class=\"fb-top__version\">v{{ session.version() }}</span>\r\n }\r\n @if (isDirty()) {\r\n <span class=\"fb-top__dirty\" title=\"Ci sono modifiche non salvate\">modificato</span>\r\n }\r\n </div>\r\n </div>\r\n\r\n <!--\r\n La ricerca **nel documento**: e\u2019 l\u2019unico modo di arrivare a un elemento su un flow che non\r\n sta in una schermata. Non e\u2019 la casella della palette, che filtra i tipi da aggiungere.\r\n -->\r\n <fb-flow-search (picked)=\"onSearchPicked($event)\" />\r\n\r\n <div class=\"fb-top__actions\">\r\n <button type=\"button\" class=\"fb-btn\" [disabled]=\"!canUndo()\" aria-label=\"Annulla\" (click)=\"undo()\">\u21B6</button>\r\n <button type=\"button\" class=\"fb-btn\" [disabled]=\"!canRedo()\" aria-label=\"Ripeti\" (click)=\"redo()\">\u21B7</button>\r\n <button type=\"button\" class=\"fb-btn\" title=\"Ricalcola le posizioni\" (click)=\"autoLayout()\">Riordina</button>\r\n <!--\r\n Un riquadro e\u2019 un commento sul canvas (\u00A73.6): non viene eseguito, quindi il comando sta\r\n qui accanto a \u00ABRiordina\u00BB e non nella palette degli elementi. Nasce attorno a cio\u2019 che e\u2019\r\n selezionato, che e\u2019 il gesto per cui serve.\r\n -->\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn\"\r\n [disabled]=\"!isEditable()\"\r\n [title]=\"\r\n canCopy()\r\n ? 'Racchiudi gli elementi selezionati in un riquadro di commento'\r\n : 'Crea un riquadro di commento vuoto: non cambia il comportamento del flow'\r\n \"\r\n aria-label=\"Nuovo riquadro di raggruppamento\"\r\n (click)=\"createGroup()\"\r\n >\r\n \u2B1A\r\n </button>\r\n\r\n <!--\r\n Copia e incolla stanno **anche** qui e non solo sui tasti: una scorciatoia che nessuno\r\n annuncia non esiste. Il titolo la dice, cos\u00EC si impara usandola una volta.\r\n L\u2019incolla e\u2019 acceso anche con la selezione vuota: cio\u2019 che si incolla sta negli appunti,\r\n e puo\u2019 venire da un altro flow.\r\n -->\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn\"\r\n [disabled]=\"!canCopy()\"\r\n title=\"Copia gli elementi selezionati, con le risorse che usano (Ctrl+C)\"\r\n aria-label=\"Copia gli elementi selezionati\"\r\n (click)=\"copySelection()\"\r\n >\r\n \u29C9\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn\"\r\n [disabled]=\"!canPaste() || !isEditable()\"\r\n title=\"Incolla gli elementi copiati, anche da un altro flow (Ctrl+V)\"\r\n aria-label=\"Incolla gli elementi copiati\"\r\n (click)=\"startPaste()\"\r\n >\r\n \u2398\r\n </button>\r\n @if (isDialogMode()) {\r\n <!-- Il doppio click sul node fa la stessa cosa, ma non si vede: questo comando s\u00EC. -->\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn\"\r\n [disabled]=\"!selectedName()\"\r\n title=\"Apri il dettaglio dell\u2019elemento selezionato\"\r\n (click)=\"openSelectedElement()\"\r\n >\r\n Dettaglio\r\n </button>\r\n }\r\n <button type=\"button\" class=\"fb-btn\" [disabled]=\"isBusy()\" (click)=\"validateNow()\">Valida</button>\r\n\r\n <!--\r\n Esegui e Debug non eseguono niente qui dentro: raccolgono i valori di ingresso e li\r\n consegnano all\u2019applicazione ospite. Si esegue la versione **salvata**, quindi su un flow\r\n mai scritto sono spenti e il titolo dice perche\u2019.\r\n -->\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn\"\r\n [disabled]=\"!canRun()\"\r\n [title]=\"canRun() ? 'Esegui il flow salvato' : 'Salva il flow prima di eseguirlo'\"\r\n (click)=\"openRun('run')\"\r\n >\r\n Esegui\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn\"\r\n [disabled]=\"!canRun()\"\r\n [title]=\"canRun() ? 'Esegui il flow salvato in debug' : 'Salva il flow prima di eseguirlo'\"\r\n (click)=\"openRun('debug')\"\r\n >\r\n Debug\r\n </button>\r\n\r\n @switch (primaryCommand()) {\r\n @case ('newVersion') {\r\n <!-- Sulla versione attiva il comando primario e' \"Nuova versione\" (\u00A78, regola 1). -->\r\n <button type=\"button\" class=\"fb-btn fb-btn--primary\" [disabled]=\"isBusy()\" (click)=\"createNewVersion()\">\r\n Nuova versione\r\n </button>\r\n }\r\n @case ('create') {\r\n <button type=\"button\" class=\"fb-btn fb-btn--primary\" [disabled]=\"isBusy()\" (click)=\"save()\">\r\n Crea\r\n </button>\r\n }\r\n @default {\r\n <button type=\"button\" class=\"fb-btn fb-btn--primary\" [disabled]=\"isBusy()\" (click)=\"save()\">\r\n Salva\r\n </button>\r\n }\r\n }\r\n\r\n @if (canDuplicate()) {\r\n <!-- \u00A76.2 \u00ABDuplica\u00BB: il flow sotto un altro nome, alla versione 1. -->\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn\"\r\n [disabled]=\"isBusy()\"\r\n title=\"Salva una copia con un altro nome\"\r\n (click)=\"startCopy()\"\r\n >\r\n Duplica\u2026\r\n </button>\r\n }\r\n\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn\"\r\n [disabled]=\"isBusy() || !canActivate()\"\r\n [title]=\"\r\n canActivate()\r\n ? 'Attiva questa versione'\r\n : 'L\u2019attivazione esige zero errori: correggili nel pannello dei problemi'\r\n \"\r\n (click)=\"activate()\"\r\n >\r\n Attiva\r\n </button>\r\n </div>\r\n </header>\r\n\r\n @if (conflict()) {\r\n <!-- \u00A79.3: qualcun altro ha salvato. Due strade, entrambe offerte. -->\r\n <div class=\"fb-banner fb-banner--warn\" role=\"alert\">\r\n <span>\r\n {{ conflict()?.message }}\r\n @if (conflict()?.conflictingAuthor) {\r\n Ha salvato {{ conflict()?.conflictingAuthor }}.\r\n }\r\n </span>\r\n <button type=\"button\" class=\"fb-btn fb-btn--icon\" (click)=\"reloadAfterConflict()\">Ricarica</button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--icon\" (click)=\"saveAsNewVersionAfterConflict()\">\r\n Salva come nuova versione\r\n </button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--ghost fb-btn--icon\" (click)=\"dismissConflict()\">\u00D7</button>\r\n </div>\r\n }\r\n\r\n @if (isObsoleteSaveOpen() && session.isObsoleteVersion()) {\r\n <!--\r\n \u00A78, regola 1 \u2014 una superata si salva, ma il primo salvataggio si conferma: le esecuzioni\r\n sospese nate quando era attiva riprendono con le modifiche. La terza strada lascia la\r\n superata com\u2019era e mette le modifiche in una versione nuova.\r\n -->\r\n <div class=\"fb-banner fb-banner--warn\" role=\"alertdialog\" aria-label=\"Salvataggio su una versione superata\">\r\n <span>\r\n Questa versione e\u2019 superata. Le esecuzioni ancora in corso su questa versione riprenderanno con\r\n le modifiche, e se il punto in cui sono ferme non c\u2019e\u2019 piu\u2019 falliranno. Salvata, torna bozza:\r\n per rimetterla in produzione andra\u2019 riattivata.\r\n </span>\r\n <button type=\"button\" class=\"fb-btn fb-btn--primary fb-btn--icon\" [disabled]=\"isBusy()\" (click)=\"confirmObsoleteSave()\">\r\n Salva comunque\r\n </button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--icon\" [disabled]=\"isBusy()\" (click)=\"saveObsoleteAsNewVersion()\">\r\n Salva come nuova versione\r\n </button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--ghost fb-btn--icon\" (click)=\"cancelObsoleteSave()\">Annulla</button>\r\n </div>\r\n }\r\n\r\n @if (isCopyOpen()) {\r\n <!--\r\n Il nome si chiede prima di scrivere: e\u2019 l\u2019unico dato che il backend non puo\u2019 inventare, e\r\n un doppione lo rifiuta con AlreadyExists (\u00A76.2). Invio conferma, Esc annulla.\r\n -->\r\n <div class=\"fb-banner fb-copy\" role=\"group\" aria-label=\"Duplica il flow\">\r\n <label class=\"fb-copy__field\">\r\n <span class=\"fb-copy__caption\">Nome tecnico della copia</span>\r\n <input\r\n #copyNameInput\r\n class=\"fb-copy__input\"\r\n [value]=\"copyName()\"\r\n placeholder=\"NomeTecnico_Copia\"\r\n (input)=\"setCopyName($any($event.target).value)\"\r\n (keydown.enter)=\"confirmCopy()\"\r\n (keydown.escape)=\"cancelCopy()\"\r\n />\r\n </label>\r\n <label class=\"fb-copy__field\">\r\n <span class=\"fb-copy__caption\">Nome visibile</span>\r\n <input\r\n class=\"fb-copy__input\"\r\n [value]=\"copyLabel()\"\r\n placeholder=\"(facoltativo)\"\r\n (input)=\"setCopyLabel($any($event.target).value)\"\r\n (keydown.enter)=\"confirmCopy()\"\r\n (keydown.escape)=\"cancelCopy()\"\r\n />\r\n </label>\r\n <span class=\"fb-copy__hint\" [class.fb-copy__hint--error]=\"!!copyNameProblem()\">\r\n {{ copyNameProblem() || copyHint() }}\r\n </span>\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-btn--primary fb-btn--icon\"\r\n [disabled]=\"isBusy() || !!copyNameProblem()\"\r\n (click)=\"confirmCopy()\"\r\n >\r\n Duplica\r\n </button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--ghost fb-btn--icon\" (click)=\"cancelCopy()\">Annulla</button>\r\n </div>\r\n }\r\n\r\n @if (notice()) {\r\n <div\r\n class=\"fb-banner\"\r\n [class.fb-banner--error]=\"notice()?.kind === 'error'\"\r\n role=\"status\"\r\n >\r\n <span>{{ notice()?.message }}</span>\r\n <button type=\"button\" class=\"fb-btn fb-btn--ghost fb-btn--icon\" aria-label=\"Chiudi\" (click)=\"dismissNotice()\">\r\n \u00D7\r\n </button>\r\n </div>\r\n }\r\n\r\n @if (hasScreensInAutoLaunched()) {\r\n <div class=\"fb-banner fb-banner--error\">\r\n Un flow AutoLaunched che contiene screen e\u2019 un errore di validazione: non c\u2019e\u2019 nessuno a cui mostrarli.\r\n </div>\r\n }\r\n @if (isOrchestrationWithoutStages()) {\r\n <div class=\"fb-banner fb-banner--warn\">\r\n Un flow Orchestration senza stage di orchestrazione viene segnalato dalla validazione\r\n (ORCHESTRATION_WITHOUT_STAGES).\r\n </div>\r\n }\r\n @if (hasNoScreensInScreenFlow()) {\r\n <div class=\"fb-banner fb-banner--warn\">\r\n Un flow di tipo Screen senza nessuno screen viene segnalato dalla validazione.\r\n </div>\r\n }\r\n @if (!isEditable()) {\r\n <!-- \u00A78, regola 1: la sola versione che non si modifica e\u2019 quella da cui partono le esecuzioni. -->\r\n <div class=\"fb-banner\">\r\n Questa e\u2019 la versione attiva, ed e\u2019 in sola lettura: per modificarla creane una nuova.\r\n </div>\r\n }\r\n\r\n <div class=\"fb-main\">\r\n <aside class=\"fb-main__palette\">\r\n <fb-element-palette\r\n [processType]=\"document().processType\"\r\n [triggerType]=\"document().start?.[0]?.triggerType\"\r\n (elementPicked)=\"onElementPicked($event)\"\r\n />\r\n </aside>\r\n\r\n <div class=\"fb-main__center\">\r\n <fb-flow-canvas\r\n class=\"fb-main__canvas\"\r\n [selectedName]=\"selectedName()\"\r\n [selectedNames]=\"selectedNames()\"\r\n [selectedGroupName]=\"selectedGroupName()\"\r\n [outline]=\"outline()\"\r\n [isEditable]=\"isEditable()\"\r\n (selectionChange)=\"onSelectionChange($event)\"\r\n (nodeOpened)=\"onNodeOpened($event)\"\r\n (nodeRemoveRequested)=\"onRemoveNode($event)\"\r\n (nodeDuplicateRequested)=\"onDuplicateNode($event)\"\r\n (elementDropped)=\"onElementDropped($event)\"\r\n (groupSelected)=\"onGroupSelected($event)\"\r\n (groupRemoveRequested)=\"onGroupRemoved($event)\"\r\n />\r\n\r\n @if (showProblems()) {\r\n <fb-problems-panel\r\n class=\"fb-main__problems\"\r\n (elementFocused)=\"revealIssueElement($event)\"\r\n (elementOpened)=\"openIssueElement($event)\"\r\n (closed)=\"toggleProblems()\"\r\n />\r\n } @else {\r\n <button type=\"button\" class=\"fb-main__problems-toggle\" (click)=\"toggleProblems()\">\r\n Problemi\r\n @if (errorCount()) {\r\n <span class=\"fb-main__count fb-main__count--error\">{{ errorCount() }}</span>\r\n }\r\n @if (warningCount()) {\r\n <span class=\"fb-main__count fb-main__count--warn\">{{ warningCount() }}</span>\r\n }\r\n </button>\r\n }\r\n </div>\r\n\r\n <aside class=\"fb-main__side\">\r\n <nav class=\"fb-side__tabs\" aria-label=\"Pannelli\">\r\n @if (!isDialogMode()) {\r\n <button\r\n type=\"button\"\r\n class=\"fb-side__tab\"\r\n [class.fb-side__tab--active]=\"sidePanel() === 'inspector'\"\r\n (click)=\"setPanel('inspector')\"\r\n >\r\n Elemento\r\n </button>\r\n }\r\n <button\r\n type=\"button\"\r\n class=\"fb-side__tab\"\r\n [class.fb-side__tab--active]=\"sidePanel() === 'resources'\"\r\n (click)=\"setPanel('resources')\"\r\n >\r\n Risorse\r\n </button>\r\n <!--\r\n I riquadri (\u00A73.6). Il tab porta il conteggio perche\u2019 e\u2019 l\u2019unico modo di sapere che un\r\n flow ne ha, quando sono chiusi o fuori dalla vista.\r\n -->\r\n <button\r\n type=\"button\"\r\n class=\"fb-side__tab\"\r\n [class.fb-side__tab--active]=\"sidePanel() === 'groups'\"\r\n (click)=\"setPanel('groups')\"\r\n >\r\n Riquadri\r\n @if (store.groups().length) {\r\n <span class=\"fb-main__count\">{{ store.groups().length }}</span>\r\n }\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-side__tab\"\r\n [class.fb-side__tab--active]=\"sidePanel() === 'versions'\"\r\n (click)=\"setPanel('versions')\"\r\n >\r\n Versioni\r\n </button>\r\n <!--\r\n Il pannello dell\u2019ultima esecuzione. \u00C8 l\u2019unico posto in cui si vede la traccia di un\r\n flow **senza schermate**: l\u00EC il runtime non apre nessuna interfaccia, e senza questo\r\n tab non ci sarebbe niente da guardare.\r\n -->\r\n <button\r\n type=\"button\"\r\n class=\"fb-side__tab\"\r\n [class.fb-side__tab--active]=\"sidePanel() === 'debug'\"\r\n (click)=\"setPanel('debug')\"\r\n >\r\n Debug\r\n @if (isRunning()) {\r\n <span class=\"fb-side__tab-dot\" aria-label=\"esecuzione in corso\"></span>\r\n }\r\n </button>\r\n </nav>\r\n\r\n <div class=\"fb-side__content\">\r\n @switch (sidePanel()) {\r\n @case ('inspector') {\r\n <fb-element-inspector\r\n [selectedName]=\"selectedName()\"\r\n [initialFieldPath]=\"pendingFieldPath()\"\r\n (removeRequested)=\"onRemoveNode($event)\"\r\n (duplicateRequested)=\"onDuplicateNode($event)\"\r\n (renamed)=\"onNodeRenamed($event)\"\r\n (closed)=\"setPanel('resources')\"\r\n />\r\n }\r\n @case ('resources') {\r\n <fb-resource-panel\r\n [target]=\"resourceTarget()\"\r\n [editing]=\"editingResource()\"\r\n (editRequested)=\"onResourceEditRequested($event)\"\r\n (closed)=\"setPanel('inspector')\"\r\n />\r\n }\r\n @case ('groups') {\r\n <fb-group-panel\r\n [isEditable]=\"isEditable()\"\r\n [selectedNodeNames]=\"selectedNames()\"\r\n [target]=\"groupTarget()\"\r\n (createRequested)=\"createGroup()\"\r\n (memberFocused)=\"focusMember($event)\"\r\n (closed)=\"setPanel('resources')\"\r\n />\r\n }\r\n @case ('versions') {\r\n <fb-version-panel\r\n (versionOpened)=\"openVersion($event)\"\r\n (notice)=\"showNotice($event)\"\r\n (closed)=\"setPanel('inspector')\"\r\n />\r\n }\r\n @case ('debug') {\r\n <!-- Evidenzia sul canvas senza rubare il pannello: la traccia si sta leggendo. -->\r\n <fb-debug-panel\r\n [outcome]=\"shownOutcome()\"\r\n [wasDebug]=\"wasRunInDebug()\"\r\n [isRunning]=\"isRunning()\"\r\n (elementFocused)=\"highlightElement($event)\"\r\n (cleared)=\"clearRunOutcome()\"\r\n (closed)=\"setPanel('inspector')\"\r\n />\r\n }\r\n }\r\n </div>\r\n\r\n <footer class=\"fb-side__footer\">\r\n <label class=\"fb-btn fb-btn--icon\">\r\n Importa JSON\r\n <input type=\"file\" accept=\"application/json,.json\" hidden (change)=\"onFileSelected($event)\" />\r\n </label>\r\n </footer>\r\n </aside>\r\n </div>\r\n\r\n @if (pastePreview(); as preview) {\r\n <!--\r\n L\u2019incolla passa da una conferma perche\u2019 non e\u2019 mai una copia identica: nomi gi\u00E0 presi,\r\n risorse che qui non esistono, riferimenti che restano orfani. Il piano e\u2019 gi\u00E0 calcolato,\r\n la finestra lo mostra e basta.\r\n -->\r\n <fb-paste-dialog\r\n [payload]=\"preview.payload\"\r\n [plan]=\"preview.plan\"\r\n (confirmed)=\"confirmPaste($event)\"\r\n (cancelled)=\"cancelPaste()\"\r\n />\r\n }\r\n\r\n @if (runMode(); as mode) {\r\n <!--\r\n La finestra degli ingressi. Non avvia niente e non chiama niente: raccoglie i valori e li\r\n annuncia con `runRequested`, perche\u2019 l\u2019unico a sapere dove gira il motore e\u2019 l\u2019ospite.\r\n -->\r\n <fb-run-dialog\r\n [mode]=\"mode\"\r\n [flowName]=\"session.flowName()\"\r\n [version]=\"session.version()\"\r\n [isDirty]=\"isDirty()\"\r\n [isBusy]=\"isRunning()\"\r\n (confirmed)=\"confirmRun($event)\"\r\n (cancelled)=\"closeRun()\"\r\n />\r\n }\r\n\r\n @if (editingResource(); as edit) {\r\n <!--\r\n La finestra di una risorsa. Sta qui e non dentro il pannello per una ragione sola: nella\r\n colonna laterale la ritaglierebbe lo scorrimento.\r\n -->\r\n <fb-resource-dialog [edit]=\"edit\" (closed)=\"onResourceEditRequested(null)\" />\r\n }\r\n\r\n @if (isDialogMode() && isDialogOpen() && selectedName()) {\r\n <!-- La dialog sta dentro il builder, non nel body: la libreria e\u2019 innestabile. -->\r\n <fb-element-dialog\r\n [selectedName]=\"selectedName()\"\r\n [initialFieldPath]=\"pendingFieldPath()\"\r\n (closed)=\"closeDialog()\"\r\n (removeRequested)=\"onRemoveNode($event)\"\r\n (duplicateRequested)=\"onDuplicateNode($event)\"\r\n (renamed)=\"onNodeRenamed($event)\"\r\n />\r\n }\r\n</div>\r\n", styles: [":host{display:block;width:100%;height:100%;min-height:0;font:inherit;color:var(--fb-text, #1d2939)}.fb-builder{position:relative;display:flex;flex-direction:column;width:100%;height:100%;max-height:100vh;min-height:0;background:var(--fb-surface, #fff)}.fb-top{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:9px 14px;border-bottom:1px solid var(--fb-border, #e2e5eb);background:var(--fb-surface, #fff)}.fb-top__identity{min-width:0}.fb-top__label{width:100%;max-width:420px;padding:2px 4px;border:1px solid transparent;border-radius:4px;background:transparent;color:var(--fb-text, #1d2939);font:inherit;font-size:15px;font-weight:600}.fb-top__label:hover:not(:disabled),.fb-top__label:focus-visible{border-color:var(--fb-border, #d6dae1);background:var(--fb-surface, #fff)}.fb-top__meta{display:flex;flex-wrap:wrap;align-items:center;gap:6px;margin-top:2px}.fb-top__name{width:180px;padding:1px 4px;border:1px solid transparent;border-radius:4px;background:transparent;color:var(--fb-text-muted, #667085);font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px}.fb-top__name:hover:not(:disabled),.fb-top__name:focus-visible{border-color:var(--fb-border, #d6dae1)}.fb-top__process{padding:1px 4px;border:1px solid var(--fb-border-subtle, #e6e9ee);border-radius:4px;background:transparent;color:var(--fb-text-muted, #667085);font:inherit;font-size:11px}.fb-top__process--fixed{border-color:transparent;background:var(--fb-surface-sunken, #eef0f4);border-radius:999px;padding:2px 7px;font-weight:600}.fb-top__status,.fb-top__version,.fb-top__dirty{padding:2px 7px;border-radius:999px;background:var(--fb-surface-sunken, #eef0f4);color:var(--fb-text-muted, #6b7086);font-size:10px;font-weight:600}.fb-top__version{font-family:ui-monospace,SFMono-Regular,Menlo,monospace}.fb-top__dirty{background:color-mix(in srgb,var(--fb-warning, #b7791f) 16%,transparent);color:var(--fb-warning, #b7791f)}.fb-top__actions{display:flex;flex-wrap:wrap;gap:4px}.fb-banner{display:flex;flex-wrap:wrap;align-items:center;gap:8px;padding:6px 12px;border-bottom:1px solid var(--fb-border-subtle, #e6e9ee);background:color-mix(in srgb,var(--fb-accent, #2f6feb) 7%,transparent);font-size:11px}.fb-banner--warn{background:color-mix(in srgb,var(--fb-warning, #b7791f) 12%,transparent);color:var(--fb-warning, #b7791f)}.fb-banner--error{background:color-mix(in srgb,var(--fb-error, #c9372c) 10%,transparent);color:var(--fb-error, #c9372c)}.fb-banner>span{flex:1;min-width:200px}.fb-copy__field{display:flex;align-items:center;gap:6px}.fb-copy__caption{color:var(--fb-text-muted, #667085);white-space:nowrap}.fb-copy__input{width:180px;padding:2px 5px;border:1px solid var(--fb-border, #d6dae1);border-radius:4px;background:var(--fb-surface, #fff);color:var(--fb-text, #1d2939);font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px}.fb-copy__hint{flex:1;min-width:180px;color:var(--fb-text-muted, #667085)}.fb-copy__hint--error{color:var(--fb-error, #c9372c)}.fb-main{display:flex;flex:1;min-height:0}.fb-main__palette{flex:0 0 190px;min-width:0}.fb-main__center{display:flex;flex:1;flex-direction:column;min-width:0;min-height:0}.fb-main__canvas{flex:1;min-height:0}.fb-main__problems{flex:0 0 auto;height:220px}.fb-main__problems-toggle{display:flex;align-items:center;gap:6px;padding:4px 12px;border:0;border-top:1px solid var(--fb-border, #d6dae1);background:var(--fb-surface-alt, #f8f9fb);color:var(--fb-text-muted, #667085);font:inherit;font-size:11px;cursor:pointer}.fb-main__count{padding:0 5px;border-radius:8px;background:var(--fb-border, #d6dae1);font-size:9px;font-weight:700}.fb-main__count--error{background:color-mix(in srgb,var(--fb-error, #c9372c) 16%,transparent);color:var(--fb-error, #c9372c)}.fb-main__count--warn{background:color-mix(in srgb,var(--fb-warning, #b7791f) 16%,transparent);color:var(--fb-warning, #b7791f)}.fb-main__side{display:flex;flex-direction:column;flex:0 0 340px;min-width:0;min-height:0;border-left:1px solid var(--fb-border, #d6dae1);background:var(--fb-surface, #fff)}.fb-side__tabs{display:flex;gap:2px;margin:8px 10px;padding:3px;border-radius:var(--fb-radius, 10px);background:var(--fb-surface-sunken, #eef0f4)}.fb-side__tab{flex:1;padding:5px 8px;border:0;border-radius:var(--fb-radius-xs, 6px);background:transparent;color:var(--fb-text-muted, #6b7086);font:inherit;font-size:11px;cursor:pointer;transition:background .12s ease,color .12s ease}.fb-side__tab-dot{display:inline-block;width:6px;height:6px;margin-left:4px;border-radius:50%;background:var(--fb-accent, #2f6feb);vertical-align:middle}.fb-side__tab:hover:not(.fb-side__tab--active){color:var(--fb-text, #1a1c23)}.fb-side__tab--active{background:var(--fb-surface, #fff);box-shadow:var(--fb-shadow-sm, 0 1px 2px rgb(16 24 40 / 6%));color:var(--fb-text, #1a1c23);font-weight:600}.fb-side__content{flex:1;min-height:0;border-top:1px solid var(--fb-border-subtle, #eef0f4)}.fb-side__content>*{height:100%}.fb-side__footer{display:flex;gap:6px;padding:6px 8px;border-top:1px solid var(--fb-border-subtle, #e6e9ee)}@media(max-width:1200px){.fb-main__palette{flex-basis:150px}.fb-main__side{flex-basis:290px}}\n"] }]
20035
20197
  }], ctorParameters: () => [], propDecorators: { flowName: [{ type: i0.Input, args: [{ isSignal: true, alias: "flowName", required: false }] }], version: [{ type: i0.Input, args: [{ isSignal: true, alias: "version", required: false }] }], author: [{ type: i0.Input, args: [{ isSignal: true, alias: "author", required: false }] }], defaultProcessType: [{ type: i0.Input, args: [{ isSignal: true, alias: "defaultProcessType", required: false }] }], inspectorMode: [{ type: i0.Input, args: [{ isSignal: true, alias: "inspectorMode", required: false }] }], runOutcome: [{ type: i0.Input, args: [{ isSignal: true, alias: "runOutcome", required: false }] }], isRunning: [{ type: i0.Input, args: [{ isSignal: true, alias: "isRunning", required: false }] }], saved: [{ type: i0.Output, args: ["saved"] }], activated: [{ type: i0.Output, args: ["activated"] }], closeRequested: [{ type: i0.Output, args: ["closeRequested"] }], runRequested: [{ type: i0.Output, args: ["runRequested"] }], canvas: [{ type: i0.ViewChild, args: [i0.forwardRef(() => FlowCanvasComponent), { isSignal: true }] }], search: [{ type: i0.ViewChild, args: [i0.forwardRef(() => FlowSearchComponent), { isSignal: true }] }], copyNameInput: [{ type: i0.ViewChild, args: ['copyNameInput', { isSignal: true }] }] } });
20036
20198
 
20037
20199
  /**