@esfaenza/flow-builder 20.3.37 → 20.3.39
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +36 -12
- package/fesm2022/esfaenza-flow-builder.mjs +501 -294
- package/fesm2022/esfaenza-flow-builder.mjs.map +1 -1
- package/index.d.ts +238 -147
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { HttpClient, HttpErrorResponse, HttpParams } from '@angular/common/http';
|
|
2
2
|
import * as i0 from '@angular/core';
|
|
3
|
-
import { InjectionToken, inject, signal, effect, Injectable, computed, DestroyRef, ElementRef, input, output, viewChild, ChangeDetectionStrategy, Component, untracked, Directive, Injector, afterNextRender } from '@angular/core';
|
|
3
|
+
import { InjectionToken, inject, signal, effect, Injectable, computed, DestroyRef, ElementRef, input, output, viewChild, ChangeDetectionStrategy, Component, untracked, Directive, Injector, afterNextRender, linkedSignal } from '@angular/core';
|
|
4
4
|
import { firstValueFrom } from 'rxjs';
|
|
5
5
|
import dagre from 'dagre';
|
|
6
6
|
import * as i1 from '@foblex/flow';
|
|
@@ -300,44 +300,19 @@ class FlowBuilderApi {
|
|
|
300
300
|
return missing('describeStructure');
|
|
301
301
|
}
|
|
302
302
|
// -------------------------------------------------------------------------
|
|
303
|
-
//
|
|
303
|
+
// «Esegui» e «Debug» non stanno qui
|
|
304
304
|
// -------------------------------------------------------------------------
|
|
305
305
|
//
|
|
306
|
-
//
|
|
307
|
-
//
|
|
308
|
-
//
|
|
309
|
-
//
|
|
310
|
-
//
|
|
311
|
-
//
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
* ha un motore, e nascondere l'assenza dietro un'esecuzione finta e' il modo piu' veloce di
|
|
317
|
-
* far credere che un flow funzioni.
|
|
318
|
-
*
|
|
319
|
-
* Il ritorno e' **facoltativo** ed e' l'unica cosa che l'editor sa dell'esecuzione: un
|
|
320
|
-
* {@link FlowRunOutcome} finisce nel pannello «Debug» — stato, traccia, risorse, output —
|
|
321
|
-
* mentre `void` significa «guardo altrove», e l'editor dice solo che e' partita. Serve
|
|
322
|
-
* soprattutto ai flow **senza schermate**: lì non c'e' nessuna interfaccia del runtime in cui
|
|
323
|
-
* vedere cos'e' successo, e la traccia accanto al grafo e' tutto cio' che si ha.
|
|
324
|
-
*/
|
|
325
|
-
runFlow(request) {
|
|
326
|
-
void request;
|
|
327
|
-
return missing('runFlow');
|
|
328
|
-
}
|
|
329
|
-
/**
|
|
330
|
-
* «Debug»: come {@link runFlow}, ma `request.debug` e' `true` — ed e' il caso in cui il
|
|
331
|
-
* ritorno conta davvero, perche' `trace` e `resources` esistono solo in debug (§6.5).
|
|
332
|
-
*
|
|
333
|
-
* Sono due metodi e non un flag perche' quasi sempre sono due strade diverse dell'ospite —
|
|
334
|
-
* l'una avvia e basta, l'altra apre un ispettore — e perche' un ambiente puo' esporre l'una
|
|
335
|
-
* senza l'altra: due `MissingService` distinti dicono quale delle due manca.
|
|
336
|
-
*/
|
|
337
|
-
debugFlow(request) {
|
|
338
|
-
void request;
|
|
339
|
-
return missing('debugFlow');
|
|
340
|
-
}
|
|
306
|
+
// C'erano due primitive, `runFlow` e `debugFlow`, e non ci sono piu': avviare un'esecuzione
|
|
307
|
+
// non e' traffico verso il backend, e' una decisione dell'applicazione che ospita l'editor —
|
|
308
|
+
// una navigazione, una chiamata a delle sue API, una finestra con l'interview dentro. Farla
|
|
309
|
+
// passare da questa interfaccia costringeva l'ospite a rispondere una promessa dentro
|
|
310
|
+
// un'implementazione di trasporto, cioe' a mettere l'interfaccia utente dove sta l'HTTP.
|
|
311
|
+
//
|
|
312
|
+
// Oggi i due comandi sono un **output** del componente (`runRequested`, payload
|
|
313
|
+
// `FlowRunRequest`) e l'esito facoltativo torna dall'input `runOutcome`. Corollario:
|
|
314
|
+
// non c'e' nessun `MissingService` da gestire su «Esegui» — un ospite che non ascolta
|
|
315
|
+
// l'output semplicemente non fa niente.
|
|
341
316
|
// -------------------------------------------------------------------------
|
|
342
317
|
// §6.5 Provare un flow dall'editor
|
|
343
318
|
// -------------------------------------------------------------------------
|
|
@@ -14242,6 +14217,56 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.28", ngImpo
|
|
|
14242
14217
|
], changeDetection: ChangeDetectionStrategy.OnPush, template: "@if (!selectedName()) {\r\n <p class=\"fb-inspector__empty\">\r\n Seleziona un elemento sul canvas per modificarlo, oppure trascina un elemento dalla palette.\r\n </p>\r\n} @else {\r\n @if (showHeader()) {\r\n <header class=\"fb-inspector__header\">\r\n <div>\r\n <span class=\"fb-inspector__type\">{{ typeLabel() }}</span>\r\n <h2 class=\"fb-inspector__title\">\r\n {{ isStart() ? 'Avvio del flow' : node()?.label || selectedName() }}\r\n </h2>\r\n </div>\r\n <button type=\"button\" class=\"fb-btn fb-btn--ghost fb-btn--icon\" aria-label=\"Chiudi\" (click)=\"close()\">\r\n \u00D7\r\n </button>\r\n </header>\r\n }\r\n\r\n <div class=\"fb-inspector__body\">\r\n @if (issues().length) {\r\n <ul class=\"fb-inspector__issues\">\r\n <!-- `$index`: due rilievi con lo stesso codice e lo stesso path sono possibili. -->\r\n @for (issue of visibleIssues(); track $index) {\r\n <li\r\n class=\"fb-inspector__issue\"\r\n [class.fb-inspector__issue--error]=\"issue.severity === 'Error'\"\r\n [class.fb-inspector__issue--warning]=\"issue.severity === 'Warning'\"\r\n >\r\n <span class=\"fb-inspector__issue-code\">{{ issue.code }}</span>\r\n {{ issue.message }}\r\n @if (issue.path) {\r\n <span class=\"fb-inspector__issue-path\">{{ issue.path }}</span>\r\n }\r\n </li>\r\n }\r\n </ul>\r\n @if (hiddenIssueCount() > 0 || issuesExpanded()) {\r\n <!-- Sei rilievi in cima riempivano la finestra prima del primo campo: si chiedono. -->\r\n <button type=\"button\" class=\"fb-inspector__issues-more\" (click)=\"toggleIssues()\">\r\n @if (issuesExpanded()) {\r\n Mostra meno\r\n } @else {\r\n Altri {{ hiddenIssueCount() }} rilievi su questo elemento\r\n }\r\n </button>\r\n }\r\n }\r\n\r\n @if (isUnsupported()) {\r\n <p class=\"fb-callout fb-callout--error\">\r\n Questo tipo di elemento non e\u2019 supportato dal motore: il flow che lo contiene non parte\r\n (ELEMENT_NOT_SUPPORTED). Non e\u2019 creabile dalla palette; se e\u2019 arrivato da un documento importato,\r\n va rimosso.\r\n </p>\r\n }\r\n\r\n @if (!isStart()) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Etichetta</label>\r\n <input\r\n class=\"fb-input\"\r\n [value]=\"$any(node()?.label) || ''\"\r\n placeholder=\"Nome mostrato sul canvas\"\r\n (input)=\"setLabel($any($event.target).value)\"\r\n />\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Nome tecnico</label>\r\n @if (pendingName() === null) {\r\n <div class=\"fb-field__row\">\r\n <input class=\"fb-input fb-input--mono\" [value]=\"selectedName() || ''\" readonly />\r\n <button type=\"button\" class=\"fb-btn\" (click)=\"startRename()\">Rinomina</button>\r\n </div>\r\n <p class=\"fb-field__hint\">\r\n \u00C8 l\u2019identificatore con cui i riferimenti raggiungono questo elemento.\r\n @if (referenceCount() > 1) {\r\n Compare {{ referenceCount() }} volte nel documento.\r\n }\r\n </p>\r\n } @else {\r\n <input\r\n class=\"fb-input fb-input--mono\"\r\n [class.fb-input--invalid]=\"!!nameError()\"\r\n [value]=\"pendingName() || ''\"\r\n (input)=\"onPendingNameInput($any($event.target).value)\"\r\n />\r\n @if (nameError()) {\r\n <p class=\"fb-field__error\">{{ nameError() }}</p>\r\n } @else {\r\n <p class=\"fb-field__hint fb-field__hint--warn\">\r\n La rinomina riscrive tutti i riferimenti che puntano a questo elemento\r\n ({{ referenceCount() }} occorrenze): nessuna primitiva del backend lo fa, lo fa l\u2019editor.\r\n </p>\r\n }\r\n <div class=\"fb-field__row\">\r\n <button type=\"button\" class=\"fb-btn fb-btn--primary\" [disabled]=\"!canRename()\" (click)=\"applyRename()\">\r\n Applica\r\n </button>\r\n <button type=\"button\" class=\"fb-btn\" (click)=\"suggestNameFromLabel()\">Genera dalla label</button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--ghost\" (click)=\"cancelRename()\">Annulla</button>\r\n </div>\r\n }\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Descrizione</label>\r\n <textarea\r\n class=\"fb-textarea\"\r\n [value]=\"$any(node()?.description) || ''\"\r\n (input)=\"setDescription($any($event.target).value)\"\r\n ></textarea>\r\n </div>\r\n }\r\n\r\n <p class=\"fb-inspector__position\">\r\n Posizione sul canvas: {{ position().x }}, {{ position().y }}\r\n </p>\r\n\r\n <hr class=\"fb-inspector__divider\" />\r\n\r\n @if (isStart()) {\r\n <fb-start-inspector />\r\n } @else if (node()) {\r\n @switch (type()) {\r\n @case ('Screen') {\r\n <fb-screen-inspector [name]=\"selectedName()!\" [node]=\"node()!\" />\r\n }\r\n @case ('DynamicScreen') {\r\n <fb-dynamic-screen-inspector\r\n [name]=\"selectedName()!\"\r\n [node]=\"node()!\"\r\n [initialSelection]=\"initialFieldPath()\"\r\n />\r\n }\r\n @case ('Assignment') {\r\n <fb-assignment-inspector [name]=\"selectedName()!\" [node]=\"node()!\" />\r\n }\r\n @case ('Decision') {\r\n <fb-decision-inspector [name]=\"selectedName()!\" [node]=\"node()!\" />\r\n }\r\n @case ('Loop') {\r\n <fb-loop-inspector [name]=\"selectedName()!\" [node]=\"node()!\" />\r\n }\r\n @case ('CollectionProcessor') {\r\n <fb-collection-processor-inspector [name]=\"selectedName()!\" [node]=\"node()!\" />\r\n }\r\n @case ('CustomError') {\r\n <fb-custom-error-inspector [name]=\"selectedName()!\" [node]=\"node()!\" />\r\n }\r\n @case ('Wait') {\r\n <fb-wait-inspector [name]=\"selectedName()!\" [node]=\"node()!\" />\r\n }\r\n @case ('RecordLookup') {\r\n <fb-record-lookup-inspector [name]=\"selectedName()!\" [node]=\"node()!\" />\r\n }\r\n @case ('RecordCreate') {\r\n <fb-record-write-inspector [name]=\"selectedName()!\" [node]=\"node()!\" type=\"RecordCreate\" />\r\n }\r\n @case ('RecordUpdate') {\r\n <fb-record-write-inspector [name]=\"selectedName()!\" [node]=\"node()!\" type=\"RecordUpdate\" />\r\n }\r\n @case ('RecordDelete') {\r\n <fb-record-write-inspector [name]=\"selectedName()!\" [node]=\"node()!\" type=\"RecordDelete\" />\r\n }\r\n @case ('RecordRollback') {\r\n <fb-record-rollback-inspector [name]=\"selectedName()!\" [node]=\"node()!\" />\r\n }\r\n @case ('ActionCall') {\r\n <fb-action-call-inspector [name]=\"selectedName()!\" [node]=\"node()!\" />\r\n }\r\n @case ('ScriptCall') {\r\n <fb-script-call-inspector [name]=\"selectedName()!\" [node]=\"node()!\" />\r\n }\r\n @case ('Subflow') {\r\n <fb-subflow-inspector [name]=\"selectedName()!\" [node]=\"node()!\" />\r\n }\r\n @case ('Transform') {\r\n <fb-transform-inspector [name]=\"selectedName()!\" [node]=\"node()!\" />\r\n }\r\n @case ('OrchestratedStage') {\r\n <fb-orchestrated-stage-inspector [name]=\"selectedName()!\" [node]=\"node()!\" />\r\n }\r\n @default {\r\n <p class=\"fb-callout fb-callout--warn\">\r\n Nessun form specifico per il tipo \u00AB{{ type() }}\u00BB: i campi comuni sono modificabili qui sopra.\r\n </p>\r\n }\r\n }\r\n }\r\n\r\n @if (!isStart()) {\r\n <hr class=\"fb-inspector__divider\" />\r\n <div class=\"fb-field__row\">\r\n <button type=\"button\" class=\"fb-btn\" (click)=\"requestDuplicate()\">Duplica</button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--danger\" (click)=\"requestRemove()\">Elimina</button>\r\n </div>\r\n }\r\n </div>\r\n}\r\n", styles: [":host{display:flex;flex-direction:column;height:100%;min-height:0;background:var(--fb-surface, #fff)}.fb-inspector__empty{margin:16px 12px;font-size:12px;line-height:1.5;color:var(--fb-text-muted, #667085)}.fb-inspector__header{display:flex;align-items:flex-start;justify-content:space-between;gap:8px;padding:10px 12px;border-bottom:1px solid var(--fb-border-subtle, #e6e9ee)}.fb-inspector__type{font-size:10px;font-weight:700;letter-spacing:.05em;text-transform:uppercase;color:var(--fb-text-subtle, #98a2b3)}.fb-inspector__title{margin:2px 0 0;font-size:14px;font-weight:600;color:var(--fb-text, #1d2939)}.fb-inspector__body{flex:1;min-height:0;overflow-y:auto;padding:14px}.fb-inspector__issues{margin:0 0 12px;padding:0;list-style:none}.fb-inspector__issue{margin-bottom:4px;padding:6px 8px;border-left:3px solid var(--fb-text-subtle, #98a2b3);border-radius:3px;background:var(--fb-surface-alt, #f8f9fb);font-size:11px;line-height:1.4;color:var(--fb-text, #1d2939)}.fb-inspector__issue--error{border-left-color:var(--fb-error, #c9372c)}.fb-inspector__issue--warning{border-left-color:var(--fb-warning, #b7791f)}.fb-inspector__issue-code{display:block;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:9px;color:var(--fb-text-subtle, #98a2b3)}.fb-inspector__issue-path{display:block;margin-top:2px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:9px;color:var(--fb-text-muted, #667085)}.fb-inspector__position{margin:0;font-size:10px;color:var(--fb-text-subtle, #98a2b3)}.fb-inspector__divider{margin:12px 0;border:0;border-top:1px solid var(--fb-border-subtle, #e6e9ee)}.fb-inspector__issues-more{display:block;margin:-4px 0 10px;padding:2px 0;border:0;background:none;color:var(--fb-accent, #4f6ef7);font:inherit;font-size:11px;cursor:pointer}.fb-inspector__issues-more:hover{text-decoration:underline}\n"] }]
|
|
14243
14218
|
}], propDecorators: { selectedName: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectedName", required: false }] }], showHeader: [{ type: i0.Input, args: [{ isSignal: true, alias: "showHeader", required: false }] }], initialFieldPath: [{ type: i0.Input, args: [{ isSignal: true, alias: "initialFieldPath", required: false }] }], closed: [{ type: i0.Output, args: ["closed"] }], removeRequested: [{ type: i0.Output, args: ["removeRequested"] }], duplicateRequested: [{ type: i0.Output, args: ["duplicateRequested"] }], renamed: [{ type: i0.Output, args: ["renamed"] }] } });
|
|
14244
14219
|
|
|
14220
|
+
/**
|
|
14221
|
+
* Il vincolo di spostamento delle finestre trascinabili dell'editor.
|
|
14222
|
+
*
|
|
14223
|
+
* Cio' che deve restare nel builder e' la **maniglia**, non la finestra: un `cdkDragBoundary`
|
|
14224
|
+
* chiede che l'elemento ci stia tutto, e con una dialog quasi grande quanto il builder non
|
|
14225
|
+
* lascia nessun margine di movimento. Qui invece si spinge la finestra fin quasi fuori — che
|
|
14226
|
+
* e' il punto, la si sposta per guardare cio' che copre — e l'intestazione resta comunque
|
|
14227
|
+
* sotto il puntatore per riportarla indietro.
|
|
14228
|
+
*
|
|
14229
|
+
* Attenzione alla firma, che non e' simmetrica e il compilatore non lo dice: **in** arriva la
|
|
14230
|
+
* posizione del puntatore in coordinate di pagina, **fuori** deve andare l'angolo in alto a
|
|
14231
|
+
* sinistra dell'**elemento** — con un `constrainPosition` il CDK calcola la trasformazione da
|
|
14232
|
+
* `_initialDomRect`, non dal punto di presa. Restituendo un puntatore la finestra si sposta di
|
|
14233
|
+
* quanto e' distante la presa dal suo bordo, e il vincolo scatta nel posto sbagliato.
|
|
14234
|
+
*
|
|
14235
|
+
* Per lo stesso motivo i conti si fanno in coordinate del **viewport**: `_initialDomRect` viene
|
|
14236
|
+
* da `getBoundingClientRect`, mentre il punto in ingresso comprende lo scorrimento della pagina.
|
|
14237
|
+
*/
|
|
14238
|
+
/** Altezza dell'intestazione: la fetta che deve restare visibile in basso. */
|
|
14239
|
+
const HEAD_HEIGHT = 48;
|
|
14240
|
+
/** Quanto della finestra resta dentro di lato: una fetta larga come un pezzo d'intestazione. */
|
|
14241
|
+
const GRIP = 180;
|
|
14242
|
+
function clamp(value, min, max) {
|
|
14243
|
+
// `max` puo' finire sotto `min` in un builder piu' piccolo della presa: lì non c'e' scelta.
|
|
14244
|
+
return Math.max(min, Math.min(max, value));
|
|
14245
|
+
}
|
|
14246
|
+
/**
|
|
14247
|
+
* Costruisce il `cdkDragConstrainPosition` di una dialog del builder. `panel` deve dare
|
|
14248
|
+
* l'elemento della finestra: il confine e' il `.fb-builder` che la contiene, e senza pannello
|
|
14249
|
+
* (finestra non ancora renderizzata) non si vincola niente.
|
|
14250
|
+
*/
|
|
14251
|
+
function dialogConstrainPosition(panel) {
|
|
14252
|
+
return (point, _drag, dimensions, pickup) => {
|
|
14253
|
+
const bounds = panel()?.closest('.fb-builder')?.getBoundingClientRect() ?? null;
|
|
14254
|
+
const left = point.x - pickup.x - window.scrollX;
|
|
14255
|
+
const top = point.y - pickup.y - window.scrollY;
|
|
14256
|
+
if (!bounds) {
|
|
14257
|
+
return { x: left, y: top };
|
|
14258
|
+
}
|
|
14259
|
+
// Un bordo di 1px lo si perde subito e la dialog diventa irraggiungibile: la fetta che
|
|
14260
|
+
// resta dentro e' larga abbastanza da poterla riafferrare.
|
|
14261
|
+
const grip = Math.min(GRIP, dimensions.width);
|
|
14262
|
+
const head = Math.min(HEAD_HEIGHT, dimensions.height);
|
|
14263
|
+
return {
|
|
14264
|
+
x: clamp(left, bounds.left + grip - dimensions.width, bounds.right - grip),
|
|
14265
|
+
y: clamp(top, bounds.top, bounds.bottom - head),
|
|
14266
|
+
};
|
|
14267
|
+
};
|
|
14268
|
+
}
|
|
14269
|
+
|
|
14245
14270
|
/**
|
|
14246
14271
|
* Il dettaglio dell'elemento in una dialog, come nel flow builder di Salesforce.
|
|
14247
14272
|
*
|
|
@@ -14267,9 +14292,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.28", ngImpo
|
|
|
14267
14292
|
*
|
|
14268
14293
|
* Il confine **non** e' `cdkDragBoundary`: la finestra e' larga 1400px e alta 860, cioe' quasi
|
|
14269
14294
|
* quanto il builder, e un confine che la vuole dentro per intero la rende immobile — la miglioria
|
|
14270
|
-
* non si vedrebbe. Al suo posto c'e' `cdkDragConstrainPosition
|
|
14271
|
-
*
|
|
14272
|
-
*
|
|
14295
|
+
* non si vedrebbe. Al suo posto c'e' `cdkDragConstrainPosition`, che tiene dentro
|
|
14296
|
+
* **l'intestazione** e nient'altro: si puo' spingere la dialog quasi fuori per guardare il grafo,
|
|
14297
|
+
* e la maniglia con cui riportarla indietro resta sempre afferrabile. La regola sta in
|
|
14298
|
+
* `ui/shared/dialog-drag.ts`, condivisa con le altre finestre trascinabili.
|
|
14273
14299
|
*
|
|
14274
14300
|
* La maniglia e' l'intestazione **e non il pannello**: dentro il corpo c'e' un altro `cdkDrag` —
|
|
14275
14301
|
* i campi del compositore dello screen dinamico (§5.2) — e senza maniglia il gesto su un campo
|
|
@@ -14278,10 +14304,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.28", ngImpo
|
|
|
14278
14304
|
* La posizione **non** si azzera cambiando elemento: la dialog resta viva quando la selezione
|
|
14279
14305
|
* passa da un elemento all'altro, e rimetterla al centro disferebbe uno spostamento voluto.
|
|
14280
14306
|
*/
|
|
14281
|
-
function clamp(value, min, max) {
|
|
14282
|
-
// `max` puo' finire sotto `min` in un builder piu' piccolo della presa: lì non c'e' scelta.
|
|
14283
|
-
return Math.max(min, Math.min(max, value));
|
|
14284
|
-
}
|
|
14285
14307
|
class ElementDialogComponent {
|
|
14286
14308
|
store = inject(FlowDocumentStore);
|
|
14287
14309
|
dictionaries = inject(FlowDictionaryStore);
|
|
@@ -14319,44 +14341,8 @@ class ElementDialogComponent {
|
|
|
14319
14341
|
}
|
|
14320
14342
|
});
|
|
14321
14343
|
}
|
|
14322
|
-
/**
|
|
14323
|
-
|
|
14324
|
-
* finestra. Un `cdkDragBoundary` chiede che l'elemento ci stia tutto, e con una dialog quasi
|
|
14325
|
-
* grande quanto il builder non lascia nessun margine di movimento; qui invece si spinge la
|
|
14326
|
-
* finestra fin quasi fuori — che e' il punto: si sposta per guardare cio' che copre — e
|
|
14327
|
-
* l'intestazione resta comunque sotto il puntatore per riportarla indietro.
|
|
14328
|
-
*
|
|
14329
|
-
* Attenzione alla firma, che non e' simmetrica e il compilatore non lo dice: **in** arriva la
|
|
14330
|
-
* posizione del puntatore in coordinate di pagina, **fuori** deve andare l'angolo in alto a
|
|
14331
|
-
* sinistra dell'**elemento** — con un `constrainPosition` il CDK calcola la trasformazione da
|
|
14332
|
-
* `_initialDomRect`, non dal punto di presa. Restituendo un puntatore la finestra si sposta di
|
|
14333
|
-
* quanto e' distante la presa dal suo bordo, e il vincolo scatta nel posto sbagliato.
|
|
14334
|
-
*
|
|
14335
|
-
* Per lo stesso motivo i conti si fanno in coordinate del **viewport**: `_initialDomRect` viene
|
|
14336
|
-
* da `getBoundingClientRect`, mentre il punto in ingresso comprende lo scorrimento della pagina.
|
|
14337
|
-
*/
|
|
14338
|
-
constrainPosition = (point, _drag, dimensions, pickup) => {
|
|
14339
|
-
const bounds = this.builderBounds();
|
|
14340
|
-
const left = point.x - pickup.x - window.scrollX;
|
|
14341
|
-
const top = point.y - pickup.y - window.scrollY;
|
|
14342
|
-
if (!bounds) {
|
|
14343
|
-
return { x: left, y: top };
|
|
14344
|
-
}
|
|
14345
|
-
// Quanto della finestra resta dentro: una fetta larga come un pezzo d'intestazione, non zero
|
|
14346
|
-
// — un bordo di 1px lo si perde subito e la dialog diventa irraggiungibile.
|
|
14347
|
-
const grip = Math.min(180, dimensions.width);
|
|
14348
|
-
const head = Math.min(ElementDialogComponent.HEAD_HEIGHT, dimensions.height);
|
|
14349
|
-
return {
|
|
14350
|
-
x: clamp(left, bounds.left + grip - dimensions.width, bounds.right - grip),
|
|
14351
|
-
y: clamp(top, bounds.top, bounds.bottom - head),
|
|
14352
|
-
};
|
|
14353
|
-
};
|
|
14354
|
-
/** Altezza dell'intestazione: la fetta che deve restare visibile in basso. */
|
|
14355
|
-
static HEAD_HEIGHT = 48;
|
|
14356
|
-
/** Il rettangolo del builder, nelle stesse coordinate in cui il CDK vuole la risposta. */
|
|
14357
|
-
builderBounds() {
|
|
14358
|
-
return this.panel()?.nativeElement.closest('.fb-builder')?.getBoundingClientRect() ?? null;
|
|
14359
|
-
}
|
|
14344
|
+
/** Il vincolo: dentro il builder resta la maniglia, non la finestra intera. */
|
|
14345
|
+
constrainPosition = dialogConstrainPosition(() => this.panel()?.nativeElement);
|
|
14360
14346
|
close() {
|
|
14361
14347
|
this.closed.emit();
|
|
14362
14348
|
}
|
|
@@ -14536,19 +14522,55 @@ function countNameUses(root, name) {
|
|
|
14536
14522
|
}
|
|
14537
14523
|
|
|
14538
14524
|
/**
|
|
14539
|
-
*
|
|
14525
|
+
* I controlli locali su una risorsa, condivisi fra l'elenco e la dialog di modifica.
|
|
14540
14526
|
*
|
|
14541
|
-
*
|
|
14542
|
-
*
|
|
14543
|
-
*
|
|
14527
|
+
* Stanno qui e non in uno dei due perche' servono a **tutti e due**, e per motivi diversi:
|
|
14528
|
+
* nell'elenco dicono quali righe hanno qualcosa che non va senza doverle aprire una per una,
|
|
14529
|
+
* nella dialog dicono **cosa** non va mentre lo si sta scrivendo. Due copie sarebbero due
|
|
14530
|
+
* copie che divergono, e il difetto sarebbe una riga verde nell'elenco su una risorsa rotta.
|
|
14531
|
+
*/
|
|
14532
|
+
/**
|
|
14533
|
+
* Nome valido e libero nello spazio dei nomi condiviso con i node (§3.3, §13.5).
|
|
14544
14534
|
*
|
|
14545
|
-
* Il
|
|
14546
|
-
*
|
|
14547
|
-
* gli insiemi — e' la trappola §13.5.
|
|
14535
|
+
* Il controllo guarda **entrambi** gli insiemi: un omonimo di un elemento e' un errore, non
|
|
14536
|
+
* una stranezza tollerata, e un controllo di unicita' che guardasse le sole risorse lo manca.
|
|
14548
14537
|
*/
|
|
14538
|
+
function resourceNameError(reference, usedNames) {
|
|
14539
|
+
const check = checkFlowName(reference.name, usedNames, reference.name);
|
|
14540
|
+
if (!check.isValid) {
|
|
14541
|
+
return check.message ?? 'Nome non valido.';
|
|
14542
|
+
}
|
|
14543
|
+
const clashes = usedNames.filter((name) => name.toLowerCase() === reference.name.toLowerCase());
|
|
14544
|
+
return clashes.length > 1 ? 'Questo nome e’ usato anche da un altro elemento o risorsa.' : null;
|
|
14545
|
+
}
|
|
14546
|
+
/** Una costante che referenzia una risorsa e' `CONSTANT_REFERENCES_RESOURCE`. */
|
|
14547
|
+
function constantReferencesResource(reference) {
|
|
14548
|
+
if (reference.collection !== 'constants') {
|
|
14549
|
+
return false;
|
|
14550
|
+
}
|
|
14551
|
+
const value = reference.resource['value'];
|
|
14552
|
+
return !!value?.elementReference || !!value?.formulaExpression;
|
|
14553
|
+
}
|
|
14554
|
+
/** `stageOrder` duplicati: quale stage sia il corrente all'avvio diventa arbitrario. */
|
|
14555
|
+
function duplicateStageOrder(reference, resources) {
|
|
14556
|
+
if (reference.collection !== 'stages') {
|
|
14557
|
+
return false;
|
|
14558
|
+
}
|
|
14559
|
+
const order = reference.resource['stageOrder'];
|
|
14560
|
+
if (order === undefined) {
|
|
14561
|
+
return false;
|
|
14562
|
+
}
|
|
14563
|
+
return (resources.filter((entry) => entry.collection === 'stages' && entry.resource['stageOrder'] === order)
|
|
14564
|
+
.length > 1);
|
|
14565
|
+
}
|
|
14566
|
+
|
|
14549
14567
|
/**
|
|
14550
|
-
* Le sette collection
|
|
14551
|
-
*
|
|
14568
|
+
* Le sette collection di risorse (§4.6).
|
|
14569
|
+
*
|
|
14570
|
+
* `resourceKinds` del dizionario le **descrive**, ma la mappa collection → comportamento del
|
|
14571
|
+
* form resta qui perche' i campi cambiano per tipo, e cambiano in modo che nessun dizionario
|
|
14572
|
+
* dichiara. Sta in un file suo perche' la usano in due: l'elenco, per le schede e il testo del
|
|
14573
|
+
* bottone «Aggiungi», e la dialog, per dire di che cosa e' il form che ha aperto.
|
|
14552
14574
|
*/
|
|
14553
14575
|
const RESOURCE_KINDS = [
|
|
14554
14576
|
{
|
|
@@ -14601,21 +14623,42 @@ const RESOURCE_KINDS = [
|
|
|
14601
14623
|
note: 'Gli stage si fanno avanzare con un Assignment su $Flow.CurrentStage.',
|
|
14602
14624
|
},
|
|
14603
14625
|
];
|
|
14626
|
+
/** Il descrittore di una collection; in mancanza il primo, che e' quello delle variabili. */
|
|
14627
|
+
function resourceKindOf(collection) {
|
|
14628
|
+
return RESOURCE_KINDS.find((kind) => kind.collection === collection) ?? RESOURCE_KINDS[0];
|
|
14629
|
+
}
|
|
14630
|
+
|
|
14631
|
+
/**
|
|
14632
|
+
* L'elenco delle risorse — FRONTEND.md §4.6.
|
|
14633
|
+
*
|
|
14634
|
+
* Sette tipi, ognuno nella propria collection. **Solo le variabili sono scrivibili**:
|
|
14635
|
+
* assegnare a una costante o a una formula e' `TARGET_NOT_WRITABLE`, e per questo il
|
|
14636
|
+
* pannello mostra quali risorse possono comparire come destinazione e quali no.
|
|
14637
|
+
*
|
|
14638
|
+
* Il pannello e' un **elenco**, non un form: aggiungere o modificare una risorsa apre la
|
|
14639
|
+
* dialog dedicata (`fb-resource-dialog`). Il form stava qui dentro, inline sotto la riga, e in
|
|
14640
|
+
* una colonna da 340px spingeva l'elenco fuori dallo schermo — cioe' faceva sparire il contesto
|
|
14641
|
+
* per cui l'elenco esiste. Cio' che resta qui e' quello che si legge **senza** aprire: nome,
|
|
14642
|
+
* tipo, quante volte e' usata, e i rilievi locali della riga.
|
|
14643
|
+
*
|
|
14644
|
+
* La finestra non la monta il pannello ma il builder, e per una ragione sola: qui dentro
|
|
14645
|
+
* sarebbe ritagliata dallo scorrimento della colonna laterale. Il pannello **annuncia** quale
|
|
14646
|
+
* risorsa si vuole configurare (`editRequested`) e riceve indietro quale sia aperta
|
|
14647
|
+
* (`editing`), invece di tenerne una copia sua che si scollerebbe alla prima chiusura fatta
|
|
14648
|
+
* dalla finestra.
|
|
14649
|
+
*/
|
|
14604
14650
|
class ResourcePanelComponent {
|
|
14605
14651
|
store = inject(FlowDocumentStore);
|
|
14606
|
-
dictionaries = inject(FlowDictionaryStore);
|
|
14607
|
-
catalog = inject(FlowCatalogStore);
|
|
14608
14652
|
closed = output();
|
|
14609
14653
|
/** Dove aprire il pannello: e' l'arrivo della ricerca su una risorsa. */
|
|
14610
14654
|
target = input(null, ...(ngDevMode ? [{ debugName: "target" }] : []));
|
|
14655
|
+
/** La risorsa aperta nella finestra di modifica: la decide il builder, non il pannello. */
|
|
14656
|
+
editing = input(null, ...(ngDevMode ? [{ debugName: "editing" }] : []));
|
|
14657
|
+
/** Quale risorsa configurare; `null` chiude la finestra. */
|
|
14658
|
+
editRequested = output();
|
|
14611
14659
|
kinds = RESOURCE_KINDS;
|
|
14612
|
-
dataTypes = computed(() => this.dictionaries.dataTypes(), ...(ngDevMode ? [{ debugName: "dataTypes" }] : []));
|
|
14613
|
-
sortOrders = computed(() => this.dictionaries.sortOrders(), ...(ngDevMode ? [{ debugName: "sortOrders" }] : []));
|
|
14614
|
-
enumTypes = signal([], ...(ngDevMode ? [{ debugName: "enumTypes" }] : []));
|
|
14615
14660
|
/** La collection aperta nel pannello. */
|
|
14616
14661
|
activeCollection = signal('variables', ...(ngDevMode ? [{ debugName: "activeCollection" }] : []));
|
|
14617
|
-
/** L'indice della risorsa in modifica, o `null`. */
|
|
14618
|
-
editingIndex = signal(null, ...(ngDevMode ? [{ debugName: "editingIndex" }] : []));
|
|
14619
14662
|
/**
|
|
14620
14663
|
* La risorsa per cui e' stata chiesta la rimozione ma che risulta **usata**: si conferma.
|
|
14621
14664
|
* La chiave e' `collection:index`, non la risorsa, perche' l'oggetto viene ricostruito a ogni
|
|
@@ -14623,42 +14666,51 @@ class ResourcePanelComponent {
|
|
|
14623
14666
|
*/
|
|
14624
14667
|
pendingRemoval = signal(null, ...(ngDevMode ? [{ debugName: "pendingRemoval" }] : []));
|
|
14625
14668
|
constructor() {
|
|
14626
|
-
|
|
14627
|
-
// L'arrivo dalla ricerca: apre la scheda giusta e la risorsa, in modifica.
|
|
14669
|
+
// L'arrivo dalla ricerca: apre la scheda giusta e la risorsa, nella finestra.
|
|
14628
14670
|
effect(() => {
|
|
14629
14671
|
const target = this.target();
|
|
14630
14672
|
if (!target) {
|
|
14631
14673
|
return;
|
|
14632
14674
|
}
|
|
14633
14675
|
this.activeCollection.set(target.collection);
|
|
14634
|
-
this.
|
|
14676
|
+
this.editRequested.emit({ collection: target.collection, index: target.index });
|
|
14635
14677
|
});
|
|
14636
14678
|
}
|
|
14637
|
-
|
|
14638
|
-
activeKind = computed(() => RESOURCE_KINDS.find((kind) => kind.collection === this.activeCollection()) ?? RESOURCE_KINDS[0], ...(ngDevMode ? [{ debugName: "activeKind" }] : []));
|
|
14679
|
+
activeKind = computed(() => resourceKindOf(this.activeCollection()), ...(ngDevMode ? [{ debugName: "activeKind" }] : []));
|
|
14639
14680
|
items = computed(() => this.store.resources().filter((reference) => reference.collection === this.activeCollection()), ...(ngDevMode ? [{ debugName: "items" }] : []));
|
|
14640
14681
|
countOf(collection) {
|
|
14641
14682
|
return this.store.resources().filter((reference) => reference.collection === collection).length;
|
|
14642
14683
|
}
|
|
14643
14684
|
select(collection) {
|
|
14644
14685
|
this.activeCollection.set(collection);
|
|
14645
|
-
|
|
14686
|
+
// Cambiare scheda con una finestra aperta su un'altra collection lascerebbe due contesti
|
|
14687
|
+
// discordi: si chiude.
|
|
14688
|
+
this.editRequested.emit(null);
|
|
14646
14689
|
}
|
|
14647
|
-
|
|
14648
|
-
this.
|
|
14690
|
+
isEditing(reference) {
|
|
14691
|
+
const editing = this.editing();
|
|
14692
|
+
return !!editing && editing.collection === reference.collection && editing.index === reference.index;
|
|
14649
14693
|
}
|
|
14694
|
+
/** Il click sul nome: apre la finestra, o la chiude se e' già quella risorsa. */
|
|
14695
|
+
edit(reference) {
|
|
14696
|
+
this.editRequested.emit(this.isEditing(reference) ? null : { collection: reference.collection, index: reference.index });
|
|
14697
|
+
}
|
|
14698
|
+
// -------------------------------------------------------------------------
|
|
14699
|
+
// Rilievi locali della riga
|
|
14700
|
+
// -------------------------------------------------------------------------
|
|
14650
14701
|
/** Nome valido e libero nello spazio dei nomi condiviso con i node (§3.3, §13.5). */
|
|
14651
14702
|
nameError(reference) {
|
|
14652
|
-
|
|
14653
|
-
if (!check.isValid) {
|
|
14654
|
-
return check.message ?? 'Nome non valido.';
|
|
14655
|
-
}
|
|
14656
|
-
// Un omonimo di un node: il controllo di unicita' che guarda solo le risorse lo manca.
|
|
14657
|
-
const clashes = this.store
|
|
14658
|
-
.usedNames()
|
|
14659
|
-
.filter((name) => name.toLowerCase() === reference.name.toLowerCase());
|
|
14660
|
-
return clashes.length > 1 ? 'Questo nome e’ usato anche da un altro elemento o risorsa.' : null;
|
|
14703
|
+
return resourceNameError(reference, this.store.usedNames());
|
|
14661
14704
|
}
|
|
14705
|
+
constantReferencesResource(reference) {
|
|
14706
|
+
return constantReferencesResource(reference);
|
|
14707
|
+
}
|
|
14708
|
+
duplicateStageOrder(reference) {
|
|
14709
|
+
return duplicateStageOrder(reference, this.store.resources());
|
|
14710
|
+
}
|
|
14711
|
+
// -------------------------------------------------------------------------
|
|
14712
|
+
// Aggiunta e rimozione
|
|
14713
|
+
// -------------------------------------------------------------------------
|
|
14662
14714
|
add() {
|
|
14663
14715
|
const kind = this.activeKind();
|
|
14664
14716
|
const name = uniqueFlowName(kind.singular.replace(/\s+/g, '_'), this.store.usedNames());
|
|
@@ -14682,7 +14734,9 @@ class ResourcePanelComponent {
|
|
|
14682
14734
|
resource['label'] = name;
|
|
14683
14735
|
}
|
|
14684
14736
|
this.store.addResource(kind.collection, resource);
|
|
14685
|
-
|
|
14737
|
+
// La risorsa nuova e' l'ultima della collection, e si apre subito: aggiungerla senza aprirla
|
|
14738
|
+
// lascerebbe una riga chiamata «variabile_1» da configurare in un secondo gesto.
|
|
14739
|
+
this.editRequested.emit({ collection: kind.collection, index: this.items().length - 1 });
|
|
14686
14740
|
}
|
|
14687
14741
|
/**
|
|
14688
14742
|
* Quante volte la risorsa e' **usata** nel documento.
|
|
@@ -14721,14 +14775,83 @@ class ResourcePanelComponent {
|
|
|
14721
14775
|
}
|
|
14722
14776
|
remove(reference) {
|
|
14723
14777
|
this.store.removeResource(reference.collection, reference.index);
|
|
14724
|
-
|
|
14778
|
+
// Gli indici delle risorse dopo di lei scalano: una finestra aperta punterebbe a un'altra
|
|
14779
|
+
// risorsa, non a nessuna. Si chiude.
|
|
14780
|
+
this.editRequested.emit(null);
|
|
14725
14781
|
this.pendingRemoval.set(null);
|
|
14726
14782
|
}
|
|
14727
|
-
|
|
14783
|
+
// -------------------------------------------------------------------------
|
|
14784
|
+
// Lettura per la riga
|
|
14785
|
+
// -------------------------------------------------------------------------
|
|
14786
|
+
string(reference, field) {
|
|
14787
|
+
const value = reference.resource[field];
|
|
14788
|
+
return value === undefined || value === null ? '' : String(value);
|
|
14789
|
+
}
|
|
14790
|
+
boolean(reference, field) {
|
|
14791
|
+
return reference.resource[field] === true;
|
|
14792
|
+
}
|
|
14793
|
+
close() {
|
|
14794
|
+
this.closed.emit();
|
|
14795
|
+
}
|
|
14796
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.28", ngImport: i0, type: ResourcePanelComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
14797
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.28", type: ResourcePanelComponent, isStandalone: true, selector: "fb-resource-panel", inputs: { target: { classPropertyName: "target", publicName: "target", isSignal: true, isRequired: false, transformFunction: null }, editing: { classPropertyName: "editing", publicName: "editing", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { closed: "closed", editRequested: "editRequested" }, ngImport: i0, template: "<header class=\"fb-res__header\">\r\n <h2 class=\"fb-res__title\">Risorse</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<nav class=\"fb-res__tabs\" aria-label=\"Tipi di risorsa\">\r\n @for (kind of kinds; track kind.collection) {\r\n <button\r\n type=\"button\"\r\n class=\"fb-res__tab\"\r\n [class.fb-res__tab--active]=\"activeCollection() === kind.collection\"\r\n (click)=\"select(kind.collection)\"\r\n >\r\n {{ kind.label }}\r\n <span class=\"fb-res__count\">{{ countOf(kind.collection) }}</span>\r\n </button>\r\n }\r\n</nav>\r\n\r\n<div class=\"fb-res__body\">\r\n <p class=\"fb-section__note\">{{ activeKind().note }}</p>\r\n\r\n <div class=\"fb-list\">\r\n @for (item of items(); track item.collection + ':' + item.index) {\r\n <div class=\"fb-list__item\" [class.fb-list__item--active]=\"isEditing(item)\">\r\n <div class=\"fb-list__header\">\r\n <!--\r\n Il nome apre la finestra di modifica: qui l\u2019elenco resta un elenco, e cio\u2019 che si\r\n configura non lo copre. \u00C8 la stessa scelta della dialog del dettaglio.\r\n -->\r\n <button\r\n type=\"button\"\r\n class=\"fb-res__name\"\r\n [title]=\"'Configura ' + (item.name || 'la risorsa')\"\r\n (click)=\"edit(item)\"\r\n >\r\n <span class=\"fb-res__name-text\">{{ item.name || '(senza nome)' }}</span>\r\n <span class=\"fb-res__meta\">\r\n {{ string(item, 'dataType') }}{{ boolean(item, 'isCollection') ? '[]' : '' }}\r\n @if (boolean(item, 'isInput')) {\r\n \u00B7 input\r\n }\r\n @if (boolean(item, 'isOutput')) {\r\n \u00B7 output\r\n }\r\n </span>\r\n </button>\r\n <span class=\"fb-list__spacer\"></span>\r\n <!--\r\n Il numero di usi sta accanto al nome perche\u2019 e\u2019 cio\u2019 che decide se si puo\u2019 togliere:\r\n \u00ABnon usata\u00BB e\u2019 un invito a fare pulizia, \u00ABusata 15 volte\u00BB e\u2019 un avvertimento.\r\n -->\r\n @let uses = usesOf(item);\r\n @if (uses === 0) {\r\n <span class=\"fb-res__uses fb-res__uses--none\" title=\"Nessun riferimento la usa: si puo\u2019 togliere\">\r\n non usata\r\n </span>\r\n } @else {\r\n <span\r\n class=\"fb-res__uses\"\r\n [title]=\"'Referenziata ' + uses + ' volte nel documento, formule comprese'\"\r\n >\r\n {{ uses }} usi\r\n </span>\r\n }\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-btn--ghost fb-btn--icon\"\r\n aria-label=\"Rimuovi la risorsa\"\r\n [title]=\"uses ? 'Rimuovi: e\u2019 usata ' + uses + ' volte, verra\u2019 chiesta conferma' : 'Rimuovi'\"\r\n (click)=\"requestRemove(item)\"\r\n >\r\n \u00D7\r\n </button>\r\n </div>\r\n\r\n @if (isPendingRemoval(item)) {\r\n <!--\r\n La conferma sta qui e non in una finestra: la riga che si sta togliendo resta sotto\r\n gli occhi, e il numero di riferimenti che si rompono e\u2019 il dato della decisione.\r\n -->\r\n <p class=\"fb-callout fb-callout--warn fb-res__confirm\">\r\n <span>\r\n \u00AB{{ item.name }}\u00BB e\u2019 usata {{ usesOf(item) }} volte: togliendola quei riferimenti\r\n restano orfani e la validazione li segnalera\u2019.\r\n </span>\r\n <button type=\"button\" class=\"fb-btn fb-btn--icon\" (click)=\"remove(item)\">Rimuovi comunque</button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--ghost fb-btn--icon\" (click)=\"cancelRemove()\">\r\n Annulla\r\n </button>\r\n </p>\r\n }\r\n\r\n <!--\r\n I rilievi locali restano nell\u2019elenco anche ora che il form sta altrove: senza, per\r\n sapere quali risorse hanno qualcosa che non va bisognerebbe aprirle una per una.\r\n -->\r\n @if (nameError(item); as message) {\r\n <p class=\"fb-field__error\">{{ message }}</p>\r\n }\r\n @if (constantReferencesResource(item)) {\r\n <p class=\"fb-callout fb-callout--error\">\r\n Una costante deve essere un valore fisso: non puo\u2019 referenziare altre risorse\r\n (CONSTANT_REFERENCES_RESOURCE).\r\n </p>\r\n }\r\n @if (duplicateStageOrder(item)) {\r\n <p class=\"fb-callout fb-callout--warn\">\r\n Due stage con lo stesso ordine: quale sia il corrente all\u2019avvio diventa arbitrario\r\n (STAGE_ORDER_DUPLICATED).\r\n </p>\r\n }\r\n </div>\r\n } @empty {\r\n <p class=\"fb-empty\">Nessuna risorsa di questo tipo.</p>\r\n }\r\n </div>\r\n\r\n <button type=\"button\" class=\"fb-btn fb-btn--primary\" (click)=\"add()\">\r\n Aggiungi {{ activeKind().singular }}\r\n </button>\r\n</div>\r\n", styles: [":host{display:flex;flex-direction:column;height:100%;min-height:0;background:var(--fb-surface, #fff)}.fb-res__header{display:flex;align-items:center;justify-content:space-between;padding:10px 12px;border-bottom:1px solid var(--fb-border-subtle, #e6e9ee)}.fb-res__title{margin:0;font-size:14px;font-weight:600;color:var(--fb-text, #1d2939)}.fb-res__tabs{display:flex;flex-wrap:wrap;gap:2px;padding:6px 8px;border-bottom:1px solid var(--fb-border-subtle, #e6e9ee)}.fb-res__tab{display:inline-flex;align-items:center;gap:4px;padding:3px 8px;border:1px solid var(--fb-border-subtle, #e6e9ee);border-radius:12px;background:transparent;color:var(--fb-text-muted, #667085);font:inherit;font-size:11px;cursor:pointer}.fb-res__tab:hover{background:var(--fb-surface-alt, #f8f9fb)}.fb-res__tab--active{border-color:var(--fb-accent, #2f6feb);background:color-mix(in srgb,var(--fb-accent, #2f6feb) 10%,transparent);color:var(--fb-accent, #2f6feb);font-weight:600}.fb-res__count{padding:0 4px;border-radius:6px;background:var(--fb-border, #d6dae1);font-size:9px;color:var(--fb-text, #1d2939)}.fb-res__body{flex:1;min-height:0;overflow-y:auto;padding:10px 12px}.fb-res__name{flex:1;min-width:0;display:flex;flex-direction:column;padding:0;border:0;background:transparent;color:var(--fb-text, #1d2939);font:inherit;text-align:left;cursor:pointer}.fb-res__name-text{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fb-res__meta{font-size:10px;color:var(--fb-text-muted, #667085)}.fb-res__uses{flex:none;padding:1px 6px;border-radius:999px;background:var(--fb-surface-sunken, #eef0f4);color:var(--fb-text-muted, #6b7086);font-size:10px;font-variant-numeric:tabular-nums;white-space:nowrap}.fb-res__uses--none{background:color-mix(in srgb,var(--fb-warning, #b7791f) 12%,transparent);color:var(--fb-warning, #b7791f)}.fb-res__confirm{display:flex;flex-wrap:wrap;align-items:center;gap:6px}.fb-list__item--active{border-color:var(--fb-accent, #2f6feb);background:color-mix(in srgb,var(--fb-accent, #2f6feb) 6%,var(--fb-surface-alt, #f8f9fb))}.fb-list__item .fb-list__header{margin-bottom:0}.fb-list__item .fb-list__header+*{margin-top:6px}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
14798
|
+
}
|
|
14799
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.28", ngImport: i0, type: ResourcePanelComponent, decorators: [{
|
|
14800
|
+
type: Component,
|
|
14801
|
+
args: [{ selector: 'fb-resource-panel', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, template: "<header class=\"fb-res__header\">\r\n <h2 class=\"fb-res__title\">Risorse</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<nav class=\"fb-res__tabs\" aria-label=\"Tipi di risorsa\">\r\n @for (kind of kinds; track kind.collection) {\r\n <button\r\n type=\"button\"\r\n class=\"fb-res__tab\"\r\n [class.fb-res__tab--active]=\"activeCollection() === kind.collection\"\r\n (click)=\"select(kind.collection)\"\r\n >\r\n {{ kind.label }}\r\n <span class=\"fb-res__count\">{{ countOf(kind.collection) }}</span>\r\n </button>\r\n }\r\n</nav>\r\n\r\n<div class=\"fb-res__body\">\r\n <p class=\"fb-section__note\">{{ activeKind().note }}</p>\r\n\r\n <div class=\"fb-list\">\r\n @for (item of items(); track item.collection + ':' + item.index) {\r\n <div class=\"fb-list__item\" [class.fb-list__item--active]=\"isEditing(item)\">\r\n <div class=\"fb-list__header\">\r\n <!--\r\n Il nome apre la finestra di modifica: qui l\u2019elenco resta un elenco, e cio\u2019 che si\r\n configura non lo copre. \u00C8 la stessa scelta della dialog del dettaglio.\r\n -->\r\n <button\r\n type=\"button\"\r\n class=\"fb-res__name\"\r\n [title]=\"'Configura ' + (item.name || 'la risorsa')\"\r\n (click)=\"edit(item)\"\r\n >\r\n <span class=\"fb-res__name-text\">{{ item.name || '(senza nome)' }}</span>\r\n <span class=\"fb-res__meta\">\r\n {{ string(item, 'dataType') }}{{ boolean(item, 'isCollection') ? '[]' : '' }}\r\n @if (boolean(item, 'isInput')) {\r\n \u00B7 input\r\n }\r\n @if (boolean(item, 'isOutput')) {\r\n \u00B7 output\r\n }\r\n </span>\r\n </button>\r\n <span class=\"fb-list__spacer\"></span>\r\n <!--\r\n Il numero di usi sta accanto al nome perche\u2019 e\u2019 cio\u2019 che decide se si puo\u2019 togliere:\r\n \u00ABnon usata\u00BB e\u2019 un invito a fare pulizia, \u00ABusata 15 volte\u00BB e\u2019 un avvertimento.\r\n -->\r\n @let uses = usesOf(item);\r\n @if (uses === 0) {\r\n <span class=\"fb-res__uses fb-res__uses--none\" title=\"Nessun riferimento la usa: si puo\u2019 togliere\">\r\n non usata\r\n </span>\r\n } @else {\r\n <span\r\n class=\"fb-res__uses\"\r\n [title]=\"'Referenziata ' + uses + ' volte nel documento, formule comprese'\"\r\n >\r\n {{ uses }} usi\r\n </span>\r\n }\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-btn--ghost fb-btn--icon\"\r\n aria-label=\"Rimuovi la risorsa\"\r\n [title]=\"uses ? 'Rimuovi: e\u2019 usata ' + uses + ' volte, verra\u2019 chiesta conferma' : 'Rimuovi'\"\r\n (click)=\"requestRemove(item)\"\r\n >\r\n \u00D7\r\n </button>\r\n </div>\r\n\r\n @if (isPendingRemoval(item)) {\r\n <!--\r\n La conferma sta qui e non in una finestra: la riga che si sta togliendo resta sotto\r\n gli occhi, e il numero di riferimenti che si rompono e\u2019 il dato della decisione.\r\n -->\r\n <p class=\"fb-callout fb-callout--warn fb-res__confirm\">\r\n <span>\r\n \u00AB{{ item.name }}\u00BB e\u2019 usata {{ usesOf(item) }} volte: togliendola quei riferimenti\r\n restano orfani e la validazione li segnalera\u2019.\r\n </span>\r\n <button type=\"button\" class=\"fb-btn fb-btn--icon\" (click)=\"remove(item)\">Rimuovi comunque</button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--ghost fb-btn--icon\" (click)=\"cancelRemove()\">\r\n Annulla\r\n </button>\r\n </p>\r\n }\r\n\r\n <!--\r\n I rilievi locali restano nell\u2019elenco anche ora che il form sta altrove: senza, per\r\n sapere quali risorse hanno qualcosa che non va bisognerebbe aprirle una per una.\r\n -->\r\n @if (nameError(item); as message) {\r\n <p class=\"fb-field__error\">{{ message }}</p>\r\n }\r\n @if (constantReferencesResource(item)) {\r\n <p class=\"fb-callout fb-callout--error\">\r\n Una costante deve essere un valore fisso: non puo\u2019 referenziare altre risorse\r\n (CONSTANT_REFERENCES_RESOURCE).\r\n </p>\r\n }\r\n @if (duplicateStageOrder(item)) {\r\n <p class=\"fb-callout fb-callout--warn\">\r\n Due stage con lo stesso ordine: quale sia il corrente all\u2019avvio diventa arbitrario\r\n (STAGE_ORDER_DUPLICATED).\r\n </p>\r\n }\r\n </div>\r\n } @empty {\r\n <p class=\"fb-empty\">Nessuna risorsa di questo tipo.</p>\r\n }\r\n </div>\r\n\r\n <button type=\"button\" class=\"fb-btn fb-btn--primary\" (click)=\"add()\">\r\n Aggiungi {{ activeKind().singular }}\r\n </button>\r\n</div>\r\n", styles: [":host{display:flex;flex-direction:column;height:100%;min-height:0;background:var(--fb-surface, #fff)}.fb-res__header{display:flex;align-items:center;justify-content:space-between;padding:10px 12px;border-bottom:1px solid var(--fb-border-subtle, #e6e9ee)}.fb-res__title{margin:0;font-size:14px;font-weight:600;color:var(--fb-text, #1d2939)}.fb-res__tabs{display:flex;flex-wrap:wrap;gap:2px;padding:6px 8px;border-bottom:1px solid var(--fb-border-subtle, #e6e9ee)}.fb-res__tab{display:inline-flex;align-items:center;gap:4px;padding:3px 8px;border:1px solid var(--fb-border-subtle, #e6e9ee);border-radius:12px;background:transparent;color:var(--fb-text-muted, #667085);font:inherit;font-size:11px;cursor:pointer}.fb-res__tab:hover{background:var(--fb-surface-alt, #f8f9fb)}.fb-res__tab--active{border-color:var(--fb-accent, #2f6feb);background:color-mix(in srgb,var(--fb-accent, #2f6feb) 10%,transparent);color:var(--fb-accent, #2f6feb);font-weight:600}.fb-res__count{padding:0 4px;border-radius:6px;background:var(--fb-border, #d6dae1);font-size:9px;color:var(--fb-text, #1d2939)}.fb-res__body{flex:1;min-height:0;overflow-y:auto;padding:10px 12px}.fb-res__name{flex:1;min-width:0;display:flex;flex-direction:column;padding:0;border:0;background:transparent;color:var(--fb-text, #1d2939);font:inherit;text-align:left;cursor:pointer}.fb-res__name-text{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fb-res__meta{font-size:10px;color:var(--fb-text-muted, #667085)}.fb-res__uses{flex:none;padding:1px 6px;border-radius:999px;background:var(--fb-surface-sunken, #eef0f4);color:var(--fb-text-muted, #6b7086);font-size:10px;font-variant-numeric:tabular-nums;white-space:nowrap}.fb-res__uses--none{background:color-mix(in srgb,var(--fb-warning, #b7791f) 12%,transparent);color:var(--fb-warning, #b7791f)}.fb-res__confirm{display:flex;flex-wrap:wrap;align-items:center;gap:6px}.fb-list__item--active{border-color:var(--fb-accent, #2f6feb);background:color-mix(in srgb,var(--fb-accent, #2f6feb) 6%,var(--fb-surface-alt, #f8f9fb))}.fb-list__item .fb-list__header{margin-bottom:0}.fb-list__item .fb-list__header+*{margin-top:6px}\n"] }]
|
|
14802
|
+
}], ctorParameters: () => [], propDecorators: { closed: [{ type: i0.Output, args: ["closed"] }], target: [{ type: i0.Input, args: [{ isSignal: true, alias: "target", required: false }] }], editing: [{ type: i0.Input, args: [{ isSignal: true, alias: "editing", required: false }] }], editRequested: [{ type: i0.Output, args: ["editRequested"] }] } });
|
|
14803
|
+
|
|
14804
|
+
/**
|
|
14805
|
+
* Il form di **una** risorsa (§4.6).
|
|
14806
|
+
*
|
|
14807
|
+
* Prima stava inline nell'elenco, sotto la riga della risorsa aperta; oggi lo monta la dialog
|
|
14808
|
+
* dedicata. La separazione non e' cosmetica: i campi cambiano molto per tipo — un dynamic
|
|
14809
|
+
* choice set porta sorgente, due campi, ordinamento, limite e un editor di filtri — e in una
|
|
14810
|
+
* colonna da 340px il form spingeva l'elenco fuori dallo schermo, cioe' faceva sparire proprio
|
|
14811
|
+
* il contesto («quali altre risorse ci sono») per cui l'elenco esiste.
|
|
14812
|
+
*
|
|
14813
|
+
* Il componente **non tiene una bozza**: come ovunque nell'editor, ogni modifica entra subito
|
|
14814
|
+
* nel documento e l'annulla generale e' la rete. Per questo non ha «Annulla» e non ha «Salva».
|
|
14815
|
+
*/
|
|
14816
|
+
class ResourceFormComponent {
|
|
14817
|
+
store = inject(FlowDocumentStore);
|
|
14818
|
+
dictionaries = inject(FlowDictionaryStore);
|
|
14819
|
+
catalog = inject(FlowCatalogStore);
|
|
14820
|
+
/** La risorsa in modifica. Arriva ricostruita a ogni ricalcolo del documento. */
|
|
14821
|
+
reference = input.required(...(ngDevMode ? [{ debugName: "reference" }] : []));
|
|
14822
|
+
dataTypes = computed(() => this.dictionaries.dataTypes(), ...(ngDevMode ? [{ debugName: "dataTypes" }] : []));
|
|
14823
|
+
sortOrders = computed(() => this.dictionaries.sortOrders(), ...(ngDevMode ? [{ debugName: "sortOrders" }] : []));
|
|
14824
|
+
enumTypes = signal([], ...(ngDevMode ? [{ debugName: "enumTypes" }] : []));
|
|
14825
|
+
enumOptions = computed(() => this.enumTypes(), ...(ngDevMode ? [{ debugName: "enumOptions" }] : []));
|
|
14826
|
+
defaultDisplayField = ENUM_CHOICE_SET_DEFAULT_DISPLAY_FIELD;
|
|
14827
|
+
defaultValueField = ENUM_CHOICE_SET_DEFAULT_VALUE_FIELD;
|
|
14828
|
+
constructor() {
|
|
14829
|
+
void this.catalog.listEnumTypes().then((list) => this.enumTypes.set(list ?? []));
|
|
14830
|
+
}
|
|
14831
|
+
collection = computed(() => this.reference().collection, ...(ngDevMode ? [{ debugName: "collection" }] : []));
|
|
14832
|
+
resource = computed(() => this.reference().resource, ...(ngDevMode ? [{ debugName: "resource" }] : []));
|
|
14833
|
+
// -------------------------------------------------------------------------
|
|
14834
|
+
// Rilievi locali (gli stessi che l'elenco mostra sulla riga)
|
|
14835
|
+
// -------------------------------------------------------------------------
|
|
14836
|
+
nameError() {
|
|
14837
|
+
return resourceNameError(this.reference(), this.store.usedNames());
|
|
14838
|
+
}
|
|
14839
|
+
constantReferencesResource() {
|
|
14840
|
+
return constantReferencesResource(this.reference());
|
|
14841
|
+
}
|
|
14842
|
+
duplicateStageOrder() {
|
|
14843
|
+
return duplicateStageOrder(this.reference(), this.store.resources());
|
|
14844
|
+
}
|
|
14845
|
+
// -------------------------------------------------------------------------
|
|
14846
|
+
// Scrittura sul documento
|
|
14847
|
+
// -------------------------------------------------------------------------
|
|
14848
|
+
rename(name) {
|
|
14728
14849
|
// Rinominare riscrive i riferimenti: nessuna primitiva lo fa per noi (§13.8).
|
|
14850
|
+
const reference = this.reference();
|
|
14729
14851
|
this.store.renameResource(reference.collection, reference.index, name);
|
|
14730
14852
|
}
|
|
14731
|
-
setField(
|
|
14853
|
+
setField(field, value) {
|
|
14854
|
+
const reference = this.reference();
|
|
14732
14855
|
this.store.updateResource(reference.collection, reference.index, (resource) => {
|
|
14733
14856
|
if (value === undefined || value === null || value === '') {
|
|
14734
14857
|
delete resource[field];
|
|
@@ -14738,11 +14861,12 @@ class ResourcePanelComponent {
|
|
|
14738
14861
|
}
|
|
14739
14862
|
});
|
|
14740
14863
|
}
|
|
14741
|
-
setNumberField(
|
|
14864
|
+
setNumberField(field, raw) {
|
|
14742
14865
|
const parsed = Number.parseInt(raw, 10);
|
|
14743
|
-
this.setField(
|
|
14866
|
+
this.setField(field, Number.isNaN(parsed) ? undefined : parsed);
|
|
14744
14867
|
}
|
|
14745
|
-
setBooleanField(
|
|
14868
|
+
setBooleanField(field, value) {
|
|
14869
|
+
const reference = this.reference();
|
|
14746
14870
|
this.store.updateResource(reference.collection, reference.index, (resource) => {
|
|
14747
14871
|
if (value) {
|
|
14748
14872
|
resource[field] = true;
|
|
@@ -14757,26 +14881,64 @@ class ResourcePanelComponent {
|
|
|
14757
14881
|
* applicare al contenitore, non i filtri gia' pronti: qui il contenitore e' la risorsa stessa,
|
|
14758
14882
|
* e la mutazione va dentro `updateResource` perche' e' lì che il documento viene rimpiazzato.
|
|
14759
14883
|
*/
|
|
14760
|
-
onFiltersChanged(
|
|
14884
|
+
onFiltersChanged(mutate) {
|
|
14885
|
+
const reference = this.reference();
|
|
14761
14886
|
this.store.updateResource(reference.collection, reference.index, (resource) => {
|
|
14762
14887
|
mutate(resource);
|
|
14763
14888
|
});
|
|
14764
14889
|
}
|
|
14890
|
+
setValue(value) {
|
|
14891
|
+
const reference = this.reference();
|
|
14892
|
+
this.store.updateResource(reference.collection, reference.index, (resource) => {
|
|
14893
|
+
if (value) {
|
|
14894
|
+
resource['value'] = value;
|
|
14895
|
+
}
|
|
14896
|
+
else {
|
|
14897
|
+
delete resource['value'];
|
|
14898
|
+
}
|
|
14899
|
+
});
|
|
14900
|
+
}
|
|
14901
|
+
setDataType(dataType) {
|
|
14902
|
+
const reference = this.reference();
|
|
14903
|
+
this.store.updateResource(reference.collection, reference.index, (resource) => {
|
|
14904
|
+
resource['dataType'] = dataType;
|
|
14905
|
+
if (!this.dictionaries.requiresObjectType(dataType)) {
|
|
14906
|
+
delete resource['objectType'];
|
|
14907
|
+
}
|
|
14908
|
+
// `scale` si applica solo ai numerici: altrove e' `SCALE_NOT_APPLICABLE`.
|
|
14909
|
+
if (!this.dictionaries.supportsScale(dataType)) {
|
|
14910
|
+
delete resource['scale'];
|
|
14911
|
+
}
|
|
14912
|
+
// Una `Structure` non ha forma letterale: un `value` rimasto lì non significherebbe
|
|
14913
|
+
// niente per il runtime e sarebbe solo rumore nel documento (§4.7).
|
|
14914
|
+
if (this.dictionaries.isStructure(dataType)) {
|
|
14915
|
+
delete resource['value'];
|
|
14916
|
+
}
|
|
14917
|
+
});
|
|
14918
|
+
}
|
|
14765
14919
|
// -------------------------------------------------------------------------
|
|
14766
14920
|
// Dynamic choice set (§5.2): tre sorgenti che si escludono a vicenda
|
|
14767
14921
|
// -------------------------------------------------------------------------
|
|
14768
|
-
/**
|
|
14922
|
+
/**
|
|
14923
|
+
* La sorgente scelta per un set che ancora non ne dichiara nessuna. La chiave e'
|
|
14924
|
+
* `collection:index` e non la risorsa: l'oggetto viene ricostruito a ogni ricalcolo del
|
|
14925
|
+
* documento, e la dialog puo' passare da una risorsa all'altra senza essere ricreata.
|
|
14926
|
+
*/
|
|
14769
14927
|
pendingSource = signal({}, ...(ngDevMode ? [{ debugName: "pendingSource" }] : []));
|
|
14928
|
+
key() {
|
|
14929
|
+
const reference = this.reference();
|
|
14930
|
+
return `${reference.collection}:${reference.index}`;
|
|
14931
|
+
}
|
|
14770
14932
|
/**
|
|
14771
14933
|
* La sorgente dichiarata vince sempre: e' cio' che c'e' nel documento. Finche' non c'e' niente
|
|
14772
|
-
* conta la scelta fatta nel
|
|
14934
|
+
* conta la scelta fatta nel form, e in mancanza si apre sulla query — la sorgente storica.
|
|
14773
14935
|
*/
|
|
14774
|
-
choiceSetSource(
|
|
14775
|
-
const declared = choiceSetSourceOf(
|
|
14936
|
+
choiceSetSource() {
|
|
14937
|
+
const declared = choiceSetSourceOf(this.resource());
|
|
14776
14938
|
if (declared) {
|
|
14777
14939
|
return declared;
|
|
14778
14940
|
}
|
|
14779
|
-
return this.pendingSource()[
|
|
14941
|
+
return this.pendingSource()[this.key()] ?? 'object';
|
|
14780
14942
|
}
|
|
14781
14943
|
/**
|
|
14782
14944
|
* Cambiare sorgente cancella le altre — dichiararne due e' `CHOICE_SET_SOURCE_AMBIGUOUS` — e
|
|
@@ -14784,11 +14946,9 @@ class ResourcePanelComponent {
|
|
|
14784
14946
|
* proprieta' di un valore di enum, quindi lasciarli lì trasformerebbe un cambio di sorgente in
|
|
14785
14947
|
* tre `FIELD_UNKNOWN` che l'utente non ha scritto.
|
|
14786
14948
|
*/
|
|
14787
|
-
setChoiceSetSource(
|
|
14788
|
-
this.pendingSource.update((map) => ({
|
|
14789
|
-
|
|
14790
|
-
[`${reference.collection}:${reference.index}`]: source,
|
|
14791
|
-
}));
|
|
14949
|
+
setChoiceSetSource(source) {
|
|
14950
|
+
this.pendingSource.update((map) => ({ ...map, [this.key()]: source }));
|
|
14951
|
+
const reference = this.reference();
|
|
14792
14952
|
this.store.updateResource(reference.collection, reference.index, (resource) => {
|
|
14793
14953
|
for (const field of otherSourceFieldsOf(source)) {
|
|
14794
14954
|
delete resource[field];
|
|
@@ -14805,15 +14965,15 @@ class ResourcePanelComponent {
|
|
|
14805
14965
|
});
|
|
14806
14966
|
}
|
|
14807
14967
|
/** Su un choice set da enum il tipo e' `enumType`: il campo generico sarebbe un doppione. */
|
|
14808
|
-
hidesObjectType(
|
|
14809
|
-
return
|
|
14968
|
+
hidesObjectType() {
|
|
14969
|
+
return this.collection() === 'dynamicChoiceSets' && this.choiceSetSource() === 'enum';
|
|
14810
14970
|
}
|
|
14811
|
-
isChoiceSetSource(
|
|
14812
|
-
return this.choiceSetSource(
|
|
14971
|
+
isChoiceSetSource(source) {
|
|
14972
|
+
return this.choiceSetSource() === source;
|
|
14813
14973
|
}
|
|
14814
14974
|
/** Piu' di una sorgente nel documento: il runtime ne usa una sola (§5.2). */
|
|
14815
|
-
ambiguousChoiceSetSource(
|
|
14816
|
-
return declaredChoiceSetSources(
|
|
14975
|
+
ambiguousChoiceSetSource() {
|
|
14976
|
+
return declaredChoiceSetSources(this.resource()).length > 1;
|
|
14817
14977
|
}
|
|
14818
14978
|
/**
|
|
14819
14979
|
* §5.2 — i campi citabili di un choice set da enum. Le etichette le dice il dizionario; il
|
|
@@ -14835,114 +14995,67 @@ class ResourcePanelComponent {
|
|
|
14835
14995
|
}));
|
|
14836
14996
|
}, ...(ngDevMode ? [{ debugName: "enumChoiceSetFields" }] : []));
|
|
14837
14997
|
/** Fuori dalle tre proprieta' di un valore di enum: `FIELD_UNKNOWN`. */
|
|
14838
|
-
unknownEnumChoiceSetField(
|
|
14839
|
-
return isUnknownEnumChoiceSetField(this.string(
|
|
14998
|
+
unknownEnumChoiceSetField(field) {
|
|
14999
|
+
return isUnknownEnumChoiceSetField(this.string(field));
|
|
14840
15000
|
}
|
|
14841
15001
|
/**
|
|
14842
15002
|
* §5.2 — qui l'elenco dei tipi e' **autorevole** quando non e' vuoto: un `enumType` che il
|
|
14843
15003
|
* catalogo non conosce e' un errore, non l'avviso che si darebbe su un catalogo di campi.
|
|
14844
15004
|
*/
|
|
14845
|
-
unknownEnumType(
|
|
14846
|
-
const declared = this.string(
|
|
15005
|
+
unknownEnumType() {
|
|
15006
|
+
const declared = this.string('enumType');
|
|
14847
15007
|
if (!declared || !this.enumOptions().length) {
|
|
14848
15008
|
return false;
|
|
14849
15009
|
}
|
|
14850
15010
|
return !this.enumOptions().some((entry) => entry.name === declared);
|
|
14851
15011
|
}
|
|
14852
|
-
|
|
14853
|
-
|
|
14854
|
-
|
|
14855
|
-
|
|
14856
|
-
|
|
14857
|
-
resource['value'] = value;
|
|
14858
|
-
}
|
|
14859
|
-
else {
|
|
14860
|
-
delete resource['value'];
|
|
14861
|
-
}
|
|
14862
|
-
});
|
|
14863
|
-
}
|
|
14864
|
-
setDataType(reference, dataType) {
|
|
14865
|
-
this.store.updateResource(reference.collection, reference.index, (resource) => {
|
|
14866
|
-
resource['dataType'] = dataType;
|
|
14867
|
-
if (!this.dictionaries.requiresObjectType(dataType)) {
|
|
14868
|
-
delete resource['objectType'];
|
|
14869
|
-
}
|
|
14870
|
-
// `scale` si applica solo ai numerici: altrove e' `SCALE_NOT_APPLICABLE`.
|
|
14871
|
-
if (!this.dictionaries.supportsScale(dataType)) {
|
|
14872
|
-
delete resource['scale'];
|
|
14873
|
-
}
|
|
14874
|
-
// Una `Structure` non ha forma letterale: un `value` rimasto lì non significherebbe
|
|
14875
|
-
// niente per il runtime e sarebbe solo rumore nel documento (§4.7).
|
|
14876
|
-
if (this.dictionaries.isStructure(dataType)) {
|
|
14877
|
-
delete resource['value'];
|
|
14878
|
-
}
|
|
14879
|
-
});
|
|
14880
|
-
}
|
|
14881
|
-
requiresObjectType(reference) {
|
|
14882
|
-
return this.dictionaries.requiresObjectType(reference.resource['dataType']);
|
|
15012
|
+
// -------------------------------------------------------------------------
|
|
15013
|
+
// Tipo della risorsa
|
|
15014
|
+
// -------------------------------------------------------------------------
|
|
15015
|
+
requiresObjectType() {
|
|
15016
|
+
return this.dictionaries.requiresObjectType(this.resource()['dataType']);
|
|
14883
15017
|
}
|
|
14884
|
-
supportsScale(
|
|
14885
|
-
return this.dictionaries.supportsScale(
|
|
15018
|
+
supportsScale() {
|
|
15019
|
+
return this.dictionaries.supportsScale(this.resource()['dataType']);
|
|
14886
15020
|
}
|
|
14887
|
-
isEnum(
|
|
14888
|
-
return
|
|
15021
|
+
isEnum() {
|
|
15022
|
+
return this.resource()['dataType'] === 'Enum';
|
|
14889
15023
|
}
|
|
14890
15024
|
/** §4.7 — un'istanza di classe: `objectType` e' il nome della classe, non di un'entita'. */
|
|
14891
|
-
isStructure(
|
|
14892
|
-
return this.dictionaries.isStructure(
|
|
15025
|
+
isStructure() {
|
|
15026
|
+
return this.dictionaries.isStructure(this.resource()['dataType']);
|
|
14893
15027
|
}
|
|
14894
15028
|
/**
|
|
14895
15029
|
* §4.7 — una `Structure` non ha una forma letterale: nasce dal runtime come istanza vuota e i
|
|
14896
15030
|
* membri si assegnano uno alla volta. Mostrare un campo «valore iniziale» prometterebbe una
|
|
14897
15031
|
* cosa che il modello non sa esprimere.
|
|
14898
15032
|
*/
|
|
14899
|
-
supportsInitialValue(
|
|
14900
|
-
return !this.isStructure(
|
|
15033
|
+
supportsInitialValue() {
|
|
15034
|
+
return !this.isStructure();
|
|
14901
15035
|
}
|
|
14902
15036
|
/** Su una `Structure` l'`objectType` mancante e' un errore, non un avviso (§4.7). */
|
|
14903
|
-
missingObjectType(
|
|
14904
|
-
return this.isStructure(
|
|
14905
|
-
}
|
|
14906
|
-
/** Una costante che referenzia una risorsa e' `CONSTANT_REFERENCES_RESOURCE`. */
|
|
14907
|
-
constantReferencesResource(reference) {
|
|
14908
|
-
if (reference.collection !== 'constants') {
|
|
14909
|
-
return false;
|
|
14910
|
-
}
|
|
14911
|
-
const value = reference.resource['value'];
|
|
14912
|
-
return !!value?.elementReference || !!value?.formulaExpression;
|
|
14913
|
-
}
|
|
14914
|
-
/** `stageOrder` duplicati: quale stage sia il corrente all'avvio diventa arbitrario. */
|
|
14915
|
-
duplicateStageOrder(reference) {
|
|
14916
|
-
if (reference.collection !== 'stages') {
|
|
14917
|
-
return false;
|
|
14918
|
-
}
|
|
14919
|
-
const order = reference.resource['stageOrder'];
|
|
14920
|
-
if (order === undefined) {
|
|
14921
|
-
return false;
|
|
14922
|
-
}
|
|
14923
|
-
return (this.store
|
|
14924
|
-
.resources()
|
|
14925
|
-
.filter((entry) => entry.collection === 'stages' && entry.resource['stageOrder'] === order).length > 1);
|
|
15037
|
+
missingObjectType() {
|
|
15038
|
+
return this.isStructure() && !this.string('objectType');
|
|
14926
15039
|
}
|
|
14927
|
-
|
|
14928
|
-
|
|
15040
|
+
// -------------------------------------------------------------------------
|
|
15041
|
+
// Lettura
|
|
15042
|
+
// -------------------------------------------------------------------------
|
|
15043
|
+
string(field) {
|
|
15044
|
+
const value = this.resource()[field];
|
|
14929
15045
|
return value === undefined || value === null ? '' : String(value);
|
|
14930
15046
|
}
|
|
14931
|
-
boolean(
|
|
14932
|
-
return
|
|
15047
|
+
boolean(field) {
|
|
15048
|
+
return this.resource()[field] === true;
|
|
14933
15049
|
}
|
|
14934
|
-
value(
|
|
14935
|
-
return
|
|
14936
|
-
}
|
|
14937
|
-
close() {
|
|
14938
|
-
this.closed.emit();
|
|
15050
|
+
value() {
|
|
15051
|
+
return this.resource()['value'];
|
|
14939
15052
|
}
|
|
14940
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.28", ngImport: i0, type:
|
|
14941
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.28", type: ResourcePanelComponent, isStandalone: true, selector: "fb-resource-panel", inputs: { target: { classPropertyName: "target", publicName: "target", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { closed: "closed" }, ngImport: i0, template: "<header class=\"fb-res__header\">\r\n <h2 class=\"fb-res__title\">Risorse</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<nav class=\"fb-res__tabs\" aria-label=\"Tipi di risorsa\">\r\n @for (kind of kinds; track kind.collection) {\r\n <button\r\n type=\"button\"\r\n class=\"fb-res__tab\"\r\n [class.fb-res__tab--active]=\"activeCollection() === kind.collection\"\r\n (click)=\"select(kind.collection)\"\r\n >\r\n {{ kind.label }}\r\n <span class=\"fb-res__count\">{{ countOf(kind.collection) }}</span>\r\n </button>\r\n }\r\n</nav>\r\n\r\n<div class=\"fb-res__body\">\r\n <p class=\"fb-section__note\">{{ activeKind().note }}</p>\r\n\r\n <div class=\"fb-list\">\r\n @for (item of items(); track item.collection + ':' + item.index) {\r\n <div class=\"fb-list__item\">\r\n <div class=\"fb-list__header\">\r\n <button type=\"button\" class=\"fb-res__name\" (click)=\"toggleEdit($index)\">\r\n <span class=\"fb-res__name-text\">{{ item.name || '(senza nome)' }}</span>\r\n <span class=\"fb-res__meta\">\r\n {{ string(item, 'dataType') }}{{ boolean(item, 'isCollection') ? '[]' : '' }}\r\n @if (boolean(item, 'isInput')) {\r\n \u00B7 input\r\n }\r\n @if (boolean(item, 'isOutput')) {\r\n \u00B7 output\r\n }\r\n </span>\r\n </button>\r\n <span class=\"fb-list__spacer\"></span>\r\n <!--\r\n Il numero di usi sta accanto al nome perche\u2019 e\u2019 cio\u2019 che decide se si puo\u2019 togliere:\r\n \u00ABnon usata\u00BB e\u2019 un invito a fare pulizia, \u00ABusata 15 volte\u00BB e\u2019 un avvertimento.\r\n -->\r\n @let uses = usesOf(item);\r\n @if (uses === 0) {\r\n <span class=\"fb-res__uses fb-res__uses--none\" title=\"Nessun riferimento la usa: si puo\u2019 togliere\">\r\n non usata\r\n </span>\r\n } @else {\r\n <span\r\n class=\"fb-res__uses\"\r\n [title]=\"'Referenziata ' + uses + ' volte nel documento, formule comprese'\"\r\n >\r\n {{ uses }} usi\r\n </span>\r\n }\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-btn--ghost fb-btn--icon\"\r\n aria-label=\"Rimuovi la risorsa\"\r\n [title]=\"uses ? 'Rimuovi: e\u2019 usata ' + uses + ' volte, verra\u2019 chiesta conferma' : 'Rimuovi'\"\r\n (click)=\"requestRemove(item)\"\r\n >\r\n \u00D7\r\n </button>\r\n </div>\r\n\r\n @if (isPendingRemoval(item)) {\r\n <!--\r\n La conferma sta qui e non in una finestra: la riga che si sta togliendo resta sotto\r\n gli occhi, e il numero di riferimenti che si rompono e\u2019 il dato della decisione.\r\n -->\r\n <p class=\"fb-callout fb-callout--warn fb-res__confirm\">\r\n <span>\r\n \u00AB{{ item.name }}\u00BB e\u2019 usata {{ usesOf(item) }} volte: togliendola quei riferimenti\r\n restano orfani e la validazione li segnalera\u2019.\r\n </span>\r\n <button type=\"button\" class=\"fb-btn fb-btn--icon\" (click)=\"remove(item)\">Rimuovi comunque</button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--ghost fb-btn--icon\" (click)=\"cancelRemove()\">\r\n Annulla\r\n </button>\r\n </p>\r\n }\r\n\r\n @if (nameError(item)) {\r\n <p class=\"fb-field__error\">{{ nameError(item) }}</p>\r\n }\r\n @if (constantReferencesResource(item)) {\r\n <p class=\"fb-callout fb-callout--error\">\r\n Una costante deve essere un valore fisso: non puo\u2019 referenziare altre risorse\r\n (CONSTANT_REFERENCES_RESOURCE).\r\n </p>\r\n }\r\n @if (duplicateStageOrder(item)) {\r\n <p class=\"fb-callout fb-callout--warn\">\r\n Due stage con lo stesso ordine: quale sia il corrente all\u2019avvio diventa arbitrario\r\n (STAGE_ORDER_DUPLICATED).\r\n </p>\r\n }\r\n\r\n @if (editingIndex() === $index) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Nome</label>\r\n <input\r\n class=\"fb-input fb-input--mono\"\r\n [value]=\"item.name\"\r\n (change)=\"rename(item, $any($event.target).value)\"\r\n />\r\n <p class=\"fb-field__hint\">\r\n Il nome vive nello stesso spazio dei nomi degli elementi. Rinominare riscrive i riferimenti.\r\n </p>\r\n </div>\r\n\r\n @if (item.collection === 'stages') {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Etichetta</label>\r\n <input\r\n class=\"fb-input\"\r\n [value]=\"string(item, 'label')\"\r\n (input)=\"setField(item, 'label', $any($event.target).value)\"\r\n />\r\n </div>\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Ordine</label>\r\n <input\r\n class=\"fb-input\"\r\n type=\"number\"\r\n min=\"1\"\r\n [value]=\"string(item, 'stageOrder')\"\r\n (input)=\"setNumberField(item, 'stageOrder', $any($event.target).value)\"\r\n />\r\n </div>\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"boolean(item, 'isActive')\"\r\n (change)=\"setBooleanField(item, 'isActive', $any($event.target).checked)\"\r\n />\r\n Attivo all\u2019avvio\r\n </label>\r\n <p class=\"fb-field__hint\">\r\n All\u2019avvio lo stage corrente e\u2019 il primo attivo per ordine. Si avanza con un Assignment su\r\n <code>$Flow.CurrentStage</code>.\r\n </p>\r\n }\r\n\r\n @if (item.collection === 'textTemplates') {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Testo</label>\r\n <textarea\r\n class=\"fb-textarea\"\r\n [value]=\"string(item, 'text')\"\r\n placeholder=\"Gentile {!Cliente.Nome},\"\r\n (input)=\"setField(item, 'text', $any($event.target).value)\"\r\n ></textarea>\r\n </div>\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"boolean(item, 'isViewedAsPlainText')\"\r\n (change)=\"setBooleanField(item, 'isViewedAsPlainText', $any($event.target).checked)\"\r\n />\r\n Testo semplice\r\n </label>\r\n }\r\n\r\n @if (item.collection !== 'textTemplates' && item.collection !== 'stages') {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Tipo</label>\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"string(item, 'dataType')\"\r\n (change)=\"setDataType(item, $any($event.target).value)\"\r\n >\r\n @for (type of dataTypes(); track type.value) {\r\n <option [value]=\"type.value\">{{ type.label }}</option>\r\n }\r\n </select>\r\n @if (item.collection === 'dynamicChoiceSets') {\r\n <!--\r\n \u00A75.2 \u2014 il valore di un'opzione **generata** non lo scrive l'autore del flow: lo produce\r\n la sorgente, nel tipo in cui il sistema ospite lo tiene, e `dataType` decide il tipo in\r\n cui viaggia. La regola e' generale \u2014 vale per tutte e tre le sorgenti \u2014 e si vede su un\r\n oggetto la cui chiave e' un enum, dove nella tendina serve di norma il numero.\r\n -->\r\n <p class=\"fb-field__hint\">\r\n Decide il tipo in cui viaggia il valore dell\u2019opzione, con qualunque sorgente:\r\n <code>String</code> il nome come testo, <code>Integer</code> o <code>Number</code> il\r\n numero sottostante, <code>Enum</code> il valore tipizzato \u2014 che e\u2019 cio\u2019 che serve in un\r\n parametro o in un membro di classe <code>Enum</code>. Una conversione impossibile non fa\r\n fallire niente: resta il valore prodotto dalla sorgente.\r\n </p>\r\n }\r\n </div>\r\n\r\n @if (requiresObjectType(item) && !hidesObjectType(item)) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">\r\n @if (isEnum(item)) {\r\n Tipo di enumerazione\r\n } @else if (isStructure(item)) {\r\n Classe\r\n } @else {\r\n Oggetto\r\n }\r\n </label>\r\n @if (isEnum(item)) {\r\n <!-- Dizionario chiuso: qui la scrittura libera non serve. -->\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"string(item, 'objectType')\"\r\n (change)=\"setField(item, 'objectType', $any($event.target).value)\"\r\n >\r\n <option value=\"\">\u2014 scegli \u2014</option>\r\n @for (entry of enumOptions(); track entry.name) {\r\n <option [value]=\"entry.name\">{{ entry.label || entry.name }}</option>\r\n }\r\n </select>\r\n } @else if (isStructure(item)) {\r\n <!-- \u00A74.7: una classe del backend, non un'entita' del modello dati. -->\r\n <fb-structure-picker\r\n [value]=\"string(item, 'objectType') || undefined\"\r\n label=\"Classe\"\r\n (valueChange)=\"setField(item, 'objectType', $event ?? '')\"\r\n />\r\n } @else {\r\n <fb-object-picker\r\n [value]=\"string(item, 'objectType') || undefined\"\r\n label=\"Oggetto\"\r\n placeholder=\"Scrivi o scegli un oggetto\"\r\n (valueChange)=\"setField(item, 'objectType', $event ?? '')\"\r\n />\r\n }\r\n @if (missingObjectType(item)) {\r\n <!-- Su una Structure non e' un avviso: senza classe non c'e' nulla da istanziare. -->\r\n <p class=\"fb-field__error\">\r\n La classe e\u2019 obbligatoria: senza, l\u2019attivazione e\u2019 bloccata (OBJECT_TYPE_MISSING).\r\n </p>\r\n } @else if (isStructure(item)) {\r\n <p class=\"fb-field__hint\">\r\n Il flow ne legge e scrive i <strong>membri</strong> (<code>Nome.Membro</code>): non si\r\n interroga con Get Records ne\u2019 si salva con Create/Update.\r\n </p>\r\n } @else {\r\n <p class=\"fb-field__hint\">Obbligatorio per Object ed Enum (OBJECT_TYPE_MISSING).</p>\r\n }\r\n </div>\r\n }\r\n\r\n @if (supportsScale(item)) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Decimali</label>\r\n <input\r\n class=\"fb-input\"\r\n type=\"number\"\r\n min=\"0\"\r\n [value]=\"string(item, 'scale')\"\r\n (input)=\"setNumberField(item, 'scale', $any($event.target).value)\"\r\n />\r\n </div>\r\n }\r\n }\r\n\r\n @if (item.collection === 'variables') {\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"boolean(item, 'isCollection')\"\r\n (change)=\"setBooleanField(item, 'isCollection', $any($event.target).checked)\"\r\n />\r\n \u00C8 una collection\r\n </label>\r\n <p class=\"fb-field__hint\">Solo una collection puo\u2019 essere iterata da un Loop.</p>\r\n\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"boolean(item, 'isInput')\"\r\n (change)=\"setBooleanField(item, 'isInput', $any($event.target).checked)\"\r\n />\r\n Valorizzabile all\u2019avvio (input)\r\n </label>\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"boolean(item, 'isOutput')\"\r\n (change)=\"setBooleanField(item, 'isOutput', $any($event.target).checked)\"\r\n />\r\n Leggibile alla fine (output)\r\n </label>\r\n <p class=\"fb-field__hint\">\r\n Input e output sono il contratto del flow verso chi lo invoca, subflow compresi.\r\n </p>\r\n }\r\n\r\n @if (item.collection === 'formulas') {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Espressione</label>\r\n <!--\r\n Il tipo atteso e' quello che la risorsa dichiara, e `scale` va con lui: sono cio'\r\n che permette al motore di dire che l'espressione produce altro (\u00A76.3).\r\n -->\r\n <fb-formula-editor\r\n [expression]=\"string(item, 'expression')\"\r\n usage=\"Resource\"\r\n [expectedDataType]=\"$any(item.resource['dataType'])\"\r\n [scale]=\"$any(item.resource['scale'])\"\r\n placeholder=\"Importo * 1.22\"\r\n ariaLabel=\"Espressione della formula\"\r\n (expressionChange)=\"setField(item, 'expression', $event)\"\r\n >\r\n <p class=\"fb-field__hint\">\r\n Passata verbatim al motore di regole: la sintassi delle funzioni e\u2019 del motore.\r\n </p>\r\n </fb-formula-editor>\r\n </div>\r\n }\r\n\r\n @if (item.collection === 'choices') {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Testo mostrato</label>\r\n <input\r\n class=\"fb-input\"\r\n [value]=\"string(item, 'choiceText')\"\r\n (input)=\"setField(item, 'choiceText', $any($event.target).value)\"\r\n />\r\n </div>\r\n }\r\n\r\n @if (item.collection === 'dynamicChoiceSets') {\r\n <!--\r\n \u00A75.2 \u2014 le sorgenti sono tre e si escludono a vicenda: i pulsanti le rendono\r\n mutuamente esclusive nel documento, non solo nel form.\r\n -->\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Da dove arrivano le opzioni</label>\r\n <div class=\"fb-filter__modes\" role=\"group\" aria-label=\"Sorgente delle opzioni\">\r\n <button\r\n type=\"button\"\r\n class=\"fb-filter__mode\"\r\n [class.fb-filter__mode--active]=\"isChoiceSetSource(item, 'collection')\"\r\n (click)=\"setChoiceSetSource(item, 'collection')\"\r\n >\r\n Collection del flow\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-filter__mode\"\r\n [class.fb-filter__mode--active]=\"isChoiceSetSource(item, 'object')\"\r\n (click)=\"setChoiceSetSource(item, 'object')\"\r\n >\r\n Query su un oggetto\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-filter__mode\"\r\n [class.fb-filter__mode--active]=\"isChoiceSetSource(item, 'enum')\"\r\n (click)=\"setChoiceSetSource(item, 'enum')\"\r\n >\r\n Valori di un enum\r\n </button>\r\n </div>\r\n </div>\r\n\r\n @if (ambiguousChoiceSetSource(item)) {\r\n <p class=\"fb-callout fb-callout--warn\">\r\n Questo choice set dichiara piu\u2019 di una sorgente: il runtime ne usa una sola\r\n (CHOICE_SET_SOURCE_AMBIGUOUS). Scegli quella giusta qui sopra: le altre vengono tolte.\r\n </p>\r\n }\r\n\r\n @if (isChoiceSetSource(item, 'collection')) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Collection</label>\r\n <fb-reference-picker\r\n [value]=\"string(item, 'collectionReference')\"\r\n [isCollection]=\"true\"\r\n placeholder=\"Scegli una collection\"\r\n (valueChange)=\"setField(item, 'collectionReference', $event ?? '')\"\r\n />\r\n <p class=\"fb-field__hint\">\r\n Tipicamente il risultato di un Get Records: le opzioni sono i record che il flow ha gi\u00E0\r\n letto.\r\n </p>\r\n </div>\r\n }\r\n\r\n @if (isChoiceSetSource(item, 'object')) {\r\n <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]=\"string(item, 'object') || undefined\"\r\n placeholder=\"Scrivi o scegli un oggetto\"\r\n (valueChange)=\"setField(item, 'object', $event ?? '')\"\r\n />\r\n </div>\r\n }\r\n\r\n @if (isChoiceSetSource(item, 'enum')) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Tipo di enumerazione</label>\r\n <!-- Dizionario chiuso, e qui e' anche **autorevole**: un tipo fuori elenco e' un errore. -->\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"string(item, 'enumType')\"\r\n (change)=\"setField(item, 'enumType', $any($event.target).value)\"\r\n >\r\n <option value=\"\">\u2014 scegli \u2014</option>\r\n @for (entry of enumOptions(); track entry.name) {\r\n <option [value]=\"entry.name\">{{ entry.label || entry.name }}</option>\r\n }\r\n </select>\r\n @if (unknownEnumType(item)) {\r\n <p class=\"fb-field__error\">\r\n Il tipo \u00AB{{ string(item, 'enumType') }}\u00BB non e\u2019 fra quelli utilizzabili\r\n (ENUM_TYPE_UNKNOWN).\r\n </p>\r\n } @else {\r\n <p class=\"fb-field__hint\">\r\n Le opzioni sono i valori dichiarati dal tipo: nessuna choice da tenere allineata a mano.\r\n </p>\r\n }\r\n </div>\r\n }\r\n\r\n @if (isChoiceSetSource(item, 'enum')) {\r\n <!--\r\n \u00A75.2 \u2014 qui i campi citabili sono le tre proprieta' di un valore di enum, non i campi\r\n di un'entita': l'elenco viene dal dizionario, e i default rendono superfluo dichiararli.\r\n -->\r\n <div class=\"fb-field__row\">\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Campo mostrato</label>\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"string(item, 'displayField')\"\r\n (change)=\"setField(item, 'displayField', $any($event.target).value)\"\r\n >\r\n <option value=\"\">Predefinito ({{ defaultDisplayField }})</option>\r\n @for (field of enumChoiceSetFields(); track field.value) {\r\n <option [value]=\"field.value\">{{ field.label }}</option>\r\n }\r\n </select>\r\n @if (unknownEnumChoiceSetField(item, 'displayField')) {\r\n <p class=\"fb-field__error\">\r\n \u00AB{{ string(item, 'displayField') }}\u00BB non e\u2019 una proprieta\u2019 di un valore di enum\r\n (FIELD_UNKNOWN).\r\n </p>\r\n }\r\n </div>\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Campo del valore</label>\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"string(item, 'valueField')\"\r\n (change)=\"setField(item, 'valueField', $any($event.target).value)\"\r\n >\r\n <option value=\"\">Predefinito ({{ defaultValueField }})</option>\r\n @for (field of enumChoiceSetFields(); track field.value) {\r\n <option [value]=\"field.value\">{{ field.label }}</option>\r\n }\r\n </select>\r\n @if (unknownEnumChoiceSetField(item, 'valueField')) {\r\n <p class=\"fb-field__error\">\r\n \u00AB{{ string(item, 'valueField') }}\u00BB non e\u2019 una proprieta\u2019 di un valore di enum\r\n (FIELD_UNKNOWN).\r\n </p>\r\n }\r\n </div>\r\n </div>\r\n <p class=\"fb-field__hint\">\r\n Dichiarare <code>Integer</code> come tipo della risorsa e\u2019 la stessa cosa che scegliere qui\r\n \u00ABValore numerico\u00BB: il valore dell\u2019opzione e\u2019 il numero sottostante.\r\n </p>\r\n } @else {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Campo mostrato</label>\r\n <fb-field-picker\r\n [value]=\"string(item, 'displayField') || undefined\"\r\n [object]=\"string(item, 'object') || undefined\"\r\n usage=\"any\"\r\n label=\"Campo mostrato\"\r\n (valueChange)=\"setField(item, 'displayField', $event ?? '')\"\r\n />\r\n </div>\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Campo del valore</label>\r\n <fb-field-picker\r\n [value]=\"string(item, 'valueField') || undefined\"\r\n [object]=\"string(item, 'object') || undefined\"\r\n usage=\"any\"\r\n label=\"Campo del valore\"\r\n (valueChange)=\"setField(item, 'valueField', $event ?? '')\"\r\n />\r\n </div>\r\n }\r\n <div class=\"fb-field__row\">\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Ordina per</label>\r\n @if (isChoiceSetSource(item, 'enum')) {\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"string(item, 'sortField')\"\r\n (change)=\"setField(item, 'sortField', $any($event.target).value)\"\r\n >\r\n <option value=\"\">Ordine di dichiarazione</option>\r\n @for (field of enumChoiceSetFields(); track field.value) {\r\n <option [value]=\"field.value\">{{ field.label }}</option>\r\n }\r\n </select>\r\n @if (unknownEnumChoiceSetField(item, 'sortField')) {\r\n <p class=\"fb-field__error\">\r\n \u00AB{{ string(item, 'sortField') }}\u00BB non e\u2019 una proprieta\u2019 di un valore di enum\r\n (FIELD_UNKNOWN).\r\n </p>\r\n }\r\n } @else {\r\n <fb-field-picker\r\n [value]=\"string(item, 'sortField') || undefined\"\r\n [object]=\"string(item, 'object') || undefined\"\r\n usage=\"sortable\"\r\n label=\"Campo di ordinamento\"\r\n placeholder=\"Nessun ordinamento\"\r\n (valueChange)=\"setField(item, 'sortField', $event ?? '')\"\r\n />\r\n }\r\n </div>\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Direzione</label>\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"string(item, 'sortOrder')\"\r\n (change)=\"setField(item, 'sortOrder', $any($event.target).value)\"\r\n >\r\n <option value=\"\">\u2014</option>\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 </div>\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Numero massimo</label>\r\n <input\r\n class=\"fb-input\"\r\n type=\"number\"\r\n min=\"1\"\r\n [value]=\"string(item, 'limit')\"\r\n (input)=\"setNumberField(item, 'limit', $any($event.target).value)\"\r\n />\r\n </div>\r\n <!--\r\n `supportsLogic` a false: qui `filterLogic` non esiste nel modello, e inviarlo lo\r\n perderebbe in silenzio al primo salvataggio (\u00A74.4). Nessun `emptyWarning`: senza\r\n filtri il set legge tutti i record dell\u2019oggetto, che qui e\u2019 un uso legittimo.\r\n Su un enum i filtri li valuta il runtime **in memoria**, e i campi sono le tre\r\n proprieta\u2019 di un valore: `fieldOptions` e\u2019 cio\u2019 che sostituisce il catalogo (\u00A75.2).\r\n -->\r\n <fb-record-filter-editor\r\n [holder]=\"$any(item.resource)\"\r\n [object]=\"isChoiceSetSource(item, 'enum') ? undefined : string(item, 'object') || undefined\"\r\n [fieldOptions]=\"isChoiceSetSource(item, 'enum') ? enumChoiceSetFields() : []\"\r\n [title]=\"\r\n isChoiceSetSource(item, 'enum') ? 'Quali valori diventano opzioni' : 'Quali record diventano opzioni'\r\n \"\r\n usage=\"filterable\"\r\n [supportsLogic]=\"false\"\r\n (changed)=\"onFiltersChanged(item, $event)\"\r\n />\r\n }\r\n\r\n @if (isStructure(item) && item.collection === 'variables') {\r\n <p class=\"fb-field__hint\">\r\n Non serve un valore iniziale: la variabile parte con un\u2019istanza vuota, e il flow ne assegna i\r\n membri uno alla volta con un Assignment.\r\n </p>\r\n }\r\n\r\n @if (\r\n supportsInitialValue(item) &&\r\n (item.collection === 'variables' || item.collection === 'constants' || item.collection === 'choices')\r\n ) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">\r\n {{ item.collection === 'variables' ? 'Valore iniziale' : 'Valore' }}\r\n </label>\r\n <fb-value-editor\r\n [value]=\"value(item)\"\r\n [dataType]=\"$any(item.resource['dataType'])\"\r\n [objectType]=\"$any(item.resource['objectType'])\"\r\n [isCollection]=\"boolean(item, 'isCollection')\"\r\n [allowFormula]=\"item.collection !== 'constants'\"\r\n label=\"Valore\"\r\n (valueChange)=\"setValue(item, $event)\"\r\n />\r\n </div>\r\n }\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Descrizione</label>\r\n <input\r\n class=\"fb-input\"\r\n [value]=\"string(item, 'description')\"\r\n (input)=\"setField(item, 'description', $any($event.target).value)\"\r\n />\r\n </div>\r\n }\r\n </div>\r\n } @empty {\r\n <p class=\"fb-empty\">Nessuna risorsa di questo tipo.</p>\r\n }\r\n </div>\r\n\r\n <button type=\"button\" class=\"fb-btn fb-btn--primary\" (click)=\"add()\">\r\n Aggiungi {{ activeKind().singular }}\r\n </button>\r\n</div>\r\n", styles: [":host{display:flex;flex-direction:column;height:100%;min-height:0;background:var(--fb-surface, #fff)}.fb-res__header{display:flex;align-items:center;justify-content:space-between;padding:10px 12px;border-bottom:1px solid var(--fb-border-subtle, #e6e9ee)}.fb-res__title{margin:0;font-size:14px;font-weight:600;color:var(--fb-text, #1d2939)}.fb-res__tabs{display:flex;flex-wrap:wrap;gap:2px;padding:6px 8px;border-bottom:1px solid var(--fb-border-subtle, #e6e9ee)}.fb-res__tab{display:inline-flex;align-items:center;gap:4px;padding:3px 8px;border:1px solid var(--fb-border-subtle, #e6e9ee);border-radius:12px;background:transparent;color:var(--fb-text-muted, #667085);font:inherit;font-size:11px;cursor:pointer}.fb-res__tab:hover{background:var(--fb-surface-alt, #f8f9fb)}.fb-res__tab--active{border-color:var(--fb-accent, #2f6feb);background:color-mix(in srgb,var(--fb-accent, #2f6feb) 10%,transparent);color:var(--fb-accent, #2f6feb);font-weight:600}.fb-res__count{padding:0 4px;border-radius:6px;background:var(--fb-border, #d6dae1);font-size:9px;color:var(--fb-text, #1d2939)}.fb-res__body{flex:1;min-height:0;overflow-y:auto;padding:10px 12px}.fb-res__name{flex:1;min-width:0;display:flex;flex-direction:column;padding:0;border:0;background:transparent;color:var(--fb-text, #1d2939);font:inherit;text-align:left;cursor:pointer}.fb-res__name-text{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fb-res__meta{font-size:10px;color:var(--fb-text-muted, #667085)}.fb-res__uses{flex:none;padding:1px 6px;border-radius:999px;background:var(--fb-surface-sunken, #eef0f4);color:var(--fb-text-muted, #6b7086);font-size:10px;font-variant-numeric:tabular-nums;white-space:nowrap}.fb-res__uses--none{background:color-mix(in srgb,var(--fb-warning, #b7791f) 12%,transparent);color:var(--fb-warning, #b7791f)}.fb-res__confirm{display:flex;flex-wrap:wrap;align-items:center;gap:6px}\n"], dependencies: [{ kind: "component", type: FieldPickerComponent, selector: "fb-field-picker", inputs: ["value", "object", "usage", "label", "placeholder", "disabled"], outputs: ["valueChange"] }, { kind: "component", type: FormulaEditorComponent, selector: "fb-formula-editor", inputs: ["expression", "usage", "expectedDataType", "scale", "placeholder", "ariaLabel", "disabled", "rows", "commitOn"], outputs: ["expressionChange"] }, { kind: "component", type: ObjectPickerComponent, selector: "fb-object-picker", inputs: ["value", "label", "placeholder", "disabled"], outputs: ["valueChange"] }, { kind: "component", type: ReferencePickerComponent, selector: "fb-reference-picker", inputs: ["value", "label", "placeholder", "disabled", "dataType", "isCollection", "objectType", "writableOnly", "elementsOnly", "extraReferences"], outputs: ["valueChange"] }, { kind: "component", type: RecordFilterEditorComponent, selector: "fb-record-filter-editor", inputs: ["holder", "object", "title", "usage", "fieldOptions", "supportsLogic", "supportsFormula", "emptyWarning", "emptyWarningSeverity"], outputs: ["changed"] }, { kind: "component", type: StructurePickerComponent, selector: "fb-structure-picker", inputs: ["value", "label", "placeholder", "disabled"], outputs: ["valueChange"] }, { kind: "component", type: ValueEditorComponent, selector: "fb-value-editor", inputs: ["value", "label", "dataType", "objectType", "isCollection", "valueSet", "disabled", "allowFormula"], outputs: ["valueChange"] }, { kind: "directive", type: SelectValueDirective, selector: "select[fbValue]", inputs: ["fbValue"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
15053
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.28", ngImport: i0, type: ResourceFormComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
15054
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.28", type: ResourceFormComponent, isStandalone: true, selector: "fb-resource-form", inputs: { reference: { classPropertyName: "reference", publicName: "reference", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: "<!--\r\n Il form di **una** risorsa: e' cio' che prima stava inline sotto la riga dell'elenco, e ora\r\n vive nella dialog. I rilievi locali stanno in cima e non accanto al campo che li produce\r\n perche' qui il form si apre gi\u00E0 puntato sulla risorsa: la domanda \u00ABcosa non va\u00BB viene prima\r\n di \u00ABdove\u00BB.\r\n-->\r\n@if (nameError(); as message) {\r\n <p class=\"fb-field__error\">{{ message }}</p>\r\n}\r\n@if (constantReferencesResource()) {\r\n <p class=\"fb-callout fb-callout--error\">\r\n Una costante deve essere un valore fisso: non puo\u2019 referenziare altre risorse\r\n (CONSTANT_REFERENCES_RESOURCE).\r\n </p>\r\n}\r\n@if (duplicateStageOrder()) {\r\n <p class=\"fb-callout fb-callout--warn\">\r\n Due stage con lo stesso ordine: quale sia il corrente all\u2019avvio diventa arbitrario\r\n (STAGE_ORDER_DUPLICATED).\r\n </p>\r\n}\r\n\r\n<div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Nome</label>\r\n <input\r\n class=\"fb-input fb-input--mono\"\r\n [value]=\"reference().name\"\r\n (change)=\"rename($any($event.target).value)\"\r\n />\r\n <p class=\"fb-field__hint\">\r\n Il nome vive nello stesso spazio dei nomi degli elementi. Rinominare riscrive i riferimenti.\r\n </p>\r\n</div>\r\n\r\n@if (collection() === 'stages') {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Etichetta</label>\r\n <input\r\n class=\"fb-input\"\r\n [value]=\"string('label')\"\r\n (input)=\"setField('label', $any($event.target).value)\"\r\n />\r\n </div>\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Ordine</label>\r\n <input\r\n class=\"fb-input\"\r\n type=\"number\"\r\n min=\"1\"\r\n [value]=\"string('stageOrder')\"\r\n (input)=\"setNumberField('stageOrder', $any($event.target).value)\"\r\n />\r\n </div>\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"boolean('isActive')\"\r\n (change)=\"setBooleanField('isActive', $any($event.target).checked)\"\r\n />\r\n Attivo all\u2019avvio\r\n </label>\r\n <p class=\"fb-field__hint\">\r\n All\u2019avvio lo stage corrente e\u2019 il primo attivo per ordine. Si avanza con un Assignment su\r\n <code>$Flow.CurrentStage</code>.\r\n </p>\r\n}\r\n\r\n@if (collection() === 'textTemplates') {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Testo</label>\r\n <textarea\r\n class=\"fb-textarea\"\r\n [value]=\"string('text')\"\r\n placeholder=\"Gentile {!Cliente.Nome},\"\r\n (input)=\"setField('text', $any($event.target).value)\"\r\n ></textarea>\r\n </div>\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"boolean('isViewedAsPlainText')\"\r\n (change)=\"setBooleanField('isViewedAsPlainText', $any($event.target).checked)\"\r\n />\r\n Testo semplice\r\n </label>\r\n}\r\n\r\n@if (collection() !== 'textTemplates' && collection() !== 'stages') {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Tipo</label>\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"string('dataType')\"\r\n (change)=\"setDataType($any($event.target).value)\"\r\n >\r\n @for (type of dataTypes(); track type.value) {\r\n <option [value]=\"type.value\">{{ type.label }}</option>\r\n }\r\n </select>\r\n @if (collection() === 'dynamicChoiceSets') {\r\n <!--\r\n \u00A75.2 \u2014 il valore di un'opzione **generata** non lo scrive l'autore del flow: lo produce\r\n la sorgente, nel tipo in cui il sistema ospite lo tiene, e `dataType` decide il tipo in\r\n cui viaggia. La regola e' generale \u2014 vale per tutte e tre le sorgenti \u2014 e si vede su un\r\n oggetto la cui chiave e' un enum, dove nella tendina serve di norma il numero.\r\n -->\r\n <p class=\"fb-field__hint\">\r\n Decide il tipo in cui viaggia il valore dell\u2019opzione, con qualunque sorgente:\r\n <code>String</code> il nome come testo, <code>Integer</code> o <code>Number</code> il\r\n numero sottostante, <code>Enum</code> il valore tipizzato \u2014 che e\u2019 cio\u2019 che serve in un\r\n parametro o in un membro di classe <code>Enum</code>. Una conversione impossibile non fa\r\n fallire niente: resta il valore prodotto dalla sorgente.\r\n </p>\r\n }\r\n </div>\r\n\r\n @if (requiresObjectType() && !hidesObjectType()) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">\r\n @if (isEnum()) {\r\n Tipo di enumerazione\r\n } @else if (isStructure()) {\r\n Classe\r\n } @else {\r\n Oggetto\r\n }\r\n </label>\r\n @if (isEnum()) {\r\n <!-- Dizionario chiuso: qui la scrittura libera non serve. -->\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"string('objectType')\"\r\n (change)=\"setField('objectType', $any($event.target).value)\"\r\n >\r\n <option value=\"\">\u2014 scegli \u2014</option>\r\n @for (entry of enumOptions(); track entry.name) {\r\n <option [value]=\"entry.name\">{{ entry.label || entry.name }}</option>\r\n }\r\n </select>\r\n } @else if (isStructure()) {\r\n <!-- \u00A74.7: una classe del backend, non un'entita' del modello dati. -->\r\n <fb-structure-picker\r\n [value]=\"string('objectType') || undefined\"\r\n label=\"Classe\"\r\n (valueChange)=\"setField('objectType', $event ?? '')\"\r\n />\r\n } @else {\r\n <fb-object-picker\r\n [value]=\"string('objectType') || undefined\"\r\n label=\"Oggetto\"\r\n placeholder=\"Scrivi o scegli un oggetto\"\r\n (valueChange)=\"setField('objectType', $event ?? '')\"\r\n />\r\n }\r\n @if (missingObjectType()) {\r\n <!-- Su una Structure non e' un avviso: senza classe non c'e' nulla da istanziare. -->\r\n <p class=\"fb-field__error\">\r\n La classe e\u2019 obbligatoria: senza, l\u2019attivazione e\u2019 bloccata (OBJECT_TYPE_MISSING).\r\n </p>\r\n } @else if (isStructure()) {\r\n <p class=\"fb-field__hint\">\r\n Il flow ne legge e scrive i <strong>membri</strong> (<code>Nome.Membro</code>): non si\r\n interroga con Get Records ne\u2019 si salva con Create/Update.\r\n </p>\r\n } @else {\r\n <p class=\"fb-field__hint\">Obbligatorio per Object ed Enum (OBJECT_TYPE_MISSING).</p>\r\n }\r\n </div>\r\n }\r\n\r\n @if (supportsScale()) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Decimali</label>\r\n <input\r\n class=\"fb-input\"\r\n type=\"number\"\r\n min=\"0\"\r\n [value]=\"string('scale')\"\r\n (input)=\"setNumberField('scale', $any($event.target).value)\"\r\n />\r\n </div>\r\n }\r\n}\r\n\r\n@if (collection() === 'variables') {\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"boolean('isCollection')\"\r\n (change)=\"setBooleanField('isCollection', $any($event.target).checked)\"\r\n />\r\n \u00C8 una collection\r\n </label>\r\n <p class=\"fb-field__hint\">Solo una collection puo\u2019 essere iterata da un Loop.</p>\r\n\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"boolean('isInput')\"\r\n (change)=\"setBooleanField('isInput', $any($event.target).checked)\"\r\n />\r\n Valorizzabile all\u2019avvio (input)\r\n </label>\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"boolean('isOutput')\"\r\n (change)=\"setBooleanField('isOutput', $any($event.target).checked)\"\r\n />\r\n Leggibile alla fine (output)\r\n </label>\r\n <p class=\"fb-field__hint\">\r\n Input e output sono il contratto del flow verso chi lo invoca, subflow compresi.\r\n </p>\r\n}\r\n\r\n@if (collection() === 'formulas') {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Espressione</label>\r\n <!--\r\n Il tipo atteso e' quello che la risorsa dichiara, e `scale` va con lui: sono cio'\r\n che permette al motore di dire che l'espressione produce altro (\u00A76.3).\r\n -->\r\n <fb-formula-editor\r\n [expression]=\"string('expression')\"\r\n usage=\"Resource\"\r\n [expectedDataType]=\"$any(resource()['dataType'])\"\r\n [scale]=\"$any(resource()['scale'])\"\r\n placeholder=\"Importo * 1.22\"\r\n ariaLabel=\"Espressione della formula\"\r\n (expressionChange)=\"setField('expression', $event)\"\r\n >\r\n <p class=\"fb-field__hint\">\r\n Passata verbatim al motore di regole: la sintassi delle funzioni e\u2019 del motore.\r\n </p>\r\n </fb-formula-editor>\r\n </div>\r\n}\r\n\r\n@if (collection() === 'choices') {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Testo mostrato</label>\r\n <input\r\n class=\"fb-input\"\r\n [value]=\"string('choiceText')\"\r\n (input)=\"setField('choiceText', $any($event.target).value)\"\r\n />\r\n </div>\r\n}\r\n\r\n@if (collection() === 'dynamicChoiceSets') {\r\n <!--\r\n \u00A75.2 \u2014 le sorgenti sono tre e si escludono a vicenda: i pulsanti le rendono\r\n mutuamente esclusive nel documento, non solo nel form.\r\n -->\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Da dove arrivano le opzioni</label>\r\n <div class=\"fb-filter__modes\" role=\"group\" aria-label=\"Sorgente delle opzioni\">\r\n <button\r\n type=\"button\"\r\n class=\"fb-filter__mode\"\r\n [class.fb-filter__mode--active]=\"isChoiceSetSource('collection')\"\r\n (click)=\"setChoiceSetSource('collection')\"\r\n >\r\n Collection del flow\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-filter__mode\"\r\n [class.fb-filter__mode--active]=\"isChoiceSetSource('object')\"\r\n (click)=\"setChoiceSetSource('object')\"\r\n >\r\n Query su un oggetto\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-filter__mode\"\r\n [class.fb-filter__mode--active]=\"isChoiceSetSource('enum')\"\r\n (click)=\"setChoiceSetSource('enum')\"\r\n >\r\n Valori di un enum\r\n </button>\r\n </div>\r\n </div>\r\n\r\n @if (ambiguousChoiceSetSource()) {\r\n <p class=\"fb-callout fb-callout--warn\">\r\n Questo choice set dichiara piu\u2019 di una sorgente: il runtime ne usa una sola\r\n (CHOICE_SET_SOURCE_AMBIGUOUS). Scegli quella giusta qui sopra: le altre vengono tolte.\r\n </p>\r\n }\r\n\r\n @if (isChoiceSetSource('collection')) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Collection</label>\r\n <fb-reference-picker\r\n [value]=\"string('collectionReference')\"\r\n [isCollection]=\"true\"\r\n placeholder=\"Scegli una collection\"\r\n (valueChange)=\"setField('collectionReference', $event ?? '')\"\r\n />\r\n <p class=\"fb-field__hint\">\r\n Tipicamente il risultato di un Get Records: le opzioni sono i record che il flow ha gi\u00E0\r\n letto.\r\n </p>\r\n </div>\r\n }\r\n\r\n @if (isChoiceSetSource('object')) {\r\n <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]=\"string('object') || undefined\"\r\n placeholder=\"Scrivi o scegli un oggetto\"\r\n (valueChange)=\"setField('object', $event ?? '')\"\r\n />\r\n </div>\r\n }\r\n\r\n @if (isChoiceSetSource('enum')) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Tipo di enumerazione</label>\r\n <!-- Dizionario chiuso, e qui e' anche **autorevole**: un tipo fuori elenco e' un errore. -->\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"string('enumType')\"\r\n (change)=\"setField('enumType', $any($event.target).value)\"\r\n >\r\n <option value=\"\">\u2014 scegli \u2014</option>\r\n @for (entry of enumOptions(); track entry.name) {\r\n <option [value]=\"entry.name\">{{ entry.label || entry.name }}</option>\r\n }\r\n </select>\r\n @if (unknownEnumType()) {\r\n <p class=\"fb-field__error\">\r\n Il tipo \u00AB{{ string('enumType') }}\u00BB non e\u2019 fra quelli utilizzabili\r\n (ENUM_TYPE_UNKNOWN).\r\n </p>\r\n } @else {\r\n <p class=\"fb-field__hint\">\r\n Le opzioni sono i valori dichiarati dal tipo: nessuna choice da tenere allineata a mano.\r\n </p>\r\n }\r\n </div>\r\n }\r\n\r\n @if (isChoiceSetSource('enum')) {\r\n <!--\r\n \u00A75.2 \u2014 qui i campi citabili sono le tre proprieta' di un valore di enum, non i campi\r\n di un'entita': l'elenco viene dal dizionario, e i default rendono superfluo dichiararli.\r\n -->\r\n <div class=\"fb-field__row\">\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Campo mostrato</label>\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"string('displayField')\"\r\n (change)=\"setField('displayField', $any($event.target).value)\"\r\n >\r\n <option value=\"\">Predefinito ({{ defaultDisplayField }})</option>\r\n @for (field of enumChoiceSetFields(); track field.value) {\r\n <option [value]=\"field.value\">{{ field.label }}</option>\r\n }\r\n </select>\r\n @if (unknownEnumChoiceSetField('displayField')) {\r\n <p class=\"fb-field__error\">\r\n \u00AB{{ string('displayField') }}\u00BB non e\u2019 una proprieta\u2019 di un valore di enum\r\n (FIELD_UNKNOWN).\r\n </p>\r\n }\r\n </div>\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Campo del valore</label>\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"string('valueField')\"\r\n (change)=\"setField('valueField', $any($event.target).value)\"\r\n >\r\n <option value=\"\">Predefinito ({{ defaultValueField }})</option>\r\n @for (field of enumChoiceSetFields(); track field.value) {\r\n <option [value]=\"field.value\">{{ field.label }}</option>\r\n }\r\n </select>\r\n @if (unknownEnumChoiceSetField('valueField')) {\r\n <p class=\"fb-field__error\">\r\n \u00AB{{ string('valueField') }}\u00BB non e\u2019 una proprieta\u2019 di un valore di enum\r\n (FIELD_UNKNOWN).\r\n </p>\r\n }\r\n </div>\r\n </div>\r\n <p class=\"fb-field__hint\">\r\n Dichiarare <code>Integer</code> come tipo della risorsa e\u2019 la stessa cosa che scegliere qui\r\n \u00ABValore numerico\u00BB: il valore dell\u2019opzione e\u2019 il numero sottostante.\r\n </p>\r\n } @else {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Campo mostrato</label>\r\n <fb-field-picker\r\n [value]=\"string('displayField') || undefined\"\r\n [object]=\"string('object') || undefined\"\r\n usage=\"any\"\r\n label=\"Campo mostrato\"\r\n (valueChange)=\"setField('displayField', $event ?? '')\"\r\n />\r\n </div>\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Campo del valore</label>\r\n <fb-field-picker\r\n [value]=\"string('valueField') || undefined\"\r\n [object]=\"string('object') || undefined\"\r\n usage=\"any\"\r\n label=\"Campo del valore\"\r\n (valueChange)=\"setField('valueField', $event ?? '')\"\r\n />\r\n </div>\r\n }\r\n <div class=\"fb-field__row\">\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Ordina per</label>\r\n @if (isChoiceSetSource('enum')) {\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"string('sortField')\"\r\n (change)=\"setField('sortField', $any($event.target).value)\"\r\n >\r\n <option value=\"\">Ordine di dichiarazione</option>\r\n @for (field of enumChoiceSetFields(); track field.value) {\r\n <option [value]=\"field.value\">{{ field.label }}</option>\r\n }\r\n </select>\r\n @if (unknownEnumChoiceSetField('sortField')) {\r\n <p class=\"fb-field__error\">\r\n \u00AB{{ string('sortField') }}\u00BB non e\u2019 una proprieta\u2019 di un valore di enum\r\n (FIELD_UNKNOWN).\r\n </p>\r\n }\r\n } @else {\r\n <fb-field-picker\r\n [value]=\"string('sortField') || undefined\"\r\n [object]=\"string('object') || undefined\"\r\n usage=\"sortable\"\r\n label=\"Campo di ordinamento\"\r\n placeholder=\"Nessun ordinamento\"\r\n (valueChange)=\"setField('sortField', $event ?? '')\"\r\n />\r\n }\r\n </div>\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Direzione</label>\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"string('sortOrder')\"\r\n (change)=\"setField('sortOrder', $any($event.target).value)\"\r\n >\r\n <option value=\"\">\u2014</option>\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 </div>\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Numero massimo</label>\r\n <input\r\n class=\"fb-input\"\r\n type=\"number\"\r\n min=\"1\"\r\n [value]=\"string('limit')\"\r\n (input)=\"setNumberField('limit', $any($event.target).value)\"\r\n />\r\n </div>\r\n <!--\r\n `supportsLogic` a false: qui `filterLogic` non esiste nel modello, e inviarlo lo\r\n perderebbe in silenzio al primo salvataggio (\u00A74.4). Nessun `emptyWarning`: senza\r\n filtri il set legge tutti i record dell\u2019oggetto, che qui e\u2019 un uso legittimo.\r\n Su un enum i filtri li valuta il runtime **in memoria**, e i campi sono le tre\r\n proprieta\u2019 di un valore: `fieldOptions` e\u2019 cio\u2019 che sostituisce il catalogo (\u00A75.2).\r\n -->\r\n <fb-record-filter-editor\r\n [holder]=\"$any(resource())\"\r\n [object]=\"isChoiceSetSource('enum') ? undefined : string('object') || undefined\"\r\n [fieldOptions]=\"isChoiceSetSource('enum') ? enumChoiceSetFields() : []\"\r\n [title]=\"\r\n isChoiceSetSource('enum') ? 'Quali valori diventano opzioni' : 'Quali record diventano opzioni'\r\n \"\r\n usage=\"filterable\"\r\n [supportsLogic]=\"false\"\r\n (changed)=\"onFiltersChanged($event)\"\r\n />\r\n}\r\n\r\n@if (isStructure() && collection() === 'variables') {\r\n <p class=\"fb-field__hint\">\r\n Non serve un valore iniziale: la variabile parte con un\u2019istanza vuota, e il flow ne assegna i\r\n membri uno alla volta con un Assignment.\r\n </p>\r\n}\r\n\r\n@if (\r\n supportsInitialValue() &&\r\n (collection() === 'variables' || collection() === 'constants' || collection() === 'choices')\r\n) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">\r\n {{ collection() === 'variables' ? 'Valore iniziale' : 'Valore' }}\r\n </label>\r\n <fb-value-editor\r\n [value]=\"value()\"\r\n [dataType]=\"$any(resource()['dataType'])\"\r\n [objectType]=\"$any(resource()['objectType'])\"\r\n [isCollection]=\"boolean('isCollection')\"\r\n [allowFormula]=\"collection() !== 'constants'\"\r\n label=\"Valore\"\r\n (valueChange)=\"setValue($event)\"\r\n />\r\n </div>\r\n}\r\n\r\n<div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Descrizione</label>\r\n <input\r\n class=\"fb-input\"\r\n [value]=\"string('description')\"\r\n (input)=\"setField('description', $any($event.target).value)\"\r\n />\r\n</div>\r\n", dependencies: [{ kind: "component", type: FieldPickerComponent, selector: "fb-field-picker", inputs: ["value", "object", "usage", "label", "placeholder", "disabled"], outputs: ["valueChange"] }, { kind: "component", type: FormulaEditorComponent, selector: "fb-formula-editor", inputs: ["expression", "usage", "expectedDataType", "scale", "placeholder", "ariaLabel", "disabled", "rows", "commitOn"], outputs: ["expressionChange"] }, { kind: "component", type: ObjectPickerComponent, selector: "fb-object-picker", inputs: ["value", "label", "placeholder", "disabled"], outputs: ["valueChange"] }, { kind: "component", type: ReferencePickerComponent, selector: "fb-reference-picker", inputs: ["value", "label", "placeholder", "disabled", "dataType", "isCollection", "objectType", "writableOnly", "elementsOnly", "extraReferences"], outputs: ["valueChange"] }, { kind: "component", type: RecordFilterEditorComponent, selector: "fb-record-filter-editor", inputs: ["holder", "object", "title", "usage", "fieldOptions", "supportsLogic", "supportsFormula", "emptyWarning", "emptyWarningSeverity"], outputs: ["changed"] }, { kind: "component", type: StructurePickerComponent, selector: "fb-structure-picker", inputs: ["value", "label", "placeholder", "disabled"], outputs: ["valueChange"] }, { kind: "component", type: ValueEditorComponent, selector: "fb-value-editor", inputs: ["value", "label", "dataType", "objectType", "isCollection", "valueSet", "disabled", "allowFormula"], outputs: ["valueChange"] }, { kind: "directive", type: SelectValueDirective, selector: "select[fbValue]", inputs: ["fbValue"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
14942
15055
|
}
|
|
14943
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.28", ngImport: i0, type:
|
|
15056
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.28", ngImport: i0, type: ResourceFormComponent, decorators: [{
|
|
14944
15057
|
type: Component,
|
|
14945
|
-
args: [{ selector: 'fb-resource-
|
|
15058
|
+
args: [{ selector: 'fb-resource-form', standalone: true, imports: [
|
|
14946
15059
|
FieldPickerComponent,
|
|
14947
15060
|
FormulaEditorComponent,
|
|
14948
15061
|
ObjectPickerComponent,
|
|
@@ -14951,8 +15064,77 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.28", ngImpo
|
|
|
14951
15064
|
StructurePickerComponent,
|
|
14952
15065
|
ValueEditorComponent,
|
|
14953
15066
|
SelectValueDirective,
|
|
14954
|
-
], changeDetection: ChangeDetectionStrategy.OnPush, template: "<header class=\"fb-res__header\">\r\n <h2 class=\"fb-res__title\">Risorse</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<nav class=\"fb-res__tabs\" aria-label=\"Tipi di risorsa\">\r\n @for (kind of kinds; track kind.collection) {\r\n <button\r\n type=\"button\"\r\n class=\"fb-res__tab\"\r\n [class.fb-res__tab--active]=\"activeCollection() === kind.collection\"\r\n (click)=\"select(kind.collection)\"\r\n >\r\n {{ kind.label }}\r\n <span class=\"fb-res__count\">{{ countOf(kind.collection) }}</span>\r\n </button>\r\n }\r\n</nav>\r\n\r\n<div class=\"fb-res__body\">\r\n <p class=\"fb-section__note\">{{ activeKind().note }}</p>\r\n\r\n <div class=\"fb-list\">\r\n @for (item of items(); track item.collection + ':' + item.index) {\r\n <div class=\"fb-list__item\">\r\n <div class=\"fb-list__header\">\r\n <button type=\"button\" class=\"fb-res__name\" (click)=\"toggleEdit($index)\">\r\n <span class=\"fb-res__name-text\">{{ item.name || '(senza nome)' }}</span>\r\n <span class=\"fb-res__meta\">\r\n {{ string(item, 'dataType') }}{{ boolean(item, 'isCollection') ? '[]' : '' }}\r\n @if (boolean(item, 'isInput')) {\r\n \u00B7 input\r\n }\r\n @if (boolean(item, 'isOutput')) {\r\n \u00B7 output\r\n }\r\n </span>\r\n </button>\r\n <span class=\"fb-list__spacer\"></span>\r\n <!--\r\n Il numero di usi sta accanto al nome perche\u2019 e\u2019 cio\u2019 che decide se si puo\u2019 togliere:\r\n \u00ABnon usata\u00BB e\u2019 un invito a fare pulizia, \u00ABusata 15 volte\u00BB e\u2019 un avvertimento.\r\n -->\r\n @let uses = usesOf(item);\r\n @if (uses === 0) {\r\n <span class=\"fb-res__uses fb-res__uses--none\" title=\"Nessun riferimento la usa: si puo\u2019 togliere\">\r\n non usata\r\n </span>\r\n } @else {\r\n <span\r\n class=\"fb-res__uses\"\r\n [title]=\"'Referenziata ' + uses + ' volte nel documento, formule comprese'\"\r\n >\r\n {{ uses }} usi\r\n </span>\r\n }\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-btn--ghost fb-btn--icon\"\r\n aria-label=\"Rimuovi la risorsa\"\r\n [title]=\"uses ? 'Rimuovi: e\u2019 usata ' + uses + ' volte, verra\u2019 chiesta conferma' : 'Rimuovi'\"\r\n (click)=\"requestRemove(item)\"\r\n >\r\n \u00D7\r\n </button>\r\n </div>\r\n\r\n @if (isPendingRemoval(item)) {\r\n <!--\r\n La conferma sta qui e non in una finestra: la riga che si sta togliendo resta sotto\r\n gli occhi, e il numero di riferimenti che si rompono e\u2019 il dato della decisione.\r\n -->\r\n <p class=\"fb-callout fb-callout--warn fb-res__confirm\">\r\n <span>\r\n \u00AB{{ item.name }}\u00BB e\u2019 usata {{ usesOf(item) }} volte: togliendola quei riferimenti\r\n restano orfani e la validazione li segnalera\u2019.\r\n </span>\r\n <button type=\"button\" class=\"fb-btn fb-btn--icon\" (click)=\"remove(item)\">Rimuovi comunque</button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--ghost fb-btn--icon\" (click)=\"cancelRemove()\">\r\n Annulla\r\n </button>\r\n </p>\r\n }\r\n\r\n @if (nameError(item)) {\r\n <p class=\"fb-field__error\">{{ nameError(item) }}</p>\r\n }\r\n @if (constantReferencesResource(item)) {\r\n <p class=\"fb-callout fb-callout--error\">\r\n Una costante deve essere un valore fisso: non puo\u2019 referenziare altre risorse\r\n (CONSTANT_REFERENCES_RESOURCE).\r\n </p>\r\n }\r\n @if (duplicateStageOrder(item)) {\r\n <p class=\"fb-callout fb-callout--warn\">\r\n Due stage con lo stesso ordine: quale sia il corrente all\u2019avvio diventa arbitrario\r\n (STAGE_ORDER_DUPLICATED).\r\n </p>\r\n }\r\n\r\n @if (editingIndex() === $index) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Nome</label>\r\n <input\r\n class=\"fb-input fb-input--mono\"\r\n [value]=\"item.name\"\r\n (change)=\"rename(item, $any($event.target).value)\"\r\n />\r\n <p class=\"fb-field__hint\">\r\n Il nome vive nello stesso spazio dei nomi degli elementi. Rinominare riscrive i riferimenti.\r\n </p>\r\n </div>\r\n\r\n @if (item.collection === 'stages') {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Etichetta</label>\r\n <input\r\n class=\"fb-input\"\r\n [value]=\"string(item, 'label')\"\r\n (input)=\"setField(item, 'label', $any($event.target).value)\"\r\n />\r\n </div>\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Ordine</label>\r\n <input\r\n class=\"fb-input\"\r\n type=\"number\"\r\n min=\"1\"\r\n [value]=\"string(item, 'stageOrder')\"\r\n (input)=\"setNumberField(item, 'stageOrder', $any($event.target).value)\"\r\n />\r\n </div>\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"boolean(item, 'isActive')\"\r\n (change)=\"setBooleanField(item, 'isActive', $any($event.target).checked)\"\r\n />\r\n Attivo all\u2019avvio\r\n </label>\r\n <p class=\"fb-field__hint\">\r\n All\u2019avvio lo stage corrente e\u2019 il primo attivo per ordine. Si avanza con un Assignment su\r\n <code>$Flow.CurrentStage</code>.\r\n </p>\r\n }\r\n\r\n @if (item.collection === 'textTemplates') {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Testo</label>\r\n <textarea\r\n class=\"fb-textarea\"\r\n [value]=\"string(item, 'text')\"\r\n placeholder=\"Gentile {!Cliente.Nome},\"\r\n (input)=\"setField(item, 'text', $any($event.target).value)\"\r\n ></textarea>\r\n </div>\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"boolean(item, 'isViewedAsPlainText')\"\r\n (change)=\"setBooleanField(item, 'isViewedAsPlainText', $any($event.target).checked)\"\r\n />\r\n Testo semplice\r\n </label>\r\n }\r\n\r\n @if (item.collection !== 'textTemplates' && item.collection !== 'stages') {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Tipo</label>\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"string(item, 'dataType')\"\r\n (change)=\"setDataType(item, $any($event.target).value)\"\r\n >\r\n @for (type of dataTypes(); track type.value) {\r\n <option [value]=\"type.value\">{{ type.label }}</option>\r\n }\r\n </select>\r\n @if (item.collection === 'dynamicChoiceSets') {\r\n <!--\r\n \u00A75.2 \u2014 il valore di un'opzione **generata** non lo scrive l'autore del flow: lo produce\r\n la sorgente, nel tipo in cui il sistema ospite lo tiene, e `dataType` decide il tipo in\r\n cui viaggia. La regola e' generale \u2014 vale per tutte e tre le sorgenti \u2014 e si vede su un\r\n oggetto la cui chiave e' un enum, dove nella tendina serve di norma il numero.\r\n -->\r\n <p class=\"fb-field__hint\">\r\n Decide il tipo in cui viaggia il valore dell\u2019opzione, con qualunque sorgente:\r\n <code>String</code> il nome come testo, <code>Integer</code> o <code>Number</code> il\r\n numero sottostante, <code>Enum</code> il valore tipizzato \u2014 che e\u2019 cio\u2019 che serve in un\r\n parametro o in un membro di classe <code>Enum</code>. Una conversione impossibile non fa\r\n fallire niente: resta il valore prodotto dalla sorgente.\r\n </p>\r\n }\r\n </div>\r\n\r\n @if (requiresObjectType(item) && !hidesObjectType(item)) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">\r\n @if (isEnum(item)) {\r\n Tipo di enumerazione\r\n } @else if (isStructure(item)) {\r\n Classe\r\n } @else {\r\n Oggetto\r\n }\r\n </label>\r\n @if (isEnum(item)) {\r\n <!-- Dizionario chiuso: qui la scrittura libera non serve. -->\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"string(item, 'objectType')\"\r\n (change)=\"setField(item, 'objectType', $any($event.target).value)\"\r\n >\r\n <option value=\"\">\u2014 scegli \u2014</option>\r\n @for (entry of enumOptions(); track entry.name) {\r\n <option [value]=\"entry.name\">{{ entry.label || entry.name }}</option>\r\n }\r\n </select>\r\n } @else if (isStructure(item)) {\r\n <!-- \u00A74.7: una classe del backend, non un'entita' del modello dati. -->\r\n <fb-structure-picker\r\n [value]=\"string(item, 'objectType') || undefined\"\r\n label=\"Classe\"\r\n (valueChange)=\"setField(item, 'objectType', $event ?? '')\"\r\n />\r\n } @else {\r\n <fb-object-picker\r\n [value]=\"string(item, 'objectType') || undefined\"\r\n label=\"Oggetto\"\r\n placeholder=\"Scrivi o scegli un oggetto\"\r\n (valueChange)=\"setField(item, 'objectType', $event ?? '')\"\r\n />\r\n }\r\n @if (missingObjectType(item)) {\r\n <!-- Su una Structure non e' un avviso: senza classe non c'e' nulla da istanziare. -->\r\n <p class=\"fb-field__error\">\r\n La classe e\u2019 obbligatoria: senza, l\u2019attivazione e\u2019 bloccata (OBJECT_TYPE_MISSING).\r\n </p>\r\n } @else if (isStructure(item)) {\r\n <p class=\"fb-field__hint\">\r\n Il flow ne legge e scrive i <strong>membri</strong> (<code>Nome.Membro</code>): non si\r\n interroga con Get Records ne\u2019 si salva con Create/Update.\r\n </p>\r\n } @else {\r\n <p class=\"fb-field__hint\">Obbligatorio per Object ed Enum (OBJECT_TYPE_MISSING).</p>\r\n }\r\n </div>\r\n }\r\n\r\n @if (supportsScale(item)) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Decimali</label>\r\n <input\r\n class=\"fb-input\"\r\n type=\"number\"\r\n min=\"0\"\r\n [value]=\"string(item, 'scale')\"\r\n (input)=\"setNumberField(item, 'scale', $any($event.target).value)\"\r\n />\r\n </div>\r\n }\r\n }\r\n\r\n @if (item.collection === 'variables') {\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"boolean(item, 'isCollection')\"\r\n (change)=\"setBooleanField(item, 'isCollection', $any($event.target).checked)\"\r\n />\r\n \u00C8 una collection\r\n </label>\r\n <p class=\"fb-field__hint\">Solo una collection puo\u2019 essere iterata da un Loop.</p>\r\n\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"boolean(item, 'isInput')\"\r\n (change)=\"setBooleanField(item, 'isInput', $any($event.target).checked)\"\r\n />\r\n Valorizzabile all\u2019avvio (input)\r\n </label>\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"boolean(item, 'isOutput')\"\r\n (change)=\"setBooleanField(item, 'isOutput', $any($event.target).checked)\"\r\n />\r\n Leggibile alla fine (output)\r\n </label>\r\n <p class=\"fb-field__hint\">\r\n Input e output sono il contratto del flow verso chi lo invoca, subflow compresi.\r\n </p>\r\n }\r\n\r\n @if (item.collection === 'formulas') {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Espressione</label>\r\n <!--\r\n Il tipo atteso e' quello che la risorsa dichiara, e `scale` va con lui: sono cio'\r\n che permette al motore di dire che l'espressione produce altro (\u00A76.3).\r\n -->\r\n <fb-formula-editor\r\n [expression]=\"string(item, 'expression')\"\r\n usage=\"Resource\"\r\n [expectedDataType]=\"$any(item.resource['dataType'])\"\r\n [scale]=\"$any(item.resource['scale'])\"\r\n placeholder=\"Importo * 1.22\"\r\n ariaLabel=\"Espressione della formula\"\r\n (expressionChange)=\"setField(item, 'expression', $event)\"\r\n >\r\n <p class=\"fb-field__hint\">\r\n Passata verbatim al motore di regole: la sintassi delle funzioni e\u2019 del motore.\r\n </p>\r\n </fb-formula-editor>\r\n </div>\r\n }\r\n\r\n @if (item.collection === 'choices') {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Testo mostrato</label>\r\n <input\r\n class=\"fb-input\"\r\n [value]=\"string(item, 'choiceText')\"\r\n (input)=\"setField(item, 'choiceText', $any($event.target).value)\"\r\n />\r\n </div>\r\n }\r\n\r\n @if (item.collection === 'dynamicChoiceSets') {\r\n <!--\r\n \u00A75.2 \u2014 le sorgenti sono tre e si escludono a vicenda: i pulsanti le rendono\r\n mutuamente esclusive nel documento, non solo nel form.\r\n -->\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Da dove arrivano le opzioni</label>\r\n <div class=\"fb-filter__modes\" role=\"group\" aria-label=\"Sorgente delle opzioni\">\r\n <button\r\n type=\"button\"\r\n class=\"fb-filter__mode\"\r\n [class.fb-filter__mode--active]=\"isChoiceSetSource(item, 'collection')\"\r\n (click)=\"setChoiceSetSource(item, 'collection')\"\r\n >\r\n Collection del flow\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-filter__mode\"\r\n [class.fb-filter__mode--active]=\"isChoiceSetSource(item, 'object')\"\r\n (click)=\"setChoiceSetSource(item, 'object')\"\r\n >\r\n Query su un oggetto\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-filter__mode\"\r\n [class.fb-filter__mode--active]=\"isChoiceSetSource(item, 'enum')\"\r\n (click)=\"setChoiceSetSource(item, 'enum')\"\r\n >\r\n Valori di un enum\r\n </button>\r\n </div>\r\n </div>\r\n\r\n @if (ambiguousChoiceSetSource(item)) {\r\n <p class=\"fb-callout fb-callout--warn\">\r\n Questo choice set dichiara piu\u2019 di una sorgente: il runtime ne usa una sola\r\n (CHOICE_SET_SOURCE_AMBIGUOUS). Scegli quella giusta qui sopra: le altre vengono tolte.\r\n </p>\r\n }\r\n\r\n @if (isChoiceSetSource(item, 'collection')) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Collection</label>\r\n <fb-reference-picker\r\n [value]=\"string(item, 'collectionReference')\"\r\n [isCollection]=\"true\"\r\n placeholder=\"Scegli una collection\"\r\n (valueChange)=\"setField(item, 'collectionReference', $event ?? '')\"\r\n />\r\n <p class=\"fb-field__hint\">\r\n Tipicamente il risultato di un Get Records: le opzioni sono i record che il flow ha gi\u00E0\r\n letto.\r\n </p>\r\n </div>\r\n }\r\n\r\n @if (isChoiceSetSource(item, 'object')) {\r\n <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]=\"string(item, 'object') || undefined\"\r\n placeholder=\"Scrivi o scegli un oggetto\"\r\n (valueChange)=\"setField(item, 'object', $event ?? '')\"\r\n />\r\n </div>\r\n }\r\n\r\n @if (isChoiceSetSource(item, 'enum')) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Tipo di enumerazione</label>\r\n <!-- Dizionario chiuso, e qui e' anche **autorevole**: un tipo fuori elenco e' un errore. -->\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"string(item, 'enumType')\"\r\n (change)=\"setField(item, 'enumType', $any($event.target).value)\"\r\n >\r\n <option value=\"\">\u2014 scegli \u2014</option>\r\n @for (entry of enumOptions(); track entry.name) {\r\n <option [value]=\"entry.name\">{{ entry.label || entry.name }}</option>\r\n }\r\n </select>\r\n @if (unknownEnumType(item)) {\r\n <p class=\"fb-field__error\">\r\n Il tipo \u00AB{{ string(item, 'enumType') }}\u00BB non e\u2019 fra quelli utilizzabili\r\n (ENUM_TYPE_UNKNOWN).\r\n </p>\r\n } @else {\r\n <p class=\"fb-field__hint\">\r\n Le opzioni sono i valori dichiarati dal tipo: nessuna choice da tenere allineata a mano.\r\n </p>\r\n }\r\n </div>\r\n }\r\n\r\n @if (isChoiceSetSource(item, 'enum')) {\r\n <!--\r\n \u00A75.2 \u2014 qui i campi citabili sono le tre proprieta' di un valore di enum, non i campi\r\n di un'entita': l'elenco viene dal dizionario, e i default rendono superfluo dichiararli.\r\n -->\r\n <div class=\"fb-field__row\">\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Campo mostrato</label>\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"string(item, 'displayField')\"\r\n (change)=\"setField(item, 'displayField', $any($event.target).value)\"\r\n >\r\n <option value=\"\">Predefinito ({{ defaultDisplayField }})</option>\r\n @for (field of enumChoiceSetFields(); track field.value) {\r\n <option [value]=\"field.value\">{{ field.label }}</option>\r\n }\r\n </select>\r\n @if (unknownEnumChoiceSetField(item, 'displayField')) {\r\n <p class=\"fb-field__error\">\r\n \u00AB{{ string(item, 'displayField') }}\u00BB non e\u2019 una proprieta\u2019 di un valore di enum\r\n (FIELD_UNKNOWN).\r\n </p>\r\n }\r\n </div>\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Campo del valore</label>\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"string(item, 'valueField')\"\r\n (change)=\"setField(item, 'valueField', $any($event.target).value)\"\r\n >\r\n <option value=\"\">Predefinito ({{ defaultValueField }})</option>\r\n @for (field of enumChoiceSetFields(); track field.value) {\r\n <option [value]=\"field.value\">{{ field.label }}</option>\r\n }\r\n </select>\r\n @if (unknownEnumChoiceSetField(item, 'valueField')) {\r\n <p class=\"fb-field__error\">\r\n \u00AB{{ string(item, 'valueField') }}\u00BB non e\u2019 una proprieta\u2019 di un valore di enum\r\n (FIELD_UNKNOWN).\r\n </p>\r\n }\r\n </div>\r\n </div>\r\n <p class=\"fb-field__hint\">\r\n Dichiarare <code>Integer</code> come tipo della risorsa e\u2019 la stessa cosa che scegliere qui\r\n \u00ABValore numerico\u00BB: il valore dell\u2019opzione e\u2019 il numero sottostante.\r\n </p>\r\n } @else {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Campo mostrato</label>\r\n <fb-field-picker\r\n [value]=\"string(item, 'displayField') || undefined\"\r\n [object]=\"string(item, 'object') || undefined\"\r\n usage=\"any\"\r\n label=\"Campo mostrato\"\r\n (valueChange)=\"setField(item, 'displayField', $event ?? '')\"\r\n />\r\n </div>\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Campo del valore</label>\r\n <fb-field-picker\r\n [value]=\"string(item, 'valueField') || undefined\"\r\n [object]=\"string(item, 'object') || undefined\"\r\n usage=\"any\"\r\n label=\"Campo del valore\"\r\n (valueChange)=\"setField(item, 'valueField', $event ?? '')\"\r\n />\r\n </div>\r\n }\r\n <div class=\"fb-field__row\">\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Ordina per</label>\r\n @if (isChoiceSetSource(item, 'enum')) {\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"string(item, 'sortField')\"\r\n (change)=\"setField(item, 'sortField', $any($event.target).value)\"\r\n >\r\n <option value=\"\">Ordine di dichiarazione</option>\r\n @for (field of enumChoiceSetFields(); track field.value) {\r\n <option [value]=\"field.value\">{{ field.label }}</option>\r\n }\r\n </select>\r\n @if (unknownEnumChoiceSetField(item, 'sortField')) {\r\n <p class=\"fb-field__error\">\r\n \u00AB{{ string(item, 'sortField') }}\u00BB non e\u2019 una proprieta\u2019 di un valore di enum\r\n (FIELD_UNKNOWN).\r\n </p>\r\n }\r\n } @else {\r\n <fb-field-picker\r\n [value]=\"string(item, 'sortField') || undefined\"\r\n [object]=\"string(item, 'object') || undefined\"\r\n usage=\"sortable\"\r\n label=\"Campo di ordinamento\"\r\n placeholder=\"Nessun ordinamento\"\r\n (valueChange)=\"setField(item, 'sortField', $event ?? '')\"\r\n />\r\n }\r\n </div>\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Direzione</label>\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"string(item, 'sortOrder')\"\r\n (change)=\"setField(item, 'sortOrder', $any($event.target).value)\"\r\n >\r\n <option value=\"\">\u2014</option>\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 </div>\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Numero massimo</label>\r\n <input\r\n class=\"fb-input\"\r\n type=\"number\"\r\n min=\"1\"\r\n [value]=\"string(item, 'limit')\"\r\n (input)=\"setNumberField(item, 'limit', $any($event.target).value)\"\r\n />\r\n </div>\r\n <!--\r\n `supportsLogic` a false: qui `filterLogic` non esiste nel modello, e inviarlo lo\r\n perderebbe in silenzio al primo salvataggio (\u00A74.4). Nessun `emptyWarning`: senza\r\n filtri il set legge tutti i record dell\u2019oggetto, che qui e\u2019 un uso legittimo.\r\n Su un enum i filtri li valuta il runtime **in memoria**, e i campi sono le tre\r\n proprieta\u2019 di un valore: `fieldOptions` e\u2019 cio\u2019 che sostituisce il catalogo (\u00A75.2).\r\n -->\r\n <fb-record-filter-editor\r\n [holder]=\"$any(item.resource)\"\r\n [object]=\"isChoiceSetSource(item, 'enum') ? undefined : string(item, 'object') || undefined\"\r\n [fieldOptions]=\"isChoiceSetSource(item, 'enum') ? enumChoiceSetFields() : []\"\r\n [title]=\"\r\n isChoiceSetSource(item, 'enum') ? 'Quali valori diventano opzioni' : 'Quali record diventano opzioni'\r\n \"\r\n usage=\"filterable\"\r\n [supportsLogic]=\"false\"\r\n (changed)=\"onFiltersChanged(item, $event)\"\r\n />\r\n }\r\n\r\n @if (isStructure(item) && item.collection === 'variables') {\r\n <p class=\"fb-field__hint\">\r\n Non serve un valore iniziale: la variabile parte con un\u2019istanza vuota, e il flow ne assegna i\r\n membri uno alla volta con un Assignment.\r\n </p>\r\n }\r\n\r\n @if (\r\n supportsInitialValue(item) &&\r\n (item.collection === 'variables' || item.collection === 'constants' || item.collection === 'choices')\r\n ) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">\r\n {{ item.collection === 'variables' ? 'Valore iniziale' : 'Valore' }}\r\n </label>\r\n <fb-value-editor\r\n [value]=\"value(item)\"\r\n [dataType]=\"$any(item.resource['dataType'])\"\r\n [objectType]=\"$any(item.resource['objectType'])\"\r\n [isCollection]=\"boolean(item, 'isCollection')\"\r\n [allowFormula]=\"item.collection !== 'constants'\"\r\n label=\"Valore\"\r\n (valueChange)=\"setValue(item, $event)\"\r\n />\r\n </div>\r\n }\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Descrizione</label>\r\n <input\r\n class=\"fb-input\"\r\n [value]=\"string(item, 'description')\"\r\n (input)=\"setField(item, 'description', $any($event.target).value)\"\r\n />\r\n </div>\r\n }\r\n </div>\r\n } @empty {\r\n <p class=\"fb-empty\">Nessuna risorsa di questo tipo.</p>\r\n }\r\n </div>\r\n\r\n <button type=\"button\" class=\"fb-btn fb-btn--primary\" (click)=\"add()\">\r\n Aggiungi {{ activeKind().singular }}\r\n </button>\r\n</div>\r\n", styles: [":host{display:flex;flex-direction:column;height:100%;min-height:0;background:var(--fb-surface, #fff)}.fb-res__header{display:flex;align-items:center;justify-content:space-between;padding:10px 12px;border-bottom:1px solid var(--fb-border-subtle, #e6e9ee)}.fb-res__title{margin:0;font-size:14px;font-weight:600;color:var(--fb-text, #1d2939)}.fb-res__tabs{display:flex;flex-wrap:wrap;gap:2px;padding:6px 8px;border-bottom:1px solid var(--fb-border-subtle, #e6e9ee)}.fb-res__tab{display:inline-flex;align-items:center;gap:4px;padding:3px 8px;border:1px solid var(--fb-border-subtle, #e6e9ee);border-radius:12px;background:transparent;color:var(--fb-text-muted, #667085);font:inherit;font-size:11px;cursor:pointer}.fb-res__tab:hover{background:var(--fb-surface-alt, #f8f9fb)}.fb-res__tab--active{border-color:var(--fb-accent, #2f6feb);background:color-mix(in srgb,var(--fb-accent, #2f6feb) 10%,transparent);color:var(--fb-accent, #2f6feb);font-weight:600}.fb-res__count{padding:0 4px;border-radius:6px;background:var(--fb-border, #d6dae1);font-size:9px;color:var(--fb-text, #1d2939)}.fb-res__body{flex:1;min-height:0;overflow-y:auto;padding:10px 12px}.fb-res__name{flex:1;min-width:0;display:flex;flex-direction:column;padding:0;border:0;background:transparent;color:var(--fb-text, #1d2939);font:inherit;text-align:left;cursor:pointer}.fb-res__name-text{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fb-res__meta{font-size:10px;color:var(--fb-text-muted, #667085)}.fb-res__uses{flex:none;padding:1px 6px;border-radius:999px;background:var(--fb-surface-sunken, #eef0f4);color:var(--fb-text-muted, #6b7086);font-size:10px;font-variant-numeric:tabular-nums;white-space:nowrap}.fb-res__uses--none{background:color-mix(in srgb,var(--fb-warning, #b7791f) 12%,transparent);color:var(--fb-warning, #b7791f)}.fb-res__confirm{display:flex;flex-wrap:wrap;align-items:center;gap:6px}\n"] }]
|
|
14955
|
-
}], ctorParameters: () => [], propDecorators: {
|
|
15067
|
+
], changeDetection: ChangeDetectionStrategy.OnPush, template: "<!--\r\n Il form di **una** risorsa: e' cio' che prima stava inline sotto la riga dell'elenco, e ora\r\n vive nella dialog. I rilievi locali stanno in cima e non accanto al campo che li produce\r\n perche' qui il form si apre gi\u00E0 puntato sulla risorsa: la domanda \u00ABcosa non va\u00BB viene prima\r\n di \u00ABdove\u00BB.\r\n-->\r\n@if (nameError(); as message) {\r\n <p class=\"fb-field__error\">{{ message }}</p>\r\n}\r\n@if (constantReferencesResource()) {\r\n <p class=\"fb-callout fb-callout--error\">\r\n Una costante deve essere un valore fisso: non puo\u2019 referenziare altre risorse\r\n (CONSTANT_REFERENCES_RESOURCE).\r\n </p>\r\n}\r\n@if (duplicateStageOrder()) {\r\n <p class=\"fb-callout fb-callout--warn\">\r\n Due stage con lo stesso ordine: quale sia il corrente all\u2019avvio diventa arbitrario\r\n (STAGE_ORDER_DUPLICATED).\r\n </p>\r\n}\r\n\r\n<div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Nome</label>\r\n <input\r\n class=\"fb-input fb-input--mono\"\r\n [value]=\"reference().name\"\r\n (change)=\"rename($any($event.target).value)\"\r\n />\r\n <p class=\"fb-field__hint\">\r\n Il nome vive nello stesso spazio dei nomi degli elementi. Rinominare riscrive i riferimenti.\r\n </p>\r\n</div>\r\n\r\n@if (collection() === 'stages') {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Etichetta</label>\r\n <input\r\n class=\"fb-input\"\r\n [value]=\"string('label')\"\r\n (input)=\"setField('label', $any($event.target).value)\"\r\n />\r\n </div>\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Ordine</label>\r\n <input\r\n class=\"fb-input\"\r\n type=\"number\"\r\n min=\"1\"\r\n [value]=\"string('stageOrder')\"\r\n (input)=\"setNumberField('stageOrder', $any($event.target).value)\"\r\n />\r\n </div>\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"boolean('isActive')\"\r\n (change)=\"setBooleanField('isActive', $any($event.target).checked)\"\r\n />\r\n Attivo all\u2019avvio\r\n </label>\r\n <p class=\"fb-field__hint\">\r\n All\u2019avvio lo stage corrente e\u2019 il primo attivo per ordine. Si avanza con un Assignment su\r\n <code>$Flow.CurrentStage</code>.\r\n </p>\r\n}\r\n\r\n@if (collection() === 'textTemplates') {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Testo</label>\r\n <textarea\r\n class=\"fb-textarea\"\r\n [value]=\"string('text')\"\r\n placeholder=\"Gentile {!Cliente.Nome},\"\r\n (input)=\"setField('text', $any($event.target).value)\"\r\n ></textarea>\r\n </div>\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"boolean('isViewedAsPlainText')\"\r\n (change)=\"setBooleanField('isViewedAsPlainText', $any($event.target).checked)\"\r\n />\r\n Testo semplice\r\n </label>\r\n}\r\n\r\n@if (collection() !== 'textTemplates' && collection() !== 'stages') {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Tipo</label>\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"string('dataType')\"\r\n (change)=\"setDataType($any($event.target).value)\"\r\n >\r\n @for (type of dataTypes(); track type.value) {\r\n <option [value]=\"type.value\">{{ type.label }}</option>\r\n }\r\n </select>\r\n @if (collection() === 'dynamicChoiceSets') {\r\n <!--\r\n \u00A75.2 \u2014 il valore di un'opzione **generata** non lo scrive l'autore del flow: lo produce\r\n la sorgente, nel tipo in cui il sistema ospite lo tiene, e `dataType` decide il tipo in\r\n cui viaggia. La regola e' generale \u2014 vale per tutte e tre le sorgenti \u2014 e si vede su un\r\n oggetto la cui chiave e' un enum, dove nella tendina serve di norma il numero.\r\n -->\r\n <p class=\"fb-field__hint\">\r\n Decide il tipo in cui viaggia il valore dell\u2019opzione, con qualunque sorgente:\r\n <code>String</code> il nome come testo, <code>Integer</code> o <code>Number</code> il\r\n numero sottostante, <code>Enum</code> il valore tipizzato \u2014 che e\u2019 cio\u2019 che serve in un\r\n parametro o in un membro di classe <code>Enum</code>. Una conversione impossibile non fa\r\n fallire niente: resta il valore prodotto dalla sorgente.\r\n </p>\r\n }\r\n </div>\r\n\r\n @if (requiresObjectType() && !hidesObjectType()) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">\r\n @if (isEnum()) {\r\n Tipo di enumerazione\r\n } @else if (isStructure()) {\r\n Classe\r\n } @else {\r\n Oggetto\r\n }\r\n </label>\r\n @if (isEnum()) {\r\n <!-- Dizionario chiuso: qui la scrittura libera non serve. -->\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"string('objectType')\"\r\n (change)=\"setField('objectType', $any($event.target).value)\"\r\n >\r\n <option value=\"\">\u2014 scegli \u2014</option>\r\n @for (entry of enumOptions(); track entry.name) {\r\n <option [value]=\"entry.name\">{{ entry.label || entry.name }}</option>\r\n }\r\n </select>\r\n } @else if (isStructure()) {\r\n <!-- \u00A74.7: una classe del backend, non un'entita' del modello dati. -->\r\n <fb-structure-picker\r\n [value]=\"string('objectType') || undefined\"\r\n label=\"Classe\"\r\n (valueChange)=\"setField('objectType', $event ?? '')\"\r\n />\r\n } @else {\r\n <fb-object-picker\r\n [value]=\"string('objectType') || undefined\"\r\n label=\"Oggetto\"\r\n placeholder=\"Scrivi o scegli un oggetto\"\r\n (valueChange)=\"setField('objectType', $event ?? '')\"\r\n />\r\n }\r\n @if (missingObjectType()) {\r\n <!-- Su una Structure non e' un avviso: senza classe non c'e' nulla da istanziare. -->\r\n <p class=\"fb-field__error\">\r\n La classe e\u2019 obbligatoria: senza, l\u2019attivazione e\u2019 bloccata (OBJECT_TYPE_MISSING).\r\n </p>\r\n } @else if (isStructure()) {\r\n <p class=\"fb-field__hint\">\r\n Il flow ne legge e scrive i <strong>membri</strong> (<code>Nome.Membro</code>): non si\r\n interroga con Get Records ne\u2019 si salva con Create/Update.\r\n </p>\r\n } @else {\r\n <p class=\"fb-field__hint\">Obbligatorio per Object ed Enum (OBJECT_TYPE_MISSING).</p>\r\n }\r\n </div>\r\n }\r\n\r\n @if (supportsScale()) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Decimali</label>\r\n <input\r\n class=\"fb-input\"\r\n type=\"number\"\r\n min=\"0\"\r\n [value]=\"string('scale')\"\r\n (input)=\"setNumberField('scale', $any($event.target).value)\"\r\n />\r\n </div>\r\n }\r\n}\r\n\r\n@if (collection() === 'variables') {\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"boolean('isCollection')\"\r\n (change)=\"setBooleanField('isCollection', $any($event.target).checked)\"\r\n />\r\n \u00C8 una collection\r\n </label>\r\n <p class=\"fb-field__hint\">Solo una collection puo\u2019 essere iterata da un Loop.</p>\r\n\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"boolean('isInput')\"\r\n (change)=\"setBooleanField('isInput', $any($event.target).checked)\"\r\n />\r\n Valorizzabile all\u2019avvio (input)\r\n </label>\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"boolean('isOutput')\"\r\n (change)=\"setBooleanField('isOutput', $any($event.target).checked)\"\r\n />\r\n Leggibile alla fine (output)\r\n </label>\r\n <p class=\"fb-field__hint\">\r\n Input e output sono il contratto del flow verso chi lo invoca, subflow compresi.\r\n </p>\r\n}\r\n\r\n@if (collection() === 'formulas') {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Espressione</label>\r\n <!--\r\n Il tipo atteso e' quello che la risorsa dichiara, e `scale` va con lui: sono cio'\r\n che permette al motore di dire che l'espressione produce altro (\u00A76.3).\r\n -->\r\n <fb-formula-editor\r\n [expression]=\"string('expression')\"\r\n usage=\"Resource\"\r\n [expectedDataType]=\"$any(resource()['dataType'])\"\r\n [scale]=\"$any(resource()['scale'])\"\r\n placeholder=\"Importo * 1.22\"\r\n ariaLabel=\"Espressione della formula\"\r\n (expressionChange)=\"setField('expression', $event)\"\r\n >\r\n <p class=\"fb-field__hint\">\r\n Passata verbatim al motore di regole: la sintassi delle funzioni e\u2019 del motore.\r\n </p>\r\n </fb-formula-editor>\r\n </div>\r\n}\r\n\r\n@if (collection() === 'choices') {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Testo mostrato</label>\r\n <input\r\n class=\"fb-input\"\r\n [value]=\"string('choiceText')\"\r\n (input)=\"setField('choiceText', $any($event.target).value)\"\r\n />\r\n </div>\r\n}\r\n\r\n@if (collection() === 'dynamicChoiceSets') {\r\n <!--\r\n \u00A75.2 \u2014 le sorgenti sono tre e si escludono a vicenda: i pulsanti le rendono\r\n mutuamente esclusive nel documento, non solo nel form.\r\n -->\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Da dove arrivano le opzioni</label>\r\n <div class=\"fb-filter__modes\" role=\"group\" aria-label=\"Sorgente delle opzioni\">\r\n <button\r\n type=\"button\"\r\n class=\"fb-filter__mode\"\r\n [class.fb-filter__mode--active]=\"isChoiceSetSource('collection')\"\r\n (click)=\"setChoiceSetSource('collection')\"\r\n >\r\n Collection del flow\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-filter__mode\"\r\n [class.fb-filter__mode--active]=\"isChoiceSetSource('object')\"\r\n (click)=\"setChoiceSetSource('object')\"\r\n >\r\n Query su un oggetto\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-filter__mode\"\r\n [class.fb-filter__mode--active]=\"isChoiceSetSource('enum')\"\r\n (click)=\"setChoiceSetSource('enum')\"\r\n >\r\n Valori di un enum\r\n </button>\r\n </div>\r\n </div>\r\n\r\n @if (ambiguousChoiceSetSource()) {\r\n <p class=\"fb-callout fb-callout--warn\">\r\n Questo choice set dichiara piu\u2019 di una sorgente: il runtime ne usa una sola\r\n (CHOICE_SET_SOURCE_AMBIGUOUS). Scegli quella giusta qui sopra: le altre vengono tolte.\r\n </p>\r\n }\r\n\r\n @if (isChoiceSetSource('collection')) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Collection</label>\r\n <fb-reference-picker\r\n [value]=\"string('collectionReference')\"\r\n [isCollection]=\"true\"\r\n placeholder=\"Scegli una collection\"\r\n (valueChange)=\"setField('collectionReference', $event ?? '')\"\r\n />\r\n <p class=\"fb-field__hint\">\r\n Tipicamente il risultato di un Get Records: le opzioni sono i record che il flow ha gi\u00E0\r\n letto.\r\n </p>\r\n </div>\r\n }\r\n\r\n @if (isChoiceSetSource('object')) {\r\n <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]=\"string('object') || undefined\"\r\n placeholder=\"Scrivi o scegli un oggetto\"\r\n (valueChange)=\"setField('object', $event ?? '')\"\r\n />\r\n </div>\r\n }\r\n\r\n @if (isChoiceSetSource('enum')) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Tipo di enumerazione</label>\r\n <!-- Dizionario chiuso, e qui e' anche **autorevole**: un tipo fuori elenco e' un errore. -->\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"string('enumType')\"\r\n (change)=\"setField('enumType', $any($event.target).value)\"\r\n >\r\n <option value=\"\">\u2014 scegli \u2014</option>\r\n @for (entry of enumOptions(); track entry.name) {\r\n <option [value]=\"entry.name\">{{ entry.label || entry.name }}</option>\r\n }\r\n </select>\r\n @if (unknownEnumType()) {\r\n <p class=\"fb-field__error\">\r\n Il tipo \u00AB{{ string('enumType') }}\u00BB non e\u2019 fra quelli utilizzabili\r\n (ENUM_TYPE_UNKNOWN).\r\n </p>\r\n } @else {\r\n <p class=\"fb-field__hint\">\r\n Le opzioni sono i valori dichiarati dal tipo: nessuna choice da tenere allineata a mano.\r\n </p>\r\n }\r\n </div>\r\n }\r\n\r\n @if (isChoiceSetSource('enum')) {\r\n <!--\r\n \u00A75.2 \u2014 qui i campi citabili sono le tre proprieta' di un valore di enum, non i campi\r\n di un'entita': l'elenco viene dal dizionario, e i default rendono superfluo dichiararli.\r\n -->\r\n <div class=\"fb-field__row\">\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Campo mostrato</label>\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"string('displayField')\"\r\n (change)=\"setField('displayField', $any($event.target).value)\"\r\n >\r\n <option value=\"\">Predefinito ({{ defaultDisplayField }})</option>\r\n @for (field of enumChoiceSetFields(); track field.value) {\r\n <option [value]=\"field.value\">{{ field.label }}</option>\r\n }\r\n </select>\r\n @if (unknownEnumChoiceSetField('displayField')) {\r\n <p class=\"fb-field__error\">\r\n \u00AB{{ string('displayField') }}\u00BB non e\u2019 una proprieta\u2019 di un valore di enum\r\n (FIELD_UNKNOWN).\r\n </p>\r\n }\r\n </div>\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Campo del valore</label>\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"string('valueField')\"\r\n (change)=\"setField('valueField', $any($event.target).value)\"\r\n >\r\n <option value=\"\">Predefinito ({{ defaultValueField }})</option>\r\n @for (field of enumChoiceSetFields(); track field.value) {\r\n <option [value]=\"field.value\">{{ field.label }}</option>\r\n }\r\n </select>\r\n @if (unknownEnumChoiceSetField('valueField')) {\r\n <p class=\"fb-field__error\">\r\n \u00AB{{ string('valueField') }}\u00BB non e\u2019 una proprieta\u2019 di un valore di enum\r\n (FIELD_UNKNOWN).\r\n </p>\r\n }\r\n </div>\r\n </div>\r\n <p class=\"fb-field__hint\">\r\n Dichiarare <code>Integer</code> come tipo della risorsa e\u2019 la stessa cosa che scegliere qui\r\n \u00ABValore numerico\u00BB: il valore dell\u2019opzione e\u2019 il numero sottostante.\r\n </p>\r\n } @else {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Campo mostrato</label>\r\n <fb-field-picker\r\n [value]=\"string('displayField') || undefined\"\r\n [object]=\"string('object') || undefined\"\r\n usage=\"any\"\r\n label=\"Campo mostrato\"\r\n (valueChange)=\"setField('displayField', $event ?? '')\"\r\n />\r\n </div>\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Campo del valore</label>\r\n <fb-field-picker\r\n [value]=\"string('valueField') || undefined\"\r\n [object]=\"string('object') || undefined\"\r\n usage=\"any\"\r\n label=\"Campo del valore\"\r\n (valueChange)=\"setField('valueField', $event ?? '')\"\r\n />\r\n </div>\r\n }\r\n <div class=\"fb-field__row\">\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Ordina per</label>\r\n @if (isChoiceSetSource('enum')) {\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"string('sortField')\"\r\n (change)=\"setField('sortField', $any($event.target).value)\"\r\n >\r\n <option value=\"\">Ordine di dichiarazione</option>\r\n @for (field of enumChoiceSetFields(); track field.value) {\r\n <option [value]=\"field.value\">{{ field.label }}</option>\r\n }\r\n </select>\r\n @if (unknownEnumChoiceSetField('sortField')) {\r\n <p class=\"fb-field__error\">\r\n \u00AB{{ string('sortField') }}\u00BB non e\u2019 una proprieta\u2019 di un valore di enum\r\n (FIELD_UNKNOWN).\r\n </p>\r\n }\r\n } @else {\r\n <fb-field-picker\r\n [value]=\"string('sortField') || undefined\"\r\n [object]=\"string('object') || undefined\"\r\n usage=\"sortable\"\r\n label=\"Campo di ordinamento\"\r\n placeholder=\"Nessun ordinamento\"\r\n (valueChange)=\"setField('sortField', $event ?? '')\"\r\n />\r\n }\r\n </div>\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Direzione</label>\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"string('sortOrder')\"\r\n (change)=\"setField('sortOrder', $any($event.target).value)\"\r\n >\r\n <option value=\"\">\u2014</option>\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 </div>\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Numero massimo</label>\r\n <input\r\n class=\"fb-input\"\r\n type=\"number\"\r\n min=\"1\"\r\n [value]=\"string('limit')\"\r\n (input)=\"setNumberField('limit', $any($event.target).value)\"\r\n />\r\n </div>\r\n <!--\r\n `supportsLogic` a false: qui `filterLogic` non esiste nel modello, e inviarlo lo\r\n perderebbe in silenzio al primo salvataggio (\u00A74.4). Nessun `emptyWarning`: senza\r\n filtri il set legge tutti i record dell\u2019oggetto, che qui e\u2019 un uso legittimo.\r\n Su un enum i filtri li valuta il runtime **in memoria**, e i campi sono le tre\r\n proprieta\u2019 di un valore: `fieldOptions` e\u2019 cio\u2019 che sostituisce il catalogo (\u00A75.2).\r\n -->\r\n <fb-record-filter-editor\r\n [holder]=\"$any(resource())\"\r\n [object]=\"isChoiceSetSource('enum') ? undefined : string('object') || undefined\"\r\n [fieldOptions]=\"isChoiceSetSource('enum') ? enumChoiceSetFields() : []\"\r\n [title]=\"\r\n isChoiceSetSource('enum') ? 'Quali valori diventano opzioni' : 'Quali record diventano opzioni'\r\n \"\r\n usage=\"filterable\"\r\n [supportsLogic]=\"false\"\r\n (changed)=\"onFiltersChanged($event)\"\r\n />\r\n}\r\n\r\n@if (isStructure() && collection() === 'variables') {\r\n <p class=\"fb-field__hint\">\r\n Non serve un valore iniziale: la variabile parte con un\u2019istanza vuota, e il flow ne assegna i\r\n membri uno alla volta con un Assignment.\r\n </p>\r\n}\r\n\r\n@if (\r\n supportsInitialValue() &&\r\n (collection() === 'variables' || collection() === 'constants' || collection() === 'choices')\r\n) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">\r\n {{ collection() === 'variables' ? 'Valore iniziale' : 'Valore' }}\r\n </label>\r\n <fb-value-editor\r\n [value]=\"value()\"\r\n [dataType]=\"$any(resource()['dataType'])\"\r\n [objectType]=\"$any(resource()['objectType'])\"\r\n [isCollection]=\"boolean('isCollection')\"\r\n [allowFormula]=\"collection() !== 'constants'\"\r\n label=\"Valore\"\r\n (valueChange)=\"setValue($event)\"\r\n />\r\n </div>\r\n}\r\n\r\n<div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Descrizione</label>\r\n <input\r\n class=\"fb-input\"\r\n [value]=\"string('description')\"\r\n (input)=\"setField('description', $any($event.target).value)\"\r\n />\r\n</div>\r\n" }]
|
|
15068
|
+
}], ctorParameters: () => [], propDecorators: { reference: [{ type: i0.Input, args: [{ isSignal: true, alias: "reference", required: true }] }] } });
|
|
15069
|
+
|
|
15070
|
+
/**
|
|
15071
|
+
* La dialog di una risorsa (§4.6) — l'equivalente, per le risorse, della dialog del dettaglio
|
|
15072
|
+
* di un elemento.
|
|
15073
|
+
*
|
|
15074
|
+
* Perche' una finestra e non il form inline sotto la riga dell'elenco: i form delle risorse non
|
|
15075
|
+
* hanno tutti la stessa taglia — un dynamic choice set porta sorgente, due campi, ordinamento,
|
|
15076
|
+
* limite e un editor di filtri — e aperto dentro la colonna da 340px spingeva l'elenco fuori
|
|
15077
|
+
* dallo schermo. Cioe' faceva sparire proprio il contesto per cui l'elenco esiste: quali altre
|
|
15078
|
+
* risorse ci sono, e come si chiamano. La finestra da' la larghezza necessaria e, mentre e'
|
|
15079
|
+
* aperta, dichiara che si sta configurando **quella** risorsa.
|
|
15080
|
+
*
|
|
15081
|
+
* Non c'e' un «Annulla», per la stessa ragione della dialog dell'elemento: le modifiche entrano
|
|
15082
|
+
* nel documento mentre si digita, e tornare indietro e' compito dell'annulla generale
|
|
15083
|
+
* dell'editor. Un bottone che promettesse di scartare le sole modifiche di questa finestra
|
|
15084
|
+
* mentirebbe.
|
|
15085
|
+
*
|
|
15086
|
+
* Vive **dentro** il componente builder e non in fondo al `body`: la libreria e' innestabile in
|
|
15087
|
+
* una pagina qualsiasi, e appropriarsi del `body` sarebbe una sorpresa per l'ospite. Si sposta
|
|
15088
|
+
* prendendola per l'intestazione, con il vincolo condiviso di `ui/shared/dialog-drag.ts`: la
|
|
15089
|
+
* maniglia resta dentro il builder, la finestra puo' uscirne quasi tutta.
|
|
15090
|
+
*
|
|
15091
|
+
* La risorsa e' indirizzata per **indice** nella sua collection, che e' l'unica chiave che il
|
|
15092
|
+
* documento offre. Rimuovendola dall'elenco l'indice non punta piu' a niente: la finestra si
|
|
15093
|
+
* chiude da se' invece di mostrare un form vuoto sul nulla.
|
|
15094
|
+
*/
|
|
15095
|
+
class ResourceDialogComponent {
|
|
15096
|
+
store = inject(FlowDocumentStore);
|
|
15097
|
+
edit = input.required(...(ngDevMode ? [{ debugName: "edit" }] : []));
|
|
15098
|
+
closed = output();
|
|
15099
|
+
panel = viewChild('panel', ...(ngDevMode ? [{ debugName: "panel" }] : []));
|
|
15100
|
+
reference = computed(() => {
|
|
15101
|
+
const edit = this.edit();
|
|
15102
|
+
return this.store
|
|
15103
|
+
.resources()
|
|
15104
|
+
.find((entry) => entry.collection === edit.collection && entry.index === edit.index);
|
|
15105
|
+
}, ...(ngDevMode ? [{ debugName: "reference" }] : []));
|
|
15106
|
+
kindLabel = computed(() => resourceKindOf(this.edit().collection).singular, ...(ngDevMode ? [{ debugName: "kindLabel" }] : []));
|
|
15107
|
+
title = computed(() => this.reference()?.name || '(senza nome)', ...(ngDevMode ? [{ debugName: "title" }] : []));
|
|
15108
|
+
constructor() {
|
|
15109
|
+
// Il focus entra nella finestra appena si apre o cambia risorsa: da tastiera si arriva
|
|
15110
|
+
// subito ai campi, e `Esc` chiude senza dover prima cliccare dentro.
|
|
15111
|
+
effect(() => {
|
|
15112
|
+
const edit = this.edit();
|
|
15113
|
+
const panel = this.panel()?.nativeElement;
|
|
15114
|
+
if (edit && panel) {
|
|
15115
|
+
panel.focus({ preventScroll: true });
|
|
15116
|
+
}
|
|
15117
|
+
});
|
|
15118
|
+
// La risorsa non c'e' piu' (rimossa dall'elenco, o documento ricaricato): non c'e' niente
|
|
15119
|
+
// da configurare, e un form vuoto sul nulla e' peggio di nessun form.
|
|
15120
|
+
effect(() => {
|
|
15121
|
+
if (!this.reference()) {
|
|
15122
|
+
this.closed.emit();
|
|
15123
|
+
}
|
|
15124
|
+
});
|
|
15125
|
+
}
|
|
15126
|
+
/** Il vincolo: dentro il builder resta la maniglia, non la finestra intera. */
|
|
15127
|
+
constrainPosition = dialogConstrainPosition(() => this.panel()?.nativeElement);
|
|
15128
|
+
close() {
|
|
15129
|
+
this.closed.emit();
|
|
15130
|
+
}
|
|
15131
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.28", ngImport: i0, type: ResourceDialogComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
15132
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.28", type: ResourceDialogComponent, isStandalone: true, selector: "fb-resource-dialog", inputs: { edit: { classPropertyName: "edit", publicName: "edit", isSignal: true, isRequired: true, transformFunction: null } }, outputs: { closed: "closed" }, viewQueries: [{ propertyName: "panel", first: true, predicate: ["panel"], descendants: true, isSignal: true }], ngImport: i0, template: "<!--\r\n Il backdrop chiude: e' il gesto che tutti si aspettano. Il pannello ferma la propagazione del\r\n click, altrimenti configurare un campo chiuderebbe la finestra.\r\n-->\r\n<div class=\"fb-dialog__backdrop\" (click)=\"close()\"></div>\r\n\r\n<div\r\n #panel\r\n class=\"fb-dialog__panel\"\r\n cdkDrag\r\n [cdkDragConstrainPosition]=\"constrainPosition\"\r\n role=\"dialog\"\r\n aria-modal=\"true\"\r\n [attr.aria-label]=\"kindLabel() + ': ' + title()\"\r\n tabindex=\"-1\"\r\n (click)=\"$event.stopPropagation()\"\r\n (keydown.escape)=\"close()\"\r\n>\r\n <!-- L\u2019intestazione e\u2019 la maniglia: il corpo ha campi che si trascinano per conto loro. -->\r\n <header class=\"fb-dialog__head\" cdkDragHandle>\r\n <div class=\"fb-dialog__identity\">\r\n <span class=\"fb-dialog__type\">{{ kindLabel() }}</span>\r\n <h2 class=\"fb-dialog__title\">{{ title() }}</h2>\r\n </div>\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-dialog__body\">\r\n @if (reference(); as item) {\r\n <fb-resource-form [reference]=\"item\" />\r\n }\r\n </div>\r\n\r\n <footer class=\"fb-dialog__foot\">\r\n <span class=\"fb-dialog__note\">\r\n Le modifiche sono gi\u00E0 nel documento: per tornare indietro c\u2019\u00E8 l\u2019annulla dell\u2019editor.\r\n </span>\r\n <button type=\"button\" class=\"fb-btn fb-btn--primary\" (click)=\"close()\">Fatto</button>\r\n </footer>\r\n</div>\r\n", styles: [":host{position:absolute;inset:0;z-index:30;display:grid;place-items:center;padding:24px}.fb-dialog__backdrop{position:absolute;inset:0;background:#10182852;-webkit-backdrop-filter:blur(1px);backdrop-filter:blur(1px)}.fb-dialog__panel{position:relative;display:flex;flex-direction:column;width:min(var(--fb-resource-dialog-width, 760px),100%);height:min(var(--fb-resource-dialog-height, 720px),100%);border:1px solid var(--fb-border, #e2e5eb);border-radius:var(--fb-radius-lg, 12px);background:var(--fb-surface, #fff);box-shadow:var(--fb-shadow-lg, 0 18px 44px rgb(16 24 40 / 18%));outline:none;overflow:hidden}.fb-dialog__head{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:12px 14px;border-bottom:1px solid var(--fb-border-subtle, #eef0f4);cursor:move;-webkit-user-select:none;user-select:none}.fb-dialog__head .fb-btn{cursor:pointer}.fb-dialog__panel.cdk-drag-dragging{box-shadow:var(--fb-shadow-xl, 0 26px 60px rgb(16 24 40 / 28%))}.fb-dialog__identity{min-width:0}.fb-dialog__type{display:block;font-size:10px;font-weight:700;letter-spacing:.06em;text-transform:uppercase;color:var(--fb-text-subtle, #98a2b3)}.fb-dialog__title{margin:1px 0 0;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:15px;font-weight:600;color:var(--fb-text, #1a1c23);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fb-dialog__body{flex:1;min-height:0;overflow-y:auto;padding:14px;scrollbar-gutter:stable;overscroll-behavior:contain}.fb-dialog__foot{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:10px 14px;border-top:1px solid var(--fb-border-subtle, #eef0f4);background:var(--fb-surface-alt, #f7f8fa)}.fb-dialog__note{font-size:11px;color:var(--fb-text-muted, #6b7086)}@media(max-height:620px){:host{padding:10px}}\n"], dependencies: [{ kind: "directive", type: CdkDrag, selector: "[cdkDrag]", inputs: ["cdkDragData", "cdkDragLockAxis", "cdkDragRootElement", "cdkDragBoundary", "cdkDragStartDelay", "cdkDragFreeDragPosition", "cdkDragDisabled", "cdkDragConstrainPosition", "cdkDragPreviewClass", "cdkDragPreviewContainer", "cdkDragScale"], outputs: ["cdkDragStarted", "cdkDragReleased", "cdkDragEnded", "cdkDragEntered", "cdkDragExited", "cdkDragDropped", "cdkDragMoved"], exportAs: ["cdkDrag"] }, { kind: "directive", type: CdkDragHandle, selector: "[cdkDragHandle]", inputs: ["cdkDragHandleDisabled"] }, { kind: "component", type: ResourceFormComponent, selector: "fb-resource-form", inputs: ["reference"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
15133
|
+
}
|
|
15134
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.28", ngImport: i0, type: ResourceDialogComponent, decorators: [{
|
|
15135
|
+
type: Component,
|
|
15136
|
+
args: [{ selector: 'fb-resource-dialog', standalone: true, imports: [CdkDrag, CdkDragHandle, ResourceFormComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<!--\r\n Il backdrop chiude: e' il gesto che tutti si aspettano. Il pannello ferma la propagazione del\r\n click, altrimenti configurare un campo chiuderebbe la finestra.\r\n-->\r\n<div class=\"fb-dialog__backdrop\" (click)=\"close()\"></div>\r\n\r\n<div\r\n #panel\r\n class=\"fb-dialog__panel\"\r\n cdkDrag\r\n [cdkDragConstrainPosition]=\"constrainPosition\"\r\n role=\"dialog\"\r\n aria-modal=\"true\"\r\n [attr.aria-label]=\"kindLabel() + ': ' + title()\"\r\n tabindex=\"-1\"\r\n (click)=\"$event.stopPropagation()\"\r\n (keydown.escape)=\"close()\"\r\n>\r\n <!-- L\u2019intestazione e\u2019 la maniglia: il corpo ha campi che si trascinano per conto loro. -->\r\n <header class=\"fb-dialog__head\" cdkDragHandle>\r\n <div class=\"fb-dialog__identity\">\r\n <span class=\"fb-dialog__type\">{{ kindLabel() }}</span>\r\n <h2 class=\"fb-dialog__title\">{{ title() }}</h2>\r\n </div>\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-dialog__body\">\r\n @if (reference(); as item) {\r\n <fb-resource-form [reference]=\"item\" />\r\n }\r\n </div>\r\n\r\n <footer class=\"fb-dialog__foot\">\r\n <span class=\"fb-dialog__note\">\r\n Le modifiche sono gi\u00E0 nel documento: per tornare indietro c\u2019\u00E8 l\u2019annulla dell\u2019editor.\r\n </span>\r\n <button type=\"button\" class=\"fb-btn fb-btn--primary\" (click)=\"close()\">Fatto</button>\r\n </footer>\r\n</div>\r\n", styles: [":host{position:absolute;inset:0;z-index:30;display:grid;place-items:center;padding:24px}.fb-dialog__backdrop{position:absolute;inset:0;background:#10182852;-webkit-backdrop-filter:blur(1px);backdrop-filter:blur(1px)}.fb-dialog__panel{position:relative;display:flex;flex-direction:column;width:min(var(--fb-resource-dialog-width, 760px),100%);height:min(var(--fb-resource-dialog-height, 720px),100%);border:1px solid var(--fb-border, #e2e5eb);border-radius:var(--fb-radius-lg, 12px);background:var(--fb-surface, #fff);box-shadow:var(--fb-shadow-lg, 0 18px 44px rgb(16 24 40 / 18%));outline:none;overflow:hidden}.fb-dialog__head{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:12px 14px;border-bottom:1px solid var(--fb-border-subtle, #eef0f4);cursor:move;-webkit-user-select:none;user-select:none}.fb-dialog__head .fb-btn{cursor:pointer}.fb-dialog__panel.cdk-drag-dragging{box-shadow:var(--fb-shadow-xl, 0 26px 60px rgb(16 24 40 / 28%))}.fb-dialog__identity{min-width:0}.fb-dialog__type{display:block;font-size:10px;font-weight:700;letter-spacing:.06em;text-transform:uppercase;color:var(--fb-text-subtle, #98a2b3)}.fb-dialog__title{margin:1px 0 0;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:15px;font-weight:600;color:var(--fb-text, #1a1c23);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fb-dialog__body{flex:1;min-height:0;overflow-y:auto;padding:14px;scrollbar-gutter:stable;overscroll-behavior:contain}.fb-dialog__foot{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:10px 14px;border-top:1px solid var(--fb-border-subtle, #eef0f4);background:var(--fb-surface-alt, #f7f8fa)}.fb-dialog__note{font-size:11px;color:var(--fb-text-muted, #6b7086)}@media(max-height:620px){:host{padding:10px}}\n"] }]
|
|
15137
|
+
}], ctorParameters: () => [], propDecorators: { edit: [{ type: i0.Input, args: [{ isSignal: true, alias: "edit", required: true }] }], closed: [{ type: i0.Output, args: ["closed"] }], panel: [{ type: i0.ViewChild, args: ['panel', { isSignal: true }] }] } });
|
|
14956
15138
|
|
|
14957
15139
|
/**
|
|
14958
15140
|
* I riquadri di raggruppamento — FRONTEND.md §3.6.
|
|
@@ -15371,9 +15553,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.28", ngImpo
|
|
|
15371
15553
|
*
|
|
15372
15554
|
* L'editor non esegue niente. Qui si raccoglie l'unica cosa che l'esecuzione non puo' inventare
|
|
15373
15555
|
* — i valori delle variabili `isInput`, cioe' il contratto del flow verso chi lo invoca (§4.6) —
|
|
15374
|
-
* e
|
|
15375
|
-
* significhi «eseguire
|
|
15376
|
-
*
|
|
15556
|
+
* e si **annuncia** il comando con l'output `runRequested` del builder. Chi integra l'editor
|
|
15557
|
+
* decide cosa significhi «eseguire»: nessuna primitiva viene chiamata, quindi non c'e' nemmeno un
|
|
15558
|
+
* avvio da fingere.
|
|
15377
15559
|
*
|
|
15378
15560
|
* Tre cose che il form generato tratta come dette:
|
|
15379
15561
|
* 1. il tipo dichiarato **vince** anche in scrittura (§6.5): un `Number` si manda come numero,
|
|
@@ -15387,14 +15569,14 @@ class RunDialogComponent {
|
|
|
15387
15569
|
store = inject(FlowDocumentStore);
|
|
15388
15570
|
dictionaries = inject(FlowDictionaryStore);
|
|
15389
15571
|
catalog = inject(FlowCatalogStore);
|
|
15390
|
-
/** `debug` cambia il titolo, l'avviso sui dati e
|
|
15572
|
+
/** `debug` cambia il titolo, l'avviso sui dati e il flag nel payload annunciato. */
|
|
15391
15573
|
mode = input.required(...(ngDevMode ? [{ debugName: "mode" }] : []));
|
|
15392
15574
|
/** Il flow che verra' eseguito: e' quello **salvato**, non il documento in mano. */
|
|
15393
15575
|
flowName = input(null, ...(ngDevMode ? [{ debugName: "flowName" }] : []));
|
|
15394
15576
|
version = input(null, ...(ngDevMode ? [{ debugName: "version" }] : []));
|
|
15395
15577
|
/** Con modifiche non salvate si esegue la versione salvata: dirlo evita la sorpresa. */
|
|
15396
15578
|
isDirty = input(false, ...(ngDevMode ? [{ debugName: "isDirty" }] : []));
|
|
15397
|
-
/** L'ospite
|
|
15579
|
+
/** L'ospite dice che l'esecuzione di prima e' in corso: il bottone resta spento. */
|
|
15398
15580
|
isBusy = input(false, ...(ngDevMode ? [{ debugName: "isBusy" }] : []));
|
|
15399
15581
|
confirmed = output();
|
|
15400
15582
|
cancelled = output();
|
|
@@ -15549,8 +15731,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.28", ngImpo
|
|
|
15549
15731
|
/**
|
|
15550
15732
|
* Il pannello «Debug»: cosa e' successo nell'ultima esecuzione lanciata dalla barra.
|
|
15551
15733
|
*
|
|
15552
|
-
* Non conduce niente — l'esecuzione e' dell'applicazione ospite,
|
|
15553
|
-
*
|
|
15734
|
+
* Non conduce niente — l'esecuzione e' dell'applicazione ospite, che l'editor ha soltanto
|
|
15735
|
+
* annunciato con `runRequested`, e questo pannello **guarda** cio' che l'ospite ha voluto
|
|
15736
|
+
* ridargli dall'input `runOutcome`. Esiste per il caso in cui non c'e' nient'altro
|
|
15554
15737
|
* da guardare: un flow senza schermate (`AutoLaunched`, o un'orchestrazione tutta in background)
|
|
15555
15738
|
* non apre nessuna interfaccia del runtime, quindi la traccia accanto al grafo e' l'unico modo di
|
|
15556
15739
|
* sapere quali elementi sono stati eseguiti e in che ordine. Da lì si torna sull'elemento con un
|
|
@@ -15561,8 +15744,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.28", ngImpo
|
|
|
15561
15744
|
* E `NotStarted` significa che i criteri dello Start non erano soddisfatti.
|
|
15562
15745
|
* 2. `trace` e `resources` arrivano solo con `debug: true`, e riportano i valori di **tutte**
|
|
15563
15746
|
* le risorse — dati personali compresi: l'avviso e' in chiaro, non in una tooltip.
|
|
15564
|
-
* 3. l'ospite puo' non
|
|
15565
|
-
* dice invece di sembrare rotto
|
|
15747
|
+
* 3. l'ospite puo' non ridare niente: allora non c'e' niente da mostrare, e il pannello lo
|
|
15748
|
+
* dice invece di sembrare rotto — anche perche' molto spesso l'esecuzione si guarda a casa
|
|
15749
|
+
* sua, e questo pannello serve a chi non ha una casa dove guardarla.
|
|
15566
15750
|
*/
|
|
15567
15751
|
class DebugPanelComponent {
|
|
15568
15752
|
/** L'esito dell'ultima esecuzione; `null` = non ne e' ancora stata lanciata una. */
|
|
@@ -15638,11 +15822,11 @@ class DebugPanelComponent {
|
|
|
15638
15822
|
}
|
|
15639
15823
|
}
|
|
15640
15824
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.28", ngImport: i0, type: DebugPanelComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
15641
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.28", type: DebugPanelComponent, isStandalone: true, selector: "fb-debug-panel", inputs: { outcome: { classPropertyName: "outcome", publicName: "outcome", isSignal: true, isRequired: false, transformFunction: null }, wasDebug: { classPropertyName: "wasDebug", publicName: "wasDebug", isSignal: true, isRequired: false, transformFunction: null }, isRunning: { classPropertyName: "isRunning", publicName: "isRunning", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { closed: "closed", elementFocused: "elementFocused", cleared: "cleared" }, ngImport: i0, template: "<header class=\"fb-dbg__header\">\r\n <h2 class=\"fb-dbg__title\">Debug</h2>\r\n @if (outcome()) {\r\n <button type=\"button\" class=\"fb-btn fb-btn--ghost fb-btn--icon\" title=\"Svuota\" (click)=\"clear()\">Svuota</button>\r\n }\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-dbg__body\">\r\n @if (isRunning()) {\r\n <p class=\"fb-field__hint\">Esecuzione in corso\u2026</p>\r\n }\r\n\r\n @if (!outcome() && !isRunning()) {\r\n <!--\r\n Non e\u2019 un errore: e\u2019 lo stato iniziale. Il pannello serve soprattutto ai flow senza\r\n schermate, dove non c\u2019e\u2019 nessun\u2019altra interfaccia in cui guardare la traccia.\r\n -->\r\n <p class=\"fb-field__hint\">\r\n Nessuna esecuzione. Usa <strong>Esegui</strong> o <strong>Debug</strong> nella barra in alto: qui\r\n compare cio\u2019 che l\u2019applicazione
|
|
15825
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.28", type: DebugPanelComponent, isStandalone: true, selector: "fb-debug-panel", inputs: { outcome: { classPropertyName: "outcome", publicName: "outcome", isSignal: true, isRequired: false, transformFunction: null }, wasDebug: { classPropertyName: "wasDebug", publicName: "wasDebug", isSignal: true, isRequired: false, transformFunction: null }, isRunning: { classPropertyName: "isRunning", publicName: "isRunning", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { closed: "closed", elementFocused: "elementFocused", cleared: "cleared" }, ngImport: i0, template: "<header class=\"fb-dbg__header\">\r\n <h2 class=\"fb-dbg__title\">Debug</h2>\r\n @if (outcome()) {\r\n <button type=\"button\" class=\"fb-btn fb-btn--ghost fb-btn--icon\" title=\"Svuota\" (click)=\"clear()\">Svuota</button>\r\n }\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-dbg__body\">\r\n @if (isRunning()) {\r\n <p class=\"fb-field__hint\">Esecuzione in corso\u2026</p>\r\n }\r\n\r\n @if (!outcome() && !isRunning()) {\r\n <!--\r\n Non e\u2019 un errore: e\u2019 lo stato iniziale. Il pannello serve soprattutto ai flow senza\r\n schermate, dove non c\u2019e\u2019 nessun\u2019altra interfaccia in cui guardare la traccia.\r\n -->\r\n <p class=\"fb-field__hint\">\r\n Nessuna esecuzione. Usa <strong>Esegui</strong> o <strong>Debug</strong> nella barra in alto: qui\r\n compare cio\u2019 che l\u2019applicazione decide di ridare all\u2019editor \u2014 stato, traccia degli elementi\r\n eseguiti, risorse e output.\r\n </p>\r\n }\r\n\r\n @if (isEmptyOutcome()) {\r\n <!--\r\n L\u2019ospite ha risposto, ma senza niente da mostrare. Distinto dal caso \u00ABnessuna esecuzione\u00BB\r\n perche\u2019 il rimedio e\u2019 un altro: qui l\u2019esito che l\u2019ospite ha passato non contiene niente.\r\n -->\r\n <p class=\"fb-callout\">\r\n L\u2019esecuzione e\u2019 partita, ma l\u2019applicazione non ha passato nessun dettaglio da mostrare.\r\n </p>\r\n }\r\n\r\n @if (outcome(); as run) {\r\n @if (status() || run.steps) {\r\n <div class=\"fb-dbg__status\">\r\n @if (status()) {\r\n <span\r\n class=\"fb-dbg__badge\"\r\n [class.fb-dbg__badge--ok]=\"status() === 'Completed'\"\r\n [class.fb-dbg__badge--fail]=\"status() === 'Failed'\"\r\n [class.fb-dbg__badge--wait]=\"status() === 'WaitingForScreen'\"\r\n >\r\n {{ statusLabel() }}\r\n </span>\r\n }\r\n @if (run.currentElementName) {\r\n <!-- Dove si e\u2019 fermata: cliccarlo porta sull\u2019elemento, come le righe della traccia. -->\r\n <button type=\"button\" class=\"fb-dbg__current\" (click)=\"elementFocused.emit(run.currentElementName!)\">\r\n su {{ run.currentElementName }}\r\n </button>\r\n }\r\n @if (run.steps) {\r\n <span class=\"fb-dbg__steps\">{{ run.steps }} passi</span>\r\n }\r\n </div>\r\n }\r\n\r\n @if (statusNote()) {\r\n <p class=\"fb-callout\">{{ statusNote() }}</p>\r\n }\r\n\r\n @if (run.fault) {\r\n <p class=\"fb-callout fb-callout--error\">{{ run.fault }}</p>\r\n }\r\n @for (message of run.errors || []; track $index) {\r\n <p class=\"fb-callout fb-callout--error\">{{ message }}</p>\r\n }\r\n\r\n @if (outputRows().length) {\r\n <fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Output del flow</legend>\r\n <table class=\"fb-dbg__table\">\r\n <tbody>\r\n @for (row of outputRows(); track row.name) {\r\n <tr>\r\n <th scope=\"row\">{{ row.name }}</th>\r\n <td class=\"fb-dbg__type\">{{ row.type }}</td>\r\n <td>{{ row.value }}</td>\r\n </tr>\r\n }\r\n </tbody>\r\n </table>\r\n </fieldset>\r\n }\r\n\r\n @if (trace().length) {\r\n <fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Traccia</legend>\r\n <p class=\"fb-callout fb-callout--warn\">\r\n La traccia riporta i valori di <strong>tutte</strong> le risorse, dati personali compresi.\r\n </p>\r\n <ol class=\"fb-dbg__trace\">\r\n @for (entry of trace(); track $index) {\r\n <li class=\"fb-dbg__trace-item\">\r\n <span class=\"fb-dbg__trace-seq\">{{ entry.sequence }}</span>\r\n @if (entry.elementName) {\r\n <!-- L\u2019ancora: dalla riga della traccia all\u2019elemento sul canvas. -->\r\n <button type=\"button\" class=\"fb-dbg__trace-el\" (click)=\"elementFocused.emit(entry.elementName!)\">\r\n {{ entry.elementName }}\r\n </button>\r\n }\r\n <span class=\"fb-dbg__trace-msg\">{{ entry.message }}</span>\r\n </li>\r\n }\r\n </ol>\r\n </fieldset>\r\n } @else if (!wasDebug() && !isEmptyOutcome()) {\r\n <!-- Non e\u2019 un difetto: `trace` e `resources` esistono solo con `debug: true` (\u00A76.5). -->\r\n <p class=\"fb-field__hint\">\r\n Traccia e risorse arrivano solo con il comando <strong>Debug</strong>.\r\n </p>\r\n }\r\n\r\n @if (resourceRows().length) {\r\n <fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Risorse</legend>\r\n <table class=\"fb-dbg__table\">\r\n <tbody>\r\n @for (row of resourceRows(); track row.name) {\r\n <tr>\r\n <th scope=\"row\">{{ row.name }}</th>\r\n <td class=\"fb-dbg__type\">{{ row.type }}</td>\r\n <td>{{ row.value }}</td>\r\n </tr>\r\n }\r\n </tbody>\r\n </table>\r\n </fieldset>\r\n }\r\n }\r\n</div>\r\n", styles: [":host{display:flex;flex-direction:column;height:100%;min-height:0;background:var(--fb-surface, #fff)}.fb-dbg__header{display:flex;align-items:center;gap:6px;padding:10px 12px;border-bottom:1px solid var(--fb-border-subtle, #e6e9ee)}.fb-dbg__title{margin:0 auto 0 0;font-size:14px;font-weight:600;color:var(--fb-text, #1d2939)}.fb-dbg__body{flex:1;min-height:0;overflow-y:auto;padding:10px 12px}.fb-dbg__status{display:flex;align-items:center;gap:8px;margin-bottom:10px}.fb-dbg__badge{padding:2px 8px;border-radius:10px;background:var(--fb-border, #d6dae1);font-size:11px;font-weight:600;color:var(--fb-text, #1d2939)}.fb-dbg__badge--ok{background:color-mix(in srgb,var(--fb-success, #3f8f5f) 18%,transparent);color:var(--fb-success, #3f8f5f)}.fb-dbg__badge--fail{background:color-mix(in srgb,var(--fb-error, #c9372c) 14%,transparent);color:var(--fb-error, #c9372c)}.fb-dbg__badge--wait{background:color-mix(in srgb,var(--fb-accent, #2f6feb) 12%,transparent);color:var(--fb-accent, #2f6feb)}.fb-dbg__steps{font-size:10px;color:var(--fb-text-muted, #667085)}.fb-dbg__current{padding:0;border:0;background:transparent;color:var(--fb-accent, #2f6feb);font:inherit;font-size:10px;cursor:pointer;text-decoration:underline}.fb-dbg__type{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:9px;color:var(--fb-text-subtle, #98a2b3)}.fb-dbg__table{width:100%;border-collapse:collapse;font-size:11px}.fb-dbg__table caption{padding-bottom:3px;font-size:10px;color:var(--fb-text-subtle, #98a2b3);text-align:left}.fb-dbg__table th,.fb-dbg__table td{padding:3px 5px;border-bottom:1px solid var(--fb-border-subtle, #e6e9ee);text-align:left;vertical-align:top}.fb-dbg__table th{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-weight:600;color:var(--fb-text, #1d2939)}.fb-dbg__table td{color:var(--fb-text-muted, #667085);word-break:break-word}.fb-dbg__trace{margin:0;padding:0;list-style:none}.fb-dbg__trace-item{display:flex;gap:6px;padding:3px 0;border-bottom:1px solid var(--fb-border-subtle, #e6e9ee);font-size:11px}.fb-dbg__trace-seq{flex:0 0 auto;width:18px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:9px;color:var(--fb-text-subtle, #98a2b3);text-align:right}.fb-dbg__trace-el{flex:0 0 auto;padding:0;border:0;background:transparent;color:var(--fb-accent, #2f6feb);font:inherit;font-size:10px;cursor:pointer;text-decoration:underline}.fb-dbg__trace-msg{color:var(--fb-text-muted, #667085);line-height:1.35}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
15642
15826
|
}
|
|
15643
15827
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.28", ngImport: i0, type: DebugPanelComponent, decorators: [{
|
|
15644
15828
|
type: Component,
|
|
15645
|
-
args: [{ selector: 'fb-debug-panel', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, template: "<header class=\"fb-dbg__header\">\r\n <h2 class=\"fb-dbg__title\">Debug</h2>\r\n @if (outcome()) {\r\n <button type=\"button\" class=\"fb-btn fb-btn--ghost fb-btn--icon\" title=\"Svuota\" (click)=\"clear()\">Svuota</button>\r\n }\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-dbg__body\">\r\n @if (isRunning()) {\r\n <p class=\"fb-field__hint\">Esecuzione in corso\u2026</p>\r\n }\r\n\r\n @if (!outcome() && !isRunning()) {\r\n <!--\r\n Non e\u2019 un errore: e\u2019 lo stato iniziale. Il pannello serve soprattutto ai flow senza\r\n schermate, dove non c\u2019e\u2019 nessun\u2019altra interfaccia in cui guardare la traccia.\r\n -->\r\n <p class=\"fb-field__hint\">\r\n Nessuna esecuzione. Usa <strong>Esegui</strong> o <strong>Debug</strong> nella barra in alto: qui\r\n compare cio\u2019 che l\u2019applicazione
|
|
15829
|
+
args: [{ selector: 'fb-debug-panel', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, template: "<header class=\"fb-dbg__header\">\r\n <h2 class=\"fb-dbg__title\">Debug</h2>\r\n @if (outcome()) {\r\n <button type=\"button\" class=\"fb-btn fb-btn--ghost fb-btn--icon\" title=\"Svuota\" (click)=\"clear()\">Svuota</button>\r\n }\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-dbg__body\">\r\n @if (isRunning()) {\r\n <p class=\"fb-field__hint\">Esecuzione in corso\u2026</p>\r\n }\r\n\r\n @if (!outcome() && !isRunning()) {\r\n <!--\r\n Non e\u2019 un errore: e\u2019 lo stato iniziale. Il pannello serve soprattutto ai flow senza\r\n schermate, dove non c\u2019e\u2019 nessun\u2019altra interfaccia in cui guardare la traccia.\r\n -->\r\n <p class=\"fb-field__hint\">\r\n Nessuna esecuzione. Usa <strong>Esegui</strong> o <strong>Debug</strong> nella barra in alto: qui\r\n compare cio\u2019 che l\u2019applicazione decide di ridare all\u2019editor \u2014 stato, traccia degli elementi\r\n eseguiti, risorse e output.\r\n </p>\r\n }\r\n\r\n @if (isEmptyOutcome()) {\r\n <!--\r\n L\u2019ospite ha risposto, ma senza niente da mostrare. Distinto dal caso \u00ABnessuna esecuzione\u00BB\r\n perche\u2019 il rimedio e\u2019 un altro: qui l\u2019esito che l\u2019ospite ha passato non contiene niente.\r\n -->\r\n <p class=\"fb-callout\">\r\n L\u2019esecuzione e\u2019 partita, ma l\u2019applicazione non ha passato nessun dettaglio da mostrare.\r\n </p>\r\n }\r\n\r\n @if (outcome(); as run) {\r\n @if (status() || run.steps) {\r\n <div class=\"fb-dbg__status\">\r\n @if (status()) {\r\n <span\r\n class=\"fb-dbg__badge\"\r\n [class.fb-dbg__badge--ok]=\"status() === 'Completed'\"\r\n [class.fb-dbg__badge--fail]=\"status() === 'Failed'\"\r\n [class.fb-dbg__badge--wait]=\"status() === 'WaitingForScreen'\"\r\n >\r\n {{ statusLabel() }}\r\n </span>\r\n }\r\n @if (run.currentElementName) {\r\n <!-- Dove si e\u2019 fermata: cliccarlo porta sull\u2019elemento, come le righe della traccia. -->\r\n <button type=\"button\" class=\"fb-dbg__current\" (click)=\"elementFocused.emit(run.currentElementName!)\">\r\n su {{ run.currentElementName }}\r\n </button>\r\n }\r\n @if (run.steps) {\r\n <span class=\"fb-dbg__steps\">{{ run.steps }} passi</span>\r\n }\r\n </div>\r\n }\r\n\r\n @if (statusNote()) {\r\n <p class=\"fb-callout\">{{ statusNote() }}</p>\r\n }\r\n\r\n @if (run.fault) {\r\n <p class=\"fb-callout fb-callout--error\">{{ run.fault }}</p>\r\n }\r\n @for (message of run.errors || []; track $index) {\r\n <p class=\"fb-callout fb-callout--error\">{{ message }}</p>\r\n }\r\n\r\n @if (outputRows().length) {\r\n <fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Output del flow</legend>\r\n <table class=\"fb-dbg__table\">\r\n <tbody>\r\n @for (row of outputRows(); track row.name) {\r\n <tr>\r\n <th scope=\"row\">{{ row.name }}</th>\r\n <td class=\"fb-dbg__type\">{{ row.type }}</td>\r\n <td>{{ row.value }}</td>\r\n </tr>\r\n }\r\n </tbody>\r\n </table>\r\n </fieldset>\r\n }\r\n\r\n @if (trace().length) {\r\n <fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Traccia</legend>\r\n <p class=\"fb-callout fb-callout--warn\">\r\n La traccia riporta i valori di <strong>tutte</strong> le risorse, dati personali compresi.\r\n </p>\r\n <ol class=\"fb-dbg__trace\">\r\n @for (entry of trace(); track $index) {\r\n <li class=\"fb-dbg__trace-item\">\r\n <span class=\"fb-dbg__trace-seq\">{{ entry.sequence }}</span>\r\n @if (entry.elementName) {\r\n <!-- L\u2019ancora: dalla riga della traccia all\u2019elemento sul canvas. -->\r\n <button type=\"button\" class=\"fb-dbg__trace-el\" (click)=\"elementFocused.emit(entry.elementName!)\">\r\n {{ entry.elementName }}\r\n </button>\r\n }\r\n <span class=\"fb-dbg__trace-msg\">{{ entry.message }}</span>\r\n </li>\r\n }\r\n </ol>\r\n </fieldset>\r\n } @else if (!wasDebug() && !isEmptyOutcome()) {\r\n <!-- Non e\u2019 un difetto: `trace` e `resources` esistono solo con `debug: true` (\u00A76.5). -->\r\n <p class=\"fb-field__hint\">\r\n Traccia e risorse arrivano solo con il comando <strong>Debug</strong>.\r\n </p>\r\n }\r\n\r\n @if (resourceRows().length) {\r\n <fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Risorse</legend>\r\n <table class=\"fb-dbg__table\">\r\n <tbody>\r\n @for (row of resourceRows(); track row.name) {\r\n <tr>\r\n <th scope=\"row\">{{ row.name }}</th>\r\n <td class=\"fb-dbg__type\">{{ row.type }}</td>\r\n <td>{{ row.value }}</td>\r\n </tr>\r\n }\r\n </tbody>\r\n </table>\r\n </fieldset>\r\n }\r\n }\r\n</div>\r\n", styles: [":host{display:flex;flex-direction:column;height:100%;min-height:0;background:var(--fb-surface, #fff)}.fb-dbg__header{display:flex;align-items:center;gap:6px;padding:10px 12px;border-bottom:1px solid var(--fb-border-subtle, #e6e9ee)}.fb-dbg__title{margin:0 auto 0 0;font-size:14px;font-weight:600;color:var(--fb-text, #1d2939)}.fb-dbg__body{flex:1;min-height:0;overflow-y:auto;padding:10px 12px}.fb-dbg__status{display:flex;align-items:center;gap:8px;margin-bottom:10px}.fb-dbg__badge{padding:2px 8px;border-radius:10px;background:var(--fb-border, #d6dae1);font-size:11px;font-weight:600;color:var(--fb-text, #1d2939)}.fb-dbg__badge--ok{background:color-mix(in srgb,var(--fb-success, #3f8f5f) 18%,transparent);color:var(--fb-success, #3f8f5f)}.fb-dbg__badge--fail{background:color-mix(in srgb,var(--fb-error, #c9372c) 14%,transparent);color:var(--fb-error, #c9372c)}.fb-dbg__badge--wait{background:color-mix(in srgb,var(--fb-accent, #2f6feb) 12%,transparent);color:var(--fb-accent, #2f6feb)}.fb-dbg__steps{font-size:10px;color:var(--fb-text-muted, #667085)}.fb-dbg__current{padding:0;border:0;background:transparent;color:var(--fb-accent, #2f6feb);font:inherit;font-size:10px;cursor:pointer;text-decoration:underline}.fb-dbg__type{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:9px;color:var(--fb-text-subtle, #98a2b3)}.fb-dbg__table{width:100%;border-collapse:collapse;font-size:11px}.fb-dbg__table caption{padding-bottom:3px;font-size:10px;color:var(--fb-text-subtle, #98a2b3);text-align:left}.fb-dbg__table th,.fb-dbg__table td{padding:3px 5px;border-bottom:1px solid var(--fb-border-subtle, #e6e9ee);text-align:left;vertical-align:top}.fb-dbg__table th{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-weight:600;color:var(--fb-text, #1d2939)}.fb-dbg__table td{color:var(--fb-text-muted, #667085);word-break:break-word}.fb-dbg__trace{margin:0;padding:0;list-style:none}.fb-dbg__trace-item{display:flex;gap:6px;padding:3px 0;border-bottom:1px solid var(--fb-border-subtle, #e6e9ee);font-size:11px}.fb-dbg__trace-seq{flex:0 0 auto;width:18px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:9px;color:var(--fb-text-subtle, #98a2b3);text-align:right}.fb-dbg__trace-el{flex:0 0 auto;padding:0;border:0;background:transparent;color:var(--fb-accent, #2f6feb);font:inherit;font-size:10px;cursor:pointer;text-decoration:underline}.fb-dbg__trace-msg{color:var(--fb-text-muted, #667085);line-height:1.35}\n"] }]
|
|
15646
15830
|
}], propDecorators: { outcome: [{ type: i0.Input, args: [{ isSignal: true, alias: "outcome", required: false }] }], wasDebug: [{ type: i0.Input, args: [{ isSignal: true, alias: "wasDebug", required: false }] }], isRunning: [{ type: i0.Input, args: [{ isSignal: true, alias: "isRunning", required: false }] }], closed: [{ type: i0.Output, args: ["closed"] }], elementFocused: [{ type: i0.Output, args: ["elementFocused"] }], cleared: [{ type: i0.Output, args: ["cleared"] }] } });
|
|
15647
15831
|
function rowsOf(values) {
|
|
15648
15832
|
return Object.entries(values ?? {}).map(([name, typed]) => ({
|
|
@@ -16108,9 +16292,34 @@ class FlowBuilderComponent {
|
|
|
16108
16292
|
* modificano molti elementi di fila perche' non c'e' niente da aprire e chiudere.
|
|
16109
16293
|
*/
|
|
16110
16294
|
inspectorMode = input('dialog', ...(ngDevMode ? [{ debugName: "inspectorMode" }] : []));
|
|
16295
|
+
/**
|
|
16296
|
+
* L'esito dell'ultima esecuzione, se l'ospite ne ha uno da mostrare.
|
|
16297
|
+
*
|
|
16298
|
+
* E' l'altra meta' di {@link runRequested}: l'editor non esegue e non aspetta niente, quindi
|
|
16299
|
+
* l'unico modo per far arrivare una traccia nel pannello «Debug» e' che l'ospite la passi di
|
|
16300
|
+
* qui quando l'ha. Lasciarlo `null` e' legittimo — chi mostra tutto in una sua interfaccia non
|
|
16301
|
+
* ha niente da dire all'editor — e il pannello distingue i tre casi (niente, in corso, esito
|
|
16302
|
+
* vuoto) perche' i rimedi sono diversi.
|
|
16303
|
+
*/
|
|
16304
|
+
runOutcome = input(null, ...(ngDevMode ? [{ debugName: "runOutcome" }] : []));
|
|
16305
|
+
/**
|
|
16306
|
+
* L'esecuzione annunciata e' ancora in corso. Solo l'ospite lo sa: dopo l'emit l'editor non ha
|
|
16307
|
+
* piu' notizie. Accende il puntino sul tab «Debug» e tiene spento il bottone della finestra.
|
|
16308
|
+
*/
|
|
16309
|
+
isRunning = input(false, ...(ngDevMode ? [{ debugName: "isRunning" }] : []));
|
|
16111
16310
|
saved = output();
|
|
16112
16311
|
activated = output();
|
|
16113
16312
|
closeRequested = output();
|
|
16313
|
+
/**
|
|
16314
|
+
* «Esegui» / «Debug»: l'editor **annuncia**, non esegue.
|
|
16315
|
+
*
|
|
16316
|
+
* Cosa significhi eseguire lo decide chi ospita l'editor — una navigazione, una chiamata a
|
|
16317
|
+
* delle sue API, una finestra con l'interview dentro — e il payload porta tutto cio' che
|
|
16318
|
+
* serve per deciderlo (nome, versione, ingressi convertiti, `processType`, `isDirty`, il
|
|
16319
|
+
* documento in mano). Un ospite che non ascolta questo output non fa niente: non c'e' nessuna
|
|
16320
|
+
* primitiva che possa mancare, quindi nessun errore da mostrare.
|
|
16321
|
+
*/
|
|
16322
|
+
runRequested = output();
|
|
16114
16323
|
selectedName = signal(START_NODE_NAME, ...(ngDevMode ? [{ debugName: "selectedName" }] : []));
|
|
16115
16324
|
activePanel = signal('inspector', ...(ngDevMode ? [{ debugName: "activePanel" }] : []));
|
|
16116
16325
|
/** La dialog del dettaglio e' aperta. Vale solo con `inspectorMode = 'dialog'`. */
|
|
@@ -16134,6 +16343,14 @@ class FlowBuilderComponent {
|
|
|
16134
16343
|
/** La risorsa su cui aprire il pannello: e' l'arrivo della ricerca su una risorsa. */
|
|
16135
16344
|
resourceTarget = signal(null, ...(ngDevMode ? [{ debugName: "resourceTarget" }] : []));
|
|
16136
16345
|
resourceReveals = 0;
|
|
16346
|
+
/**
|
|
16347
|
+
* La risorsa aperta nella sua finestra di modifica (§4.6).
|
|
16348
|
+
*
|
|
16349
|
+
* Sta qui e non nel pannello perche' la finestra va montata **fuori** dalla colonna laterale:
|
|
16350
|
+
* dentro sarebbe ritagliata dallo scorrimento del pannello. Il pannello annuncia quale
|
|
16351
|
+
* risorsa aprire e riceve indietro quale sia aperta, così i due non possono discordare.
|
|
16352
|
+
*/
|
|
16353
|
+
editingResource = signal(null, ...(ngDevMode ? [{ debugName: "editingResource" }] : []));
|
|
16137
16354
|
/**
|
|
16138
16355
|
* Il riquadro corrente (§3.6). E' una selezione **separata** da `selectedName`: un riquadro
|
|
16139
16356
|
* non e' un elemento — non si copia, non si duplica, non si valida come tale — e tenerlo nella
|
|
@@ -16244,6 +16461,7 @@ class FlowBuilderComponent {
|
|
|
16244
16461
|
this.notice.set(null);
|
|
16245
16462
|
// Si sta cambiando documento: una dialog aperta guarderebbe un elemento di un altro flow.
|
|
16246
16463
|
this.isDialogOpen.set(false);
|
|
16464
|
+
this.editingResource.set(null);
|
|
16247
16465
|
try {
|
|
16248
16466
|
if (flowName) {
|
|
16249
16467
|
await this.session.open(flowName, version ?? undefined);
|
|
@@ -16456,6 +16674,14 @@ class FlowBuilderComponent {
|
|
|
16456
16674
|
closeDialog() {
|
|
16457
16675
|
this.isDialogOpen.set(false);
|
|
16458
16676
|
}
|
|
16677
|
+
/**
|
|
16678
|
+
* La finestra di una risorsa (§4.6): la apre il pannello, `null` la chiude. Il pannello non
|
|
16679
|
+
* tiene una copia sua di questo stato — riceve indietro questo — così chiudere dalla finestra
|
|
16680
|
+
* e chiudere dall'elenco sono la stessa cosa.
|
|
16681
|
+
*/
|
|
16682
|
+
onResourceEditRequested(edit) {
|
|
16683
|
+
this.editingResource.set(edit);
|
|
16684
|
+
}
|
|
16459
16685
|
/**
|
|
16460
16686
|
* Evidenzia l'elemento sul canvas **senza** cambiare pannello.
|
|
16461
16687
|
* Serve alla traccia del debug: portare l'utente sull'elemento e' il punto del pannello, ma
|
|
@@ -16998,22 +17224,28 @@ class FlowBuilderComponent {
|
|
|
16998
17224
|
}
|
|
16999
17225
|
// ------------------------------------------------------------ esegui e debug
|
|
17000
17226
|
//
|
|
17001
|
-
// I due comandi non eseguono niente: raccolgono i valori di ingresso e
|
|
17002
|
-
// `
|
|
17003
|
-
//
|
|
17227
|
+
// I due comandi non eseguono niente e non chiamano niente: raccolgono i valori di ingresso e
|
|
17228
|
+
// li **annunciano** con `runRequested`. Cosa significhi eseguire lo sa solo chi ospita
|
|
17229
|
+
// l'editor, e non e' una cosa che possa stare dietro un'interfaccia di trasporto: puo' essere
|
|
17230
|
+
// una navigazione, una chiamata a delle sue API, una finestra con l'interview dentro. Quindi
|
|
17231
|
+
// qui non c'e' nessuna promessa da attendere, nessun `MissingService` da tradurre in un
|
|
17232
|
+
// banner, e cio' che si vede dopo l'emit e' cio' che l'ospite decide di far vedere.
|
|
17004
17233
|
/** Quale delle due finestre e' aperta; `null` = nessuna. */
|
|
17005
17234
|
runMode = signal(null, ...(ngDevMode ? [{ debugName: "runMode" }] : []));
|
|
17006
|
-
/** La chiamata all'ospite e' in volo: il bottone della finestra resta spento. */
|
|
17007
|
-
isStartingRun = signal(false, ...(ngDevMode ? [{ debugName: "isStartingRun" }] : []));
|
|
17008
17235
|
/**
|
|
17009
|
-
* L'esito
|
|
17236
|
+
* L'esito da mostrare nel pannello.
|
|
17010
17237
|
*
|
|
17011
|
-
*
|
|
17012
|
-
*
|
|
17013
|
-
*
|
|
17014
|
-
|
|
17015
|
-
|
|
17016
|
-
|
|
17238
|
+
* E' l'input dell'ospite, ma **scartabile**: «Svuota» e' un gesto locale — l'esito di prima
|
|
17239
|
+
* non riguarda piu' il documento che si sta editando — e un input non si puo' rimettere a
|
|
17240
|
+
* `null` da dentro. Con `linkedSignal` il valore torna quello dell'ospite ogni volta che ne
|
|
17241
|
+
* arriva uno nuovo, quindi due esecuzioni di fila non restano nascoste da una pulizia.
|
|
17242
|
+
*/
|
|
17243
|
+
shownOutcome = linkedSignal(...(ngDevMode ? [{ debugName: "shownOutcome", source: () => this.runOutcome(),
|
|
17244
|
+
computation: (outcome) => outcome }] : [{
|
|
17245
|
+
source: () => this.runOutcome(),
|
|
17246
|
+
computation: (outcome) => outcome,
|
|
17247
|
+
}]));
|
|
17248
|
+
/** L'ultima esecuzione annunciata era in debug: senza, traccia e risorse non arrivano ed e' normale. */
|
|
17017
17249
|
wasRunInDebug = signal(false, ...(ngDevMode ? [{ debugName: "wasRunInDebug" }] : []));
|
|
17018
17250
|
/**
|
|
17019
17251
|
* Si esegue cio' che e' **salvato**: su un flow mai scritto non c'e' niente da avviare, e il
|
|
@@ -17031,69 +17263,42 @@ class FlowBuilderComponent {
|
|
|
17031
17263
|
}
|
|
17032
17264
|
/** Svuota il pannello: l'esito di prima non riguarda piu' il documento che si sta editando. */
|
|
17033
17265
|
clearRunOutcome() {
|
|
17034
|
-
this.
|
|
17266
|
+
this.shownOutcome.set(null);
|
|
17035
17267
|
}
|
|
17036
17268
|
/**
|
|
17037
|
-
*
|
|
17269
|
+
* Annuncia l'esecuzione all'ospite, con tutto cio' che sappiamo del momento in cui e' stato
|
|
17270
|
+
* premuto il comando.
|
|
17271
|
+
*
|
|
17272
|
+
* La finestra si chiude **subito**: non c'e' niente da attendere — l'emit e' sincrono — e
|
|
17273
|
+
* un'esecuzione puo' durare quanto vuole, con schermate da compilare puo' finire minuti dopo.
|
|
17274
|
+
* Tenere aperto il form degli ingressi per tutto quel tempo coprirebbe proprio il grafo che si
|
|
17275
|
+
* sta guardando.
|
|
17038
17276
|
*
|
|
17039
|
-
*
|
|
17040
|
-
*
|
|
17041
|
-
* degli ingressi per tutto quel tempo coprirebbe proprio il grafo che si sta guardando. Il
|
|
17042
|
-
* pannello «Debug» prende il posto dell'attesa, e l'esito ci arriva quando arriva.
|
|
17277
|
+
* Il pannello «Debug» si apre lo stesso, e non e' l'editor che presume un esito: e' il posto in
|
|
17278
|
+
* cui l'esito arrivera' **se** arrivera', e dice a chiare lettere quando non c'e' niente.
|
|
17043
17279
|
*/
|
|
17044
|
-
|
|
17280
|
+
confirmRun(inputs) {
|
|
17045
17281
|
const mode = this.runMode();
|
|
17046
17282
|
const flowName = this.session.flowName();
|
|
17047
17283
|
if (!mode || !flowName) {
|
|
17048
17284
|
return;
|
|
17049
17285
|
}
|
|
17050
17286
|
const request = {
|
|
17287
|
+
debug: mode === 'debug',
|
|
17051
17288
|
flowName,
|
|
17052
17289
|
version: this.session.version() ?? undefined,
|
|
17053
17290
|
inputs,
|
|
17054
|
-
|
|
17291
|
+
processType: this.store.document().processType,
|
|
17292
|
+
isDirty: this.isDirty(),
|
|
17293
|
+
definition: this.store.document(),
|
|
17055
17294
|
};
|
|
17056
|
-
this.isStartingRun.set(true);
|
|
17057
|
-
this.runOutcome.set(null);
|
|
17058
17295
|
this.wasRunInDebug.set(mode === 'debug');
|
|
17059
|
-
//
|
|
17296
|
+
// Un esito rimasto lì e' di un'altra esecuzione: sparisce prima di annunciarne una nuova.
|
|
17297
|
+
this.shownOutcome.set(null);
|
|
17298
|
+
// Il pannello e' il posto in cui si guarda cosa sta succedendo: si apre insieme all'annuncio.
|
|
17060
17299
|
this.activePanel.set('debug');
|
|
17061
17300
|
this.runMode.set(null);
|
|
17062
|
-
|
|
17063
|
-
const outcome = await (mode === 'debug' ? this.api.debugFlow(request) : this.api.runFlow(request));
|
|
17064
|
-
/**
|
|
17065
|
-
* Restituire un esito e' facoltativo: un ospite che mostra tutto in una sua interfaccia
|
|
17066
|
-
* risolve con `void`, e allora non c'e' niente da mettere nel pannello.
|
|
17067
|
-
*/
|
|
17068
|
-
this.runOutcome.set(outcome ?? null);
|
|
17069
|
-
if (!outcome) {
|
|
17070
|
-
this.notice.set({
|
|
17071
|
-
kind: 'info',
|
|
17072
|
-
message: mode === 'debug' ? 'Debug avviato.' : 'Esecuzione avviata.',
|
|
17073
|
-
});
|
|
17074
|
-
}
|
|
17075
|
-
}
|
|
17076
|
-
catch (error) {
|
|
17077
|
-
/**
|
|
17078
|
-
* `MissingService` non e' un guasto: e' un ambiente che quel comando non lo espone. Va
|
|
17079
|
-
* detto con parole sue — «il messaggio della primitiva» sarebbe «primitiva non
|
|
17080
|
-
* disponibile», che non aiuta chi ha appena premuto «Esegui».
|
|
17081
|
-
*/
|
|
17082
|
-
if (FlowApiError.is(error) && error.category === 'MissingService') {
|
|
17083
|
-
this.notice.set({
|
|
17084
|
-
kind: 'error',
|
|
17085
|
-
message: mode === 'debug'
|
|
17086
|
-
? 'Questo ambiente non espone il debug dei flow: manca l’implementazione di debugFlow.'
|
|
17087
|
-
: 'Questo ambiente non espone l’esecuzione dei flow: manca l’implementazione di runFlow.',
|
|
17088
|
-
});
|
|
17089
|
-
return;
|
|
17090
|
-
}
|
|
17091
|
-
// Un errore vero dell'ospite: si dice com'e' arrivato, senza travestirlo da esito.
|
|
17092
|
-
this.showError(error);
|
|
17093
|
-
}
|
|
17094
|
-
finally {
|
|
17095
|
-
this.isStartingRun.set(false);
|
|
17096
|
-
}
|
|
17301
|
+
this.runRequested.emit(request);
|
|
17097
17302
|
}
|
|
17098
17303
|
async createNewVersion() {
|
|
17099
17304
|
try {
|
|
@@ -17201,6 +17406,7 @@ class FlowBuilderComponent {
|
|
|
17201
17406
|
}
|
|
17202
17407
|
this.isLoading.set(true);
|
|
17203
17408
|
this.isDialogOpen.set(false);
|
|
17409
|
+
this.editingResource.set(null);
|
|
17204
17410
|
try {
|
|
17205
17411
|
await this.session.open(flowName, version);
|
|
17206
17412
|
this.selectedName.set(START_NODE_NAME);
|
|
@@ -17240,7 +17446,7 @@ class FlowBuilderComponent {
|
|
|
17240
17446
|
});
|
|
17241
17447
|
}
|
|
17242
17448
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.28", ngImport: i0, type: FlowBuilderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
17243
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.28", type: FlowBuilderComponent, isStandalone: true, selector: "fb-flow-builder", inputs: { flowName: { classPropertyName: "flowName", publicName: "flowName", isSignal: true, isRequired: false, transformFunction: null }, version: { classPropertyName: "version", publicName: "version", isSignal: true, isRequired: false, transformFunction: null }, author: { classPropertyName: "author", publicName: "author", isSignal: true, isRequired: false, transformFunction: null }, defaultProcessType: { classPropertyName: "defaultProcessType", publicName: "defaultProcessType", isSignal: true, isRequired: false, transformFunction: null }, inspectorMode: { classPropertyName: "inspectorMode", publicName: "inspectorMode", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { saved: "saved", activated: "activated", closeRequested: "closeRequested" }, host: { listeners: { "keydown": "onKeyDown($event)" } }, providers: [
|
|
17449
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.28", type: FlowBuilderComponent, isStandalone: true, selector: "fb-flow-builder", inputs: { flowName: { classPropertyName: "flowName", publicName: "flowName", isSignal: true, isRequired: false, transformFunction: null }, version: { classPropertyName: "version", publicName: "version", isSignal: true, isRequired: false, transformFunction: null }, author: { classPropertyName: "author", publicName: "author", isSignal: true, isRequired: false, transformFunction: null }, defaultProcessType: { classPropertyName: "defaultProcessType", publicName: "defaultProcessType", isSignal: true, isRequired: false, transformFunction: null }, inspectorMode: { classPropertyName: "inspectorMode", publicName: "inspectorMode", isSignal: true, isRequired: false, transformFunction: null }, runOutcome: { classPropertyName: "runOutcome", publicName: "runOutcome", isSignal: true, isRequired: false, transformFunction: null }, isRunning: { classPropertyName: "isRunning", publicName: "isRunning", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { saved: "saved", activated: "activated", closeRequested: "closeRequested", runRequested: "runRequested" }, host: { listeners: { "keydown": "onKeyDown($event)" } }, providers: [
|
|
17244
17450
|
FlowDocumentStore,
|
|
17245
17451
|
FlowDictionaryStore,
|
|
17246
17452
|
FlowCatalogStore,
|
|
@@ -17248,7 +17454,7 @@ class FlowBuilderComponent {
|
|
|
17248
17454
|
FormulaValidationService,
|
|
17249
17455
|
FlowEditorSession,
|
|
17250
17456
|
FlowLayoutService,
|
|
17251
|
-
], 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 <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 <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 [processType]=\"document().processType\" (elementPicked)=\"onElementPicked($event)\" />\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 (isStartingRun()) {\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 [target]=\"resourceTarget()\" (closed)=\"setPanel('inspector')\" />\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]=\"runOutcome()\"\r\n [wasDebug]=\"wasRunInDebug()\"\r\n [isRunning]=\"isStartingRun()\"\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: raccoglie i valori e li passa all\u2019ospite,\r\n che e\u2019 l\u2019unico a sapere dove gira il motore.\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]=\"isStartingRun()\"\r\n (confirmed)=\"confirmRun($event)\"\r\n (cancelled)=\"closeRun()\"\r\n />\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%;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__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"], 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: ResourcePanelComponent, selector: "fb-resource-panel", inputs: ["target"], outputs: ["closed"] }, { 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 });
|
|
17457
|
+
], 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 <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 <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 [processType]=\"document().processType\" (elementPicked)=\"onElementPicked($event)\" />\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%;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__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"], 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 });
|
|
17252
17458
|
}
|
|
17253
17459
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.28", ngImport: i0, type: FlowBuilderComponent, decorators: [{
|
|
17254
17460
|
type: Component,
|
|
@@ -17262,6 +17468,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.28", ngImpo
|
|
|
17262
17468
|
GroupPanelComponent,
|
|
17263
17469
|
PasteDialogComponent,
|
|
17264
17470
|
ProblemsPanelComponent,
|
|
17471
|
+
ResourceDialogComponent,
|
|
17265
17472
|
ResourcePanelComponent,
|
|
17266
17473
|
RunDialogComponent,
|
|
17267
17474
|
VersionPanelComponent, SelectValueDirective
|
|
@@ -17273,12 +17480,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.28", ngImpo
|
|
|
17273
17480
|
FormulaValidationService,
|
|
17274
17481
|
FlowEditorSession,
|
|
17275
17482
|
FlowLayoutService,
|
|
17276
|
-
], 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 <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 <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 [processType]=\"document().processType\" (elementPicked)=\"onElementPicked($event)\" />\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 (isStartingRun()) {\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 [target]=\"resourceTarget()\" (closed)=\"setPanel('inspector')\" />\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]=\"runOutcome()\"\r\n [wasDebug]=\"wasRunInDebug()\"\r\n [isRunning]=\"isStartingRun()\"\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: raccoglie i valori e li passa all\u2019ospite,\r\n che e\u2019 l\u2019unico a sapere dove gira il motore.\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]=\"isStartingRun()\"\r\n (confirmed)=\"confirmRun($event)\"\r\n (cancelled)=\"closeRun()\"\r\n />\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%;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__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"] }]
|
|
17277
|
-
}], 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 }] }], saved: [{ type: i0.Output, args: ["saved"] }], activated: [{ type: i0.Output, args: ["activated"] }], closeRequested: [{ type: i0.Output, args: ["closeRequested"] }], 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 }] }] } });
|
|
17483
|
+
], 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 <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 <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 [processType]=\"document().processType\" (elementPicked)=\"onElementPicked($event)\" />\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%;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__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"] }]
|
|
17484
|
+
}], 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 }] }] } });
|
|
17278
17485
|
|
|
17279
17486
|
/**
|
|
17280
17487
|
* Generated bundle index. Do not edit.
|
|
17281
17488
|
*/
|
|
17282
17489
|
|
|
17283
|
-
export { ConditionEditorComponent, ConnectorEditorComponent, DebugPanelComponent, DynamicScreenInspectorComponent, ENUM_CHOICE_SET_DEFAULT_DISPLAY_FIELD, ENUM_CHOICE_SET_DEFAULT_VALUE_FIELD, ENUM_CHOICE_SET_FIELDS, ElementDialogComponent, ElementInspectorComponent, ElementPaletteComponent, EnumValuePickerComponent, FALLBACK_COLLECTION_BY_TYPE, FALLBACK_TYPE_LABEL, FLOW_BUILDER_HTTP_CONFIG, FLOW_CLIPBOARD_KIND, FLOW_CLIPBOARD_VERSION, FLOW_ELEMENT_ICONS, FLOW_ELEMENT_VARIANT_FIELDS, FLOW_ELEMENT_VARIANT_ICONS, FLOW_ERROR_FALLBACK_MESSAGE, FLOW_ERROR_HTTP_STATUS, FLOW_FOLD_HEIGHT, FLOW_FOLD_WIDTH, FLOW_GROUP_COLORS, FLOW_NAME_PATTERN, FLOW_NODE_COLLECTIONS, FLOW_NODE_HEIGHT, FLOW_NODE_WIDTH, FLOW_REFERENCE_FIELDS, FLOW_RESOURCE_COLLECTIONS, FLOW_VALUE_FIELDS, FLOW_VALUE_LITERAL_FIELDS, FieldAssignmentEditorComponent, FieldPickerComponent, FieldValuePickerComponent, FlowApiError, FlowBuilderApi, FlowBuilderComponent, FlowCanvasComponent, FlowCatalogStore, FlowClipboardService, FlowDictionaryStore, FlowDocumentStore, FlowEditorSession, FlowLayoutService, FlowValidationStore, FormulaEditorComponent, FormulaValidationService, GROUP_HEADER_HEIGHT, GROUP_MIN_HEIGHT, GROUP_MIN_WIDTH, GROUP_PADDING, GroupPanelComponent, HttpFlowBuilderApi, NamePickerComponent, NodeInspectorBase, ORCHESTRATION_CONDITION_OUTPUT, ObjectPickerComponent, OrchestratedStageInspectorComponent, ParameterEditorComponent, PasteDialogComponent, ProblemsPanelComponent, RecordFilterEditorComponent, ReferencePickerComponent, ResourcePanelComponent, RunDialogComponent, SEVERITY_BUCKETS, SEVERITY_ICON, SEVERITY_LABEL, START_NODE_NAME, SelectValueDirective, StartInspectorComponent, StructureMemberPickerComponent, StructurePickerComponent, TYPES_WITH_AUTOMATIC_OUTPUT, TYPE_BY_COLLECTION, UNSUPPORTED_TYPES, ValueEditorComponent, VersionPanelComponent, allFieldsOf, applyPaste, areTypesComparable, boundsAround, buildClipboardPayload, canvasFoldId, canvasGroupId, canvasNodeId, checkConditionLogic, checkFlowName, choiceSetSourceOf, declaredChoiceSetSources, describeFieldPath, describePathEntry, duplicateField, elementIcon, emptyFlowDefinition, enumChoiceSetFieldType, fieldAt, filterReferences, flattenFields, flowNodeWidth, flowNodeWidthClass, foldSourceConnectorId, foldTargetConnectorId, groupColorClass, groupRect, hasAutoSize, innerNamesOf, insertField, isClipboardPayload, isCustomConditionLogic, isEmptyReferenceFilter, isFieldResource, isFoldReference, isGlobalReference, isNumericType, isPathInside, isTypeCheckedOperator, isUnknownEnumChoiceSetField, isValidFlowName, isValued, loadPathLevel, matchesReferenceFilter, membersInside, moveCondition, moveField, navigatePath, otherSourceFieldsOf, outletByKey, outletsOf, parseCanvasFoldId, parseCanvasGroupId, parseCanvasNodeId, parseFieldPath, parseInvariantNumber, parseSourceConnectorId, parseTargetConnectorId, pathAvailableNames, pathContainerLabel, pathKey, pathNotVerifiableMessage, planPaste, recomputeMembers, referenceRoot, referencedRootsOf, remapConditionLogic, removeCondition, removeField, resolvePath, rewriteReferences, sameMembers, samePath, screenActionNames, screenFieldNames, severityBucket, slugifyFlowName, sourceConnectorId, stageStepNames, stageStepOutputReferenced, stepsOf, targetConnectorId, typeOfCollection, uniqueFlowName, valuedFieldOf, valuedFieldsOf, variantFieldOf, variantOf, variantPresetOf };
|
|
17490
|
+
export { ConditionEditorComponent, ConnectorEditorComponent, DebugPanelComponent, DynamicScreenInspectorComponent, ENUM_CHOICE_SET_DEFAULT_DISPLAY_FIELD, ENUM_CHOICE_SET_DEFAULT_VALUE_FIELD, ENUM_CHOICE_SET_FIELDS, ElementDialogComponent, ElementInspectorComponent, ElementPaletteComponent, EnumValuePickerComponent, FALLBACK_COLLECTION_BY_TYPE, FALLBACK_TYPE_LABEL, FLOW_BUILDER_HTTP_CONFIG, FLOW_CLIPBOARD_KIND, FLOW_CLIPBOARD_VERSION, FLOW_ELEMENT_ICONS, FLOW_ELEMENT_VARIANT_FIELDS, FLOW_ELEMENT_VARIANT_ICONS, FLOW_ERROR_FALLBACK_MESSAGE, FLOW_ERROR_HTTP_STATUS, FLOW_FOLD_HEIGHT, FLOW_FOLD_WIDTH, FLOW_GROUP_COLORS, FLOW_NAME_PATTERN, FLOW_NODE_COLLECTIONS, FLOW_NODE_HEIGHT, FLOW_NODE_WIDTH, FLOW_REFERENCE_FIELDS, FLOW_RESOURCE_COLLECTIONS, FLOW_VALUE_FIELDS, FLOW_VALUE_LITERAL_FIELDS, FieldAssignmentEditorComponent, FieldPickerComponent, FieldValuePickerComponent, FlowApiError, FlowBuilderApi, FlowBuilderComponent, FlowCanvasComponent, FlowCatalogStore, FlowClipboardService, FlowDictionaryStore, FlowDocumentStore, FlowEditorSession, FlowLayoutService, FlowValidationStore, FormulaEditorComponent, FormulaValidationService, GROUP_HEADER_HEIGHT, GROUP_MIN_HEIGHT, GROUP_MIN_WIDTH, GROUP_PADDING, GroupPanelComponent, HttpFlowBuilderApi, NamePickerComponent, NodeInspectorBase, ORCHESTRATION_CONDITION_OUTPUT, ObjectPickerComponent, OrchestratedStageInspectorComponent, ParameterEditorComponent, PasteDialogComponent, ProblemsPanelComponent, RecordFilterEditorComponent, ReferencePickerComponent, ResourceDialogComponent, ResourceFormComponent, ResourcePanelComponent, RunDialogComponent, SEVERITY_BUCKETS, SEVERITY_ICON, SEVERITY_LABEL, START_NODE_NAME, SelectValueDirective, StartInspectorComponent, StructureMemberPickerComponent, StructurePickerComponent, TYPES_WITH_AUTOMATIC_OUTPUT, TYPE_BY_COLLECTION, UNSUPPORTED_TYPES, ValueEditorComponent, VersionPanelComponent, allFieldsOf, applyPaste, areTypesComparable, boundsAround, buildClipboardPayload, canvasFoldId, canvasGroupId, canvasNodeId, checkConditionLogic, checkFlowName, choiceSetSourceOf, declaredChoiceSetSources, describeFieldPath, describePathEntry, duplicateField, elementIcon, emptyFlowDefinition, enumChoiceSetFieldType, fieldAt, filterReferences, flattenFields, flowNodeWidth, flowNodeWidthClass, foldSourceConnectorId, foldTargetConnectorId, groupColorClass, groupRect, hasAutoSize, innerNamesOf, insertField, isClipboardPayload, isCustomConditionLogic, isEmptyReferenceFilter, isFieldResource, isFoldReference, isGlobalReference, isNumericType, isPathInside, isTypeCheckedOperator, isUnknownEnumChoiceSetField, isValidFlowName, isValued, loadPathLevel, matchesReferenceFilter, membersInside, moveCondition, moveField, navigatePath, otherSourceFieldsOf, outletByKey, outletsOf, parseCanvasFoldId, parseCanvasGroupId, parseCanvasNodeId, parseFieldPath, parseInvariantNumber, parseSourceConnectorId, parseTargetConnectorId, pathAvailableNames, pathContainerLabel, pathKey, pathNotVerifiableMessage, planPaste, recomputeMembers, referenceRoot, referencedRootsOf, remapConditionLogic, removeCondition, removeField, resolvePath, rewriteReferences, sameMembers, samePath, screenActionNames, screenFieldNames, severityBucket, slugifyFlowName, sourceConnectorId, stageStepNames, stageStepOutputReferenced, stepsOf, targetConnectorId, typeOfCollection, uniqueFlowName, valuedFieldOf, valuedFieldsOf, variantFieldOf, variantOf, variantPresetOf };
|
|
17284
17491
|
//# sourceMappingURL=esfaenza-flow-builder.mjs.map
|