@esfaenza/flow-builder 20.3.42 → 20.3.44
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 +77 -7
- package/fesm2022/esfaenza-flow-builder.mjs +809 -146
- package/fesm2022/esfaenza-flow-builder.mjs.map +1 -1
- package/index.d.ts +263 -8
- package/package.json +1 -1
|
@@ -1162,8 +1162,14 @@ function moveCondition(holder, from, to) {
|
|
|
1162
1162
|
* Verifica che un'espressione custom sia sintatticamente accettabile e che ogni indice
|
|
1163
1163
|
* esista. Serve al feedback immediato nel form: la verita' resta della validazione
|
|
1164
1164
|
* del backend (`CONDITION_LOGIC_INVALID`).
|
|
1165
|
+
*
|
|
1166
|
+
* Il `term` esiste perche' la stessa regola vale su **due strutture diverse**: le condizioni
|
|
1167
|
+
* (§4.3) e i filtri sui record (§4.4), che dalla revisione del contratto hanno la logica
|
|
1168
|
+
* personalizzata anche sui dynamic choice set. Il progetto tiene distinte le due cose in ogni
|
|
1169
|
+
* altro punto, e un messaggio che dice «condizione» dentro un elenco di filtri manda a cercare
|
|
1170
|
+
* qualcosa che in quel form non c'e'.
|
|
1165
1171
|
*/
|
|
1166
|
-
function checkConditionLogic(logic, conditionCount) {
|
|
1172
|
+
function checkConditionLogic(logic, conditionCount, term = 'condizione') {
|
|
1167
1173
|
const trimmed = logic?.trim();
|
|
1168
1174
|
if (!trimmed) {
|
|
1169
1175
|
return null;
|
|
@@ -1192,13 +1198,14 @@ function checkConditionLogic(logic, conditionCount) {
|
|
|
1192
1198
|
return 'Parentesi non bilanciate.';
|
|
1193
1199
|
}
|
|
1194
1200
|
const indexes = trimmed.match(/\d+/g) ?? [];
|
|
1201
|
+
const plural = term === 'filtro' ? 'filtri' : 'condizioni';
|
|
1195
1202
|
if (indexes.length === 0) {
|
|
1196
|
-
return
|
|
1203
|
+
return `L’espressione non referenzia nessun${term === 'filtro' ? '' : 'a'} ${term}.`;
|
|
1197
1204
|
}
|
|
1198
1205
|
for (const raw of indexes) {
|
|
1199
1206
|
const index = Number(raw);
|
|
1200
1207
|
if (index < 1 || index > conditionCount) {
|
|
1201
|
-
return
|
|
1208
|
+
return `${term === 'filtro' ? 'Il' : 'La'} ${term} ${index} non esiste: ${term === 'filtro' ? 'sono definiti' : 'sono definite'} ${conditionCount} ${plural}.`;
|
|
1202
1209
|
}
|
|
1203
1210
|
}
|
|
1204
1211
|
return null;
|
|
@@ -2307,6 +2314,83 @@ function sameMembers(left, right) {
|
|
|
2307
2314
|
return first.length === right.length && first.every((name, index) => name === right[index]);
|
|
2308
2315
|
}
|
|
2309
2316
|
|
|
2317
|
+
/**
|
|
2318
|
+
* I **merge field** `{!...}` — FRONTEND.md §5.12.
|
|
2319
|
+
*
|
|
2320
|
+
* Un testo del flow puo' citare una risorsa dentro un'isola: `«Gentile {!Cliente.Nome},»`. E'
|
|
2321
|
+
* un uso a tutti gli effetti — il runtime lo risolve quando l'elemento scatta — ma non e' un
|
|
2322
|
+
* campo di riferimento, quindi chi cammina il documento non lo trova da se'.
|
|
2323
|
+
*
|
|
2324
|
+
* Questo modulo tiene le tre cose che servono a trovarlo, e sta **a parte** per una ragione
|
|
2325
|
+
* strutturale: le usano in due, il conteggio degli usi (`flow-references.ts`) e la riscrittura
|
|
2326
|
+
* della rinomina (`flow-clipboard.ts`), e quel secondo modulo e' già importato dal primo —
|
|
2327
|
+
* metterle lì dentro avrebbe chiuso un ciclo, metterle nel primo lo avrebbe aperto al contrario.
|
|
2328
|
+
* Un modulo foglia che nessuno dei due deve importare a sua volta e' l'unica forma senza cicli, e
|
|
2329
|
+
* ha comunque un senso proprio: la sintassi dell'isola e' del **contratto**, non del motore delle
|
|
2330
|
+
* formule (§6.3), quindi riconoscerla non e' un'approssimazione come interpretare un'espressione.
|
|
2331
|
+
*/
|
|
2332
|
+
/**
|
|
2333
|
+
* I campi che il contratto dichiara con i merge field.
|
|
2334
|
+
*
|
|
2335
|
+
* Sono quelli e solo quelli: `interviewLabel` (§3.1), il `text` di un text template (§4.6),
|
|
2336
|
+
* `fieldText` e `helpText` di uno screen (§5.2), il `choiceText` di una choice (§5.2) e
|
|
2337
|
+
* l'`errorMessage` di un Custom Error (§5.12).
|
|
2338
|
+
*
|
|
2339
|
+
* E' un **elenco** e non «tutte le stringhe», per la stessa ragione per cui i riferimenti hanno
|
|
2340
|
+
* il loro: `{!Totale}` scritto in un campo che il contratto non dichiara con merge field non
|
|
2341
|
+
* viene espanso da nessuno, e trattarlo come un uso direbbe «usata» di una risorsa che il runtime
|
|
2342
|
+
* non legge — cioe' farebbe tenere una variabile morta credendo di averne una viva. Con lo stesso
|
|
2343
|
+
* patto degli altri elenchi: se il contratto ne aggiunge uno, si aggiunge **qui**, una volta.
|
|
2344
|
+
*/
|
|
2345
|
+
const FLOW_MERGE_TEXT_FIELDS = new Set([
|
|
2346
|
+
'interviewLabel',
|
|
2347
|
+
'text',
|
|
2348
|
+
'fieldText',
|
|
2349
|
+
'helpText',
|
|
2350
|
+
'choiceText',
|
|
2351
|
+
'errorMessage',
|
|
2352
|
+
]);
|
|
2353
|
+
/** `true` se il testo contiene almeno un'isola aperta: e' il filtro piu' economico. */
|
|
2354
|
+
function hasMergeField(text) {
|
|
2355
|
+
return text.includes('{!');
|
|
2356
|
+
}
|
|
2357
|
+
/** Il contenuto delle isole `{!...}`, nell'ordine in cui compaiono. */
|
|
2358
|
+
function mergeFieldIslands(text) {
|
|
2359
|
+
const islands = [];
|
|
2360
|
+
const pattern = /\{!([^}]*)\}/g;
|
|
2361
|
+
let match;
|
|
2362
|
+
while ((match = pattern.exec(text)) !== null) {
|
|
2363
|
+
islands.push(match[1]);
|
|
2364
|
+
}
|
|
2365
|
+
return islands;
|
|
2366
|
+
}
|
|
2367
|
+
/**
|
|
2368
|
+
* Riscrive un nome dentro le isole di un testo, lasciando intatto il resto.
|
|
2369
|
+
*
|
|
2370
|
+
* Tre cose che la regex deve rispettare, e sono le stesse che rendono corretta la riduzione alla
|
|
2371
|
+
* radice nel conteggio.
|
|
2372
|
+
*
|
|
2373
|
+
* Si sostituisce solo un **token intero**: in `{!Totale_Con_IVA}` il nome `Totale` non compare,
|
|
2374
|
+
* compare un altro identificatore che comincia allo stesso modo, e sostituirlo produrrebbe un
|
|
2375
|
+
* riferimento inventato.
|
|
2376
|
+
*
|
|
2377
|
+
* Il carattere precedente non puo' essere un **punto**: in `{!Ordine.Cliente}` il `Cliente` e' un
|
|
2378
|
+
* membro di `Ordine`, non la risorsa `Cliente`, ed e' l'errore che si fa piu' facilmente perche'
|
|
2379
|
+
* il testo da cercare c'e' davvero. Vale anche per il `$` — `{!$Record.Numero}` non e' una
|
|
2380
|
+
* risorsa del flow.
|
|
2381
|
+
*
|
|
2382
|
+
* E si tocca **solo** cio' che sta dentro `{!...}`: la stessa parola nella frase intorno e' una
|
|
2383
|
+
* parola, non un riferimento, e riscriverla cambierebbe un messaggio per l'utente.
|
|
2384
|
+
*/
|
|
2385
|
+
function rewriteMergeText(text, oldName, newName) {
|
|
2386
|
+
if (!hasMergeField(text)) {
|
|
2387
|
+
return text;
|
|
2388
|
+
}
|
|
2389
|
+
const escaped = oldName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
2390
|
+
const token = new RegExp(`(^|[^A-Za-z0-9_.$])(${escaped})(?![A-Za-z0-9_$])`, 'g');
|
|
2391
|
+
return text.replace(/\{![^}]*\}/g, (island) => island.replace(token, (_match, before) => `${before}${newName}`));
|
|
2392
|
+
}
|
|
2393
|
+
|
|
2310
2394
|
/**
|
|
2311
2395
|
* Copia e incolla di **elementi** — la logica pura, senza Angular e senza storage.
|
|
2312
2396
|
*
|
|
@@ -2362,11 +2446,21 @@ const FLOW_REFERENCE_FIELDS = new Set([
|
|
|
2362
2446
|
'triggerFieldName',
|
|
2363
2447
|
]);
|
|
2364
2448
|
/**
|
|
2365
|
-
* Riscrive ogni occorrenza di un nome nei campi che contengono riferimenti
|
|
2449
|
+
* Riscrive ogni occorrenza di un nome nei campi che contengono riferimenti **e nei merge field**.
|
|
2366
2450
|
*
|
|
2367
2451
|
* Cammina tutto cio' che gli si passa — un documento intero per la rinomina, il solo frammento
|
|
2368
2452
|
* copiato per l'incolla — perche' i riferimenti compaiono in una ventina di posti diversi (§4)
|
|
2369
2453
|
* e l'elenco dei posti sarebbe la cosa che si dimentica di aggiornare.
|
|
2454
|
+
*
|
|
2455
|
+
* I **merge field** ci sono entrati insieme al loro conteggio, e non e' un caso: mostrare «5 usi»
|
|
2456
|
+
* contando i testi e poi riscriverne solo tre significa lasciare due messaggi rotti proprio a chi
|
|
2457
|
+
* ha letto il numero per fidarsi della rinomina. Il testo intorno all'isola non si tocca — lì la
|
|
2458
|
+
* stessa parola e' una parola — e la regola sta in `flow-merge-text.ts`.
|
|
2459
|
+
*
|
|
2460
|
+
* Restano fuori le **formule**: la loro sintassi e' del motore (§6.3), e sostituire un
|
|
2461
|
+
* identificatore in un'espressione che non si interpreta vuol dire indovinare. Chi mostra il
|
|
2462
|
+
* conteggio lo dice, invece di far scoprire dopo che una formula punta a un nome che non c'e'
|
|
2463
|
+
* piu' (`describeNameUses` le tiene contate a parte proprio per questo).
|
|
2370
2464
|
*/
|
|
2371
2465
|
function rewriteReferences(root, oldName, newName) {
|
|
2372
2466
|
if (!oldName || !newName || oldName === newName) {
|
|
@@ -2392,6 +2486,10 @@ function rewriteReferences(root, oldName, newName) {
|
|
|
2392
2486
|
}
|
|
2393
2487
|
const record = value;
|
|
2394
2488
|
for (const [key, child] of Object.entries(record)) {
|
|
2489
|
+
if (FLOW_MERGE_TEXT_FIELDS.has(key) && typeof child === 'string') {
|
|
2490
|
+
record[key] = rewriteMergeText(child, oldName, newName);
|
|
2491
|
+
continue;
|
|
2492
|
+
}
|
|
2395
2493
|
if (!FLOW_REFERENCE_FIELDS.has(key)) {
|
|
2396
2494
|
walk(child);
|
|
2397
2495
|
continue;
|
|
@@ -6633,16 +6731,25 @@ function pathTypes(catalog, request) {
|
|
|
6633
6731
|
* `position: absolute` dentro il controllo, quindi `contains` basta e non serve registrare
|
|
6634
6732
|
* l'elenco. Un bersaglio che nel frattempo e' stato staccato dal DOM (`isConnected: false`)
|
|
6635
6733
|
* non si giudica: era dentro, e chiudere lì vorrebbe dire chiudere su un proprio gesto.
|
|
6734
|
+
*
|
|
6735
|
+
* L'host va bene finche' il componente **e'** il controllo, che e' il caso dei cinque picker.
|
|
6736
|
+
* Dove invece il controllo e' una parte di un componente piu' grande — la tendina dei codici nel
|
|
6737
|
+
* pannello dei problemi, che e' alto quanto il pannello — l'host e' il confine sbagliato: un clic
|
|
6738
|
+
* su una riga dei rilievi e' «altrove» per l'utente e «dentro» per `contains`, e la tendina
|
|
6739
|
+
* restava aperta. Per questo il confine si puo' passare (`boundary`), e resta l'host quando non
|
|
6740
|
+
* lo si passa.
|
|
6636
6741
|
*/
|
|
6637
|
-
function closeOnOutside(isOpen, close) {
|
|
6742
|
+
function closeOnOutside(isOpen, close, boundary) {
|
|
6638
6743
|
const host = inject(ElementRef).nativeElement;
|
|
6639
6744
|
const doc = inject(DOCUMENT);
|
|
6745
|
+
/** Il confine si rilegge a ogni gesto: un `viewChild` non e' risolto alla registrazione. */
|
|
6746
|
+
const edgeOf = () => boundary?.() ?? host;
|
|
6640
6747
|
const onPointerDown = (event) => {
|
|
6641
6748
|
const target = event.target;
|
|
6642
6749
|
if (!target || (target instanceof Node && !target.isConnected)) {
|
|
6643
6750
|
return;
|
|
6644
6751
|
}
|
|
6645
|
-
if (isOpen() && !
|
|
6752
|
+
if (isOpen() && !edgeOf().contains(target)) {
|
|
6646
6753
|
close();
|
|
6647
6754
|
}
|
|
6648
6755
|
};
|
|
@@ -6656,6 +6763,8 @@ function closeOnOutside(isOpen, close) {
|
|
|
6656
6763
|
* perdere il posto in cui si stava lavorando.
|
|
6657
6764
|
*/
|
|
6658
6765
|
const onKeyDown = (event) => {
|
|
6766
|
+
// `Esc` si accetta da tutto il componente e non dal solo controllo: il focus puo' essere
|
|
6767
|
+
// finito altrove nel pannello, e la via d'uscita da tastiera non deve dipendere da dov'e'.
|
|
6659
6768
|
if (event.key !== 'Escape' || !isOpen() || !host.contains(event.target)) {
|
|
6660
6769
|
return;
|
|
6661
6770
|
}
|
|
@@ -9364,11 +9473,23 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.28", ngImpo
|
|
|
9364
9473
|
* Due dettagli che il modello impone e che qui sono espliciti:
|
|
9365
9474
|
*
|
|
9366
9475
|
* - **`filterLogic` non esiste su tutti gli elementi che hanno `filters`.** C'e' su Get
|
|
9367
|
-
* Records, Create Records, Start
|
|
9368
|
-
*
|
|
9476
|
+
* Records, Create Records, Start, sugli eventi di un Wait e sui **dynamic choice set**;
|
|
9477
|
+
* **non** c'e' su Update Records e Delete Records, dove i filtri sono sempre in AND.
|
|
9369
9478
|
* Inviarlo dove non esiste significa perderlo silenziosamente al primo salvataggio:
|
|
9370
9479
|
* l'input `supportsLogic` decide se il controllo viene mostrato.
|
|
9371
9480
|
*
|
|
9481
|
+
* - **`Formula` non e' un valore di `filterLogic`** (§4.4): su un dynamic choice set e' un
|
|
9482
|
+
* errore di validazione, altrove passa la validazione e **fa fallire l'esecuzione**. La
|
|
9483
|
+
* formula di un filtro e' un campo suo, `filterFormula`, che oggi esiste sullo Start e su Get
|
|
9484
|
+
* Records: la modalita' «Formula» scrive quindi **quello** e cancella `filterLogic`. Un
|
|
9485
|
+
* documento scritto altrove puo' comunque portarcelo, e allora si dice — trattarlo come una
|
|
9486
|
+
* modalita' valida vorrebbe dire riscriverlo a ogni salvataggio.
|
|
9487
|
+
*
|
|
9488
|
+
* - **gli indici dell'espressione sono 1-based e scalano.** Cancellare un filtro senza
|
|
9489
|
+
* riscrivere l'espressione produce un `CONDITION_LOGIC_INVALID` oppure — peggio — un'
|
|
9490
|
+
* espressione valida che punta al filtro sbagliato: la rimappatura e la verifica locale sono
|
|
9491
|
+
* le stesse delle condizioni (`core/condition-logic.util.ts`), perche' la regola e' la stessa.
|
|
9492
|
+
*
|
|
9372
9493
|
* - I campi proposti passano da `GET /schema/{object}/fields?usage=filterable`, così un
|
|
9373
9494
|
* campo non filtrabile non arriva nemmeno alla validazione (`FIELD_NOT_FILTERABLE`).
|
|
9374
9495
|
* La scrittura libera resta possibile — un catalogo incompleto non deve bloccare il campo — ma i
|
|
@@ -9447,12 +9568,21 @@ class RecordFilterEditorComponent {
|
|
|
9447
9568
|
}
|
|
9448
9569
|
return this.allFields().some((field) => field.name === 'Id' && field.isFilterable === false);
|
|
9449
9570
|
}, ...(ngDevMode ? [{ debugName: "identifierNotFilterable" }] : []));
|
|
9571
|
+
/**
|
|
9572
|
+
* La modalita' attiva. La **formula si riconosce dal suo campo** e non da `filterLogic`: il
|
|
9573
|
+
* valore `Formula` non e' supportato da nessun elemento (§4.4), quindi non puo' essere cio' che
|
|
9574
|
+
* accende la modalita' — altrimenti l'editor riscriverebbe nel documento un valore che fa
|
|
9575
|
+
* fallire l'esecuzione.
|
|
9576
|
+
*/
|
|
9450
9577
|
logicMode = computed(() => {
|
|
9578
|
+
if (this.supportsFormula() && this.holder().filterFormula !== undefined) {
|
|
9579
|
+
return 'formula';
|
|
9580
|
+
}
|
|
9451
9581
|
const logic = this.holder().filterLogic;
|
|
9452
9582
|
if (!logic) {
|
|
9453
9583
|
return 'and';
|
|
9454
9584
|
}
|
|
9455
|
-
const normalized = logic
|
|
9585
|
+
const normalized = logic.trim().toLowerCase();
|
|
9456
9586
|
if (normalized === 'and') {
|
|
9457
9587
|
return 'and';
|
|
9458
9588
|
}
|
|
@@ -9460,14 +9590,39 @@ class RecordFilterEditorComponent {
|
|
|
9460
9590
|
return 'or';
|
|
9461
9591
|
}
|
|
9462
9592
|
if (normalized === 'formula') {
|
|
9463
|
-
|
|
9593
|
+
// Valore non supportato: si segnala (`unsupportedFormulaLogic`) e nel frattempo i filtri
|
|
9594
|
+
// si combinano come dice il default, che e' AND.
|
|
9595
|
+
return 'and';
|
|
9464
9596
|
}
|
|
9465
9597
|
return 'custom';
|
|
9466
9598
|
}, ...(ngDevMode ? [{ debugName: "logicMode" }] : []));
|
|
9599
|
+
/**
|
|
9600
|
+
* `filterLogic: "Formula"` letto dal documento. Non lo scrive nessun gesto di questo editor:
|
|
9601
|
+
* arriva da un flow scritto altrove, e va detto invece di essere corretto di soppiatto —
|
|
9602
|
+
* cancellarlo sarebbe una modifica che l'utente non ha chiesto, su un campo che potrebbe
|
|
9603
|
+
* essere l'unico indizio di cosa quel flow voleva fare.
|
|
9604
|
+
*/
|
|
9605
|
+
unsupportedFormulaLogic = computed(() => this.holder().filterLogic?.trim().toLowerCase() === 'formula', ...(ngDevMode ? [{ debugName: "unsupportedFormulaLogic" }] : []));
|
|
9467
9606
|
customLogic = computed(() => {
|
|
9468
9607
|
const mode = this.logicMode();
|
|
9469
9608
|
return mode === 'custom' ? this.holder().filterLogic : '';
|
|
9470
9609
|
}, ...(ngDevMode ? [{ debugName: "customLogic" }] : []));
|
|
9610
|
+
/**
|
|
9611
|
+
* Il riscontro locale sull'espressione: indici che non esistono, parentesi sbilanciate, termini
|
|
9612
|
+
* rimasti orfani (`?`) dopo la cancellazione di un filtro. La verita' resta della validazione
|
|
9613
|
+
* del backend (`CONDITION_LOGIC_INVALID`, che e' un **errore** e blocca l'attivazione), ma
|
|
9614
|
+
* scoprirlo lì significa scoprirlo dopo.
|
|
9615
|
+
*/
|
|
9616
|
+
customLogicError = computed(() => {
|
|
9617
|
+
if (this.logicMode() !== 'custom') {
|
|
9618
|
+
return null;
|
|
9619
|
+
}
|
|
9620
|
+
const logic = this.customLogic();
|
|
9621
|
+
if (logic.includes('?')) {
|
|
9622
|
+
return 'Un termine si riferiva a un filtro cancellato: correggi l’espressione.';
|
|
9623
|
+
}
|
|
9624
|
+
return checkConditionLogic(logic, this.filters().length, 'filtro');
|
|
9625
|
+
}, ...(ngDevMode ? [{ debugName: "customLogicError" }] : []));
|
|
9471
9626
|
showEmptyWarning = computed(() => !!this.emptyWarning() && this.filters().length === 0, ...(ngDevMode ? [{ debugName: "showEmptyWarning" }] : []));
|
|
9472
9627
|
addFilter() {
|
|
9473
9628
|
this.changed.emit((holder) => {
|
|
@@ -9477,12 +9632,22 @@ class RecordFilterEditorComponent {
|
|
|
9477
9632
|
}
|
|
9478
9633
|
removeFilter(index) {
|
|
9479
9634
|
this.changed.emit((holder) => {
|
|
9635
|
+
const count = holder.filters?.length ?? 0;
|
|
9480
9636
|
holder.filters?.splice(index, 1);
|
|
9481
9637
|
if (holder.filters && holder.filters.length === 0) {
|
|
9482
9638
|
// Lista vuota dichiarata vs assente: il backend le distingue, ma per un diff
|
|
9483
9639
|
// pulito conviene omettere le liste vuote (§2).
|
|
9484
9640
|
delete holder.filters;
|
|
9485
9641
|
}
|
|
9642
|
+
// Gli indici dei filtri dopo quello cancellato scalano: senza riscrivere l'espressione
|
|
9643
|
+
// resterebbe valida ma puntata sul filtro sbagliato, che e' il difetto peggiore dei due.
|
|
9644
|
+
if (isCustomConditionLogic(holder.filterLogic)) {
|
|
9645
|
+
const mapping = [];
|
|
9646
|
+
for (let position = 0; position < count; position += 1) {
|
|
9647
|
+
mapping.push(position < index ? position : position === index ? -1 : position - 1);
|
|
9648
|
+
}
|
|
9649
|
+
holder.filterLogic = remapConditionLogic(holder.filterLogic, mapping).logic;
|
|
9650
|
+
}
|
|
9486
9651
|
});
|
|
9487
9652
|
}
|
|
9488
9653
|
setField(index, field) {
|
|
@@ -9530,6 +9695,12 @@ class RecordFilterEditorComponent {
|
|
|
9530
9695
|
}
|
|
9531
9696
|
setLogicMode(mode) {
|
|
9532
9697
|
this.changed.emit((holder) => {
|
|
9698
|
+
if (mode !== 'formula') {
|
|
9699
|
+
// Uscendo dalla formula il campo se ne va con la modalita': lasciarlo scritto vorrebbe
|
|
9700
|
+
// dire mandare filtri **e** formula, cioe' due criteri e nessun modo di sapere quale
|
|
9701
|
+
// vince (§4.4 la chiama «alternativa ai filtri»).
|
|
9702
|
+
delete holder.filterFormula;
|
|
9703
|
+
}
|
|
9533
9704
|
switch (mode) {
|
|
9534
9705
|
case 'and':
|
|
9535
9706
|
holder.filterLogic = 'and';
|
|
@@ -9545,7 +9716,9 @@ class RecordFilterEditorComponent {
|
|
|
9545
9716
|
break;
|
|
9546
9717
|
}
|
|
9547
9718
|
case 'formula':
|
|
9548
|
-
|
|
9719
|
+
// **Non** `filterLogic: 'Formula'`: quel valore non e' supportato (§4.4). La modalita'
|
|
9720
|
+
// e' la presenza del campo, e la logica dei filtri non c'entra piu' niente.
|
|
9721
|
+
delete holder.filterLogic;
|
|
9549
9722
|
holder.filterFormula ??= '';
|
|
9550
9723
|
break;
|
|
9551
9724
|
}
|
|
@@ -9596,11 +9769,11 @@ class RecordFilterEditorComponent {
|
|
|
9596
9769
|
return this.types.valueSetOf(filter.field);
|
|
9597
9770
|
}
|
|
9598
9771
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.28", ngImport: i0, type: RecordFilterEditorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
9599
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.28", type: RecordFilterEditorComponent, isStandalone: true, selector: "fb-record-filter-editor", inputs: { holder: { classPropertyName: "holder", publicName: "holder", isSignal: true, isRequired: true, transformFunction: null }, object: { classPropertyName: "object", publicName: "object", isSignal: true, isRequired: false, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, usage: { classPropertyName: "usage", publicName: "usage", isSignal: true, isRequired: false, transformFunction: null }, fieldOptions: { classPropertyName: "fieldOptions", publicName: "fieldOptions", isSignal: true, isRequired: false, transformFunction: null }, supportsLogic: { classPropertyName: "supportsLogic", publicName: "supportsLogic", isSignal: true, isRequired: false, transformFunction: null }, supportsFormula: { classPropertyName: "supportsFormula", publicName: "supportsFormula", isSignal: true, isRequired: false, transformFunction: null }, emptyWarning: { classPropertyName: "emptyWarning", publicName: "emptyWarning", isSignal: true, isRequired: false, transformFunction: null }, emptyWarningSeverity: { classPropertyName: "emptyWarningSeverity", publicName: "emptyWarningSeverity", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { changed: "changed" }, ngImport: i0, template: "<fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">{{ title() }}</legend>\r\n\r\n @if (!object() && !fieldOptions().length) {\r\n <p class=\"fb-field__hint\">Scegli prima un oggetto per poter filtrare sui suoi campi.</p>\r\n }\r\n\r\n @if (supportsLogic()) {\r\n <div class=\"fb-filter__modes\" role=\"group\" aria-label=\"Logica dei filtri\">\r\n <button\r\n type=\"button\"\r\n class=\"fb-filter__mode\"\r\n [class.fb-filter__mode--active]=\"logicMode() === 'and'\"\r\n (click)=\"setLogicMode('and')\"\r\n >\r\n Tutti (AND)\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-filter__mode\"\r\n [class.fb-filter__mode--active]=\"logicMode() === 'or'\"\r\n (click)=\"setLogicMode('or')\"\r\n >\r\n Almeno uno (OR)\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-filter__mode\"\r\n [class.fb-filter__mode--active]=\"logicMode() === 'custom'\"\r\n (click)=\"setLogicMode('custom')\"\r\n >\r\n Espressione\r\n </button>\r\n @if (supportsFormula()) {\r\n <button\r\n type=\"button\"\r\n class=\"fb-filter__mode\"\r\n [class.fb-filter__mode--active]=\"logicMode() === 'formula'\"\r\n (click)=\"setLogicMode('formula')\"\r\n >\r\n Formula\r\n </button>\r\n }\r\n </div>\r\n } @else {\r\n <!-- Qui il modello non ha `filterLogic`: mostrarlo lo farebbe perdere al salvataggio. -->\r\n <p class=\"fb-field__hint\">Su questo elemento i filtri sono sempre combinati in AND.</p>\r\n }\r\n\r\n @if (supportsLogic() && logicMode() === 'custom') {\r\n <div class=\"fb-field\">\r\n <input\r\n class=\"fb-input fb-input--mono\"\r\n [value]=\"customLogic()\"\r\n placeholder=\"1 AND (2 OR 3)\"\r\n aria-label=\"Espressione sugli indici dei filtri\"\r\n (input)=\"setCustomLogic($any($event.target).value)\"\r\n />\r\n </div>\r\n }\r\n\r\n @if (supportsFormula() && logicMode() === 'formula') {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Formula di filtro</label>\r\n <fb-formula-editor\r\n [expression]=\"holder().filterFormula || ''\"\r\n usage=\"Condition\"\r\n expectedDataType=\"Boolean\"\r\n ariaLabel=\"Formula di filtro\"\r\n (expressionChange)=\"setFormula($event)\"\r\n >\r\n <p class=\"fb-field__hint\">Valutata dal motore di regole, in alternativa ai filtri.</p>\r\n </fb-formula-editor>\r\n </div>\r\n }\r\n\r\n @if (identifierNotFilterable()) {\r\n <p class=\"fb-field__hint\">\r\n Su questo oggetto l\u2019identificativo non e\u2019 filtrabile: la chiave e\u2019 composta e porta la propria\r\n forma canonica. Filtra per le colonne della chiave, una per colonna.\r\n </p>\r\n }\r\n\r\n @if (showEmptyWarning()) {\r\n <p\r\n class=\"fb-callout\"\r\n [class.fb-callout--warn]=\"emptyWarningSeverity() === 'warn'\"\r\n [class.fb-callout--error]=\"emptyWarningSeverity() === 'error'\"\r\n >\r\n {{ emptyWarning() }}\r\n </p>\r\n }\r\n\r\n <div class=\"fb-list\">\r\n @for (filter of filters(); track $index) {\r\n <div class=\"fb-list__item\">\r\n <div class=\"fb-list__header\">\r\n <span class=\"fb-list__index\">{{ $index + 1 }}</span>\r\n <span class=\"fb-list__spacer\"></span>\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-btn--ghost fb-btn--icon\"\r\n aria-label=\"Rimuovi il filtro\"\r\n (click)=\"removeFilter($index)\"\r\n >\r\n \u00D7\r\n </button>\r\n </div>\r\n\r\n <!-- Campo, operatore e valore sono una frase sola: si leggono in riga. -->\r\n <div class=\"fb-fields-row\">\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Campo</label>\r\n @if (fieldOptions().length) {\r\n <!-- Insieme chiuso: qui non c'e' un catalogo incompleto da compensare. -->\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"filter.field || ''\"\r\n aria-label=\"Campo del filtro\"\r\n (change)=\"setField($index, $any($event.target).value)\"\r\n >\r\n <option value=\"\">\u2014 scegli \u2014</option>\r\n @for (option of fieldOptions(); track option.value) {\r\n <option [value]=\"option.value\">{{ option.label }}</option>\r\n }\r\n </select>\r\n @if (unknownFixedField(filter)) {\r\n <p class=\"fb-field__error\">\r\n \u00AB{{ filter.field }}\u00BB non e\u2019 fra i campi citabili qui (FIELD_UNKNOWN).\r\n </p>\r\n }\r\n } @else {\r\n <fb-field-picker\r\n [value]=\"filter.field\"\r\n [object]=\"object()\"\r\n [usage]=\"usage()\"\r\n placeholder=\"Scrivi o scegli un campo\"\r\n (valueChange)=\"setField($index, $event ?? '')\"\r\n />\r\n }\r\n </div>\r\n\r\n <div class=\"fb-field fb-field--compact\">\r\n <label class=\"fb-field__label\">Operatore</label>\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"filter.operator || ''\"\r\n (change)=\"setOperator($index, $any($event.target).value)\"\r\n >\r\n @for (operator of operators(); track operator.value) {\r\n <option [value]=\"operator.value\">{{ operator.label }}</option>\r\n }\r\n </select>\r\n </div>\r\n\r\n @if (isNullOperator(filter)) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Esito atteso</label>\r\n <div class=\"fb-filter__modes\">\r\n <button\r\n type=\"button\"\r\n class=\"fb-filter__mode\"\r\n [class.fb-filter__mode--active]=\"nullExpectation(filter)\"\r\n (click)=\"setNullExpectation($index, true)\"\r\n >\r\n \u00E8 vuoto\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-filter__mode\"\r\n [class.fb-filter__mode--active]=\"!nullExpectation(filter)\"\r\n (click)=\"setNullExpectation($index, false)\"\r\n >\r\n non \u00E8 vuoto\r\n </button>\r\n </div>\r\n </div>\r\n } @else {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Valore</label>\r\n <fb-value-editor\r\n [value]=\"filter.value\"\r\n [dataType]=\"fieldDataType(filter)\"\r\n [objectType]=\"fieldObjectType(filter)\"\r\n [valueSet]=\"fieldValueSet(filter)\"\r\n label=\"Valore del filtro\"\r\n [allowFormula]=\"false\"\r\n (valueChange)=\"setValue($index, $event)\"\r\n />\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n } @empty {\r\n <p class=\"fb-empty\">Nessun filtro.</p>\r\n }\r\n </div>\r\n\r\n <button type=\"button\" class=\"fb-btn\" (click)=\"addFilter()\">Aggiungi filtro</button>\r\n</fieldset>\r\n", styles: [":host{display:block}.fb-filter__modes{margin-bottom:8px}.fb-field .fb-filter__modes{margin-bottom:0}\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: 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 });
|
|
9772
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.28", type: RecordFilterEditorComponent, isStandalone: true, selector: "fb-record-filter-editor", inputs: { holder: { classPropertyName: "holder", publicName: "holder", isSignal: true, isRequired: true, transformFunction: null }, object: { classPropertyName: "object", publicName: "object", isSignal: true, isRequired: false, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, usage: { classPropertyName: "usage", publicName: "usage", isSignal: true, isRequired: false, transformFunction: null }, fieldOptions: { classPropertyName: "fieldOptions", publicName: "fieldOptions", isSignal: true, isRequired: false, transformFunction: null }, supportsLogic: { classPropertyName: "supportsLogic", publicName: "supportsLogic", isSignal: true, isRequired: false, transformFunction: null }, supportsFormula: { classPropertyName: "supportsFormula", publicName: "supportsFormula", isSignal: true, isRequired: false, transformFunction: null }, emptyWarning: { classPropertyName: "emptyWarning", publicName: "emptyWarning", isSignal: true, isRequired: false, transformFunction: null }, emptyWarningSeverity: { classPropertyName: "emptyWarningSeverity", publicName: "emptyWarningSeverity", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { changed: "changed" }, ngImport: i0, template: "<fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">{{ title() }}</legend>\r\n\r\n @if (!object() && !fieldOptions().length) {\r\n <p class=\"fb-field__hint\">Scegli prima un oggetto per poter filtrare sui suoi campi.</p>\r\n }\r\n\r\n @if (supportsLogic()) {\r\n <div class=\"fb-filter__modes\" role=\"group\" aria-label=\"Logica dei filtri\">\r\n <button\r\n type=\"button\"\r\n class=\"fb-filter__mode\"\r\n [class.fb-filter__mode--active]=\"logicMode() === 'and'\"\r\n (click)=\"setLogicMode('and')\"\r\n >\r\n Tutti (AND)\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-filter__mode\"\r\n [class.fb-filter__mode--active]=\"logicMode() === 'or'\"\r\n (click)=\"setLogicMode('or')\"\r\n >\r\n Almeno uno (OR)\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-filter__mode\"\r\n [class.fb-filter__mode--active]=\"logicMode() === 'custom'\"\r\n (click)=\"setLogicMode('custom')\"\r\n >\r\n Espressione\r\n </button>\r\n @if (supportsFormula()) {\r\n <button\r\n type=\"button\"\r\n class=\"fb-filter__mode\"\r\n [class.fb-filter__mode--active]=\"logicMode() === 'formula'\"\r\n (click)=\"setLogicMode('formula')\"\r\n >\r\n Formula\r\n </button>\r\n }\r\n </div>\r\n } @else {\r\n <!-- Qui il modello non ha `filterLogic`: mostrarlo lo farebbe perdere al salvataggio. -->\r\n <p class=\"fb-field__hint\">Su questo elemento i filtri sono sempre combinati in AND.</p>\r\n }\r\n\r\n @if (supportsLogic() && logicMode() === 'custom') {\r\n <div class=\"fb-field\">\r\n <input\r\n class=\"fb-input fb-input--mono\"\r\n [class.fb-input--invalid]=\"!!customLogicError()\"\r\n [value]=\"customLogic()\"\r\n placeholder=\"1 AND (2 OR 3)\"\r\n aria-label=\"Espressione sugli indici dei filtri\"\r\n (input)=\"setCustomLogic($any($event.target).value)\"\r\n />\r\n <!--\r\n Gli indici sono quelli numerati nell\u2019elenco qui sotto, e sono 1-based. Un indice\r\n inesistente e\u2019 `CONDITION_LOGIC_INVALID`, che e\u2019 un **errore**: blocca l\u2019attivazione,\r\n quindi conviene dirlo mentre si scrive e non alla validazione.\r\n -->\r\n @if (customLogicError()) {\r\n <p class=\"fb-field__error\">{{ customLogicError() }}</p>\r\n } @else {\r\n <p class=\"fb-field__hint\">\r\n Gli indici sono 1-based e si riferiscono ai filtri numerati sotto. Cancellare un filtro\r\n riscrive l\u2019espressione automaticamente.\r\n </p>\r\n }\r\n </div>\r\n }\r\n\r\n <!--\r\n `filterLogic: \"Formula\"` non e\u2019 supportato da nessun elemento (\u00A74.4): non lo scrive nessun\r\n gesto di questo editor, ma un flow scritto altrove puo\u2019 portarcelo. Si dice invece di\r\n correggerlo di soppiatto \u2014 e\u2019 l\u2019unico indizio di cosa quel flow voleva fare.\r\n -->\r\n @if (unsupportedFormulaLogic()) {\r\n <p class=\"fb-callout fb-callout--error\">\r\n <code>filterLogic: \"Formula\"</code> non e\u2019 un valore supportato: sui choice set e\u2019 un errore\r\n di validazione, altrove fa fallire l\u2019esecuzione. I filtri qui sotto si combinano in AND.\r\n @if (supportsFormula()) {\r\n Per una formula usa la modalita\u2019 \u00ABFormula\u00BB, che scrive <code>filterFormula</code>.\r\n }\r\n </p>\r\n }\r\n\r\n @if (supportsFormula() && logicMode() === 'formula') {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Formula di filtro</label>\r\n <fb-formula-editor\r\n [expression]=\"holder().filterFormula || ''\"\r\n usage=\"Condition\"\r\n expectedDataType=\"Boolean\"\r\n ariaLabel=\"Formula di filtro\"\r\n (expressionChange)=\"setFormula($event)\"\r\n >\r\n <p class=\"fb-field__hint\">\r\n Valutata dal motore di regole, <strong>in alternativa</strong> ai filtri: sta nel campo\r\n <code>filterFormula</code>, e la logica dei filtri non si applica.\r\n </p>\r\n </fb-formula-editor>\r\n </div>\r\n }\r\n\r\n @if (identifierNotFilterable()) {\r\n <p class=\"fb-field__hint\">\r\n Su questo oggetto l\u2019identificativo non e\u2019 filtrabile: la chiave e\u2019 composta e porta la propria\r\n forma canonica. Filtra per le colonne della chiave, una per colonna.\r\n </p>\r\n }\r\n\r\n @if (showEmptyWarning()) {\r\n <p\r\n class=\"fb-callout\"\r\n [class.fb-callout--warn]=\"emptyWarningSeverity() === 'warn'\"\r\n [class.fb-callout--error]=\"emptyWarningSeverity() === 'error'\"\r\n >\r\n {{ emptyWarning() }}\r\n </p>\r\n }\r\n\r\n <div class=\"fb-list\">\r\n @for (filter of filters(); track $index) {\r\n <div class=\"fb-list__item\">\r\n <div class=\"fb-list__header\">\r\n <span class=\"fb-list__index\">{{ $index + 1 }}</span>\r\n <span class=\"fb-list__spacer\"></span>\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-btn--ghost fb-btn--icon\"\r\n aria-label=\"Rimuovi il filtro\"\r\n (click)=\"removeFilter($index)\"\r\n >\r\n \u00D7\r\n </button>\r\n </div>\r\n\r\n <!-- Campo, operatore e valore sono una frase sola: si leggono in riga. -->\r\n <div class=\"fb-fields-row\">\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Campo</label>\r\n @if (fieldOptions().length) {\r\n <!-- Insieme chiuso: qui non c'e' un catalogo incompleto da compensare. -->\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"filter.field || ''\"\r\n aria-label=\"Campo del filtro\"\r\n (change)=\"setField($index, $any($event.target).value)\"\r\n >\r\n <option value=\"\">\u2014 scegli \u2014</option>\r\n @for (option of fieldOptions(); track option.value) {\r\n <option [value]=\"option.value\">{{ option.label }}</option>\r\n }\r\n </select>\r\n @if (unknownFixedField(filter)) {\r\n <p class=\"fb-field__error\">\r\n \u00AB{{ filter.field }}\u00BB non e\u2019 fra i campi citabili qui (FIELD_UNKNOWN).\r\n </p>\r\n }\r\n } @else {\r\n <fb-field-picker\r\n [value]=\"filter.field\"\r\n [object]=\"object()\"\r\n [usage]=\"usage()\"\r\n placeholder=\"Scrivi o scegli un campo\"\r\n (valueChange)=\"setField($index, $event ?? '')\"\r\n />\r\n }\r\n </div>\r\n\r\n <div class=\"fb-field fb-field--compact\">\r\n <label class=\"fb-field__label\">Operatore</label>\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"filter.operator || ''\"\r\n (change)=\"setOperator($index, $any($event.target).value)\"\r\n >\r\n @for (operator of operators(); track operator.value) {\r\n <option [value]=\"operator.value\">{{ operator.label }}</option>\r\n }\r\n </select>\r\n </div>\r\n\r\n @if (isNullOperator(filter)) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Esito atteso</label>\r\n <div class=\"fb-filter__modes\">\r\n <button\r\n type=\"button\"\r\n class=\"fb-filter__mode\"\r\n [class.fb-filter__mode--active]=\"nullExpectation(filter)\"\r\n (click)=\"setNullExpectation($index, true)\"\r\n >\r\n \u00E8 vuoto\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-filter__mode\"\r\n [class.fb-filter__mode--active]=\"!nullExpectation(filter)\"\r\n (click)=\"setNullExpectation($index, false)\"\r\n >\r\n non \u00E8 vuoto\r\n </button>\r\n </div>\r\n </div>\r\n } @else {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Valore</label>\r\n <fb-value-editor\r\n [value]=\"filter.value\"\r\n [dataType]=\"fieldDataType(filter)\"\r\n [objectType]=\"fieldObjectType(filter)\"\r\n [valueSet]=\"fieldValueSet(filter)\"\r\n label=\"Valore del filtro\"\r\n [allowFormula]=\"false\"\r\n (valueChange)=\"setValue($index, $event)\"\r\n />\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n } @empty {\r\n <p class=\"fb-empty\">Nessun filtro.</p>\r\n }\r\n </div>\r\n\r\n <button type=\"button\" class=\"fb-btn\" (click)=\"addFilter()\">Aggiungi filtro</button>\r\n</fieldset>\r\n", styles: [":host{display:block}.fb-filter__modes{margin-bottom:8px}.fb-field .fb-filter__modes{margin-bottom:0}\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: 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 });
|
|
9600
9773
|
}
|
|
9601
9774
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.28", ngImport: i0, type: RecordFilterEditorComponent, decorators: [{
|
|
9602
9775
|
type: Component,
|
|
9603
|
-
args: [{ selector: 'fb-record-filter-editor', standalone: true, imports: [FieldPickerComponent, FormulaEditorComponent, ValueEditorComponent, SelectValueDirective], changeDetection: ChangeDetectionStrategy.OnPush, template: "<fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">{{ title() }}</legend>\r\n\r\n @if (!object() && !fieldOptions().length) {\r\n <p class=\"fb-field__hint\">Scegli prima un oggetto per poter filtrare sui suoi campi.</p>\r\n }\r\n\r\n @if (supportsLogic()) {\r\n <div class=\"fb-filter__modes\" role=\"group\" aria-label=\"Logica dei filtri\">\r\n <button\r\n type=\"button\"\r\n class=\"fb-filter__mode\"\r\n [class.fb-filter__mode--active]=\"logicMode() === 'and'\"\r\n (click)=\"setLogicMode('and')\"\r\n >\r\n Tutti (AND)\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-filter__mode\"\r\n [class.fb-filter__mode--active]=\"logicMode() === 'or'\"\r\n (click)=\"setLogicMode('or')\"\r\n >\r\n Almeno uno (OR)\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-filter__mode\"\r\n [class.fb-filter__mode--active]=\"logicMode() === 'custom'\"\r\n (click)=\"setLogicMode('custom')\"\r\n >\r\n Espressione\r\n </button>\r\n @if (supportsFormula()) {\r\n <button\r\n type=\"button\"\r\n class=\"fb-filter__mode\"\r\n [class.fb-filter__mode--active]=\"logicMode() === 'formula'\"\r\n (click)=\"setLogicMode('formula')\"\r\n >\r\n Formula\r\n </button>\r\n }\r\n </div>\r\n } @else {\r\n <!-- Qui il modello non ha `filterLogic`: mostrarlo lo farebbe perdere al salvataggio. -->\r\n <p class=\"fb-field__hint\">Su questo elemento i filtri sono sempre combinati in AND.</p>\r\n }\r\n\r\n @if (supportsLogic() && logicMode() === 'custom') {\r\n <div class=\"fb-field\">\r\n <input\r\n class=\"fb-input fb-input--mono\"\r\n [value]=\"customLogic()\"\r\n placeholder=\"1 AND (2 OR 3)\"\r\n aria-label=\"Espressione sugli indici dei filtri\"\r\n (input)=\"setCustomLogic($any($event.target).value)\"\r\n />\r\n </div>\r\n }\r\n\r\n @if (supportsFormula() && logicMode() === 'formula') {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Formula di filtro</label>\r\n <fb-formula-editor\r\n [expression]=\"holder().filterFormula || ''\"\r\n usage=\"Condition\"\r\n expectedDataType=\"Boolean\"\r\n ariaLabel=\"Formula di filtro\"\r\n (expressionChange)=\"setFormula($event)\"\r\n >\r\n <p class=\"fb-field__hint\"
|
|
9776
|
+
args: [{ selector: 'fb-record-filter-editor', standalone: true, imports: [FieldPickerComponent, FormulaEditorComponent, ValueEditorComponent, SelectValueDirective], changeDetection: ChangeDetectionStrategy.OnPush, template: "<fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">{{ title() }}</legend>\r\n\r\n @if (!object() && !fieldOptions().length) {\r\n <p class=\"fb-field__hint\">Scegli prima un oggetto per poter filtrare sui suoi campi.</p>\r\n }\r\n\r\n @if (supportsLogic()) {\r\n <div class=\"fb-filter__modes\" role=\"group\" aria-label=\"Logica dei filtri\">\r\n <button\r\n type=\"button\"\r\n class=\"fb-filter__mode\"\r\n [class.fb-filter__mode--active]=\"logicMode() === 'and'\"\r\n (click)=\"setLogicMode('and')\"\r\n >\r\n Tutti (AND)\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-filter__mode\"\r\n [class.fb-filter__mode--active]=\"logicMode() === 'or'\"\r\n (click)=\"setLogicMode('or')\"\r\n >\r\n Almeno uno (OR)\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-filter__mode\"\r\n [class.fb-filter__mode--active]=\"logicMode() === 'custom'\"\r\n (click)=\"setLogicMode('custom')\"\r\n >\r\n Espressione\r\n </button>\r\n @if (supportsFormula()) {\r\n <button\r\n type=\"button\"\r\n class=\"fb-filter__mode\"\r\n [class.fb-filter__mode--active]=\"logicMode() === 'formula'\"\r\n (click)=\"setLogicMode('formula')\"\r\n >\r\n Formula\r\n </button>\r\n }\r\n </div>\r\n } @else {\r\n <!-- Qui il modello non ha `filterLogic`: mostrarlo lo farebbe perdere al salvataggio. -->\r\n <p class=\"fb-field__hint\">Su questo elemento i filtri sono sempre combinati in AND.</p>\r\n }\r\n\r\n @if (supportsLogic() && logicMode() === 'custom') {\r\n <div class=\"fb-field\">\r\n <input\r\n class=\"fb-input fb-input--mono\"\r\n [class.fb-input--invalid]=\"!!customLogicError()\"\r\n [value]=\"customLogic()\"\r\n placeholder=\"1 AND (2 OR 3)\"\r\n aria-label=\"Espressione sugli indici dei filtri\"\r\n (input)=\"setCustomLogic($any($event.target).value)\"\r\n />\r\n <!--\r\n Gli indici sono quelli numerati nell\u2019elenco qui sotto, e sono 1-based. Un indice\r\n inesistente e\u2019 `CONDITION_LOGIC_INVALID`, che e\u2019 un **errore**: blocca l\u2019attivazione,\r\n quindi conviene dirlo mentre si scrive e non alla validazione.\r\n -->\r\n @if (customLogicError()) {\r\n <p class=\"fb-field__error\">{{ customLogicError() }}</p>\r\n } @else {\r\n <p class=\"fb-field__hint\">\r\n Gli indici sono 1-based e si riferiscono ai filtri numerati sotto. Cancellare un filtro\r\n riscrive l\u2019espressione automaticamente.\r\n </p>\r\n }\r\n </div>\r\n }\r\n\r\n <!--\r\n `filterLogic: \"Formula\"` non e\u2019 supportato da nessun elemento (\u00A74.4): non lo scrive nessun\r\n gesto di questo editor, ma un flow scritto altrove puo\u2019 portarcelo. Si dice invece di\r\n correggerlo di soppiatto \u2014 e\u2019 l\u2019unico indizio di cosa quel flow voleva fare.\r\n -->\r\n @if (unsupportedFormulaLogic()) {\r\n <p class=\"fb-callout fb-callout--error\">\r\n <code>filterLogic: \"Formula\"</code> non e\u2019 un valore supportato: sui choice set e\u2019 un errore\r\n di validazione, altrove fa fallire l\u2019esecuzione. I filtri qui sotto si combinano in AND.\r\n @if (supportsFormula()) {\r\n Per una formula usa la modalita\u2019 \u00ABFormula\u00BB, che scrive <code>filterFormula</code>.\r\n }\r\n </p>\r\n }\r\n\r\n @if (supportsFormula() && logicMode() === 'formula') {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Formula di filtro</label>\r\n <fb-formula-editor\r\n [expression]=\"holder().filterFormula || ''\"\r\n usage=\"Condition\"\r\n expectedDataType=\"Boolean\"\r\n ariaLabel=\"Formula di filtro\"\r\n (expressionChange)=\"setFormula($event)\"\r\n >\r\n <p class=\"fb-field__hint\">\r\n Valutata dal motore di regole, <strong>in alternativa</strong> ai filtri: sta nel campo\r\n <code>filterFormula</code>, e la logica dei filtri non si applica.\r\n </p>\r\n </fb-formula-editor>\r\n </div>\r\n }\r\n\r\n @if (identifierNotFilterable()) {\r\n <p class=\"fb-field__hint\">\r\n Su questo oggetto l\u2019identificativo non e\u2019 filtrabile: la chiave e\u2019 composta e porta la propria\r\n forma canonica. Filtra per le colonne della chiave, una per colonna.\r\n </p>\r\n }\r\n\r\n @if (showEmptyWarning()) {\r\n <p\r\n class=\"fb-callout\"\r\n [class.fb-callout--warn]=\"emptyWarningSeverity() === 'warn'\"\r\n [class.fb-callout--error]=\"emptyWarningSeverity() === 'error'\"\r\n >\r\n {{ emptyWarning() }}\r\n </p>\r\n }\r\n\r\n <div class=\"fb-list\">\r\n @for (filter of filters(); track $index) {\r\n <div class=\"fb-list__item\">\r\n <div class=\"fb-list__header\">\r\n <span class=\"fb-list__index\">{{ $index + 1 }}</span>\r\n <span class=\"fb-list__spacer\"></span>\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-btn--ghost fb-btn--icon\"\r\n aria-label=\"Rimuovi il filtro\"\r\n (click)=\"removeFilter($index)\"\r\n >\r\n \u00D7\r\n </button>\r\n </div>\r\n\r\n <!-- Campo, operatore e valore sono una frase sola: si leggono in riga. -->\r\n <div class=\"fb-fields-row\">\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Campo</label>\r\n @if (fieldOptions().length) {\r\n <!-- Insieme chiuso: qui non c'e' un catalogo incompleto da compensare. -->\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"filter.field || ''\"\r\n aria-label=\"Campo del filtro\"\r\n (change)=\"setField($index, $any($event.target).value)\"\r\n >\r\n <option value=\"\">\u2014 scegli \u2014</option>\r\n @for (option of fieldOptions(); track option.value) {\r\n <option [value]=\"option.value\">{{ option.label }}</option>\r\n }\r\n </select>\r\n @if (unknownFixedField(filter)) {\r\n <p class=\"fb-field__error\">\r\n \u00AB{{ filter.field }}\u00BB non e\u2019 fra i campi citabili qui (FIELD_UNKNOWN).\r\n </p>\r\n }\r\n } @else {\r\n <fb-field-picker\r\n [value]=\"filter.field\"\r\n [object]=\"object()\"\r\n [usage]=\"usage()\"\r\n placeholder=\"Scrivi o scegli un campo\"\r\n (valueChange)=\"setField($index, $event ?? '')\"\r\n />\r\n }\r\n </div>\r\n\r\n <div class=\"fb-field fb-field--compact\">\r\n <label class=\"fb-field__label\">Operatore</label>\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"filter.operator || ''\"\r\n (change)=\"setOperator($index, $any($event.target).value)\"\r\n >\r\n @for (operator of operators(); track operator.value) {\r\n <option [value]=\"operator.value\">{{ operator.label }}</option>\r\n }\r\n </select>\r\n </div>\r\n\r\n @if (isNullOperator(filter)) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Esito atteso</label>\r\n <div class=\"fb-filter__modes\">\r\n <button\r\n type=\"button\"\r\n class=\"fb-filter__mode\"\r\n [class.fb-filter__mode--active]=\"nullExpectation(filter)\"\r\n (click)=\"setNullExpectation($index, true)\"\r\n >\r\n \u00E8 vuoto\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-filter__mode\"\r\n [class.fb-filter__mode--active]=\"!nullExpectation(filter)\"\r\n (click)=\"setNullExpectation($index, false)\"\r\n >\r\n non \u00E8 vuoto\r\n </button>\r\n </div>\r\n </div>\r\n } @else {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Valore</label>\r\n <fb-value-editor\r\n [value]=\"filter.value\"\r\n [dataType]=\"fieldDataType(filter)\"\r\n [objectType]=\"fieldObjectType(filter)\"\r\n [valueSet]=\"fieldValueSet(filter)\"\r\n label=\"Valore del filtro\"\r\n [allowFormula]=\"false\"\r\n (valueChange)=\"setValue($index, $event)\"\r\n />\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n } @empty {\r\n <p class=\"fb-empty\">Nessun filtro.</p>\r\n }\r\n </div>\r\n\r\n <button type=\"button\" class=\"fb-btn\" (click)=\"addFilter()\">Aggiungi filtro</button>\r\n</fieldset>\r\n", styles: [":host{display:block}.fb-filter__modes{margin-bottom:8px}.fb-field .fb-filter__modes{margin-bottom:0}\n"] }]
|
|
9604
9777
|
}], ctorParameters: () => [], propDecorators: { holder: [{ type: i0.Input, args: [{ isSignal: true, alias: "holder", required: true }] }], object: [{ type: i0.Input, args: [{ isSignal: true, alias: "object", required: false }] }], title: [{ type: i0.Input, args: [{ isSignal: true, alias: "title", required: false }] }], usage: [{ type: i0.Input, args: [{ isSignal: true, alias: "usage", required: false }] }], fieldOptions: [{ type: i0.Input, args: [{ isSignal: true, alias: "fieldOptions", required: false }] }], supportsLogic: [{ type: i0.Input, args: [{ isSignal: true, alias: "supportsLogic", required: false }] }], supportsFormula: [{ type: i0.Input, args: [{ isSignal: true, alias: "supportsFormula", required: false }] }], emptyWarning: [{ type: i0.Input, args: [{ isSignal: true, alias: "emptyWarning", required: false }] }], emptyWarningSeverity: [{ type: i0.Input, args: [{ isSignal: true, alias: "emptyWarningSeverity", required: false }] }], changed: [{ type: i0.Output, args: ["changed"] }] } });
|
|
9605
9778
|
|
|
9606
9779
|
/**
|
|
@@ -10334,6 +10507,136 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.28", ngImpo
|
|
|
10334
10507
|
type: Directive
|
|
10335
10508
|
}], propDecorators: { name: [{ type: i0.Input, args: [{ isSignal: true, alias: "name", required: true }] }], node: [{ type: i0.Input, args: [{ isSignal: true, alias: "node", required: true }] }] } });
|
|
10336
10509
|
|
|
10510
|
+
/**
|
|
10511
|
+
* Quante volte un nome e' **usato** nel documento.
|
|
10512
|
+
*
|
|
10513
|
+
* Serve dove togliere qualcosa rompe il resto: cancellare una risorsa non e' come cancellare
|
|
10514
|
+
* un elemento — nessun arco sparisce dal canvas, nessun rilievo compare finche' non si valida,
|
|
10515
|
+
* e i riferimenti orfani si scoprono dopo. Il conteggio e' cio' che rende visibile il danno
|
|
10516
|
+
* **prima**.
|
|
10517
|
+
*
|
|
10518
|
+
* Due regole, e sono le stesse della rinomina:
|
|
10519
|
+
*
|
|
10520
|
+
* - i riferimenti stanno nei campi di {@link FLOW_REFERENCE_FIELDS} — l'elenco e' **uno**, e
|
|
10521
|
+
* questo modulo lo importa invece di riscriverlo: due elenchi diventano due elenchi diversi
|
|
10522
|
+
* al primo campo aggiunto (§13.8);
|
|
10523
|
+
* - un riferimento vale anche **navigato**: `Ordine.Numero` e' un uso di `Ordine`.
|
|
10524
|
+
*
|
|
10525
|
+
* Piu' **due** che la rinomina non ha, e sono i due modi in cui una risorsa si cita fuori da un
|
|
10526
|
+
* campo di riferimento.
|
|
10527
|
+
*
|
|
10528
|
+
* Le **formule**: il motore di regole cita le risorse per nome dentro un'espressione
|
|
10529
|
+
* (`GetStreetToponym(Residenza_Street)`), quindi un conteggio che guardasse solo i campi di
|
|
10530
|
+
* riferimento direbbe «non usata» di una variabile che tre formule leggono. L'espressione non la
|
|
10531
|
+
* interpretiamo — la sintassi e' del motore (§6.3) — quindi si spezza in identificatori e si
|
|
10532
|
+
* confronta la radice: e' un'approssimazione, ed e' per questo che il risultato serve ad
|
|
10533
|
+
* **avvertire**, non a decidere al posto dell'utente.
|
|
10534
|
+
*
|
|
10535
|
+
* I **merge field**: `«Gentile {!Cliente.Nome},»` usa `Cliente` esattamente come lo userebbe una
|
|
10536
|
+
* condizione, e senza contarli una variabile citata da tre messaggi risultava «non usata» — che
|
|
10537
|
+
* e' l'informazione su cui si decide di cancellarla. Qui la sintassi dell'isola e' del
|
|
10538
|
+
* **contratto** e non del motore (§5.12), quindi riconoscerla non e' un'approssimazione; cio' che
|
|
10539
|
+
* resta approssimato e' il contenuto, che si tratta come un'espressione perche' puo' esserlo.
|
|
10540
|
+
*
|
|
10541
|
+
* L'elenco dei campi con merge field, la sintassi dell'isola e la sua riscrittura stanno in
|
|
10542
|
+
* `flow-merge-text.ts`, che e' un modulo foglia: le usa anche la rinomina, e quel modulo
|
|
10543
|
+
* (`flow-clipboard.ts`) questo qui lo importa già.
|
|
10544
|
+
*/
|
|
10545
|
+
/**
|
|
10546
|
+
* I campi che contengono un'espressione del motore di regole. `expression` e' quello di una
|
|
10547
|
+
* risorsa `Formula` (§4.6), `formulaExpression` quello di un valore e di una validation rule
|
|
10548
|
+
* (§5.2), `filterFormula` quello di un filtro di record (§5.8).
|
|
10549
|
+
*/
|
|
10550
|
+
const FLOW_FORMULA_FIELDS = new Set([
|
|
10551
|
+
'expression',
|
|
10552
|
+
'formulaExpression',
|
|
10553
|
+
'filterFormula',
|
|
10554
|
+
]);
|
|
10555
|
+
/** `true` se la stringa e' quel nome, o un percorso che parte da quel nome. */
|
|
10556
|
+
function isUseOf(value, name) {
|
|
10557
|
+
return value === name || value.startsWith(`${name}.`);
|
|
10558
|
+
}
|
|
10559
|
+
/**
|
|
10560
|
+
* Gli identificatori citati da un'espressione, ridotti alla loro **radice**: di
|
|
10561
|
+
* `OCRCIE.TaxCode` conta `OCRCIE`, perche' e' quella la risorsa. Senza la riduzione, un
|
|
10562
|
+
* membro chiamato come una variabile la farebbe sembrare usata.
|
|
10563
|
+
*/
|
|
10564
|
+
function rootsIn(expression) {
|
|
10565
|
+
return expression
|
|
10566
|
+
.split(/[^A-Za-z0-9_.$]+/)
|
|
10567
|
+
.filter(Boolean)
|
|
10568
|
+
.map((token) => token.split('.')[0])
|
|
10569
|
+
.filter(Boolean);
|
|
10570
|
+
}
|
|
10571
|
+
/**
|
|
10572
|
+
* Quante volte `name` compare come riferimento nel documento (o in un frammento).
|
|
10573
|
+
*
|
|
10574
|
+
* Non conta la **dichiarazione**: il campo `name` della risorsa o dell'elemento non e' un
|
|
10575
|
+
* campo di riferimento, quindi resta fuori da se'. E' la differenza con il conteggio della
|
|
10576
|
+
* rinomina, che serializza il documento e cerca la stringa: lì un elemento mai referenziato
|
|
10577
|
+
* «compare 1 volta».
|
|
10578
|
+
*/
|
|
10579
|
+
function countNameUses(root, name) {
|
|
10580
|
+
return describeNameUses(root, name).total;
|
|
10581
|
+
}
|
|
10582
|
+
/**
|
|
10583
|
+
* Gli usi di un nome **divisi per come sono scritti**.
|
|
10584
|
+
*
|
|
10585
|
+
* Il totale da solo non basta piu' da quando le categorie sono tre, e non perche' sia bello
|
|
10586
|
+
* dettagliarlo: le tre non si comportano allo stesso modo davanti a una **rinomina**. I
|
|
10587
|
+
* riferimenti e i merge field li riscrive l'editor; le espressioni delle formule no — la loro
|
|
10588
|
+
* sintassi e' del motore, e riscrivere dentro un'espressione che non si interpreta significa
|
|
10589
|
+
* indovinare. Chi mostra il numero puo' quindi dire quante occorrenze restano da correggere a
|
|
10590
|
+
* mano, invece di far scoprire dopo che una formula punta a un nome che non esiste piu'.
|
|
10591
|
+
*/
|
|
10592
|
+
function describeNameUses(root, name) {
|
|
10593
|
+
const uses = { references: 0, formulas: 0, mergeFields: 0, total: 0 };
|
|
10594
|
+
if (!name) {
|
|
10595
|
+
return uses;
|
|
10596
|
+
}
|
|
10597
|
+
const walk = (value) => {
|
|
10598
|
+
if (Array.isArray(value)) {
|
|
10599
|
+
value.forEach(walk);
|
|
10600
|
+
return;
|
|
10601
|
+
}
|
|
10602
|
+
if (!value || typeof value !== 'object') {
|
|
10603
|
+
return;
|
|
10604
|
+
}
|
|
10605
|
+
for (const [key, child] of Object.entries(value)) {
|
|
10606
|
+
if (typeof child === 'string') {
|
|
10607
|
+
if (FLOW_REFERENCE_FIELDS.has(key)) {
|
|
10608
|
+
if (isUseOf(child, name)) {
|
|
10609
|
+
uses.references += 1;
|
|
10610
|
+
}
|
|
10611
|
+
}
|
|
10612
|
+
else if (FLOW_FORMULA_FIELDS.has(key)) {
|
|
10613
|
+
uses.formulas += rootsIn(child).filter((root) => root === name).length;
|
|
10614
|
+
}
|
|
10615
|
+
else if (FLOW_MERGE_TEXT_FIELDS.has(key)) {
|
|
10616
|
+
// Il contenuto dell'isola si tratta come un'espressione: dentro `{!...}` ci puo'
|
|
10617
|
+
// stare un percorso (`{!Cliente.Nome}`) o una chiamata, e la radice e' cio' che
|
|
10618
|
+
// nomina la risorsa. Un testo senza `{!` non produce nessun candidato, quindi la
|
|
10619
|
+
// frase in italiano di una label non fa comparire usi che non esistono.
|
|
10620
|
+
for (const island of mergeFieldIslands(child)) {
|
|
10621
|
+
uses.mergeFields += rootsIn(island).filter((root) => root === name).length;
|
|
10622
|
+
}
|
|
10623
|
+
}
|
|
10624
|
+
continue;
|
|
10625
|
+
}
|
|
10626
|
+
if (FLOW_REFERENCE_FIELDS.has(key) && Array.isArray(child) && child.every((item) => typeof item === 'string')) {
|
|
10627
|
+
// `choiceReferences` e' un array di **nomi**: scendendoci dentro non si troverebbe
|
|
10628
|
+
// nessuna stringa da confrontare, perche' le stringhe sono gli elementi.
|
|
10629
|
+
uses.references += child.filter((item) => isUseOf(item, name)).length;
|
|
10630
|
+
continue;
|
|
10631
|
+
}
|
|
10632
|
+
walk(child);
|
|
10633
|
+
}
|
|
10634
|
+
};
|
|
10635
|
+
walk(root);
|
|
10636
|
+
uses.total = uses.references + uses.formulas + uses.mergeFields;
|
|
10637
|
+
return uses;
|
|
10638
|
+
}
|
|
10639
|
+
|
|
10337
10640
|
/**
|
|
10338
10641
|
* Action — FRONTEND.md §5.9.
|
|
10339
10642
|
*
|
|
@@ -14663,17 +14966,28 @@ class ElementInspectorComponent {
|
|
|
14663
14966
|
* Quanti riferimenti verrebbero riscritti dalla rinomina. Serve a rendere visibile che
|
|
14664
14967
|
* l'operazione tocca il resto del documento, non solo questo elemento.
|
|
14665
14968
|
*/
|
|
14666
|
-
|
|
14969
|
+
/**
|
|
14970
|
+
* Gli usi del nome, con la stessa funzione del pannello risorse: e' la stessa domanda, e due
|
|
14971
|
+
* numeri diversi per la stessa domanda sono un difetto — questo era una regex sul JSON
|
|
14972
|
+
* serializzato, che contava le stringhe uguali al nome in **qualunque** campo (anche dove non
|
|
14973
|
+
* sono riferimenti) e non vedeva ne' le formule ne' i merge field, cioe' sovrastimava e
|
|
14974
|
+
* sottostimava insieme.
|
|
14975
|
+
*/
|
|
14976
|
+
nameUses = computed(() => {
|
|
14667
14977
|
const name = this.selectedName();
|
|
14668
14978
|
if (!name || this.isStart()) {
|
|
14669
|
-
return 0;
|
|
14670
|
-
}
|
|
14671
|
-
|
|
14672
|
-
|
|
14673
|
-
|
|
14674
|
-
|
|
14675
|
-
|
|
14676
|
-
|
|
14979
|
+
return { references: 0, formulas: 0, mergeFields: 0, total: 0 };
|
|
14980
|
+
}
|
|
14981
|
+
return describeNameUses(this.store.document(), name);
|
|
14982
|
+
}, ...(ngDevMode ? [{ debugName: "nameUses" }] : []));
|
|
14983
|
+
/** Le occorrenze che la rinomina **riscrive**: riferimenti piu' merge field nei testi. */
|
|
14984
|
+
referenceCount = computed(() => this.nameUses().references + this.nameUses().mergeFields, ...(ngDevMode ? [{ debugName: "referenceCount" }] : []));
|
|
14985
|
+
/**
|
|
14986
|
+
* Le occorrenze che restano indietro: le espressioni delle formule, che l'editor non riscrive
|
|
14987
|
+
* perche' la loro sintassi e' del motore (§6.3). Dirlo qui e' l'unico modo di non farlo
|
|
14988
|
+
* scoprire dopo, quando la formula punta a un nome che non esiste piu'.
|
|
14989
|
+
*/
|
|
14990
|
+
formulaUseCount = computed(() => this.nameUses().formulas, ...(ngDevMode ? [{ debugName: "formulaUseCount" }] : []));
|
|
14677
14991
|
canRename = computed(() => {
|
|
14678
14992
|
const pending = this.pendingName();
|
|
14679
14993
|
return pending !== null && pending !== this.selectedName() && !this.nameError();
|
|
@@ -14760,7 +15074,7 @@ class ElementInspectorComponent {
|
|
|
14760
15074
|
return { x: node?.locationX ?? 0, y: node?.locationY ?? 0 };
|
|
14761
15075
|
}, ...(ngDevMode ? [{ debugName: "position" }] : []));
|
|
14762
15076
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.28", ngImport: i0, type: ElementInspectorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
14763
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.28", type: ElementInspectorComponent, isStandalone: true, selector: "fb-element-inspector", inputs: { selectedName: { classPropertyName: "selectedName", publicName: "selectedName", isSignal: true, isRequired: false, transformFunction: null }, showHeader: { classPropertyName: "showHeader", publicName: "showHeader", isSignal: true, isRequired: false, transformFunction: null }, initialFieldPath: { classPropertyName: "initialFieldPath", publicName: "initialFieldPath", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { closed: "closed", removeRequested: "removeRequested", duplicateRequested: "duplicateRequested", renamed: "renamed" }, viewQueries: [{ propertyName: "labelInput", first: true, predicate: ["labelInput"], descendants: true, isSignal: true }], ngImport: i0, 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 #labelInput\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 <!--\r\n Elemento appena creato: il nome non e\u2019 ancora niente, si costruisce dall\u2019etichetta e\r\n si puo\u2019 scrivere a mano. Su un elemento gi\u00E0 nominato, invece, cambiare il nome riscrive\r\n i riferimenti di tutto il documento (\u00A713.8) e sta dietro il comando \u00ABRinomina\u00BB.\r\n -->\r\n @if (isComposingName()) {\r\n <input\r\n class=\"fb-input fb-input--mono\"\r\n [class.fb-input--invalid]=\"!!composedNameError()\"\r\n [value]=\"composedName()\"\r\n placeholder=\"si genera dall\u2019etichetta\"\r\n aria-label=\"Nome tecnico\"\r\n (input)=\"onComposedNameInput($any($event.target).value)\"\r\n />\r\n @if (composedNameError()) {\r\n <p class=\"fb-field__error\">{{ composedNameError() }}</p>\r\n } @else {\r\n <p class=\"fb-field__hint\">\r\n \u00C8 l\u2019identificatore con cui i riferimenti raggiungono questo elemento: si costruisce\r\n dall\u2019etichetta finch\u00E9 non lo si scrive a mano, e svuotandolo torna a seguirla.\r\n @if (!composedName()) {\r\n Finch\u00E9 resta vuoto vale \u00AB{{ selectedName() }}\u00BB.\r\n }\r\n </p>\r\n }\r\n } @else 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"], dependencies: [{ kind: "component", type: ActionCallInspectorComponent, selector: "fb-action-call-inspector" }, { kind: "component", type: AssignmentInspectorComponent, selector: "fb-assignment-inspector" }, { kind: "component", type: CollectionProcessorInspectorComponent, selector: "fb-collection-processor-inspector" }, { kind: "component", type: CustomErrorInspectorComponent, selector: "fb-custom-error-inspector" }, { kind: "component", type: DecisionInspectorComponent, selector: "fb-decision-inspector" }, { kind: "component", type: DynamicScreenInspectorComponent, selector: "fb-dynamic-screen-inspector", inputs: ["initialSelection"] }, { kind: "component", type: LoopInspectorComponent, selector: "fb-loop-inspector" }, { kind: "component", type: OrchestratedStageInspectorComponent, selector: "fb-orchestrated-stage-inspector" }, { kind: "component", type: RecordLookupInspectorComponent, selector: "fb-record-lookup-inspector" }, { kind: "component", type: RecordRollbackInspectorComponent, selector: "fb-record-rollback-inspector" }, { kind: "component", type: RecordWriteInspectorComponent, selector: "fb-record-write-inspector", inputs: ["type"] }, { kind: "component", type: ScreenInspectorComponent, selector: "fb-screen-inspector" }, { kind: "component", type: ScriptCallInspectorComponent, selector: "fb-script-call-inspector" }, { kind: "component", type: StartInspectorComponent, selector: "fb-start-inspector" }, { kind: "component", type: SubflowInspectorComponent, selector: "fb-subflow-inspector" }, { kind: "component", type: TransformInspectorComponent, selector: "fb-transform-inspector" }, { kind: "component", type: WaitInspectorComponent, selector: "fb-wait-inspector" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
15077
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.28", type: ElementInspectorComponent, isStandalone: true, selector: "fb-element-inspector", inputs: { selectedName: { classPropertyName: "selectedName", publicName: "selectedName", isSignal: true, isRequired: false, transformFunction: null }, showHeader: { classPropertyName: "showHeader", publicName: "showHeader", isSignal: true, isRequired: false, transformFunction: null }, initialFieldPath: { classPropertyName: "initialFieldPath", publicName: "initialFieldPath", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { closed: "closed", removeRequested: "removeRequested", duplicateRequested: "duplicateRequested", renamed: "renamed" }, viewQueries: [{ propertyName: "labelInput", first: true, predicate: ["labelInput"], descendants: true, isSignal: true }], ngImport: i0, 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 #labelInput\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 <!--\r\n Elemento appena creato: il nome non e\u2019 ancora niente, si costruisce dall\u2019etichetta e\r\n si puo\u2019 scrivere a mano. Su un elemento gi\u00E0 nominato, invece, cambiare il nome riscrive\r\n i riferimenti di tutto il documento (\u00A713.8) e sta dietro il comando \u00ABRinomina\u00BB.\r\n -->\r\n @if (isComposingName()) {\r\n <input\r\n class=\"fb-input fb-input--mono\"\r\n [class.fb-input--invalid]=\"!!composedNameError()\"\r\n [value]=\"composedName()\"\r\n placeholder=\"si genera dall\u2019etichetta\"\r\n aria-label=\"Nome tecnico\"\r\n (input)=\"onComposedNameInput($any($event.target).value)\"\r\n />\r\n @if (composedNameError()) {\r\n <p class=\"fb-field__error\">{{ composedNameError() }}</p>\r\n } @else {\r\n <p class=\"fb-field__hint\">\r\n \u00C8 l\u2019identificatore con cui i riferimenti raggiungono questo elemento: si costruisce\r\n dall\u2019etichetta finch\u00E9 non lo si scrive a mano, e svuotandolo torna a seguirla.\r\n @if (!composedName()) {\r\n Finch\u00E9 resta vuoto vale \u00AB{{ selectedName() }}\u00BB.\r\n }\r\n </p>\r\n }\r\n } @else 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 i riferimenti che puntano a questo elemento e i merge field\r\n <code>{!\u2026}</code> nei testi ({{ referenceCount() }} occorrenze): nessuna\r\n primitiva del backend lo fa, lo fa l\u2019editor.\r\n <!--\r\n Le formule restano indietro di proposito: la loro sintassi e\u2019 del motore e\r\n riscriverci dentro vorrebbe dire indovinare. Il numero va detto **qui**, perche\u2019\r\n e\u2019 l\u2019unico momento in cui si puo\u2019 correggerle sapendo cosa cercare.\r\n -->\r\n @if (formulaUseCount()) {\r\n Le {{ formulaUseCount() }} occorrenze dentro le formule <strong>no</strong>: la\r\n sintassi e\u2019 del motore di regole, e vanno corrette a mano.\r\n }\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"], dependencies: [{ kind: "component", type: ActionCallInspectorComponent, selector: "fb-action-call-inspector" }, { kind: "component", type: AssignmentInspectorComponent, selector: "fb-assignment-inspector" }, { kind: "component", type: CollectionProcessorInspectorComponent, selector: "fb-collection-processor-inspector" }, { kind: "component", type: CustomErrorInspectorComponent, selector: "fb-custom-error-inspector" }, { kind: "component", type: DecisionInspectorComponent, selector: "fb-decision-inspector" }, { kind: "component", type: DynamicScreenInspectorComponent, selector: "fb-dynamic-screen-inspector", inputs: ["initialSelection"] }, { kind: "component", type: LoopInspectorComponent, selector: "fb-loop-inspector" }, { kind: "component", type: OrchestratedStageInspectorComponent, selector: "fb-orchestrated-stage-inspector" }, { kind: "component", type: RecordLookupInspectorComponent, selector: "fb-record-lookup-inspector" }, { kind: "component", type: RecordRollbackInspectorComponent, selector: "fb-record-rollback-inspector" }, { kind: "component", type: RecordWriteInspectorComponent, selector: "fb-record-write-inspector", inputs: ["type"] }, { kind: "component", type: ScreenInspectorComponent, selector: "fb-screen-inspector" }, { kind: "component", type: ScriptCallInspectorComponent, selector: "fb-script-call-inspector" }, { kind: "component", type: StartInspectorComponent, selector: "fb-start-inspector" }, { kind: "component", type: SubflowInspectorComponent, selector: "fb-subflow-inspector" }, { kind: "component", type: TransformInspectorComponent, selector: "fb-transform-inspector" }, { kind: "component", type: WaitInspectorComponent, selector: "fb-wait-inspector" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
14764
15078
|
}
|
|
14765
15079
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.28", ngImport: i0, type: ElementInspectorComponent, decorators: [{
|
|
14766
15080
|
type: Component,
|
|
@@ -14782,7 +15096,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.28", ngImpo
|
|
|
14782
15096
|
SubflowInspectorComponent,
|
|
14783
15097
|
TransformInspectorComponent,
|
|
14784
15098
|
WaitInspectorComponent,
|
|
14785
|
-
], 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 #labelInput\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 <!--\r\n Elemento appena creato: il nome non e\u2019 ancora niente, si costruisce dall\u2019etichetta e\r\n si puo\u2019 scrivere a mano. Su un elemento gi\u00E0 nominato, invece, cambiare il nome riscrive\r\n i riferimenti di tutto il documento (\u00A713.8) e sta dietro il comando \u00ABRinomina\u00BB.\r\n -->\r\n @if (isComposingName()) {\r\n <input\r\n class=\"fb-input fb-input--mono\"\r\n [class.fb-input--invalid]=\"!!composedNameError()\"\r\n [value]=\"composedName()\"\r\n placeholder=\"si genera dall\u2019etichetta\"\r\n aria-label=\"Nome tecnico\"\r\n (input)=\"onComposedNameInput($any($event.target).value)\"\r\n />\r\n @if (composedNameError()) {\r\n <p class=\"fb-field__error\">{{ composedNameError() }}</p>\r\n } @else {\r\n <p class=\"fb-field__hint\">\r\n \u00C8 l\u2019identificatore con cui i riferimenti raggiungono questo elemento: si costruisce\r\n dall\u2019etichetta finch\u00E9 non lo si scrive a mano, e svuotandolo torna a seguirla.\r\n @if (!composedName()) {\r\n Finch\u00E9 resta vuoto vale \u00AB{{ selectedName() }}\u00BB.\r\n }\r\n </p>\r\n }\r\n } @else 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"] }]
|
|
15099
|
+
], 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 #labelInput\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 <!--\r\n Elemento appena creato: il nome non e\u2019 ancora niente, si costruisce dall\u2019etichetta e\r\n si puo\u2019 scrivere a mano. Su un elemento gi\u00E0 nominato, invece, cambiare il nome riscrive\r\n i riferimenti di tutto il documento (\u00A713.8) e sta dietro il comando \u00ABRinomina\u00BB.\r\n -->\r\n @if (isComposingName()) {\r\n <input\r\n class=\"fb-input fb-input--mono\"\r\n [class.fb-input--invalid]=\"!!composedNameError()\"\r\n [value]=\"composedName()\"\r\n placeholder=\"si genera dall\u2019etichetta\"\r\n aria-label=\"Nome tecnico\"\r\n (input)=\"onComposedNameInput($any($event.target).value)\"\r\n />\r\n @if (composedNameError()) {\r\n <p class=\"fb-field__error\">{{ composedNameError() }}</p>\r\n } @else {\r\n <p class=\"fb-field__hint\">\r\n \u00C8 l\u2019identificatore con cui i riferimenti raggiungono questo elemento: si costruisce\r\n dall\u2019etichetta finch\u00E9 non lo si scrive a mano, e svuotandolo torna a seguirla.\r\n @if (!composedName()) {\r\n Finch\u00E9 resta vuoto vale \u00AB{{ selectedName() }}\u00BB.\r\n }\r\n </p>\r\n }\r\n } @else 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 i riferimenti che puntano a questo elemento e i merge field\r\n <code>{!\u2026}</code> nei testi ({{ referenceCount() }} occorrenze): nessuna\r\n primitiva del backend lo fa, lo fa l\u2019editor.\r\n <!--\r\n Le formule restano indietro di proposito: la loro sintassi e\u2019 del motore e\r\n riscriverci dentro vorrebbe dire indovinare. Il numero va detto **qui**, perche\u2019\r\n e\u2019 l\u2019unico momento in cui si puo\u2019 correggerle sapendo cosa cercare.\r\n -->\r\n @if (formulaUseCount()) {\r\n Le {{ formulaUseCount() }} occorrenze dentro le formule <strong>no</strong>: la\r\n sintassi e\u2019 del motore di regole, e vanno corrette a mano.\r\n }\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"] }]
|
|
14786
15100
|
}], ctorParameters: () => [], propDecorators: { labelInput: [{ type: i0.ViewChild, args: ['labelInput', { isSignal: true }] }], 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"] }] } });
|
|
14787
15101
|
|
|
14788
15102
|
/**
|
|
@@ -15009,100 +15323,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.28", ngImpo
|
|
|
15009
15323
|
args: [{ selector: 'fb-paste-dialog', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"fb-dialog__backdrop\" (click)=\"cancel()\"></div>\r\n\r\n<div\r\n class=\"fb-dialog__panel fb-paste\"\r\n role=\"dialog\"\r\n aria-modal=\"true\"\r\n aria-label=\"Incolla elementi\"\r\n tabindex=\"-1\"\r\n (click)=\"$event.stopPropagation()\"\r\n (keydown.escape)=\"cancel()\"\r\n>\r\n <header class=\"fb-dialog__head\">\r\n <div class=\"fb-dialog__identity\">\r\n <span class=\"fb-dialog__type\">Appunti</span>\r\n <h2 class=\"fb-dialog__title\">Incolla {{ elementLabel() }}</h2>\r\n </div>\r\n <button type=\"button\" class=\"fb-btn fb-btn--ghost fb-btn--icon\" aria-label=\"Chiudi\" (click)=\"cancel()\">\u00D7</button>\r\n </header>\r\n\r\n <div class=\"fb-dialog__body fb-paste__body\">\r\n @if (payload().flowName) {\r\n <p class=\"fb-paste__origin\">Copiati da <strong>{{ payload().flowName }}</strong>.</p>\r\n }\r\n\r\n <ul class=\"fb-paste__elements\">\r\n @for (entry of payload().nodes; track $index) {\r\n <li>{{ entry.node.label || entry.node.name }} <span class=\"fb-paste__tech\"> {{ entry.node.name }}</span></li>\r\n }\r\n </ul>\r\n\r\n @if (plan().processTypeMismatch) {\r\n <!--\r\n Le globali dipendono dal `processType` con cui le si chiede: `$Record` non esiste in uno\r\n screen flow (\u00A74.1). Non blocca \u2014 il documento e' comunque salvabile \u2014 ma un riferimento\r\n che di la' era valido puo' non esserlo qui, e dirlo prima evita la caccia al rilievo.\r\n -->\r\n <p class=\"fb-paste__warn\">\r\n Il flow di origine e\u2019 di tipo <strong>{{ plan().processTypeMismatch }}</strong>: le variabili globali\r\n disponibili qui sono altre, e i riferimenti che le usano potrebbero non essere validi.\r\n </p>\r\n }\r\n\r\n @if (nodeRenames().length || innerRenames().length) {\r\n <section class=\"fb-paste__section\">\r\n <h3 class=\"fb-paste__title\">Nomi gi\u00E0 usati, quindi cambiati</h3>\r\n <p class=\"fb-field__hint\">\r\n Elementi, risorse, step, campi e screen action condividono un unico spazio di nomi: un omonimo\r\n sarebbe <code>NAME_DUPLICATED</code>. I riferimenti interni al blocco seguono il nome nuovo.\r\n </p>\r\n <ul class=\"fb-paste__list\">\r\n @for (rename of nodeRenames(); track $index) {\r\n <li><code>{{ rename.from }}</code> \u2192 <code>{{ rename.to }}</code></li>\r\n }\r\n @for (rename of innerRenames(); track $index) {\r\n <li><code>{{ rename.from }}</code> \u2192 <code>{{ rename.to }}</code> <span class=\"fb-paste__tech\"> interno</span></li>\r\n }\r\n </ul>\r\n </section>\r\n }\r\n\r\n @if (newResources().length) {\r\n <section class=\"fb-paste__section\">\r\n <h3 class=\"fb-paste__title\">Risorse che qui non esistono</h3>\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"createMissingResources()\"\r\n (change)=\"setCreateMissingResources($any($event.target).checked)\"\r\n />\r\n <span>Crearle insieme agli elementi</span>\r\n </label>\r\n <ul class=\"fb-paste__list\">\r\n @for (resource of newResources(); track $index) {\r\n <li><code>{{ resource.name }}</code> <span class=\"fb-paste__tech\"> {{ resourceLabel(resource.collection) }}</span></li>\r\n }\r\n </ul>\r\n @if (!createMissingResources()) {\r\n <p class=\"fb-paste__warn\">\r\n Senza di esse i riferimenti resteranno da ricollegare: il pannello dei problemi li segnalera\u2019\r\n come <code>REFERENCE_UNKNOWN</code>.\r\n </p>\r\n }\r\n </section>\r\n }\r\n\r\n @if (reusedResources().length) {\r\n <section class=\"fb-paste__section\">\r\n <h3 class=\"fb-paste__title\">Risorse gi\u00E0 presenti</h3>\r\n <p class=\"fb-field__hint\">\r\n Un nome uguale esiste gi\u00E0 in questo flow: si usa quello, non se ne crea una copia. Controlla che\r\n sia davvero la stessa cosa \u2014 il confronto e\u2019 sul nome, non sul contenuto.\r\n </p>\r\n <ul class=\"fb-paste__list\">\r\n @for (resource of reusedResources(); track $index) {\r\n <li><code>{{ resource.name }}</code></li>\r\n }\r\n </ul>\r\n </section>\r\n }\r\n\r\n @if (plan().unresolved.length) {\r\n <section class=\"fb-paste__section\">\r\n <h3 class=\"fb-paste__title\">Riferimenti che restano da sistemare</h3>\r\n <p class=\"fb-field__hint\">\r\n Puntano a qualcosa che in questo flow non c\u2019e\u2019 \u2014 quasi sempre un elemento rimasto nel flow di\r\n origine. L\u2019incolla non puo\u2019 portarseli dietro: vanno ricollegati a mano.\r\n </p>\r\n <ul class=\"fb-paste__list\">\r\n @for (name of plan().unresolved; track $index) {\r\n <li><code>{{ name }}</code></li>\r\n }\r\n </ul>\r\n </section>\r\n }\r\n </div>\r\n\r\n <footer class=\"fb-dialog__foot\">\r\n <span class=\"fb-dialog__note\">Un solo passo di annulla riporta tutto com\u2019era.</span>\r\n <div class=\"fb-paste__actions\">\r\n <button type=\"button\" class=\"fb-btn\" (click)=\"cancel()\">Annulla</button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--primary\" (click)=\"confirm()\">Incolla</button>\r\n </div>\r\n </footer>\r\n</div>\r\n", styles: [":host{position:absolute;inset:0;z-index:40;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(560px,100%);max-height:min(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,.fb-dialog__foot{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:12px 14px}.fb-dialog__head{border-bottom:1px solid var(--fb-border-subtle, #eef0f4)}.fb-dialog__foot{border-top:1px solid var(--fb-border-subtle, #eef0f4)}.fb-dialog__type{display:block;font-size:11px;text-transform:uppercase;letter-spacing:.04em;color:var(--fb-text-muted, #6b7086)}.fb-dialog__title{margin:0;font-size:15px;font-weight:600}.fb-dialog__note{color:var(--fb-text-muted, #6b7086);font-size:12px}.fb-dialog__body{display:flex;flex-direction:column;gap:14px;padding:14px;overflow:auto}.fb-paste__origin{margin:0;color:var(--fb-text-muted, #6b7086);font-size:12px}.fb-paste__elements,.fb-paste__list{margin:0;padding-left:18px;display:flex;flex-direction:column;gap:4px;font-size:13px}.fb-paste__section{display:flex;flex-direction:column;gap:6px;padding-top:12px;border-top:1px solid var(--fb-border-subtle, #eef0f4)}.fb-paste__title{margin:0;font-size:12px;font-weight:600;text-transform:uppercase;letter-spacing:.04em;color:var(--fb-text-muted, #6b7086)}.fb-paste__tech{color:var(--fb-text-muted, #6b7086);font-size:11px}.fb-paste__warn{margin:0;padding:8px 10px;border-radius:var(--fb-radius-sm, 8px);background:var(--fb-surface-alt, #f7f8fa);color:var(--fb-warning, #b7791f);font-size:12px}.fb-paste__actions{display:flex;gap:8px}\n"] }]
|
|
15010
15324
|
}], propDecorators: { payload: [{ type: i0.Input, args: [{ isSignal: true, alias: "payload", required: true }] }], plan: [{ type: i0.Input, args: [{ isSignal: true, alias: "plan", required: true }] }], confirmed: [{ type: i0.Output, args: ["confirmed"] }], cancelled: [{ type: i0.Output, args: ["cancelled"] }] } });
|
|
15011
15325
|
|
|
15012
|
-
/**
|
|
15013
|
-
* Quante volte un nome e' **usato** nel documento.
|
|
15014
|
-
*
|
|
15015
|
-
* Serve dove togliere qualcosa rompe il resto: cancellare una risorsa non e' come cancellare
|
|
15016
|
-
* un elemento — nessun arco sparisce dal canvas, nessun rilievo compare finche' non si valida,
|
|
15017
|
-
* e i riferimenti orfani si scoprono dopo. Il conteggio e' cio' che rende visibile il danno
|
|
15018
|
-
* **prima**.
|
|
15019
|
-
*
|
|
15020
|
-
* Due regole, e sono le stesse della rinomina:
|
|
15021
|
-
*
|
|
15022
|
-
* - i riferimenti stanno nei campi di {@link FLOW_REFERENCE_FIELDS} — l'elenco e' **uno**, e
|
|
15023
|
-
* questo modulo lo importa invece di riscriverlo: due elenchi diventano due elenchi diversi
|
|
15024
|
-
* al primo campo aggiunto (§13.8);
|
|
15025
|
-
* - un riferimento vale anche **navigato**: `Ordine.Numero` e' un uso di `Ordine`.
|
|
15026
|
-
*
|
|
15027
|
-
* Piu' una che la rinomina non ha: le **formule**. Il motore di regole cita le risorse per
|
|
15028
|
-
* nome dentro un'espressione (`GetStreetToponym(Residenza_Street)`), quindi un conteggio che
|
|
15029
|
-
* guardasse solo i campi di riferimento direbbe «non usata» di una variabile che tre formule
|
|
15030
|
-
* leggono. L'espressione non la interpretiamo — la sintassi e' del motore (§6.3) — quindi si
|
|
15031
|
-
* spezza in identificatori e si confronta la radice: e' un'approssimazione, ed e' per questo
|
|
15032
|
-
* che il risultato serve ad **avvertire**, non a decidere al posto dell'utente.
|
|
15033
|
-
*/
|
|
15034
|
-
/**
|
|
15035
|
-
* I campi che contengono un'espressione del motore di regole. `expression` e' quello di una
|
|
15036
|
-
* risorsa `Formula` (§4.6), `formulaExpression` quello di un valore e di una validation rule
|
|
15037
|
-
* (§5.2), `filterFormula` quello di un filtro di record (§5.8).
|
|
15038
|
-
*/
|
|
15039
|
-
const FLOW_FORMULA_FIELDS = new Set([
|
|
15040
|
-
'expression',
|
|
15041
|
-
'formulaExpression',
|
|
15042
|
-
'filterFormula',
|
|
15043
|
-
]);
|
|
15044
|
-
/** `true` se la stringa e' quel nome, o un percorso che parte da quel nome. */
|
|
15045
|
-
function isUseOf(value, name) {
|
|
15046
|
-
return value === name || value.startsWith(`${name}.`);
|
|
15047
|
-
}
|
|
15048
|
-
/**
|
|
15049
|
-
* Gli identificatori citati da un'espressione, ridotti alla loro **radice**: di
|
|
15050
|
-
* `OCRCIE.TaxCode` conta `OCRCIE`, perche' e' quella la risorsa. Senza la riduzione, un
|
|
15051
|
-
* membro chiamato come una variabile la farebbe sembrare usata.
|
|
15052
|
-
*/
|
|
15053
|
-
function rootsIn(expression) {
|
|
15054
|
-
return expression
|
|
15055
|
-
.split(/[^A-Za-z0-9_.$]+/)
|
|
15056
|
-
.filter(Boolean)
|
|
15057
|
-
.map((token) => token.split('.')[0])
|
|
15058
|
-
.filter(Boolean);
|
|
15059
|
-
}
|
|
15060
|
-
/**
|
|
15061
|
-
* Quante volte `name` compare come riferimento nel documento (o in un frammento).
|
|
15062
|
-
*
|
|
15063
|
-
* Non conta la **dichiarazione**: il campo `name` della risorsa o dell'elemento non e' un
|
|
15064
|
-
* campo di riferimento, quindi resta fuori da se'. E' la differenza con il conteggio della
|
|
15065
|
-
* rinomina, che serializza il documento e cerca la stringa: lì un elemento mai referenziato
|
|
15066
|
-
* «compare 1 volta».
|
|
15067
|
-
*/
|
|
15068
|
-
function countNameUses(root, name) {
|
|
15069
|
-
if (!name) {
|
|
15070
|
-
return 0;
|
|
15071
|
-
}
|
|
15072
|
-
let count = 0;
|
|
15073
|
-
const walk = (value) => {
|
|
15074
|
-
if (Array.isArray(value)) {
|
|
15075
|
-
value.forEach(walk);
|
|
15076
|
-
return;
|
|
15077
|
-
}
|
|
15078
|
-
if (!value || typeof value !== 'object') {
|
|
15079
|
-
return;
|
|
15080
|
-
}
|
|
15081
|
-
for (const [key, child] of Object.entries(value)) {
|
|
15082
|
-
if (typeof child === 'string') {
|
|
15083
|
-
if (FLOW_REFERENCE_FIELDS.has(key)) {
|
|
15084
|
-
if (isUseOf(child, name)) {
|
|
15085
|
-
count++;
|
|
15086
|
-
}
|
|
15087
|
-
}
|
|
15088
|
-
else if (FLOW_FORMULA_FIELDS.has(key)) {
|
|
15089
|
-
count += rootsIn(child).filter((root) => root === name).length;
|
|
15090
|
-
}
|
|
15091
|
-
continue;
|
|
15092
|
-
}
|
|
15093
|
-
if (FLOW_REFERENCE_FIELDS.has(key) && Array.isArray(child) && child.every((item) => typeof item === 'string')) {
|
|
15094
|
-
// `choiceReferences` e' un array di **nomi**: scendendoci dentro non si troverebbe
|
|
15095
|
-
// nessuna stringa da confrontare, perche' le stringhe sono gli elementi.
|
|
15096
|
-
count += child.filter((item) => isUseOf(item, name)).length;
|
|
15097
|
-
continue;
|
|
15098
|
-
}
|
|
15099
|
-
walk(child);
|
|
15100
|
-
}
|
|
15101
|
-
};
|
|
15102
|
-
walk(root);
|
|
15103
|
-
return count;
|
|
15104
|
-
}
|
|
15105
|
-
|
|
15106
15326
|
/**
|
|
15107
15327
|
* I controlli locali su una risorsa, condivisi fra l'elenco e la dialog di modifica.
|
|
15108
15328
|
*
|
|
@@ -15133,6 +15353,47 @@ function constantReferencesResource(reference) {
|
|
|
15133
15353
|
const value = reference.resource['value'];
|
|
15134
15354
|
return !!value?.elementReference || !!value?.formulaExpression;
|
|
15135
15355
|
}
|
|
15356
|
+
/**
|
|
15357
|
+
* §5.2 — i campi che su una sorgente `collectionReference` il runtime **ignora**.
|
|
15358
|
+
*
|
|
15359
|
+
* Filtri, logica e ordinamento valgono sulle sole sorgenti `object` ed `enumType`: le opzioni di
|
|
15360
|
+
* una collection sono i suoi elementi nell'ordine in cui ci sono. Dichiararli lì non rompe niente
|
|
15361
|
+
* — per questo e' `CHOICE_SET_FILTERS_IGNORED` e resta un **avviso** — ma e' peggio di un errore
|
|
15362
|
+
* di sintassi: il flow gira, il choice set propone tutto, e chi l'ha scritto crede di aver
|
|
15363
|
+
* selezionato qualcosa. Restituisce le **etichette** dei campi trovati, perche' l'avviso deve
|
|
15364
|
+
* dire quali sono: «i filtri» e «l'ordinamento» sono due rimedi diversi.
|
|
15365
|
+
*/
|
|
15366
|
+
function ignoredChoiceSetFiltersOf(reference) {
|
|
15367
|
+
if (reference.collection !== 'dynamicChoiceSets') {
|
|
15368
|
+
return [];
|
|
15369
|
+
}
|
|
15370
|
+
const set = reference.resource;
|
|
15371
|
+
if (choiceSetSourceOf(set) !== 'collection') {
|
|
15372
|
+
return [];
|
|
15373
|
+
}
|
|
15374
|
+
const found = [];
|
|
15375
|
+
if (set.filters?.length) {
|
|
15376
|
+
found.push('i filtri');
|
|
15377
|
+
}
|
|
15378
|
+
if (set.filterLogic) {
|
|
15379
|
+
found.push('la logica dei filtri');
|
|
15380
|
+
}
|
|
15381
|
+
if (set.sortField || set.sortOrder) {
|
|
15382
|
+
found.push('l’ordinamento');
|
|
15383
|
+
}
|
|
15384
|
+
return found;
|
|
15385
|
+
}
|
|
15386
|
+
/**
|
|
15387
|
+
* La frase dell'avviso, con l'accordo giusto: «i filtri e l'ordinamento non si applicano».
|
|
15388
|
+
* Sta accanto al controllo perche' la dicono in due — l'elenco e il form — e due frasi scritte a
|
|
15389
|
+
* mano diventano due frasi diverse per lo stesso rilievo.
|
|
15390
|
+
*/
|
|
15391
|
+
function describeIgnoredChoiceSetFilters(ignored) {
|
|
15392
|
+
const list = ignored.length > 1
|
|
15393
|
+
? `${ignored.slice(0, -1).join(', ')} e ${ignored[ignored.length - 1]}`
|
|
15394
|
+
: ignored[0] ?? '';
|
|
15395
|
+
return `${list} ${ignored.length > 1 ? 'non si applicano' : 'non si applica'}`;
|
|
15396
|
+
}
|
|
15136
15397
|
/** `stageOrder` duplicati: quale stage sia il corrente all'avvio diventa arbitrario. */
|
|
15137
15398
|
function duplicateStageOrder(reference, resources) {
|
|
15138
15399
|
if (reference.collection !== 'stages') {
|
|
@@ -15145,6 +15406,67 @@ function duplicateStageOrder(reference, resources) {
|
|
|
15145
15406
|
return (resources.filter((entry) => entry.collection === 'stages' && entry.resource['stageOrder'] === order)
|
|
15146
15407
|
.length > 1);
|
|
15147
15408
|
}
|
|
15409
|
+
/**
|
|
15410
|
+
* `true` se la risorsa ha **almeno un** rilievo locale.
|
|
15411
|
+
*
|
|
15412
|
+
* E' la sintesi dei tre controlli qui sopra, e serve al filtro dell'elenco: «quali risorse
|
|
15413
|
+
* hanno qualcosa che non va» e' la domanda che su venti righe si risolveva scorrendole tutte.
|
|
15414
|
+
* Sta qui e non nel pannello perche' un quarto controllo aggiunto sopra deve entrare nel
|
|
15415
|
+
* filtro da se': una sintesi che elenca i check a mano si dimentica l'ultimo aggiunto, e il
|
|
15416
|
+
* difetto sarebbe una riga rotta che il filtro non mostra.
|
|
15417
|
+
*/
|
|
15418
|
+
function hasResourceIssue(reference, usedNames, resources) {
|
|
15419
|
+
return (resourceNameError(reference, usedNames) !== null ||
|
|
15420
|
+
constantReferencesResource(reference) ||
|
|
15421
|
+
duplicateStageOrder(reference, resources) ||
|
|
15422
|
+
ignoredChoiceSetFiltersOf(reference).length > 0);
|
|
15423
|
+
}
|
|
15424
|
+
|
|
15425
|
+
/**
|
|
15426
|
+
* Il filtro dell'elenco delle risorse.
|
|
15427
|
+
*
|
|
15428
|
+
* Esiste per la **scala**: un flow di produzione ha ventun variabili e dodici dynamic choice
|
|
15429
|
+
* set, e a quella misura il problema dell'elenco non e' leggerlo ma trovarci dentro una riga.
|
|
15430
|
+
* Non e' la ricerca del flow (`core/flow-search.ts`), che sa dov'e' un nome che si conosce
|
|
15431
|
+
* **già**: qui si restringe un elenco che si sta guardando, e le due domande che si fanno
|
|
15432
|
+
* davvero — «quali non sono usate» e «quali hanno qualcosa che non va» — non sono nomi.
|
|
15433
|
+
*
|
|
15434
|
+
* Cio' che il testo guarda e' cio' che l'elenco **mostra**, piu' il nome e l'etichetta: e' la
|
|
15435
|
+
* stessa scelta di `flow-search`, dove il contenuto dei valori resta fuori perche' «chi cita
|
|
15436
|
+
* `Totale`» e' un'altra domanda — quella la risponde il conteggio degli usi. Quindi
|
|
15437
|
+
* l'espressione di una formula e il valore di una costante **non** si cercano: chi digita
|
|
15438
|
+
* «IVA» e vede comparire una formula che non si chiama così crede di aver trovato una risorsa
|
|
15439
|
+
* che non c'e'.
|
|
15440
|
+
*/
|
|
15441
|
+
/**
|
|
15442
|
+
* Il testo su cui il filtro confronta: nome, etichetta, tipo, e le **parole dei flag** così
|
|
15443
|
+
* come la riga le scrive.
|
|
15444
|
+
*
|
|
15445
|
+
* I flag ci stanno perche' sull'elenco si leggono come testo (`· input`), e chi digita
|
|
15446
|
+
* «input» sta cercando proprio quelle: cercare in cio' che si vede e non trovarlo e' il modo
|
|
15447
|
+
* piu' rapido di far credere che il filtro sia rotto. `objectType` invece non si vede, ed e'
|
|
15448
|
+
* lì lo stesso perche' il tipo di una variabile record e' `Ordine`, non `Object`.
|
|
15449
|
+
*/
|
|
15450
|
+
function haystackOf(reference) {
|
|
15451
|
+
const resource = reference.resource;
|
|
15452
|
+
const parts = [
|
|
15453
|
+
reference.name,
|
|
15454
|
+
typeof resource['label'] === 'string' ? resource['label'] : '',
|
|
15455
|
+
typeof resource['dataType'] === 'string' ? resource['dataType'] : '',
|
|
15456
|
+
typeof resource['objectType'] === 'string' ? resource['objectType'] : '',
|
|
15457
|
+
resource['isCollection'] === true ? 'collection' : '',
|
|
15458
|
+
resource['isInput'] === true ? 'input' : '',
|
|
15459
|
+
resource['isOutput'] === true ? 'output' : '',
|
|
15460
|
+
];
|
|
15461
|
+
return parts.join(' ').toLowerCase();
|
|
15462
|
+
}
|
|
15463
|
+
/** `true` se la risorsa corrisponde al testo digitato; un testo vuoto non filtra niente. */
|
|
15464
|
+
function matchesResourceQuery(reference, needle) {
|
|
15465
|
+
if (!needle) {
|
|
15466
|
+
return true;
|
|
15467
|
+
}
|
|
15468
|
+
return haystackOf(reference).includes(needle);
|
|
15469
|
+
}
|
|
15148
15470
|
|
|
15149
15471
|
/**
|
|
15150
15472
|
* Le sette collection di risorse (§4.6).
|
|
@@ -15195,7 +15517,7 @@ const RESOURCE_KINDS = [
|
|
|
15195
15517
|
label: 'Dynamic choice set',
|
|
15196
15518
|
singular: 'dynamic choice set',
|
|
15197
15519
|
isWritable: false,
|
|
15198
|
-
note: 'Scelte generate da una collection, da una query o da un tipo enum.
|
|
15520
|
+
note: 'Scelte generate da una collection, da una query o da un tipo enum. Su una collection filtri e ordinamento non si applicano.',
|
|
15199
15521
|
},
|
|
15200
15522
|
{
|
|
15201
15523
|
collection: 'stages',
|
|
@@ -15223,12 +15545,18 @@ function resourceKindOf(collection) {
|
|
|
15223
15545
|
* per cui l'elenco esiste. Cio' che resta qui e' quello che si legge **senza** aprire: nome,
|
|
15224
15546
|
* tipo, quante volte e' usata, e i rilievi locali della riga.
|
|
15225
15547
|
*
|
|
15548
|
+
* Il **filtro** e' l'altra cosa che resta qui, e per la stessa ragione dell'elenco: su un flow
|
|
15549
|
+
* di produzione le righe sono cinquanta, e allora la domanda non e' piu' «cosa dice questa
|
|
15550
|
+
* risorsa» ma «dov'e' quella che cerco». Le regole del filtro stanno in `resource-filter.ts`.
|
|
15551
|
+
*
|
|
15226
15552
|
* La finestra non la monta il pannello ma il builder, e per una ragione sola: qui dentro
|
|
15227
15553
|
* sarebbe ritagliata dallo scorrimento della colonna laterale. Il pannello **annuncia** quale
|
|
15228
15554
|
* risorsa si vuole configurare (`editRequested`) e riceve indietro quale sia aperta
|
|
15229
15555
|
* (`editing`), invece di tenerne una copia sua che si scollerebbe alla prima chiusura fatta
|
|
15230
15556
|
* dalla finestra.
|
|
15231
15557
|
*/
|
|
15558
|
+
/** Nessun uso: il valore per una risorsa che la mappa non conosce ancora. */
|
|
15559
|
+
const EMPTY_USES = { references: 0, formulas: 0, mergeFields: 0, total: 0 };
|
|
15232
15560
|
class ResourcePanelComponent {
|
|
15233
15561
|
store = inject(FlowDocumentStore);
|
|
15234
15562
|
closed = output();
|
|
@@ -15247,6 +15575,11 @@ class ResourcePanelComponent {
|
|
|
15247
15575
|
* ricalcolo del documento.
|
|
15248
15576
|
*/
|
|
15249
15577
|
pendingRemoval = signal(null, ...(ngDevMode ? [{ debugName: "pendingRemoval" }] : []));
|
|
15578
|
+
/** Il testo del filtro. Non si azzera cambiando scheda: vedi `select`. */
|
|
15579
|
+
query = signal('', ...(ngDevMode ? [{ debugName: "query" }] : []));
|
|
15580
|
+
/** Lo stato su cui si restringe: sono le due domande che un nome non sa fare. */
|
|
15581
|
+
stateFilter = signal('all', ...(ngDevMode ? [{ debugName: "stateFilter" }] : []));
|
|
15582
|
+
hasFilter = computed(() => this.query().trim() !== '' || this.stateFilter() !== 'all', ...(ngDevMode ? [{ debugName: "hasFilter" }] : []));
|
|
15250
15583
|
constructor() {
|
|
15251
15584
|
// L'arrivo dalla ricerca: apre la scheda giusta e la risorsa, nella finestra.
|
|
15252
15585
|
effect(() => {
|
|
@@ -15255,19 +15588,126 @@ class ResourcePanelComponent {
|
|
|
15255
15588
|
return;
|
|
15256
15589
|
}
|
|
15257
15590
|
this.activeCollection.set(target.collection);
|
|
15591
|
+
// Il filtro si azzera: la ricerca promette **quella** risorsa, e un filtro rimasto acceso
|
|
15592
|
+
// la terrebbe fuori dall'elenco proprio mentre la sua finestra si apre — cioe' una riga
|
|
15593
|
+
// evidenziata che non si vede. E' la stessa regola con cui `bringIntoView` apre il
|
|
15594
|
+
// riquadro chiuso che nasconde l'elemento cercato.
|
|
15595
|
+
this.resetFilter();
|
|
15258
15596
|
this.editRequested.emit({ collection: target.collection, index: target.index });
|
|
15259
15597
|
});
|
|
15260
15598
|
}
|
|
15261
15599
|
activeKind = computed(() => resourceKindOf(this.activeCollection()), ...(ngDevMode ? [{ debugName: "activeKind" }] : []));
|
|
15262
|
-
|
|
15263
|
-
|
|
15264
|
-
|
|
15265
|
-
}
|
|
15600
|
+
/** Tutte le risorse della collection aperta, **prima** del filtro. */
|
|
15601
|
+
collectionItems = computed(() => this.store.resources().filter((reference) => reference.collection === this.activeCollection()), ...(ngDevMode ? [{ debugName: "collectionItems" }] : []));
|
|
15602
|
+
/** Le righe che l'elenco disegna: la collection aperta, filtrata. */
|
|
15603
|
+
items = computed(() => this.collectionItems().filter((reference) => this.matches(reference)), ...(ngDevMode ? [{ debugName: "items" }] : []));
|
|
15266
15604
|
select(collection) {
|
|
15267
15605
|
this.activeCollection.set(collection);
|
|
15268
15606
|
// Cambiare scheda con una finestra aperta su un'altra collection lascerebbe due contesti
|
|
15269
|
-
// discordi: si chiude.
|
|
15607
|
+
// discordi: si chiude. La conferma di rimozione pure: la riga a cui si riferisce non e'
|
|
15608
|
+
// piu' nell'elenco, e resterebbe appesa su una risorsa che non si vede.
|
|
15270
15609
|
this.editRequested.emit(null);
|
|
15610
|
+
this.pendingRemoval.set(null);
|
|
15611
|
+
}
|
|
15612
|
+
// -------------------------------------------------------------------------
|
|
15613
|
+
// Filtro
|
|
15614
|
+
// -------------------------------------------------------------------------
|
|
15615
|
+
needle = computed(() => this.query().trim().toLowerCase(), ...(ngDevMode ? [{ debugName: "needle" }] : []));
|
|
15616
|
+
/** `true` se la risorsa passa testo **e** stato. */
|
|
15617
|
+
matches(reference) {
|
|
15618
|
+
if (!matchesResourceQuery(reference, this.needle())) {
|
|
15619
|
+
return false;
|
|
15620
|
+
}
|
|
15621
|
+
switch (this.stateFilter()) {
|
|
15622
|
+
case 'unused':
|
|
15623
|
+
return this.usesOf(reference) === 0;
|
|
15624
|
+
case 'issues':
|
|
15625
|
+
return hasResourceIssue(reference, this.store.usedNames(), this.store.resources());
|
|
15626
|
+
default:
|
|
15627
|
+
return true;
|
|
15628
|
+
}
|
|
15629
|
+
}
|
|
15630
|
+
/**
|
|
15631
|
+
* Quante risorse di ogni collection passano il filtro, e quante ce ne sono in tutto.
|
|
15632
|
+
*
|
|
15633
|
+
* Il numero sulla scheda diventa quello delle **corrispondenze** appena un filtro e' attivo,
|
|
15634
|
+
* ed e' il solo modo di rispondere alla domanda vera di chi digita: la risorsa che cerca sta
|
|
15635
|
+
* spesso in un'altra collection, e senza questo numero bisognerebbe aprire le sette schede
|
|
15636
|
+
* una per una per scoprire che non c'e' da nessuna parte. Il conteggio e' **uno** per tutte
|
|
15637
|
+
* le schede e non una `filter` per scheda nel template: quello lo rifaceva a ogni giro di
|
|
15638
|
+
* change detection, e con il filtro «non usate» dentro sarebbero cinquanta walk del
|
|
15639
|
+
* documento per volta.
|
|
15640
|
+
*/
|
|
15641
|
+
tabCounts = computed(() => {
|
|
15642
|
+
const counts = {};
|
|
15643
|
+
for (const kind of RESOURCE_KINDS) {
|
|
15644
|
+
counts[kind.collection] = { total: 0, matching: 0 };
|
|
15645
|
+
}
|
|
15646
|
+
for (const reference of this.store.resources()) {
|
|
15647
|
+
const entry = counts[reference.collection];
|
|
15648
|
+
if (!entry) {
|
|
15649
|
+
continue;
|
|
15650
|
+
}
|
|
15651
|
+
entry.total += 1;
|
|
15652
|
+
if (this.matches(reference)) {
|
|
15653
|
+
entry.matching += 1;
|
|
15654
|
+
}
|
|
15655
|
+
}
|
|
15656
|
+
return counts;
|
|
15657
|
+
}, ...(ngDevMode ? [{ debugName: "tabCounts" }] : []));
|
|
15658
|
+
countOf(collection) {
|
|
15659
|
+
return this.tabCounts()[collection]?.matching ?? 0;
|
|
15660
|
+
}
|
|
15661
|
+
totalOf(collection) {
|
|
15662
|
+
return this.tabCounts()[collection]?.total ?? 0;
|
|
15663
|
+
}
|
|
15664
|
+
/**
|
|
15665
|
+
* Quante risorse ha ogni stato nella scheda aperta, contate **dopo** il testo: i due chip
|
|
15666
|
+
* dicono quanto resterebbe, non quanto ce n'e' in assoluto. A zero si disabilitano, perche'
|
|
15667
|
+
* un filtro che da' certamente un elenco vuoto non e' un filtro ma un modo di nascondere
|
|
15668
|
+
* l'elenco.
|
|
15669
|
+
*/
|
|
15670
|
+
stateCounts = computed(() => {
|
|
15671
|
+
const needle = this.needle();
|
|
15672
|
+
const usedNames = this.store.usedNames();
|
|
15673
|
+
const resources = this.store.resources();
|
|
15674
|
+
let unused = 0;
|
|
15675
|
+
let issues = 0;
|
|
15676
|
+
for (const reference of this.collectionItems()) {
|
|
15677
|
+
if (!matchesResourceQuery(reference, needle)) {
|
|
15678
|
+
continue;
|
|
15679
|
+
}
|
|
15680
|
+
if (this.usesOf(reference) === 0) {
|
|
15681
|
+
unused += 1;
|
|
15682
|
+
}
|
|
15683
|
+
if (hasResourceIssue(reference, usedNames, resources)) {
|
|
15684
|
+
issues += 1;
|
|
15685
|
+
}
|
|
15686
|
+
}
|
|
15687
|
+
return { unused, issues };
|
|
15688
|
+
}, ...(ngDevMode ? [{ debugName: "stateCounts" }] : []));
|
|
15689
|
+
/**
|
|
15690
|
+
* Le corrispondenze nelle **altre** schede: e' cio' che si dice quando qui non c'e' niente.
|
|
15691
|
+
* «Nessuna corrispondenza» su un elenco vuoto e' vero e inutile — il rimedio e' cambiare
|
|
15692
|
+
* scheda, e bisogna sapere se ce n'e' una da cambiare.
|
|
15693
|
+
*/
|
|
15694
|
+
matchesElsewhere = computed(() => {
|
|
15695
|
+
const active = this.activeCollection();
|
|
15696
|
+
return RESOURCE_KINDS.filter((kind) => kind.collection !== active).reduce((total, kind) => total + this.countOf(kind.collection), 0);
|
|
15697
|
+
}, ...(ngDevMode ? [{ debugName: "matchesElsewhere" }] : []));
|
|
15698
|
+
onQuery(value) {
|
|
15699
|
+
this.query.set(value);
|
|
15700
|
+
// La riga della conferma potrebbe non corrispondere piu': la conferma se ne va con lei.
|
|
15701
|
+
this.pendingRemoval.set(null);
|
|
15702
|
+
}
|
|
15703
|
+
/** Il secondo clic sullo stesso stato lo spegne, come nel pannello dei problemi. */
|
|
15704
|
+
setStateFilter(filter) {
|
|
15705
|
+
this.stateFilter.set(this.stateFilter() === filter ? 'all' : filter);
|
|
15706
|
+
this.pendingRemoval.set(null);
|
|
15707
|
+
}
|
|
15708
|
+
resetFilter() {
|
|
15709
|
+
this.query.set('');
|
|
15710
|
+
this.stateFilter.set('all');
|
|
15271
15711
|
}
|
|
15272
15712
|
isEditing(reference) {
|
|
15273
15713
|
const editing = this.editing();
|
|
@@ -15290,6 +15730,15 @@ class ResourcePanelComponent {
|
|
|
15290
15730
|
duplicateStageOrder(reference) {
|
|
15291
15731
|
return duplicateStageOrder(reference, this.store.resources());
|
|
15292
15732
|
}
|
|
15733
|
+
/**
|
|
15734
|
+
* §5.2 — cio' che su una sorgente collection il runtime ignora. Sta anche qui e non solo nel
|
|
15735
|
+
* form per la ragione di sempre: il filtro «con rilievi» porta la riga a galla, e una riga che
|
|
15736
|
+
* non dice **cosa** non va costringe ad aprirla per scoprirlo.
|
|
15737
|
+
*/
|
|
15738
|
+
ignoredChoiceSetFilters(reference) {
|
|
15739
|
+
const ignored = ignoredChoiceSetFiltersOf(reference);
|
|
15740
|
+
return ignored.length ? describeIgnoredChoiceSetFilters(ignored) : null;
|
|
15741
|
+
}
|
|
15293
15742
|
// -------------------------------------------------------------------------
|
|
15294
15743
|
// Aggiunta e rimozione
|
|
15295
15744
|
// -------------------------------------------------------------------------
|
|
@@ -15316,9 +15765,14 @@ class ResourcePanelComponent {
|
|
|
15316
15765
|
resource['label'] = name;
|
|
15317
15766
|
}
|
|
15318
15767
|
this.store.addResource(kind.collection, resource);
|
|
15768
|
+
// Il filtro si spegne, e l'indice si conta sulla collection **intera**: la risorsa appena
|
|
15769
|
+
// aggiunta si chiama «variabile_1» e non corrisponderebbe a niente di cio' che si stava
|
|
15770
|
+
// cercando — con il filtro acceso si aprirebbe la finestra su una riga invisibile, e
|
|
15771
|
+
// `items()` filtrato darebbe l'indice di un'altra risorsa.
|
|
15772
|
+
this.resetFilter();
|
|
15319
15773
|
// La risorsa nuova e' l'ultima della collection, e si apre subito: aggiungerla senza aprirla
|
|
15320
15774
|
// lascerebbe una riga chiamata «variabile_1» da configurare in un secondo gesto.
|
|
15321
|
-
this.editRequested.emit({ collection: kind.collection, index: this.
|
|
15775
|
+
this.editRequested.emit({ collection: kind.collection, index: this.collectionItems().length - 1 });
|
|
15322
15776
|
}
|
|
15323
15777
|
/**
|
|
15324
15778
|
* Quante volte la risorsa e' **usata** nel documento.
|
|
@@ -15332,8 +15786,56 @@ class ResourcePanelComponent {
|
|
|
15332
15786
|
* senza, tre formule che leggono una variabile la direbbero «non usata».
|
|
15333
15787
|
*/
|
|
15334
15788
|
usesOf(reference) {
|
|
15335
|
-
return
|
|
15789
|
+
return this.usesDetailOf(reference).total;
|
|
15790
|
+
}
|
|
15791
|
+
/**
|
|
15792
|
+
* Il dettaglio degli usi: quanti riferimenti, quante formule, quanti merge field.
|
|
15793
|
+
*
|
|
15794
|
+
* Serve al `title` della riga, e non e' un vezzo: `{!Cliente.Nome}` in un messaggio e' un uso
|
|
15795
|
+
* che non si vede da nessuna parte nel documento, quindi «13 usi» senza dire **dove** manda a
|
|
15796
|
+
* cercarli nei posti sbagliati. E le tre categorie non si comportano allo stesso modo davanti a
|
|
15797
|
+
* una rinomina, che e' l'altra cosa che si fa con questo numero.
|
|
15798
|
+
*/
|
|
15799
|
+
usesDetailOf(reference) {
|
|
15800
|
+
return this.useCounts().get(reference.name) ?? EMPTY_USES;
|
|
15801
|
+
}
|
|
15802
|
+
/** La riga del `title`: si scrivono le sole categorie presenti, altrimenti sono sempre tre. */
|
|
15803
|
+
usesDetailLabel(reference) {
|
|
15804
|
+
const uses = this.usesDetailOf(reference);
|
|
15805
|
+
const parts = [];
|
|
15806
|
+
if (uses.references) {
|
|
15807
|
+
parts.push(uses.references === 1 ? '1 come riferimento' : `${uses.references} come riferimenti`);
|
|
15808
|
+
}
|
|
15809
|
+
if (uses.formulas) {
|
|
15810
|
+
parts.push(`${uses.formulas} in formule`);
|
|
15811
|
+
}
|
|
15812
|
+
if (uses.mergeFields) {
|
|
15813
|
+
parts.push(`${uses.mergeFields} nei testi con {!…}`);
|
|
15814
|
+
}
|
|
15815
|
+
return parts.join(', ');
|
|
15336
15816
|
}
|
|
15817
|
+
/**
|
|
15818
|
+
* Il conteggio di **tutte** le risorse, calcolato una volta per documento.
|
|
15819
|
+
*
|
|
15820
|
+
* `countNameUses` cammina il documento intero a ogni chiamata, e da quando il filtro lo
|
|
15821
|
+
* chiede per ogni risorsa di ogni collection — i numeri delle schede, il chip «non usate» —
|
|
15822
|
+
* chiamarlo dal template significherebbe cinquanta walk per giro di change detection.
|
|
15823
|
+
*
|
|
15824
|
+
* La chiave e' il nome **esatto** e non quello minuscolo: il conteggio confronta le stringhe
|
|
15825
|
+
* come sono, quindi due nomi che differiscono solo per il case — che sono un `NAME_DUPLICATED`,
|
|
15826
|
+
* non un caso da normalizzare qui — hanno due conteggi diversi, ed e' quello che il pannello
|
|
15827
|
+
* mostrava anche prima della memoizzazione.
|
|
15828
|
+
*/
|
|
15829
|
+
useCounts = computed(() => {
|
|
15830
|
+
const document = this.store.document();
|
|
15831
|
+
const counts = new Map();
|
|
15832
|
+
for (const reference of this.store.resources()) {
|
|
15833
|
+
if (!counts.has(reference.name)) {
|
|
15834
|
+
counts.set(reference.name, describeNameUses(document, reference.name));
|
|
15835
|
+
}
|
|
15836
|
+
}
|
|
15837
|
+
return counts;
|
|
15838
|
+
}, ...(ngDevMode ? [{ debugName: "useCounts" }] : []));
|
|
15337
15839
|
keyOf(reference) {
|
|
15338
15840
|
return `${reference.collection}:${reference.index}`;
|
|
15339
15841
|
}
|
|
@@ -15376,11 +15878,11 @@ class ResourcePanelComponent {
|
|
|
15376
15878
|
this.closed.emit();
|
|
15377
15879
|
}
|
|
15378
15880
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.28", ngImport: i0, type: ResourcePanelComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
15379
|
-
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 });
|
|
15881
|
+
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 <!--\r\n Con un filtro acceso il numero della scheda e\u2019 quello delle **corrispondenze**: e\u2019 cio\u2019\r\n che dice dove sta la risorsa che si cerca, che il piu\u2019 delle volte e\u2019 in un\u2019altra\r\n collection. La scheda a zero si sbiadisce invece di sparire \u2014 nasconderla farebbe\r\n ballare la barra a ogni lettera digitata.\r\n -->\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 [class.fb-res__tab--dim]=\"hasFilter() && countOf(kind.collection) === 0\"\r\n [title]=\"\r\n hasFilter()\r\n ? countOf(kind.collection) + ' su ' + totalOf(kind.collection) + ' con il filtro attivo'\r\n : kind.label\r\n \"\r\n (click)=\"select(kind.collection)\"\r\n >\r\n {{ kind.label }}\r\n <span class=\"fb-res__count\" [class.fb-res__count--match]=\"hasFilter()\">\r\n {{ countOf(kind.collection) }}\r\n </span>\r\n </button>\r\n }\r\n</nav>\r\n\r\n<!--\r\n Il filtro sta fra le schede e l\u2019elenco, e non scorre con esso: su una collection lunga\r\n scorrere per raggiungere la casella con cui accorciarla e\u2019 il gesto che non deve servire.\r\n-->\r\n<div class=\"fb-res__filter\">\r\n <input\r\n type=\"search\"\r\n class=\"fb-res__search\"\r\n placeholder=\"Filtra per nome o tipo\u2026\"\r\n aria-label=\"Filtra le risorse per nome o tipo\"\r\n title=\"Cerca nel nome, nell\u2019etichetta, nel tipo e nei flag (input, output, collection). Non guarda dentro i valori e le formule: a dire chi cita una risorsa e\u2019 il conteggio degli usi\"\r\n [value]=\"query()\"\r\n (input)=\"onQuery($any($event.target).value)\"\r\n />\r\n\r\n <div class=\"fb-res__chips\" role=\"group\" aria-label=\"Filtra per stato\">\r\n @let counts = stateCounts();\r\n <!--\r\n I due stati sono le domande che un nome non sa fare, e sono quelle che si fanno davvero\r\n su un flow grande: \u00ABquali variabili sono morte\u00BB e \u00ABquali righe hanno qualcosa che non\r\n va\u00BB. A zero il chip si disabilita: un filtro che darebbe certamente un elenco vuoto non\r\n restringe niente, nasconde tutto.\r\n -->\r\n <button\r\n type=\"button\"\r\n class=\"fb-res__chip\"\r\n [class.fb-res__chip--active]=\"stateFilter() === 'unused'\"\r\n [attr.aria-pressed]=\"stateFilter() === 'unused'\"\r\n [disabled]=\"counts.unused === 0 && stateFilter() !== 'unused'\"\r\n title=\"Solo le risorse che nessun riferimento e nessuna formula usa\"\r\n (click)=\"setStateFilter('unused')\"\r\n >\r\n Non usate <span class=\"fb-res__count\">{{ counts.unused }}</span>\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-res__chip fb-res__chip--issues\"\r\n [class.fb-res__chip--active]=\"stateFilter() === 'issues'\"\r\n [attr.aria-pressed]=\"stateFilter() === 'issues'\"\r\n [disabled]=\"counts.issues === 0 && stateFilter() !== 'issues'\"\r\n title=\"Solo le risorse con un rilievo locale: nome duplicato, costante che referenzia, ordine di stage doppio\"\r\n (click)=\"setStateFilter('issues')\"\r\n >\r\n Con rilievi <span class=\"fb-res__count\">{{ counts.issues }}</span>\r\n </button>\r\n @if (hasFilter()) {\r\n <button\r\n type=\"button\"\r\n class=\"fb-res__chip fb-res__chip--reset\"\r\n title=\"Mostra tutte le risorse della scheda\"\r\n (click)=\"resetFilter()\"\r\n >\r\n Azzera\r\n </button>\r\n }\r\n </div>\r\n</div>\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 <!--\r\n L\u2019`objectType` sta accanto al tipo perche\u2019 su una `Structure` il tipo da solo non\r\n dice niente: sei variabili su ventuno dicevano \u00ABStructure\u00BB e la classe si leggeva\r\n solo aprendole. E\u2019 anche cio\u2019 che rende spiegabile il filtro, che l\u2019`objectType`\r\n lo guarda: senza, cercare \u00ABdto\u00BB faceva comparire tre righe che \u00ABdto\u00BB non lo\r\n mostrano da nessuna parte \u2014 un filtro che sembra sbagliato.\r\n -->\r\n <span class=\"fb-res__meta\">\r\n {{ string(item, 'dataType') }}{{ boolean(item, 'isCollection') ? '[]' : '' }}\r\n @if (string(item, 'objectType')) {\r\n \u00B7 {{ string(item, 'objectType') }}\r\n }\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 <!--\r\n Il dettaglio sta nel `title` e non nella riga: in 340px non ci sta, e serve a una\r\n domanda che si fa **dopo** (\u00AB13 dove?\u00BB). Un merge field e\u2019 l\u2019uso che non si vede\r\n scorrendo il documento, quindi dirlo cambia dove si va a cercare.\r\n -->\r\n <span\r\n class=\"fb-res__uses\"\r\n [title]=\"\r\n (uses === 1 ? 'Usata una volta nel documento: ' : 'Usata ' + uses + ' volte nel documento: ') +\r\n usesDetailLabel(item)\r\n \"\r\n >\r\n {{ uses }} {{ uses === 1 ? 'uso' : '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 @if (ignoredChoiceSetFilters(item); as ignored) {\r\n <p class=\"fb-callout fb-callout--warn\">\r\n Le opzioni vengono da una collection: {{ ignored }} (CHOICE_SET_FILTERS_IGNORED).\r\n </p>\r\n }\r\n </div>\r\n } @empty {\r\n <!--\r\n Tre casi e tre rimedi: la collection e\u2019 vuota (si aggiunge), il filtro non trova\r\n niente qui ma trova altrove (si cambia scheda), il filtro non trova niente da nessuna\r\n parte (si azzera). Un solo \u00ABnessuna risorsa\u00BB per tutti e tre manda a cercare il\r\n difetto dove non c\u2019e\u2019.\r\n -->\r\n @if (!hasFilter()) {\r\n <p class=\"fb-empty\">Nessuna risorsa di questo tipo.</p>\r\n } @else if (matchesElsewhere() > 0) {\r\n <p class=\"fb-empty\">\r\n Nessuna corrispondenza in questa scheda, ma {{ matchesElsewhere() }} in altre: il\r\n numero accanto a ognuna dice dove.\r\n </p>\r\n } @else {\r\n <p class=\"fb-empty\">\r\n Nessuna risorsa corrisponde al filtro.\r\n <button type=\"button\" class=\"fb-btn fb-btn--ghost fb-btn--icon\" (click)=\"resetFilter()\">\r\n Azzera il filtro\r\n </button>\r\n </p>\r\n }\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);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.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}.fb-res__filter{display:flex;flex-direction:column;gap:6px;padding:8px 12px;border-bottom:1px solid var(--fb-border-subtle, #e6e9ee)}.fb-res__search{box-sizing:border-box;width:100%;padding:5px 8px;border:1px solid var(--fb-border, #d6dae1);border-radius:6px;background:var(--fb-surface, #fff);color:var(--fb-text, #1d2939);font:inherit;font-size:12px}.fb-res__search:focus-visible{outline:2px solid var(--fb-accent, #2f6feb);outline-offset:-1px}.fb-res__chips{display:flex;flex-wrap:wrap;gap:4px}.fb-res__chip{display:inline-flex;align-items:center;gap:4px;padding:3px 8px;border:0;border-radius:6px;background:var(--fb-surface-sunken, #eef0f4);color:var(--fb-text-muted, #667085);font:inherit;font-size:11px;cursor:pointer}.fb-res__chip:hover:not(:disabled){background:var(--fb-border, #d6dae1)}.fb-res__chip:disabled{opacity:.45;cursor:default}.fb-res__chip--active{background:var(--fb-accent, #2f6feb);color:#fff;font-weight:600}.fb-res__chip--active .fb-res__count{background:color-mix(in srgb,#fff 30%,transparent);color:#fff}.fb-res__chip--reset{margin-left:auto}.fb-res__tab--dim{opacity:.4}.fb-res__count--match{background:color-mix(in srgb,var(--fb-accent, #2f6feb) 20%,transparent);color:var(--fb-accent, #2f6feb);font-weight:600}.fb-empty .fb-btn{margin-left:6px}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
15380
15882
|
}
|
|
15381
15883
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.28", ngImport: i0, type: ResourcePanelComponent, decorators: [{
|
|
15382
15884
|
type: Component,
|
|
15383
|
-
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"] }]
|
|
15885
|
+
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 <!--\r\n Con un filtro acceso il numero della scheda e\u2019 quello delle **corrispondenze**: e\u2019 cio\u2019\r\n che dice dove sta la risorsa che si cerca, che il piu\u2019 delle volte e\u2019 in un\u2019altra\r\n collection. La scheda a zero si sbiadisce invece di sparire \u2014 nasconderla farebbe\r\n ballare la barra a ogni lettera digitata.\r\n -->\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 [class.fb-res__tab--dim]=\"hasFilter() && countOf(kind.collection) === 0\"\r\n [title]=\"\r\n hasFilter()\r\n ? countOf(kind.collection) + ' su ' + totalOf(kind.collection) + ' con il filtro attivo'\r\n : kind.label\r\n \"\r\n (click)=\"select(kind.collection)\"\r\n >\r\n {{ kind.label }}\r\n <span class=\"fb-res__count\" [class.fb-res__count--match]=\"hasFilter()\">\r\n {{ countOf(kind.collection) }}\r\n </span>\r\n </button>\r\n }\r\n</nav>\r\n\r\n<!--\r\n Il filtro sta fra le schede e l\u2019elenco, e non scorre con esso: su una collection lunga\r\n scorrere per raggiungere la casella con cui accorciarla e\u2019 il gesto che non deve servire.\r\n-->\r\n<div class=\"fb-res__filter\">\r\n <input\r\n type=\"search\"\r\n class=\"fb-res__search\"\r\n placeholder=\"Filtra per nome o tipo\u2026\"\r\n aria-label=\"Filtra le risorse per nome o tipo\"\r\n title=\"Cerca nel nome, nell\u2019etichetta, nel tipo e nei flag (input, output, collection). Non guarda dentro i valori e le formule: a dire chi cita una risorsa e\u2019 il conteggio degli usi\"\r\n [value]=\"query()\"\r\n (input)=\"onQuery($any($event.target).value)\"\r\n />\r\n\r\n <div class=\"fb-res__chips\" role=\"group\" aria-label=\"Filtra per stato\">\r\n @let counts = stateCounts();\r\n <!--\r\n I due stati sono le domande che un nome non sa fare, e sono quelle che si fanno davvero\r\n su un flow grande: \u00ABquali variabili sono morte\u00BB e \u00ABquali righe hanno qualcosa che non\r\n va\u00BB. A zero il chip si disabilita: un filtro che darebbe certamente un elenco vuoto non\r\n restringe niente, nasconde tutto.\r\n -->\r\n <button\r\n type=\"button\"\r\n class=\"fb-res__chip\"\r\n [class.fb-res__chip--active]=\"stateFilter() === 'unused'\"\r\n [attr.aria-pressed]=\"stateFilter() === 'unused'\"\r\n [disabled]=\"counts.unused === 0 && stateFilter() !== 'unused'\"\r\n title=\"Solo le risorse che nessun riferimento e nessuna formula usa\"\r\n (click)=\"setStateFilter('unused')\"\r\n >\r\n Non usate <span class=\"fb-res__count\">{{ counts.unused }}</span>\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-res__chip fb-res__chip--issues\"\r\n [class.fb-res__chip--active]=\"stateFilter() === 'issues'\"\r\n [attr.aria-pressed]=\"stateFilter() === 'issues'\"\r\n [disabled]=\"counts.issues === 0 && stateFilter() !== 'issues'\"\r\n title=\"Solo le risorse con un rilievo locale: nome duplicato, costante che referenzia, ordine di stage doppio\"\r\n (click)=\"setStateFilter('issues')\"\r\n >\r\n Con rilievi <span class=\"fb-res__count\">{{ counts.issues }}</span>\r\n </button>\r\n @if (hasFilter()) {\r\n <button\r\n type=\"button\"\r\n class=\"fb-res__chip fb-res__chip--reset\"\r\n title=\"Mostra tutte le risorse della scheda\"\r\n (click)=\"resetFilter()\"\r\n >\r\n Azzera\r\n </button>\r\n }\r\n </div>\r\n</div>\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 <!--\r\n L\u2019`objectType` sta accanto al tipo perche\u2019 su una `Structure` il tipo da solo non\r\n dice niente: sei variabili su ventuno dicevano \u00ABStructure\u00BB e la classe si leggeva\r\n solo aprendole. E\u2019 anche cio\u2019 che rende spiegabile il filtro, che l\u2019`objectType`\r\n lo guarda: senza, cercare \u00ABdto\u00BB faceva comparire tre righe che \u00ABdto\u00BB non lo\r\n mostrano da nessuna parte \u2014 un filtro che sembra sbagliato.\r\n -->\r\n <span class=\"fb-res__meta\">\r\n {{ string(item, 'dataType') }}{{ boolean(item, 'isCollection') ? '[]' : '' }}\r\n @if (string(item, 'objectType')) {\r\n \u00B7 {{ string(item, 'objectType') }}\r\n }\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 <!--\r\n Il dettaglio sta nel `title` e non nella riga: in 340px non ci sta, e serve a una\r\n domanda che si fa **dopo** (\u00AB13 dove?\u00BB). Un merge field e\u2019 l\u2019uso che non si vede\r\n scorrendo il documento, quindi dirlo cambia dove si va a cercare.\r\n -->\r\n <span\r\n class=\"fb-res__uses\"\r\n [title]=\"\r\n (uses === 1 ? 'Usata una volta nel documento: ' : 'Usata ' + uses + ' volte nel documento: ') +\r\n usesDetailLabel(item)\r\n \"\r\n >\r\n {{ uses }} {{ uses === 1 ? 'uso' : '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 @if (ignoredChoiceSetFilters(item); as ignored) {\r\n <p class=\"fb-callout fb-callout--warn\">\r\n Le opzioni vengono da una collection: {{ ignored }} (CHOICE_SET_FILTERS_IGNORED).\r\n </p>\r\n }\r\n </div>\r\n } @empty {\r\n <!--\r\n Tre casi e tre rimedi: la collection e\u2019 vuota (si aggiunge), il filtro non trova\r\n niente qui ma trova altrove (si cambia scheda), il filtro non trova niente da nessuna\r\n parte (si azzera). Un solo \u00ABnessuna risorsa\u00BB per tutti e tre manda a cercare il\r\n difetto dove non c\u2019e\u2019.\r\n -->\r\n @if (!hasFilter()) {\r\n <p class=\"fb-empty\">Nessuna risorsa di questo tipo.</p>\r\n } @else if (matchesElsewhere() > 0) {\r\n <p class=\"fb-empty\">\r\n Nessuna corrispondenza in questa scheda, ma {{ matchesElsewhere() }} in altre: il\r\n numero accanto a ognuna dice dove.\r\n </p>\r\n } @else {\r\n <p class=\"fb-empty\">\r\n Nessuna risorsa corrisponde al filtro.\r\n <button type=\"button\" class=\"fb-btn fb-btn--ghost fb-btn--icon\" (click)=\"resetFilter()\">\r\n Azzera il filtro\r\n </button>\r\n </p>\r\n }\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);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.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}.fb-res__filter{display:flex;flex-direction:column;gap:6px;padding:8px 12px;border-bottom:1px solid var(--fb-border-subtle, #e6e9ee)}.fb-res__search{box-sizing:border-box;width:100%;padding:5px 8px;border:1px solid var(--fb-border, #d6dae1);border-radius:6px;background:var(--fb-surface, #fff);color:var(--fb-text, #1d2939);font:inherit;font-size:12px}.fb-res__search:focus-visible{outline:2px solid var(--fb-accent, #2f6feb);outline-offset:-1px}.fb-res__chips{display:flex;flex-wrap:wrap;gap:4px}.fb-res__chip{display:inline-flex;align-items:center;gap:4px;padding:3px 8px;border:0;border-radius:6px;background:var(--fb-surface-sunken, #eef0f4);color:var(--fb-text-muted, #667085);font:inherit;font-size:11px;cursor:pointer}.fb-res__chip:hover:not(:disabled){background:var(--fb-border, #d6dae1)}.fb-res__chip:disabled{opacity:.45;cursor:default}.fb-res__chip--active{background:var(--fb-accent, #2f6feb);color:#fff;font-weight:600}.fb-res__chip--active .fb-res__count{background:color-mix(in srgb,#fff 30%,transparent);color:#fff}.fb-res__chip--reset{margin-left:auto}.fb-res__tab--dim{opacity:.4}.fb-res__count--match{background:color-mix(in srgb,var(--fb-accent, #2f6feb) 20%,transparent);color:var(--fb-accent, #2f6feb);font-weight:600}.fb-empty .fb-btn{margin-left:6px}\n"] }]
|
|
15384
15886
|
}], 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"] }] } });
|
|
15385
15887
|
|
|
15386
15888
|
/**
|
|
@@ -15538,7 +16040,12 @@ class ResourceFormComponent {
|
|
|
15538
16040
|
delete resource['displayField'];
|
|
15539
16041
|
delete resource['valueField'];
|
|
15540
16042
|
delete resource['sortField'];
|
|
16043
|
+
// `sortOrder` e `filterLogic` stanno qui per la stessa ragione degli altri: cambiando
|
|
16044
|
+
// sorgente i campi citati non esistono piu', e un ordinamento o una logica rimasti
|
|
16045
|
+
// indietro diventano un rilievo che l'utente non ha scritto.
|
|
16046
|
+
delete resource['sortOrder'];
|
|
15541
16047
|
delete resource['filters'];
|
|
16048
|
+
delete resource['filterLogic'];
|
|
15542
16049
|
if (source === 'enum') {
|
|
15543
16050
|
// Il tipo lo dichiara `enumType`: lasciare anche `objectType` vorrebbe dire due campi
|
|
15544
16051
|
// che dicono la stessa cosa, e due occasioni di dirla in modo diverso.
|
|
@@ -15546,6 +16053,33 @@ class ResourceFormComponent {
|
|
|
15546
16053
|
}
|
|
15547
16054
|
});
|
|
15548
16055
|
}
|
|
16056
|
+
/**
|
|
16057
|
+
* §5.2 — cio' che su una sorgente collection il runtime ignora, se il documento lo dichiara.
|
|
16058
|
+
* Il controllo sta in `resource-checks.ts` perche' serve anche all'**elenco**: senza, la riga
|
|
16059
|
+
* sarebbe verde e il rilievo si vedrebbe solo aprendo il form.
|
|
16060
|
+
*/
|
|
16061
|
+
ignoredChoiceSetFilters() {
|
|
16062
|
+
const ignored = ignoredChoiceSetFiltersOf(this.reference());
|
|
16063
|
+
return ignored.length ? describeIgnoredChoiceSetFilters(ignored) : null;
|
|
16064
|
+
}
|
|
16065
|
+
/** La ripulitura e' un comando, non un effetto dell'apertura del form. */
|
|
16066
|
+
clearIgnoredChoiceSetFilters() {
|
|
16067
|
+
const reference = this.reference();
|
|
16068
|
+
this.store.updateResource(reference.collection, reference.index, (resource) => {
|
|
16069
|
+
delete resource['filters'];
|
|
16070
|
+
delete resource['filterLogic'];
|
|
16071
|
+
delete resource['sortField'];
|
|
16072
|
+
delete resource['sortOrder'];
|
|
16073
|
+
});
|
|
16074
|
+
}
|
|
16075
|
+
/**
|
|
16076
|
+
* Quante volte il nome di questa risorsa compare dentro un'**espressione**.
|
|
16077
|
+
*
|
|
16078
|
+
* E' l'unica delle tre categorie che la rinomina non tocca (§6.3: la sintassi e' del motore), e
|
|
16079
|
+
* va detta dove si rinomina: il conteggio della riga lo dice già nel suo `title`, ma chi sta
|
|
16080
|
+
* cambiando il nome sta guardando questo campo.
|
|
16081
|
+
*/
|
|
16082
|
+
formulaUseCount = computed(() => describeNameUses(this.store.document(), this.reference().name).formulas, ...(ngDevMode ? [{ debugName: "formulaUseCount" }] : []));
|
|
15549
16083
|
/** Su un choice set da enum il tipo e' `enumType`: il campo generico sarebbe un doppione. */
|
|
15550
16084
|
hidesObjectType() {
|
|
15551
16085
|
return this.collection() === 'dynamicChoiceSets' && this.choiceSetSource() === 'enum';
|
|
@@ -15633,7 +16167,7 @@ class ResourceFormComponent {
|
|
|
15633
16167
|
return this.resource()['value'];
|
|
15634
16168
|
}
|
|
15635
16169
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.28", ngImport: i0, type: ResourceFormComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
15636
|
-
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 });
|
|
16170
|
+
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 <!--\r\n Le tre categorie non si comportano allo stesso modo davanti a una rinomina, e questo e' il\r\n punto in cui il gesto avviene: i riferimenti e i merge field li riscrive l\u2019editor, le\r\n espressioni delle formule no \u2014 la loro sintassi e\u2019 del motore. Dirlo altrove vorrebbe dire\r\n dirlo a chi non sta rinominando.\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 e i\r\n merge field <code>{!\u2026}</code> nei testi.\r\n </p>\r\n @if (formulaUseCount()) {\r\n <p class=\"fb-field__hint fb-field__hint--warn\">\r\n Attenzione: {{ formulaUseCount() }}\r\n {{ formulaUseCount() === 1 ? 'occorrenza sta' : 'occorrenze stanno' }} dentro\r\n {{ formulaUseCount() === 1 ? 'una formula' : 'delle formule' }} e\r\n <strong>non {{ formulaUseCount() === 1 ? 'viene' : 'vengono' }} riscritt{{ formulaUseCount() === 1 ? 'a' : 'e' }}</strong>:\r\n la sintassi e\u2019 del motore di regole, quindi\r\n {{ formulaUseCount() === 1 ? 'va corretta' : 'vanno corrette' }} a mano.\r\n </p>\r\n }\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, <strong>nell\u2019ordine in cui ci sono</strong>. Filtri e ordinamento non si applicano\r\n qui: per sceglierne una parte, filtra la collection con un elemento Filter prima dello\r\n screen.\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 <!--\r\n \u00A75.2 \u2014 `filters`, `filterLogic`, `sortField` e `sortOrder` valgono sulle **sole** sorgenti\r\n `object` (dove diventano la query) ed `enumType` (valutati in memoria). Su una collection le\r\n opzioni sono i suoi elementi nell\u2019ordine in cui ci sono: mostrare i controlli l\u00EC\r\n inviterebbe a dichiarare cio\u2019 che il runtime ignora, ed e\u2019 `CHOICE_SET_FILTERS_IGNORED`.\r\n -->\r\n @if (!isChoiceSetSource('collection')) {\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 }\r\n\r\n <!-- `limit` e' l'unico dei quattro che vale su tutt'e tre le sorgenti (\u00A75.2). -->\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 @if (!isChoiceSetSource('collection')) {\r\n <!--\r\n `supportsLogic` a true: dalla revisione del contratto `filterLogic` **esiste** anche qui, con\r\n le stesse forme e gli stessi rilievi degli elementi che interrogano il database (\u00A74.4).\r\n Nessun `emptyWarning`: senza filtri il set legge tutti i record dell\u2019oggetto, che qui e\u2019 un\r\n uso legittimo. 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]=\"true\"\r\n (changed)=\"onFiltersChanged($event)\"\r\n />\r\n } @else if (ignoredChoiceSetFilters(); as ignored) {\r\n <!--\r\n Il documento dichiara cio' che su una collection il runtime ignora. E\u2019 un **avviso**, e la\r\n ripulitura e\u2019 un comando: cancellare da se\u2019 i filtri di un flow scritto altrove sarebbe una\r\n modifica che nessuno ha chiesto, e farebbe sparire l\u2019unico indizio di cosa quel flow\r\n voleva selezionare. E\u2019 la stessa scelta dei membri di un riquadro (\u00A73.6).\r\n -->\r\n <p class=\"fb-callout fb-callout--warn\">\r\n Su una collection {{ ignored }}: le opzioni sono gli elementi della collection nell\u2019ordine in\r\n cui ci sono (CHOICE_SET_FILTERS_IGNORED).\r\n <button type=\"button\" class=\"fb-btn fb-btn--ghost fb-btn--icon\" (click)=\"clearIgnoredChoiceSetFilters()\">\r\n Togli\r\n </button>\r\n </p>\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 });
|
|
15637
16171
|
}
|
|
15638
16172
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.28", ngImport: i0, type: ResourceFormComponent, decorators: [{
|
|
15639
16173
|
type: Component,
|
|
@@ -15646,7 +16180,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.28", ngImpo
|
|
|
15646
16180
|
StructurePickerComponent,
|
|
15647
16181
|
ValueEditorComponent,
|
|
15648
16182
|
SelectValueDirective,
|
|
15649
|
-
], 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" }]
|
|
16183
|
+
], 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 <!--\r\n Le tre categorie non si comportano allo stesso modo davanti a una rinomina, e questo e' il\r\n punto in cui il gesto avviene: i riferimenti e i merge field li riscrive l\u2019editor, le\r\n espressioni delle formule no \u2014 la loro sintassi e\u2019 del motore. Dirlo altrove vorrebbe dire\r\n dirlo a chi non sta rinominando.\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 e i\r\n merge field <code>{!\u2026}</code> nei testi.\r\n </p>\r\n @if (formulaUseCount()) {\r\n <p class=\"fb-field__hint fb-field__hint--warn\">\r\n Attenzione: {{ formulaUseCount() }}\r\n {{ formulaUseCount() === 1 ? 'occorrenza sta' : 'occorrenze stanno' }} dentro\r\n {{ formulaUseCount() === 1 ? 'una formula' : 'delle formule' }} e\r\n <strong>non {{ formulaUseCount() === 1 ? 'viene' : 'vengono' }} riscritt{{ formulaUseCount() === 1 ? 'a' : 'e' }}</strong>:\r\n la sintassi e\u2019 del motore di regole, quindi\r\n {{ formulaUseCount() === 1 ? 'va corretta' : 'vanno corrette' }} a mano.\r\n </p>\r\n }\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, <strong>nell\u2019ordine in cui ci sono</strong>. Filtri e ordinamento non si applicano\r\n qui: per sceglierne una parte, filtra la collection con un elemento Filter prima dello\r\n screen.\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 <!--\r\n \u00A75.2 \u2014 `filters`, `filterLogic`, `sortField` e `sortOrder` valgono sulle **sole** sorgenti\r\n `object` (dove diventano la query) ed `enumType` (valutati in memoria). Su una collection le\r\n opzioni sono i suoi elementi nell\u2019ordine in cui ci sono: mostrare i controlli l\u00EC\r\n inviterebbe a dichiarare cio\u2019 che il runtime ignora, ed e\u2019 `CHOICE_SET_FILTERS_IGNORED`.\r\n -->\r\n @if (!isChoiceSetSource('collection')) {\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 }\r\n\r\n <!-- `limit` e' l'unico dei quattro che vale su tutt'e tre le sorgenti (\u00A75.2). -->\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 @if (!isChoiceSetSource('collection')) {\r\n <!--\r\n `supportsLogic` a true: dalla revisione del contratto `filterLogic` **esiste** anche qui, con\r\n le stesse forme e gli stessi rilievi degli elementi che interrogano il database (\u00A74.4).\r\n Nessun `emptyWarning`: senza filtri il set legge tutti i record dell\u2019oggetto, che qui e\u2019 un\r\n uso legittimo. 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]=\"true\"\r\n (changed)=\"onFiltersChanged($event)\"\r\n />\r\n } @else if (ignoredChoiceSetFilters(); as ignored) {\r\n <!--\r\n Il documento dichiara cio' che su una collection il runtime ignora. E\u2019 un **avviso**, e la\r\n ripulitura e\u2019 un comando: cancellare da se\u2019 i filtri di un flow scritto altrove sarebbe una\r\n modifica che nessuno ha chiesto, e farebbe sparire l\u2019unico indizio di cosa quel flow\r\n voleva selezionare. E\u2019 la stessa scelta dei membri di un riquadro (\u00A73.6).\r\n -->\r\n <p class=\"fb-callout fb-callout--warn\">\r\n Su una collection {{ ignored }}: le opzioni sono gli elementi della collection nell\u2019ordine in\r\n cui ci sono (CHOICE_SET_FILTERS_IGNORED).\r\n <button type=\"button\" class=\"fb-btn fb-btn--ghost fb-btn--icon\" (click)=\"clearIgnoredChoiceSetFilters()\">\r\n Togli\r\n </button>\r\n </p>\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" }]
|
|
15650
16184
|
}], ctorParameters: () => [], propDecorators: { reference: [{ type: i0.Input, args: [{ isSignal: true, alias: "reference", required: true }] }] } });
|
|
15651
16185
|
|
|
15652
16186
|
/**
|
|
@@ -15898,6 +16432,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.28", ngImpo
|
|
|
15898
16432
|
* raggruppare e per decidere il comportamento; `message` e' testo per l'utente e non va
|
|
15899
16433
|
* interpretato. `elementName` e' l'ancora per portare l'utente sull'elemento con un clic.
|
|
15900
16434
|
*
|
|
16435
|
+
* Su un flow di produzione l'elenco e' lungo — quello della demo ne ha 64 righe — e a quella
|
|
16436
|
+
* misura serve **restringerlo**: la casella filtra su cio' che la riga mostra (messaggio, codice,
|
|
16437
|
+
* elemento, percorso) e la tendina dei codici e' una selezione **multipla**, perche' «fammi vedere
|
|
16438
|
+
* i tre codici che vengono dai cataloghi non popolati» non e' una gravita' e non e' un nome. I
|
|
16439
|
+
* codici che elenca sono quelli **presenti**, non quelli della §7: l'elenco del contratto e'
|
|
16440
|
+
* dichiarato aperto, e proporre codici che questo flow non ha significa proporre filtri che danno
|
|
16441
|
+
* zero righe.
|
|
16442
|
+
*
|
|
15901
16443
|
* Tre effetti che l'interfaccia deve rispettare:
|
|
15902
16444
|
* - `Error` blocca l'**attivazione**, non il salvataggio;
|
|
15903
16445
|
* - `Warning` non blocca nulla, ma dice che qualcosa non fara' cio' che sembra;
|
|
@@ -15926,31 +16468,152 @@ class ProblemsPanelComponent {
|
|
|
15926
16468
|
elementOpened = output();
|
|
15927
16469
|
closed = output();
|
|
15928
16470
|
filter = signal('all', ...(ngDevMode ? [{ debugName: "filter" }] : []));
|
|
16471
|
+
/** Il testo del filtro: cerca in cio' che la riga mostra. */
|
|
16472
|
+
query = signal('', ...(ngDevMode ? [{ debugName: "query" }] : []));
|
|
16473
|
+
/**
|
|
16474
|
+
* I codici selezionati. Vuoto significa «tutti», non «nessuno»: e' l'unico modo di far
|
|
16475
|
+
* coincidere lo stato iniziale con l'assenza di filtro.
|
|
16476
|
+
*/
|
|
16477
|
+
selectedCodes = signal(new Set(), ...(ngDevMode ? [{ debugName: "selectedCodes" }] : []));
|
|
16478
|
+
isCodeMenuOpen = signal(false, ...(ngDevMode ? [{ debugName: "isCodeMenuOpen" }] : []));
|
|
16479
|
+
/** Il confine della tendina: il controllo, non il pannello. Vedi il commento nel costruttore. */
|
|
16480
|
+
codesRef = viewChild('codes', ...(ngDevMode ? [{ debugName: "codesRef" }] : []));
|
|
16481
|
+
constructor() {
|
|
16482
|
+
// La tendina si chiude con un gesto fuori o con `Esc`, come i picker: la regola sta in un
|
|
16483
|
+
// posto solo (`close-on-outside`). Il confine e' **il controllo** e non l'host, perche' qui
|
|
16484
|
+
// l'host e' tutto il pannello: un clic su una riga dei rilievi e' «altrove» per chi guarda,
|
|
16485
|
+
// e con l'host la tendina restava aperta sopra l'elenco che si stava leggendo.
|
|
16486
|
+
closeOnOutside(() => this.isCodeMenuOpen(), () => this.isCodeMenuOpen.set(false), () => this.codesRef()?.nativeElement);
|
|
16487
|
+
}
|
|
15929
16488
|
isRunning = computed(() => this.validation.isRunning(), ...(ngDevMode ? [{ debugName: "isRunning" }] : []));
|
|
15930
16489
|
hasResult = computed(() => this.validation.hasResult(), ...(ngDevMode ? [{ debugName: "hasResult" }] : []));
|
|
15931
16490
|
lastError = computed(() => this.validation.lastError(), ...(ngDevMode ? [{ debugName: "lastError" }] : []));
|
|
16491
|
+
/**
|
|
16492
|
+
* Il testo su cui la casella confronta, **una stringa per rilievo** e allineata per indice a
|
|
16493
|
+
* `validation.issues()`.
|
|
16494
|
+
*
|
|
16495
|
+
* Ci sta anche il percorso **tradotto per nome** (`pathLabel`), che e' quello che la riga
|
|
16496
|
+
* mostra: su uno screen dinamico il path arriva a indici, e cercare il nome del campo che si
|
|
16497
|
+
* legge sullo schermo non troverebbe niente. Si calcola una volta per risultato invece che a
|
|
16498
|
+
* ogni battuta, perche' tradurre un path significa camminare l'albero della schermata.
|
|
16499
|
+
*/
|
|
16500
|
+
haystacks = computed(() => this.validation
|
|
16501
|
+
.issues()
|
|
16502
|
+
.map((issue) => `${issue.message ?? ''} ${issue.code ?? ''} ${issue.elementName ?? ''} ${issue.path ?? ''} ${this.pathLabel(issue)}`.toLowerCase()), ...(ngDevMode ? [{ debugName: "haystacks" }] : []));
|
|
16503
|
+
needle = computed(() => this.query().trim().toLowerCase(), ...(ngDevMode ? [{ debugName: "needle" }] : []));
|
|
16504
|
+
/** Testo e codici: i due filtri che non sono la gravita'. */
|
|
16505
|
+
matchesText(index) {
|
|
16506
|
+
const needle = this.needle();
|
|
16507
|
+
return !needle || (this.haystacks()[index] ?? '').includes(needle);
|
|
16508
|
+
}
|
|
16509
|
+
matchesCode(issue) {
|
|
16510
|
+
const selected = this.selectedCodes();
|
|
16511
|
+
return selected.size === 0 || selected.has(issue.code ?? '');
|
|
16512
|
+
}
|
|
15932
16513
|
/**
|
|
15933
16514
|
* I conteggi dei chip vengono dai rilievi, non dal risultato: il numero sul filtro deve
|
|
15934
|
-
* essere quello che il filtro apre (vedi `FlowValidationStore.issueCounts`).
|
|
16515
|
+
* essere quello che il filtro apre (vedi `FlowValidationStore.issueCounts`). E' anche il
|
|
16516
|
+
* motivo per cui **non** si leggono piu' da `issueCounts` quando gli altri filtri sono attivi:
|
|
16517
|
+
* «Errori 47» accanto a una casella che ne lascia passare tre e' un numero che promette
|
|
16518
|
+
* quarantaquattro righe che non compariranno.
|
|
15935
16519
|
*/
|
|
15936
|
-
counts = computed(() =>
|
|
16520
|
+
counts = computed(() => {
|
|
16521
|
+
const counts = { Error: 0, Warning: 0, Info: 0 };
|
|
16522
|
+
this.validation.issues().forEach((issue, index) => {
|
|
16523
|
+
if (this.matchesText(index) && this.matchesCode(issue)) {
|
|
16524
|
+
counts[severityBucket(issue.severity)] += 1;
|
|
16525
|
+
}
|
|
16526
|
+
});
|
|
16527
|
+
return counts;
|
|
16528
|
+
}, ...(ngDevMode ? [{ debugName: "counts" }] : []));
|
|
15937
16529
|
errorCount = computed(() => this.counts().Error, ...(ngDevMode ? [{ debugName: "errorCount" }] : []));
|
|
15938
16530
|
warningCount = computed(() => this.counts().Warning, ...(ngDevMode ? [{ debugName: "warningCount" }] : []));
|
|
15939
16531
|
infoCount = computed(() => this.counts().Info, ...(ngDevMode ? [{ debugName: "infoCount" }] : []));
|
|
15940
|
-
|
|
16532
|
+
/** Quante righe aprirebbe «Tutti»: il totale **dopo** testo e codici. */
|
|
16533
|
+
totalCount = computed(() => this.counts().Error + this.counts().Warning + this.counts().Info, ...(ngDevMode ? [{ debugName: "totalCount" }] : []));
|
|
16534
|
+
/**
|
|
16535
|
+
* Il totale assoluto, che non guarda nessun filtro. Serve a due cose diverse dai chip: dire
|
|
16536
|
+
* che il flow **non** e' senza rilievi quando i filtri li nascondono tutti, e tenere in fondo
|
|
16537
|
+
* la nota sugli errori che bloccano l'attivazione — filtrando per «Note» quella nota spariva
|
|
16538
|
+
* pur essendoci quarantasette errori, cioe' proprio quando serve.
|
|
16539
|
+
*/
|
|
16540
|
+
absoluteCount = computed(() => this.validation.issues().length, ...(ngDevMode ? [{ debugName: "absoluteCount" }] : []));
|
|
16541
|
+
hasErrors = computed(() => this.validation.issueCounts().Error > 0, ...(ngDevMode ? [{ debugName: "hasErrors" }] : []));
|
|
15941
16542
|
issues = computed(() => {
|
|
15942
16543
|
const filter = this.filter();
|
|
15943
|
-
|
|
15944
|
-
|
|
15945
|
-
|
|
15946
|
-
|
|
15947
|
-
|
|
15948
|
-
|
|
15949
|
-
|
|
16544
|
+
return this.validation.issues().filter((issue, index) => {
|
|
16545
|
+
if (!this.matchesText(index) || !this.matchesCode(issue)) {
|
|
16546
|
+
return false;
|
|
16547
|
+
}
|
|
16548
|
+
// Il secchio, non la stringa: `severity` e' un tipo aperto e un `error` minuscolo o un
|
|
16549
|
+
// `Log` non corrisponderebbero a niente, svuotando la lista senza dire perche'.
|
|
16550
|
+
return filter === 'all' || severityBucket(issue.severity) === filter;
|
|
16551
|
+
});
|
|
15950
16552
|
}, ...(ngDevMode ? [{ debugName: "issues" }] : []));
|
|
15951
|
-
|
|
15952
|
-
|
|
15953
|
-
|
|
16553
|
+
/**
|
|
16554
|
+
* I codici della tendina: quelli **presenti** dopo gravita' e testo, col loro conteggio.
|
|
16555
|
+
*
|
|
16556
|
+
* Piu' quelli **selezionati** anche se non compaiono piu', a zero: una selezione che sparisce
|
|
16557
|
+
* dall'elenco quando si cambia gravita' e' una selezione che continua a filtrare senza che si
|
|
16558
|
+
* possa vedere ne' togliere. Ordinati per frequenza e poi per nome, perche' su sessanta righe
|
|
16559
|
+
* il codice che conta e' quello che si ripete.
|
|
16560
|
+
*/
|
|
16561
|
+
codeFacets = computed(() => {
|
|
16562
|
+
const filter = this.filter();
|
|
16563
|
+
const selected = this.selectedCodes();
|
|
16564
|
+
const counts = new Map();
|
|
16565
|
+
this.validation.issues().forEach((issue, index) => {
|
|
16566
|
+
if (!this.matchesText(index)) {
|
|
16567
|
+
return;
|
|
16568
|
+
}
|
|
16569
|
+
if (filter !== 'all' && severityBucket(issue.severity) !== filter) {
|
|
16570
|
+
return;
|
|
16571
|
+
}
|
|
16572
|
+
const code = issue.code ?? '';
|
|
16573
|
+
counts.set(code, (counts.get(code) ?? 0) + 1);
|
|
16574
|
+
});
|
|
16575
|
+
for (const code of selected) {
|
|
16576
|
+
if (!counts.has(code)) {
|
|
16577
|
+
counts.set(code, 0);
|
|
16578
|
+
}
|
|
16579
|
+
}
|
|
16580
|
+
return [...counts.entries()]
|
|
16581
|
+
.map(([code, count]) => ({ code, count, selected: selected.has(code) }))
|
|
16582
|
+
.sort((a, b) => b.count - a.count || a.code.localeCompare(b.code));
|
|
16583
|
+
}, ...(ngDevMode ? [{ debugName: "codeFacets" }] : []));
|
|
16584
|
+
selectedCodeCount = computed(() => this.selectedCodes().size, ...(ngDevMode ? [{ debugName: "selectedCodeCount" }] : []));
|
|
16585
|
+
/** `true` se c'e' un filtro **oltre** la gravita': decide il messaggio dell'elenco vuoto. */
|
|
16586
|
+
hasTextOrCodeFilter = computed(() => this.needle() !== '' || this.selectedCodes().size > 0, ...(ngDevMode ? [{ debugName: "hasTextOrCodeFilter" }] : []));
|
|
16587
|
+
hasAnyFilter = computed(() => this.hasTextOrCodeFilter() || this.filter() !== 'all', ...(ngDevMode ? [{ debugName: "hasAnyFilter" }] : []));
|
|
16588
|
+
onQuery(value) {
|
|
16589
|
+
this.query.set(value);
|
|
16590
|
+
}
|
|
16591
|
+
toggleCodeMenu() {
|
|
16592
|
+
this.isCodeMenuOpen.update((open) => !open);
|
|
16593
|
+
}
|
|
16594
|
+
/** La selezione e' un insieme nuovo a ogni gesto: un `Set` mutato non fa scattare il signal. */
|
|
16595
|
+
toggleCode(code) {
|
|
16596
|
+
this.selectedCodes.update((selected) => {
|
|
16597
|
+
const next = new Set(selected);
|
|
16598
|
+
if (!next.delete(code)) {
|
|
16599
|
+
next.add(code);
|
|
16600
|
+
}
|
|
16601
|
+
return next;
|
|
16602
|
+
});
|
|
16603
|
+
}
|
|
16604
|
+
clearCodes() {
|
|
16605
|
+
this.selectedCodes.set(new Set());
|
|
16606
|
+
}
|
|
16607
|
+
resetFilters() {
|
|
16608
|
+
this.filter.set('all');
|
|
16609
|
+
this.query.set('');
|
|
16610
|
+
this.clearCodes();
|
|
16611
|
+
this.isCodeMenuOpen.set(false);
|
|
16612
|
+
}
|
|
16613
|
+
/** Nessun rilievo **nel flow**: e' un'altra cosa dal «nessuno che passi i filtri». */
|
|
16614
|
+
isClean = computed(() => this.hasResult() && this.absoluteCount() === 0, ...(ngDevMode ? [{ debugName: "isClean" }] : []));
|
|
16615
|
+
/** Ci sono rilievi, ma i filtri attivi non ne mostrano nessuno: va detto, non lasciato in bianco. */
|
|
16616
|
+
isFilteredEmpty = computed(() => this.absoluteCount() > 0 && this.issues().length === 0, ...(ngDevMode ? [{ debugName: "isFilteredEmpty" }] : []));
|
|
15954
16617
|
activeFilterLabel = computed(() => {
|
|
15955
16618
|
const filter = this.filter();
|
|
15956
16619
|
return filter === 'all' ? '' : SEVERITY_LABEL[filter];
|
|
@@ -16003,12 +16666,12 @@ class ProblemsPanelComponent {
|
|
|
16003
16666
|
this.closed.emit();
|
|
16004
16667
|
}
|
|
16005
16668
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.28", ngImport: i0, type: ProblemsPanelComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
16006
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.28", type: ProblemsPanelComponent, isStandalone: true, selector: "fb-problems-panel", outputs: { elementFocused: "elementFocused", elementOpened: "elementOpened", closed: "closed" }, ngImport: i0, template: "<header class=\"fb-prob__header\">\r\n <h2 class=\"fb-prob__title\">\r\n Problemi\r\n @if (isRunning()) {\r\n <span class=\"fb-prob__running\">validazione in corso\u2026</span>\r\n }\r\n </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-prob__filters\" aria-label=\"Filtra per gravita\u2019\">\r\n <button\r\n type=\"button\"\r\n class=\"fb-prob__filter\"\r\n [class.fb-prob__filter--active]=\"filter() === 'all'\"\r\n (click)=\"setFilter('all')\"\r\n >\r\n Tutti <span class=\"fb-prob__count\">{{ totalCount() }}</span>\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-prob__filter fb-prob__filter--error\"\r\n [class.fb-prob__filter--active]=\"filter() === 'Error'\"\r\n [attr.aria-pressed]=\"filter() === 'Error'\"\r\n (click)=\"setFilter('Error')\"\r\n >\r\n <span class=\"fb-prob__icon fb-prob__icon--error\" aria-hidden=\"true\">\u2715</span>\r\n Errori <span class=\"fb-prob__count\">{{ errorCount() }}</span>\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-prob__filter fb-prob__filter--warning\"\r\n [class.fb-prob__filter--active]=\"filter() === 'Warning'\"\r\n [attr.aria-pressed]=\"filter() === 'Warning'\"\r\n (click)=\"setFilter('Warning')\"\r\n >\r\n <span class=\"fb-prob__icon fb-prob__icon--warning\" aria-hidden=\"true\">!</span>\r\n Avvisi <span class=\"fb-prob__count\">{{ warningCount() }}</span>\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-prob__filter fb-prob__filter--info\"\r\n [class.fb-prob__filter--active]=\"filter() === 'Info'\"\r\n [attr.aria-pressed]=\"filter() === 'Info'\"\r\n (click)=\"setFilter('Info')\"\r\n >\r\n <span class=\"fb-prob__icon fb-prob__icon--info\" aria-hidden=\"true\">i</span>\r\n Note <span class=\"fb-prob__count\">{{ infoCount() }}</span>\r\n </button>\r\n</nav>\r\n\r\n<div class=\"fb-prob__body\">\r\n @if (lastError()) {\r\n <p class=\"fb-callout fb-callout--warn\">\r\n Non e\u2019 stato possibile validare: {{ lastError() }}\r\n </p>\r\n }\r\n\r\n @if (!hasResult() && !isRunning()) {\r\n <p class=\"fb-empty\">La validazione non e\u2019 ancora stata eseguita.</p>\r\n }\r\n\r\n @if (isClean()) {\r\n <p class=\"fb-empty\">\r\n Nessun rilievo. Attenzione: non e\u2019 una garanzia di correttezza \u2014 la validazione non verifica i percorsi\r\n di relazione nei campi, la sintassi delle formule, ne\u2019 action, form e parametri quando il catalogo\r\n corrispondente non e\u2019 popolato.\r\n </p>\r\n }\r\n\r\n @if (isFilteredEmpty()) {\r\n <p class=\"fb-empty\">\r\n Nessun rilievo di tipo \u00AB{{ activeFilterLabel() }}\u00BB. Ce ne sono altri: scegli\r\n <button type=\"button\" class=\"fb-prob__inline-link\" (click)=\"setFilter('all')\">Tutti</button>.\r\n </p>\r\n }\r\n\r\n <ul class=\"fb-prob__list\">\r\n <!-- `$index`: due rilievi possono avere codice, elemento e path identici (due condizioni\r\n incomplete nella stessa regola), e una chiave duplicata e' un errore di Angular. -->\r\n @for (issue of issues(); track $index) {\r\n <!-- Il secchio si calcola una volta: `severity` e' una stringa aperta e serve\r\n normalizzata a classe, icona ed etichetta. -->\r\n @let bucket = bucketOf(issue);\r\n <li\r\n class=\"fb-prob__item\"\r\n [class.fb-prob__item--error]=\"bucket === 'Error'\"\r\n [class.fb-prob__item--warning]=\"bucket === 'Warning'\"\r\n [class.fb-prob__item--info]=\"bucket === 'Info'\"\r\n >\r\n <!--\r\n Un clic **mostra** (seleziona e centra), il doppio clic apre: e\u2019 la stessa coppia di\r\n gesti del canvas, dove un clic seleziona il node e il doppio ne apre il dettaglio.\r\n -->\r\n <button\r\n type=\"button\"\r\n class=\"fb-prob__link\"\r\n (click)=\"focus(issue)\"\r\n (dblclick)=\"open(issue, $event)\"\r\n >\r\n <!-- Il glifo porta il colore: `role=\"img\"` + `aria-label` perche' la gravita\u2019 non\r\n sia leggibile solo a vista. -->\r\n <span\r\n class=\"fb-prob__icon\"\r\n [class.fb-prob__icon--error]=\"bucket === 'Error'\"\r\n [class.fb-prob__icon--warning]=\"bucket === 'Warning'\"\r\n [class.fb-prob__icon--info]=\"bucket === 'Info'\"\r\n role=\"img\"\r\n [attr.aria-label]=\"labelText(bucket)\"\r\n [title]=\"labelText(bucket)\"\r\n >{{ iconText(bucket) }}</span\r\n >\r\n <span class=\"fb-prob__message\">{{ issue.message }}</span>\r\n <span class=\"fb-prob__meta\">\r\n <code>{{ issue.code }}</code>\r\n @if (issue.elementName) {\r\n \u00B7 {{ issue.elementName }}\r\n }\r\n @if (issue.path) {\r\n <!-- Il path per nome, non a indici: vedi `pathLabel`. -->\r\n \u00B7 {{ pathLabel(issue) }}\r\n }\r\n </span>\r\n </button>\r\n <!--\r\n Il comando per aprire il form. Sta accanto alla riga e non **e\u2019** la riga: scorrere\r\n l\u2019elenco dei problemi e aprire il form di ognuno sono due intenzioni diverse, e finche\u2019\r\n erano lo stesso clic la seconda accadeva per forza.\r\n -->\r\n <button\r\n type=\"button\"\r\n class=\"fb-prob__open\"\r\n [attr.aria-label]=\"'Apri il dettaglio di ' + (issue.elementName || 'questo rilievo')\"\r\n title=\"Apri il dettaglio (anche con un doppio clic sulla riga)\"\r\n (click)=\"open(issue, $event)\"\r\n >\r\n \u270E\r\n </button>\r\n </li>\r\n }\r\n </ul>\r\n\r\n @if (errorCount() > 0) {\r\n <p class=\"fb-prob__note\">\r\n Gli errori bloccano l\u2019<strong>attivazione</strong>, non il salvataggio: puoi interrompere il lavoro a\r\n met\u00E0 e riprenderlo.\r\n </p>\r\n }\r\n</div>\r\n", styles: [":host{display:flex;flex-direction:column;height:100%;min-height:0;background:var(--fb-surface, #fff);border-top:1px solid var(--fb-border, #d6dae1)}.fb-prob__header{display:flex;align-items:center;justify-content:space-between;padding:6px 12px;border-bottom:1px solid var(--fb-border-subtle, #e6e9ee)}.fb-prob__title{display:flex;align-items:baseline;gap:8px;margin:0;font-size:13px;font-weight:600;color:var(--fb-text, #1d2939)}.fb-prob__running{font-size:10px;font-weight:400;color:var(--fb-text-subtle, #98a2b3)}.fb-prob__filters{display:flex;gap:3px;padding:5px 12px;border-bottom:1px solid var(--fb-border-subtle, #e6e9ee)}.fb-prob__filter{display:inline-flex;align-items:center;gap:4px;padding:2px 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-prob__filter--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-prob__filter--error.fb-prob__filter--active{border-color:var(--fb-error, #c9372c);background:color-mix(in srgb,var(--fb-error, #c9372c) 10%,transparent);color:var(--fb-error, #c9372c)}.fb-prob__filter--warning.fb-prob__filter--active{border-color:var(--fb-warning, #b7791f);background:color-mix(in srgb,var(--fb-warning, #b7791f) 12%,transparent);color:var(--fb-warning, #b7791f)}.fb-prob__icon{display:inline-grid;place-items:center;flex:0 0 auto;width:13px;height:13px;border-radius:50%;background:var(--fb-text-subtle, #98a2b3);color:var(--fb-surface, #fff);font-size:9px;font-weight:700;line-height:1}.fb-prob__icon--error{background:var(--fb-error, #c9372c)}.fb-prob__icon--warning{background:var(--fb-warning, #b7791f)}.fb-prob__icon--info{background:var(--fb-accent, #2f6feb)}.fb-prob__count{padding:0 4px;border-radius:6px;background:var(--fb-border, #d6dae1);font-size:9px;color:var(--fb-text, #1d2939)}.fb-prob__body{flex:1;min-height:0;overflow-y:auto;padding:8px 12px}.fb-prob__list{margin:0;padding:0;list-style:none}.fb-prob__item{display:flex;align-items:stretch;border-left:3px solid var(--fb-text-subtle, #98a2b3);margin-bottom:3px;border-radius:3px;background:var(--fb-surface-alt, #f8f9fb)}.fb-prob__item--error{border-left-color:var(--fb-error, #c9372c)}.fb-prob__item--warning{border-left-color:var(--fb-warning, #b7791f)}.fb-prob__item--info{border-left-color:var(--fb-accent, #2f6feb)}.fb-prob__link{display:grid;grid-template-columns:auto minmax(0,1fr);column-gap:7px;align-items:start;width:100%;padding:5px 8px;border:0;background:transparent;color:var(--fb-text, #1d2939);font:inherit;text-align:left;cursor:pointer}.fb-prob__link:hover{background:color-mix(in srgb,var(--fb-accent, #2f6feb) 6%,transparent)}.fb-prob__link>.fb-prob__icon{grid-column:1;grid-row:1 / span 2;margin-top:2px}.fb-prob__message{grid-column:2;grid-row:1;font-size:12px;line-height:1.4}.fb-prob__meta{grid-column:2;grid-row:2;font-size:10px;color:var(--fb-text-muted, #667085)}.fb-prob__inline-link{padding:0;border:0;background:transparent;color:var(--fb-accent, #2f6feb);font:inherit;text-decoration:underline;cursor:pointer}.fb-prob__note{margin:8px 0 0;font-size:10px;color:var(--fb-text-subtle, #98a2b3)}.fb-prob__open{flex:none;align-self:flex-start;width:22px;height:22px;margin:2px 4px 0 0;padding:0;border:1px solid transparent;border-radius:var(--fb-radius-xs, 6px);background:transparent;color:var(--fb-text-muted, #667085);font:inherit;font-size:12px;line-height:1;cursor:pointer}.fb-prob__open:hover,.fb-prob__open:focus-visible{border-color:var(--fb-border, #d6dae1);background:var(--fb-surface, #fff);color:var(--fb-text, #1d2939)}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
16669
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.28", type: ProblemsPanelComponent, isStandalone: true, selector: "fb-problems-panel", outputs: { elementFocused: "elementFocused", elementOpened: "elementOpened", closed: "closed" }, viewQueries: [{ propertyName: "codesRef", first: true, predicate: ["codes"], descendants: true, isSignal: true }], ngImport: i0, template: "<header class=\"fb-prob__header\">\r\n <h2 class=\"fb-prob__title\">\r\n Problemi\r\n @if (isRunning()) {\r\n <span class=\"fb-prob__running\">validazione in corso\u2026</span>\r\n }\r\n </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-prob__filters\" aria-label=\"Filtra per gravita\u2019\">\r\n <button\r\n type=\"button\"\r\n class=\"fb-prob__filter\"\r\n [class.fb-prob__filter--active]=\"filter() === 'all'\"\r\n (click)=\"setFilter('all')\"\r\n >\r\n Tutti <span class=\"fb-prob__count\">{{ totalCount() }}</span>\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-prob__filter fb-prob__filter--error\"\r\n [class.fb-prob__filter--active]=\"filter() === 'Error'\"\r\n [attr.aria-pressed]=\"filter() === 'Error'\"\r\n (click)=\"setFilter('Error')\"\r\n >\r\n <span class=\"fb-prob__icon fb-prob__icon--error\" aria-hidden=\"true\">\u2715</span>\r\n Errori <span class=\"fb-prob__count\">{{ errorCount() }}</span>\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-prob__filter fb-prob__filter--warning\"\r\n [class.fb-prob__filter--active]=\"filter() === 'Warning'\"\r\n [attr.aria-pressed]=\"filter() === 'Warning'\"\r\n (click)=\"setFilter('Warning')\"\r\n >\r\n <span class=\"fb-prob__icon fb-prob__icon--warning\" aria-hidden=\"true\">!</span>\r\n Avvisi <span class=\"fb-prob__count\">{{ warningCount() }}</span>\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-prob__filter fb-prob__filter--info\"\r\n [class.fb-prob__filter--active]=\"filter() === 'Info'\"\r\n [attr.aria-pressed]=\"filter() === 'Info'\"\r\n (click)=\"setFilter('Info')\"\r\n >\r\n <span class=\"fb-prob__icon fb-prob__icon--info\" aria-hidden=\"true\">i</span>\r\n Note <span class=\"fb-prob__count\">{{ infoCount() }}</span>\r\n </button>\r\n</nav>\r\n\r\n<!--\r\n La seconda barra sta **fuori** dal corpo che scorre, come i chip della gravita\u2019: su sessanta\r\n righe scorrere per raggiungere il controllo che le accorcia e\u2019 il gesto che il filtro esiste\r\n per togliere.\r\n-->\r\n<div class=\"fb-prob__tools\">\r\n <input\r\n type=\"search\"\r\n class=\"fb-prob__search\"\r\n placeholder=\"Filtra i rilievi\u2026\"\r\n aria-label=\"Filtra i rilievi per messaggio, codice, elemento o percorso\"\r\n title=\"Cerca nel messaggio, nel codice, nel nome dell\u2019elemento e nel percorso \u2014 cioe\u2019 in cio\u2019 che la riga mostra\"\r\n [value]=\"query()\"\r\n (input)=\"onQuery($any($event.target).value)\"\r\n />\r\n\r\n <!--\r\n La tendina dei codici. E\u2019 **multipla** perche\u2019 la domanda vera e\u2019 \u00ABfammi vedere questi tre\r\n codici\u00BB, e i codici che elenca sono quelli **presenti** nel risultato con il loro conteggio:\r\n l\u2019elenco della \u00A77 e\u2019 aperto e proporre codici che questo flow non ha vorrebbe dire proporre\r\n filtri che danno zero righe.\r\n -->\r\n <div class=\"fb-prob__codes\" #codes>\r\n <button\r\n type=\"button\"\r\n class=\"fb-prob__codes-toggle\"\r\n [class.fb-prob__codes-toggle--active]=\"selectedCodeCount() > 0\"\r\n [attr.aria-expanded]=\"isCodeMenuOpen()\"\r\n aria-haspopup=\"true\"\r\n [title]=\"\r\n selectedCodeCount()\r\n ? 'Filtra per codice: ' + selectedCodeCount() + ' selezionati'\r\n : 'Filtra per uno o piu\u2019 codici presenti nel risultato'\r\n \"\r\n (click)=\"toggleCodeMenu()\"\r\n >\r\n @if (selectedCodeCount()) {\r\n {{ selectedCodeCount() }} codici\r\n } @else {\r\n Codici\r\n }\r\n <span class=\"fb-prob__caret\" aria-hidden=\"true\">\u25BE</span>\r\n </button>\r\n\r\n @if (isCodeMenuOpen()) {\r\n <div class=\"fb-prob__menu\" role=\"group\" aria-label=\"Codici dei rilievi\">\r\n @if (selectedCodeCount()) {\r\n <button type=\"button\" class=\"fb-prob__menu-clear\" (click)=\"clearCodes()\">\r\n Deseleziona tutti\r\n </button>\r\n }\r\n @for (facet of codeFacets(); track facet.code) {\r\n <!--\r\n Un codice **selezionato** resta nell\u2019elenco anche quando la gravita\u2019 scelta non ne\r\n contiene piu\u2019 nessuno: si vede a zero. Senza, continuerebbe a filtrare senza che si\r\n possa vederlo ne\u2019 togliere.\r\n -->\r\n <button\r\n type=\"button\"\r\n class=\"fb-prob__menu-item\"\r\n role=\"menuitemcheckbox\"\r\n [attr.aria-checked]=\"facet.selected\"\r\n [class.fb-prob__menu-item--selected]=\"facet.selected\"\r\n (click)=\"toggleCode(facet.code)\"\r\n >\r\n <span class=\"fb-prob__check\" aria-hidden=\"true\">{{ facet.selected ? '\u2611' : '\u2610' }}</span>\r\n <code class=\"fb-prob__menu-code\">{{ facet.code || '(senza codice)' }}</code>\r\n <span class=\"fb-prob__menu-count\">{{ facet.count }}</span>\r\n </button>\r\n } @empty {\r\n <p class=\"fb-prob__menu-empty\">Nessun codice da filtrare.</p>\r\n }\r\n </div>\r\n }\r\n </div>\r\n\r\n @if (hasAnyFilter()) {\r\n <button type=\"button\" class=\"fb-prob__reset\" (click)=\"resetFilters()\">Azzera i filtri</button>\r\n }\r\n</div>\r\n\r\n<div class=\"fb-prob__body\">\r\n @if (lastError()) {\r\n <p class=\"fb-callout fb-callout--warn\">\r\n Non e\u2019 stato possibile validare: {{ lastError() }}\r\n </p>\r\n }\r\n\r\n @if (!hasResult() && !isRunning()) {\r\n <p class=\"fb-empty\">La validazione non e\u2019 ancora stata eseguita.</p>\r\n }\r\n\r\n @if (isClean()) {\r\n <p class=\"fb-empty\">\r\n Nessun rilievo. Attenzione: non e\u2019 una garanzia di correttezza \u2014 la validazione non verifica i percorsi\r\n di relazione nei campi, la sintassi delle formule, ne\u2019 action, form e parametri quando il catalogo\r\n corrispondente non e\u2019 popolato.\r\n </p>\r\n }\r\n\r\n @if (isFilteredEmpty()) {\r\n <!--\r\n Due messaggi perche\u2019 i rimedi sono due: con la sola gravita\u2019 attiva basta tornare a\r\n \u00ABTutti\u00BB, con testo o codici attivi il rimedio e\u2019 azzerarli \u2014 e dirlo con la frase della\r\n gravita\u2019 manderebbe a cercare il filtro sbagliato.\r\n -->\r\n @if (hasTextOrCodeFilter()) {\r\n <p class=\"fb-empty\">\r\n Nessuno dei {{ absoluteCount() }} rilievi corrisponde ai filtri attivi.\r\n <button type=\"button\" class=\"fb-prob__inline-link\" (click)=\"resetFilters()\">Azzerali</button>.\r\n </p>\r\n } @else {\r\n <p class=\"fb-empty\">\r\n Nessun rilievo di tipo \u00AB{{ activeFilterLabel() }}\u00BB. Ce ne sono altri: scegli\r\n <button type=\"button\" class=\"fb-prob__inline-link\" (click)=\"setFilter('all')\">Tutti</button>.\r\n </p>\r\n }\r\n }\r\n\r\n <ul class=\"fb-prob__list\">\r\n <!-- `$index`: due rilievi possono avere codice, elemento e path identici (due condizioni\r\n incomplete nella stessa regola), e una chiave duplicata e' un errore di Angular. -->\r\n @for (issue of issues(); track $index) {\r\n <!-- Il secchio si calcola una volta: `severity` e' una stringa aperta e serve\r\n normalizzata a classe, icona ed etichetta. -->\r\n @let bucket = bucketOf(issue);\r\n <li\r\n class=\"fb-prob__item\"\r\n [class.fb-prob__item--error]=\"bucket === 'Error'\"\r\n [class.fb-prob__item--warning]=\"bucket === 'Warning'\"\r\n [class.fb-prob__item--info]=\"bucket === 'Info'\"\r\n >\r\n <!--\r\n Un clic **mostra** (seleziona e centra), il doppio clic apre: e\u2019 la stessa coppia di\r\n gesti del canvas, dove un clic seleziona il node e il doppio ne apre il dettaglio.\r\n -->\r\n <button\r\n type=\"button\"\r\n class=\"fb-prob__link\"\r\n (click)=\"focus(issue)\"\r\n (dblclick)=\"open(issue, $event)\"\r\n >\r\n <!-- Il glifo porta il colore: `role=\"img\"` + `aria-label` perche' la gravita\u2019 non\r\n sia leggibile solo a vista. -->\r\n <span\r\n class=\"fb-prob__icon\"\r\n [class.fb-prob__icon--error]=\"bucket === 'Error'\"\r\n [class.fb-prob__icon--warning]=\"bucket === 'Warning'\"\r\n [class.fb-prob__icon--info]=\"bucket === 'Info'\"\r\n role=\"img\"\r\n [attr.aria-label]=\"labelText(bucket)\"\r\n [title]=\"labelText(bucket)\"\r\n >{{ iconText(bucket) }}</span\r\n >\r\n <span class=\"fb-prob__message\">{{ issue.message }}</span>\r\n <span class=\"fb-prob__meta\">\r\n <code>{{ issue.code }}</code>\r\n @if (issue.elementName) {\r\n \u00B7 {{ issue.elementName }}\r\n }\r\n @if (issue.path) {\r\n <!-- Il path per nome, non a indici: vedi `pathLabel`. -->\r\n \u00B7 {{ pathLabel(issue) }}\r\n }\r\n </span>\r\n </button>\r\n <!--\r\n Il comando per aprire il form. Sta accanto alla riga e non **e\u2019** la riga: scorrere\r\n l\u2019elenco dei problemi e aprire il form di ognuno sono due intenzioni diverse, e finche\u2019\r\n erano lo stesso clic la seconda accadeva per forza.\r\n -->\r\n <button\r\n type=\"button\"\r\n class=\"fb-prob__open\"\r\n [attr.aria-label]=\"'Apri il dettaglio di ' + (issue.elementName || 'questo rilievo')\"\r\n title=\"Apri il dettaglio (anche con un doppio clic sulla riga)\"\r\n (click)=\"open(issue, $event)\"\r\n >\r\n \u270E\r\n </button>\r\n </li>\r\n }\r\n </ul>\r\n\r\n <!--\r\n `hasErrors` e non `errorCount`: quello segue i filtri, e filtrando per \u00ABNote\u00BB la nota\r\n spariva pur essendoci quarantasette errori \u2014 cioe' proprio quando serve.\r\n -->\r\n @if (hasErrors()) {\r\n <p class=\"fb-prob__note\">\r\n Gli errori bloccano l\u2019<strong>attivazione</strong>, non il salvataggio: puoi interrompere il lavoro a\r\n met\u00E0 e riprenderlo.\r\n </p>\r\n }\r\n</div>\r\n", styles: [":host{display:flex;flex-direction:column;height:100%;min-height:0;background:var(--fb-surface, #fff);border-top:1px solid var(--fb-border, #d6dae1)}.fb-prob__header{display:flex;align-items:center;justify-content:space-between;padding:6px 12px;border-bottom:1px solid var(--fb-border-subtle, #e6e9ee)}.fb-prob__title{display:flex;align-items:baseline;gap:8px;margin:0;font-size:13px;font-weight:600;color:var(--fb-text, #1d2939)}.fb-prob__running{font-size:10px;font-weight:400;color:var(--fb-text-subtle, #98a2b3)}.fb-prob__filters{display:flex;gap:3px;padding:5px 12px;border-bottom:1px solid var(--fb-border-subtle, #e6e9ee)}.fb-prob__filter{display:inline-flex;align-items:center;gap:4px;padding:2px 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-prob__filter--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-prob__filter--error.fb-prob__filter--active{border-color:var(--fb-error, #c9372c);background:color-mix(in srgb,var(--fb-error, #c9372c) 10%,transparent);color:var(--fb-error, #c9372c)}.fb-prob__filter--warning.fb-prob__filter--active{border-color:var(--fb-warning, #b7791f);background:color-mix(in srgb,var(--fb-warning, #b7791f) 12%,transparent);color:var(--fb-warning, #b7791f)}.fb-prob__icon{display:inline-grid;place-items:center;flex:0 0 auto;width:13px;height:13px;border-radius:50%;background:var(--fb-text-subtle, #98a2b3);color:var(--fb-surface, #fff);font-size:9px;font-weight:700;line-height:1}.fb-prob__icon--error{background:var(--fb-error, #c9372c)}.fb-prob__icon--warning{background:var(--fb-warning, #b7791f)}.fb-prob__icon--info{background:var(--fb-accent, #2f6feb)}.fb-prob__count{padding:0 4px;border-radius:6px;background:var(--fb-border, #d6dae1);font-size:9px;color:var(--fb-text, #1d2939)}.fb-prob__body{flex:1;min-height:0;overflow-y:auto;padding:8px 12px}.fb-prob__list{margin:0;padding:0;list-style:none}.fb-prob__item{display:flex;align-items:stretch;border-left:3px solid var(--fb-text-subtle, #98a2b3);margin-bottom:3px;border-radius:3px;background:var(--fb-surface-alt, #f8f9fb)}.fb-prob__item--error{border-left-color:var(--fb-error, #c9372c)}.fb-prob__item--warning{border-left-color:var(--fb-warning, #b7791f)}.fb-prob__item--info{border-left-color:var(--fb-accent, #2f6feb)}.fb-prob__link{display:grid;grid-template-columns:auto minmax(0,1fr);column-gap:7px;align-items:start;width:100%;padding:5px 8px;border:0;background:transparent;color:var(--fb-text, #1d2939);font:inherit;text-align:left;cursor:pointer}.fb-prob__link:hover{background:color-mix(in srgb,var(--fb-accent, #2f6feb) 6%,transparent)}.fb-prob__link>.fb-prob__icon{grid-column:1;grid-row:1 / span 2;margin-top:2px}.fb-prob__message{grid-column:2;grid-row:1;font-size:12px;line-height:1.4}.fb-prob__meta{grid-column:2;grid-row:2;font-size:10px;color:var(--fb-text-muted, #667085)}.fb-prob__inline-link{padding:0;border:0;background:transparent;color:var(--fb-accent, #2f6feb);font:inherit;text-decoration:underline;cursor:pointer}.fb-prob__note{margin:8px 0 0;font-size:10px;color:var(--fb-text-subtle, #98a2b3)}.fb-prob__open{flex:none;align-self:flex-start;width:22px;height:22px;margin:2px 4px 0 0;padding:0;border:1px solid transparent;border-radius:var(--fb-radius-xs, 6px);background:transparent;color:var(--fb-text-muted, #667085);font:inherit;font-size:12px;line-height:1;cursor:pointer}.fb-prob__open:hover,.fb-prob__open:focus-visible{border-color:var(--fb-border, #d6dae1);background:var(--fb-surface, #fff);color:var(--fb-text, #1d2939)}.fb-prob__tools{display:flex;align-items:center;gap:6px;padding:6px 12px;border-bottom:1px solid var(--fb-border-subtle, #e6e9ee)}.fb-prob__search{box-sizing:border-box;flex:1 1 260px;max-width:420px;padding:4px 8px;border:1px solid var(--fb-border, #d6dae1);border-radius:6px;background:var(--fb-surface, #fff);color:var(--fb-text, #1d2939);font:inherit;font-size:12px}.fb-prob__search:focus-visible{outline:2px solid var(--fb-accent, #2f6feb);outline-offset:-1px}.fb-prob__codes{position:relative;flex:none}.fb-prob__codes-toggle{display:inline-flex;align-items:center;gap:4px;padding:3px 8px;border:1px solid var(--fb-border, #d6dae1);border-radius:6px;background:var(--fb-surface, #fff);color:var(--fb-text-muted, #667085);font:inherit;font-size:11px;cursor:pointer}.fb-prob__codes-toggle:hover{background:var(--fb-surface-alt, #f8f9fb)}.fb-prob__codes-toggle--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-prob__caret{font-size:9px}.fb-prob__menu{position:absolute;top:calc(100% + 4px);left:0;z-index:30;min-width:260px;max-height:280px;overflow-y:auto;padding:4px;border:1px solid var(--fb-border, #d6dae1);border-radius:8px;background:var(--fb-surface, #fff);box-shadow:0 8px 24px #10182824}.fb-prob__menu-clear{width:100%;padding:4px 6px;border:0;border-bottom:1px solid var(--fb-border-subtle, #e6e9ee);margin-bottom:4px;background:transparent;color:var(--fb-accent, #2f6feb);font:inherit;font-size:11px;text-align:left;cursor:pointer}.fb-prob__menu-item{display:flex;align-items:center;gap:6px;width:100%;padding:4px 6px;border:0;border-radius:6px;background:transparent;color:var(--fb-text, #1d2939);font:inherit;font-size:11px;text-align:left;cursor:pointer}.fb-prob__menu-item:hover{background:var(--fb-surface-alt, #f8f9fb)}.fb-prob__menu-item--selected{background:color-mix(in srgb,var(--fb-accent, #2f6feb) 8%,transparent)}.fb-prob__check{flex:none;font-size:12px;color:var(--fb-accent, #2f6feb)}.fb-prob__menu-code{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:10px}.fb-prob__menu-count{flex:none;padding:0 5px;border-radius:999px;background:var(--fb-surface-sunken, #eef0f4);color:var(--fb-text-muted, #667085);font-size:10px;font-variant-numeric:tabular-nums}.fb-prob__menu-empty{margin:6px;color:var(--fb-text-muted, #667085);font-size:11px}.fb-prob__reset{flex:none;padding:3px 8px;border:0;border-radius:6px;background:var(--fb-surface-sunken, #eef0f4);color:var(--fb-text-muted, #667085);font:inherit;font-size:11px;cursor:pointer}.fb-prob__reset:hover{background:var(--fb-border, #d6dae1)}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
16007
16670
|
}
|
|
16008
16671
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.28", ngImport: i0, type: ProblemsPanelComponent, decorators: [{
|
|
16009
16672
|
type: Component,
|
|
16010
|
-
args: [{ selector: 'fb-problems-panel', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, template: "<header class=\"fb-prob__header\">\r\n <h2 class=\"fb-prob__title\">\r\n Problemi\r\n @if (isRunning()) {\r\n <span class=\"fb-prob__running\">validazione in corso\u2026</span>\r\n }\r\n </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-prob__filters\" aria-label=\"Filtra per gravita\u2019\">\r\n <button\r\n type=\"button\"\r\n class=\"fb-prob__filter\"\r\n [class.fb-prob__filter--active]=\"filter() === 'all'\"\r\n (click)=\"setFilter('all')\"\r\n >\r\n Tutti <span class=\"fb-prob__count\">{{ totalCount() }}</span>\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-prob__filter fb-prob__filter--error\"\r\n [class.fb-prob__filter--active]=\"filter() === 'Error'\"\r\n [attr.aria-pressed]=\"filter() === 'Error'\"\r\n (click)=\"setFilter('Error')\"\r\n >\r\n <span class=\"fb-prob__icon fb-prob__icon--error\" aria-hidden=\"true\">\u2715</span>\r\n Errori <span class=\"fb-prob__count\">{{ errorCount() }}</span>\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-prob__filter fb-prob__filter--warning\"\r\n [class.fb-prob__filter--active]=\"filter() === 'Warning'\"\r\n [attr.aria-pressed]=\"filter() === 'Warning'\"\r\n (click)=\"setFilter('Warning')\"\r\n >\r\n <span class=\"fb-prob__icon fb-prob__icon--warning\" aria-hidden=\"true\">!</span>\r\n Avvisi <span class=\"fb-prob__count\">{{ warningCount() }}</span>\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-prob__filter fb-prob__filter--info\"\r\n [class.fb-prob__filter--active]=\"filter() === 'Info'\"\r\n [attr.aria-pressed]=\"filter() === 'Info'\"\r\n (click)=\"setFilter('Info')\"\r\n >\r\n <span class=\"fb-prob__icon fb-prob__icon--info\" aria-hidden=\"true\">i</span>\r\n Note <span class=\"fb-prob__count\">{{ infoCount() }}</span>\r\n </button>\r\n</nav>\r\n\r\n<div class=\"fb-prob__body\">\r\n @if (lastError()) {\r\n <p class=\"fb-callout fb-callout--warn\">\r\n Non e\u2019 stato possibile validare: {{ lastError() }}\r\n </p>\r\n }\r\n\r\n @if (!hasResult() && !isRunning()) {\r\n <p class=\"fb-empty\">La validazione non e\u2019 ancora stata eseguita.</p>\r\n }\r\n\r\n @if (isClean()) {\r\n <p class=\"fb-empty\">\r\n Nessun rilievo. Attenzione: non e\u2019 una garanzia di correttezza \u2014 la validazione non verifica i percorsi\r\n di relazione nei campi, la sintassi delle formule, ne\u2019 action, form e parametri quando il catalogo\r\n corrispondente non e\u2019 popolato.\r\n </p>\r\n }\r\n\r\n @if (isFilteredEmpty()) {\r\n <p class=\"fb-empty\">\r\n Nessun rilievo di tipo \u00AB{{ activeFilterLabel() }}\u00BB. Ce ne sono altri: scegli\r\n <button type=\"button\" class=\"fb-prob__inline-link\" (click)=\"setFilter('all')\">Tutti</button>.\r\n </p>\r\n }\r\n\r\n <ul class=\"fb-prob__list\">\r\n <!-- `$index`: due rilievi possono avere codice, elemento e path identici (due condizioni\r\n incomplete nella stessa regola), e una chiave duplicata e' un errore di Angular. -->\r\n @for (issue of issues(); track $index) {\r\n <!-- Il secchio si calcola una volta: `severity` e' una stringa aperta e serve\r\n normalizzata a classe, icona ed etichetta. -->\r\n @let bucket = bucketOf(issue);\r\n <li\r\n class=\"fb-prob__item\"\r\n [class.fb-prob__item--error]=\"bucket === 'Error'\"\r\n [class.fb-prob__item--warning]=\"bucket === 'Warning'\"\r\n [class.fb-prob__item--info]=\"bucket === 'Info'\"\r\n >\r\n <!--\r\n Un clic **mostra** (seleziona e centra), il doppio clic apre: e\u2019 la stessa coppia di\r\n gesti del canvas, dove un clic seleziona il node e il doppio ne apre il dettaglio.\r\n -->\r\n <button\r\n type=\"button\"\r\n class=\"fb-prob__link\"\r\n (click)=\"focus(issue)\"\r\n (dblclick)=\"open(issue, $event)\"\r\n >\r\n <!-- Il glifo porta il colore: `role=\"img\"` + `aria-label` perche' la gravita\u2019 non\r\n sia leggibile solo a vista. -->\r\n <span\r\n class=\"fb-prob__icon\"\r\n [class.fb-prob__icon--error]=\"bucket === 'Error'\"\r\n [class.fb-prob__icon--warning]=\"bucket === 'Warning'\"\r\n [class.fb-prob__icon--info]=\"bucket === 'Info'\"\r\n role=\"img\"\r\n [attr.aria-label]=\"labelText(bucket)\"\r\n [title]=\"labelText(bucket)\"\r\n >{{ iconText(bucket) }}</span\r\n >\r\n <span class=\"fb-prob__message\">{{ issue.message }}</span>\r\n <span class=\"fb-prob__meta\">\r\n <code>{{ issue.code }}</code>\r\n @if (issue.elementName) {\r\n \u00B7 {{ issue.elementName }}\r\n }\r\n @if (issue.path) {\r\n <!-- Il path per nome, non a indici: vedi `pathLabel`. -->\r\n \u00B7 {{ pathLabel(issue) }}\r\n }\r\n </span>\r\n </button>\r\n <!--\r\n Il comando per aprire il form. Sta accanto alla riga e non **e\u2019** la riga: scorrere\r\n l\u2019elenco dei problemi e aprire il form di ognuno sono due intenzioni diverse, e finche\u2019\r\n erano lo stesso clic la seconda accadeva per forza.\r\n -->\r\n <button\r\n type=\"button\"\r\n class=\"fb-prob__open\"\r\n [attr.aria-label]=\"'Apri il dettaglio di ' + (issue.elementName || 'questo rilievo')\"\r\n title=\"Apri il dettaglio (anche con un doppio clic sulla riga)\"\r\n (click)=\"open(issue, $event)\"\r\n >\r\n \u270E\r\n </button>\r\n </li>\r\n }\r\n </ul>\r\n\r\n @if (errorCount() > 0) {\r\n <p class=\"fb-prob__note\">\r\n Gli errori bloccano l\u2019<strong>attivazione</strong>, non il salvataggio: puoi interrompere il lavoro a\r\n met\u00E0 e riprenderlo.\r\n </p>\r\n }\r\n</div>\r\n", styles: [":host{display:flex;flex-direction:column;height:100%;min-height:0;background:var(--fb-surface, #fff);border-top:1px solid var(--fb-border, #d6dae1)}.fb-prob__header{display:flex;align-items:center;justify-content:space-between;padding:6px 12px;border-bottom:1px solid var(--fb-border-subtle, #e6e9ee)}.fb-prob__title{display:flex;align-items:baseline;gap:8px;margin:0;font-size:13px;font-weight:600;color:var(--fb-text, #1d2939)}.fb-prob__running{font-size:10px;font-weight:400;color:var(--fb-text-subtle, #98a2b3)}.fb-prob__filters{display:flex;gap:3px;padding:5px 12px;border-bottom:1px solid var(--fb-border-subtle, #e6e9ee)}.fb-prob__filter{display:inline-flex;align-items:center;gap:4px;padding:2px 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-prob__filter--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-prob__filter--error.fb-prob__filter--active{border-color:var(--fb-error, #c9372c);background:color-mix(in srgb,var(--fb-error, #c9372c) 10%,transparent);color:var(--fb-error, #c9372c)}.fb-prob__filter--warning.fb-prob__filter--active{border-color:var(--fb-warning, #b7791f);background:color-mix(in srgb,var(--fb-warning, #b7791f) 12%,transparent);color:var(--fb-warning, #b7791f)}.fb-prob__icon{display:inline-grid;place-items:center;flex:0 0 auto;width:13px;height:13px;border-radius:50%;background:var(--fb-text-subtle, #98a2b3);color:var(--fb-surface, #fff);font-size:9px;font-weight:700;line-height:1}.fb-prob__icon--error{background:var(--fb-error, #c9372c)}.fb-prob__icon--warning{background:var(--fb-warning, #b7791f)}.fb-prob__icon--info{background:var(--fb-accent, #2f6feb)}.fb-prob__count{padding:0 4px;border-radius:6px;background:var(--fb-border, #d6dae1);font-size:9px;color:var(--fb-text, #1d2939)}.fb-prob__body{flex:1;min-height:0;overflow-y:auto;padding:8px 12px}.fb-prob__list{margin:0;padding:0;list-style:none}.fb-prob__item{display:flex;align-items:stretch;border-left:3px solid var(--fb-text-subtle, #98a2b3);margin-bottom:3px;border-radius:3px;background:var(--fb-surface-alt, #f8f9fb)}.fb-prob__item--error{border-left-color:var(--fb-error, #c9372c)}.fb-prob__item--warning{border-left-color:var(--fb-warning, #b7791f)}.fb-prob__item--info{border-left-color:var(--fb-accent, #2f6feb)}.fb-prob__link{display:grid;grid-template-columns:auto minmax(0,1fr);column-gap:7px;align-items:start;width:100%;padding:5px 8px;border:0;background:transparent;color:var(--fb-text, #1d2939);font:inherit;text-align:left;cursor:pointer}.fb-prob__link:hover{background:color-mix(in srgb,var(--fb-accent, #2f6feb) 6%,transparent)}.fb-prob__link>.fb-prob__icon{grid-column:1;grid-row:1 / span 2;margin-top:2px}.fb-prob__message{grid-column:2;grid-row:1;font-size:12px;line-height:1.4}.fb-prob__meta{grid-column:2;grid-row:2;font-size:10px;color:var(--fb-text-muted, #667085)}.fb-prob__inline-link{padding:0;border:0;background:transparent;color:var(--fb-accent, #2f6feb);font:inherit;text-decoration:underline;cursor:pointer}.fb-prob__note{margin:8px 0 0;font-size:10px;color:var(--fb-text-subtle, #98a2b3)}.fb-prob__open{flex:none;align-self:flex-start;width:22px;height:22px;margin:2px 4px 0 0;padding:0;border:1px solid transparent;border-radius:var(--fb-radius-xs, 6px);background:transparent;color:var(--fb-text-muted, #667085);font:inherit;font-size:12px;line-height:1;cursor:pointer}.fb-prob__open:hover,.fb-prob__open:focus-visible{border-color:var(--fb-border, #d6dae1);background:var(--fb-surface, #fff);color:var(--fb-text, #1d2939)}\n"] }]
|
|
16011
|
-
}], propDecorators: { elementFocused: [{ type: i0.Output, args: ["elementFocused"] }], elementOpened: [{ type: i0.Output, args: ["elementOpened"] }], closed: [{ type: i0.Output, args: ["closed"] }] } });
|
|
16673
|
+
args: [{ selector: 'fb-problems-panel', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, template: "<header class=\"fb-prob__header\">\r\n <h2 class=\"fb-prob__title\">\r\n Problemi\r\n @if (isRunning()) {\r\n <span class=\"fb-prob__running\">validazione in corso\u2026</span>\r\n }\r\n </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-prob__filters\" aria-label=\"Filtra per gravita\u2019\">\r\n <button\r\n type=\"button\"\r\n class=\"fb-prob__filter\"\r\n [class.fb-prob__filter--active]=\"filter() === 'all'\"\r\n (click)=\"setFilter('all')\"\r\n >\r\n Tutti <span class=\"fb-prob__count\">{{ totalCount() }}</span>\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-prob__filter fb-prob__filter--error\"\r\n [class.fb-prob__filter--active]=\"filter() === 'Error'\"\r\n [attr.aria-pressed]=\"filter() === 'Error'\"\r\n (click)=\"setFilter('Error')\"\r\n >\r\n <span class=\"fb-prob__icon fb-prob__icon--error\" aria-hidden=\"true\">\u2715</span>\r\n Errori <span class=\"fb-prob__count\">{{ errorCount() }}</span>\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-prob__filter fb-prob__filter--warning\"\r\n [class.fb-prob__filter--active]=\"filter() === 'Warning'\"\r\n [attr.aria-pressed]=\"filter() === 'Warning'\"\r\n (click)=\"setFilter('Warning')\"\r\n >\r\n <span class=\"fb-prob__icon fb-prob__icon--warning\" aria-hidden=\"true\">!</span>\r\n Avvisi <span class=\"fb-prob__count\">{{ warningCount() }}</span>\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-prob__filter fb-prob__filter--info\"\r\n [class.fb-prob__filter--active]=\"filter() === 'Info'\"\r\n [attr.aria-pressed]=\"filter() === 'Info'\"\r\n (click)=\"setFilter('Info')\"\r\n >\r\n <span class=\"fb-prob__icon fb-prob__icon--info\" aria-hidden=\"true\">i</span>\r\n Note <span class=\"fb-prob__count\">{{ infoCount() }}</span>\r\n </button>\r\n</nav>\r\n\r\n<!--\r\n La seconda barra sta **fuori** dal corpo che scorre, come i chip della gravita\u2019: su sessanta\r\n righe scorrere per raggiungere il controllo che le accorcia e\u2019 il gesto che il filtro esiste\r\n per togliere.\r\n-->\r\n<div class=\"fb-prob__tools\">\r\n <input\r\n type=\"search\"\r\n class=\"fb-prob__search\"\r\n placeholder=\"Filtra i rilievi\u2026\"\r\n aria-label=\"Filtra i rilievi per messaggio, codice, elemento o percorso\"\r\n title=\"Cerca nel messaggio, nel codice, nel nome dell\u2019elemento e nel percorso \u2014 cioe\u2019 in cio\u2019 che la riga mostra\"\r\n [value]=\"query()\"\r\n (input)=\"onQuery($any($event.target).value)\"\r\n />\r\n\r\n <!--\r\n La tendina dei codici. E\u2019 **multipla** perche\u2019 la domanda vera e\u2019 \u00ABfammi vedere questi tre\r\n codici\u00BB, e i codici che elenca sono quelli **presenti** nel risultato con il loro conteggio:\r\n l\u2019elenco della \u00A77 e\u2019 aperto e proporre codici che questo flow non ha vorrebbe dire proporre\r\n filtri che danno zero righe.\r\n -->\r\n <div class=\"fb-prob__codes\" #codes>\r\n <button\r\n type=\"button\"\r\n class=\"fb-prob__codes-toggle\"\r\n [class.fb-prob__codes-toggle--active]=\"selectedCodeCount() > 0\"\r\n [attr.aria-expanded]=\"isCodeMenuOpen()\"\r\n aria-haspopup=\"true\"\r\n [title]=\"\r\n selectedCodeCount()\r\n ? 'Filtra per codice: ' + selectedCodeCount() + ' selezionati'\r\n : 'Filtra per uno o piu\u2019 codici presenti nel risultato'\r\n \"\r\n (click)=\"toggleCodeMenu()\"\r\n >\r\n @if (selectedCodeCount()) {\r\n {{ selectedCodeCount() }} codici\r\n } @else {\r\n Codici\r\n }\r\n <span class=\"fb-prob__caret\" aria-hidden=\"true\">\u25BE</span>\r\n </button>\r\n\r\n @if (isCodeMenuOpen()) {\r\n <div class=\"fb-prob__menu\" role=\"group\" aria-label=\"Codici dei rilievi\">\r\n @if (selectedCodeCount()) {\r\n <button type=\"button\" class=\"fb-prob__menu-clear\" (click)=\"clearCodes()\">\r\n Deseleziona tutti\r\n </button>\r\n }\r\n @for (facet of codeFacets(); track facet.code) {\r\n <!--\r\n Un codice **selezionato** resta nell\u2019elenco anche quando la gravita\u2019 scelta non ne\r\n contiene piu\u2019 nessuno: si vede a zero. Senza, continuerebbe a filtrare senza che si\r\n possa vederlo ne\u2019 togliere.\r\n -->\r\n <button\r\n type=\"button\"\r\n class=\"fb-prob__menu-item\"\r\n role=\"menuitemcheckbox\"\r\n [attr.aria-checked]=\"facet.selected\"\r\n [class.fb-prob__menu-item--selected]=\"facet.selected\"\r\n (click)=\"toggleCode(facet.code)\"\r\n >\r\n <span class=\"fb-prob__check\" aria-hidden=\"true\">{{ facet.selected ? '\u2611' : '\u2610' }}</span>\r\n <code class=\"fb-prob__menu-code\">{{ facet.code || '(senza codice)' }}</code>\r\n <span class=\"fb-prob__menu-count\">{{ facet.count }}</span>\r\n </button>\r\n } @empty {\r\n <p class=\"fb-prob__menu-empty\">Nessun codice da filtrare.</p>\r\n }\r\n </div>\r\n }\r\n </div>\r\n\r\n @if (hasAnyFilter()) {\r\n <button type=\"button\" class=\"fb-prob__reset\" (click)=\"resetFilters()\">Azzera i filtri</button>\r\n }\r\n</div>\r\n\r\n<div class=\"fb-prob__body\">\r\n @if (lastError()) {\r\n <p class=\"fb-callout fb-callout--warn\">\r\n Non e\u2019 stato possibile validare: {{ lastError() }}\r\n </p>\r\n }\r\n\r\n @if (!hasResult() && !isRunning()) {\r\n <p class=\"fb-empty\">La validazione non e\u2019 ancora stata eseguita.</p>\r\n }\r\n\r\n @if (isClean()) {\r\n <p class=\"fb-empty\">\r\n Nessun rilievo. Attenzione: non e\u2019 una garanzia di correttezza \u2014 la validazione non verifica i percorsi\r\n di relazione nei campi, la sintassi delle formule, ne\u2019 action, form e parametri quando il catalogo\r\n corrispondente non e\u2019 popolato.\r\n </p>\r\n }\r\n\r\n @if (isFilteredEmpty()) {\r\n <!--\r\n Due messaggi perche\u2019 i rimedi sono due: con la sola gravita\u2019 attiva basta tornare a\r\n \u00ABTutti\u00BB, con testo o codici attivi il rimedio e\u2019 azzerarli \u2014 e dirlo con la frase della\r\n gravita\u2019 manderebbe a cercare il filtro sbagliato.\r\n -->\r\n @if (hasTextOrCodeFilter()) {\r\n <p class=\"fb-empty\">\r\n Nessuno dei {{ absoluteCount() }} rilievi corrisponde ai filtri attivi.\r\n <button type=\"button\" class=\"fb-prob__inline-link\" (click)=\"resetFilters()\">Azzerali</button>.\r\n </p>\r\n } @else {\r\n <p class=\"fb-empty\">\r\n Nessun rilievo di tipo \u00AB{{ activeFilterLabel() }}\u00BB. Ce ne sono altri: scegli\r\n <button type=\"button\" class=\"fb-prob__inline-link\" (click)=\"setFilter('all')\">Tutti</button>.\r\n </p>\r\n }\r\n }\r\n\r\n <ul class=\"fb-prob__list\">\r\n <!-- `$index`: due rilievi possono avere codice, elemento e path identici (due condizioni\r\n incomplete nella stessa regola), e una chiave duplicata e' un errore di Angular. -->\r\n @for (issue of issues(); track $index) {\r\n <!-- Il secchio si calcola una volta: `severity` e' una stringa aperta e serve\r\n normalizzata a classe, icona ed etichetta. -->\r\n @let bucket = bucketOf(issue);\r\n <li\r\n class=\"fb-prob__item\"\r\n [class.fb-prob__item--error]=\"bucket === 'Error'\"\r\n [class.fb-prob__item--warning]=\"bucket === 'Warning'\"\r\n [class.fb-prob__item--info]=\"bucket === 'Info'\"\r\n >\r\n <!--\r\n Un clic **mostra** (seleziona e centra), il doppio clic apre: e\u2019 la stessa coppia di\r\n gesti del canvas, dove un clic seleziona il node e il doppio ne apre il dettaglio.\r\n -->\r\n <button\r\n type=\"button\"\r\n class=\"fb-prob__link\"\r\n (click)=\"focus(issue)\"\r\n (dblclick)=\"open(issue, $event)\"\r\n >\r\n <!-- Il glifo porta il colore: `role=\"img\"` + `aria-label` perche' la gravita\u2019 non\r\n sia leggibile solo a vista. -->\r\n <span\r\n class=\"fb-prob__icon\"\r\n [class.fb-prob__icon--error]=\"bucket === 'Error'\"\r\n [class.fb-prob__icon--warning]=\"bucket === 'Warning'\"\r\n [class.fb-prob__icon--info]=\"bucket === 'Info'\"\r\n role=\"img\"\r\n [attr.aria-label]=\"labelText(bucket)\"\r\n [title]=\"labelText(bucket)\"\r\n >{{ iconText(bucket) }}</span\r\n >\r\n <span class=\"fb-prob__message\">{{ issue.message }}</span>\r\n <span class=\"fb-prob__meta\">\r\n <code>{{ issue.code }}</code>\r\n @if (issue.elementName) {\r\n \u00B7 {{ issue.elementName }}\r\n }\r\n @if (issue.path) {\r\n <!-- Il path per nome, non a indici: vedi `pathLabel`. -->\r\n \u00B7 {{ pathLabel(issue) }}\r\n }\r\n </span>\r\n </button>\r\n <!--\r\n Il comando per aprire il form. Sta accanto alla riga e non **e\u2019** la riga: scorrere\r\n l\u2019elenco dei problemi e aprire il form di ognuno sono due intenzioni diverse, e finche\u2019\r\n erano lo stesso clic la seconda accadeva per forza.\r\n -->\r\n <button\r\n type=\"button\"\r\n class=\"fb-prob__open\"\r\n [attr.aria-label]=\"'Apri il dettaglio di ' + (issue.elementName || 'questo rilievo')\"\r\n title=\"Apri il dettaglio (anche con un doppio clic sulla riga)\"\r\n (click)=\"open(issue, $event)\"\r\n >\r\n \u270E\r\n </button>\r\n </li>\r\n }\r\n </ul>\r\n\r\n <!--\r\n `hasErrors` e non `errorCount`: quello segue i filtri, e filtrando per \u00ABNote\u00BB la nota\r\n spariva pur essendoci quarantasette errori \u2014 cioe' proprio quando serve.\r\n -->\r\n @if (hasErrors()) {\r\n <p class=\"fb-prob__note\">\r\n Gli errori bloccano l\u2019<strong>attivazione</strong>, non il salvataggio: puoi interrompere il lavoro a\r\n met\u00E0 e riprenderlo.\r\n </p>\r\n }\r\n</div>\r\n", styles: [":host{display:flex;flex-direction:column;height:100%;min-height:0;background:var(--fb-surface, #fff);border-top:1px solid var(--fb-border, #d6dae1)}.fb-prob__header{display:flex;align-items:center;justify-content:space-between;padding:6px 12px;border-bottom:1px solid var(--fb-border-subtle, #e6e9ee)}.fb-prob__title{display:flex;align-items:baseline;gap:8px;margin:0;font-size:13px;font-weight:600;color:var(--fb-text, #1d2939)}.fb-prob__running{font-size:10px;font-weight:400;color:var(--fb-text-subtle, #98a2b3)}.fb-prob__filters{display:flex;gap:3px;padding:5px 12px;border-bottom:1px solid var(--fb-border-subtle, #e6e9ee)}.fb-prob__filter{display:inline-flex;align-items:center;gap:4px;padding:2px 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-prob__filter--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-prob__filter--error.fb-prob__filter--active{border-color:var(--fb-error, #c9372c);background:color-mix(in srgb,var(--fb-error, #c9372c) 10%,transparent);color:var(--fb-error, #c9372c)}.fb-prob__filter--warning.fb-prob__filter--active{border-color:var(--fb-warning, #b7791f);background:color-mix(in srgb,var(--fb-warning, #b7791f) 12%,transparent);color:var(--fb-warning, #b7791f)}.fb-prob__icon{display:inline-grid;place-items:center;flex:0 0 auto;width:13px;height:13px;border-radius:50%;background:var(--fb-text-subtle, #98a2b3);color:var(--fb-surface, #fff);font-size:9px;font-weight:700;line-height:1}.fb-prob__icon--error{background:var(--fb-error, #c9372c)}.fb-prob__icon--warning{background:var(--fb-warning, #b7791f)}.fb-prob__icon--info{background:var(--fb-accent, #2f6feb)}.fb-prob__count{padding:0 4px;border-radius:6px;background:var(--fb-border, #d6dae1);font-size:9px;color:var(--fb-text, #1d2939)}.fb-prob__body{flex:1;min-height:0;overflow-y:auto;padding:8px 12px}.fb-prob__list{margin:0;padding:0;list-style:none}.fb-prob__item{display:flex;align-items:stretch;border-left:3px solid var(--fb-text-subtle, #98a2b3);margin-bottom:3px;border-radius:3px;background:var(--fb-surface-alt, #f8f9fb)}.fb-prob__item--error{border-left-color:var(--fb-error, #c9372c)}.fb-prob__item--warning{border-left-color:var(--fb-warning, #b7791f)}.fb-prob__item--info{border-left-color:var(--fb-accent, #2f6feb)}.fb-prob__link{display:grid;grid-template-columns:auto minmax(0,1fr);column-gap:7px;align-items:start;width:100%;padding:5px 8px;border:0;background:transparent;color:var(--fb-text, #1d2939);font:inherit;text-align:left;cursor:pointer}.fb-prob__link:hover{background:color-mix(in srgb,var(--fb-accent, #2f6feb) 6%,transparent)}.fb-prob__link>.fb-prob__icon{grid-column:1;grid-row:1 / span 2;margin-top:2px}.fb-prob__message{grid-column:2;grid-row:1;font-size:12px;line-height:1.4}.fb-prob__meta{grid-column:2;grid-row:2;font-size:10px;color:var(--fb-text-muted, #667085)}.fb-prob__inline-link{padding:0;border:0;background:transparent;color:var(--fb-accent, #2f6feb);font:inherit;text-decoration:underline;cursor:pointer}.fb-prob__note{margin:8px 0 0;font-size:10px;color:var(--fb-text-subtle, #98a2b3)}.fb-prob__open{flex:none;align-self:flex-start;width:22px;height:22px;margin:2px 4px 0 0;padding:0;border:1px solid transparent;border-radius:var(--fb-radius-xs, 6px);background:transparent;color:var(--fb-text-muted, #667085);font:inherit;font-size:12px;line-height:1;cursor:pointer}.fb-prob__open:hover,.fb-prob__open:focus-visible{border-color:var(--fb-border, #d6dae1);background:var(--fb-surface, #fff);color:var(--fb-text, #1d2939)}.fb-prob__tools{display:flex;align-items:center;gap:6px;padding:6px 12px;border-bottom:1px solid var(--fb-border-subtle, #e6e9ee)}.fb-prob__search{box-sizing:border-box;flex:1 1 260px;max-width:420px;padding:4px 8px;border:1px solid var(--fb-border, #d6dae1);border-radius:6px;background:var(--fb-surface, #fff);color:var(--fb-text, #1d2939);font:inherit;font-size:12px}.fb-prob__search:focus-visible{outline:2px solid var(--fb-accent, #2f6feb);outline-offset:-1px}.fb-prob__codes{position:relative;flex:none}.fb-prob__codes-toggle{display:inline-flex;align-items:center;gap:4px;padding:3px 8px;border:1px solid var(--fb-border, #d6dae1);border-radius:6px;background:var(--fb-surface, #fff);color:var(--fb-text-muted, #667085);font:inherit;font-size:11px;cursor:pointer}.fb-prob__codes-toggle:hover{background:var(--fb-surface-alt, #f8f9fb)}.fb-prob__codes-toggle--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-prob__caret{font-size:9px}.fb-prob__menu{position:absolute;top:calc(100% + 4px);left:0;z-index:30;min-width:260px;max-height:280px;overflow-y:auto;padding:4px;border:1px solid var(--fb-border, #d6dae1);border-radius:8px;background:var(--fb-surface, #fff);box-shadow:0 8px 24px #10182824}.fb-prob__menu-clear{width:100%;padding:4px 6px;border:0;border-bottom:1px solid var(--fb-border-subtle, #e6e9ee);margin-bottom:4px;background:transparent;color:var(--fb-accent, #2f6feb);font:inherit;font-size:11px;text-align:left;cursor:pointer}.fb-prob__menu-item{display:flex;align-items:center;gap:6px;width:100%;padding:4px 6px;border:0;border-radius:6px;background:transparent;color:var(--fb-text, #1d2939);font:inherit;font-size:11px;text-align:left;cursor:pointer}.fb-prob__menu-item:hover{background:var(--fb-surface-alt, #f8f9fb)}.fb-prob__menu-item--selected{background:color-mix(in srgb,var(--fb-accent, #2f6feb) 8%,transparent)}.fb-prob__check{flex:none;font-size:12px;color:var(--fb-accent, #2f6feb)}.fb-prob__menu-code{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:10px}.fb-prob__menu-count{flex:none;padding:0 5px;border-radius:999px;background:var(--fb-surface-sunken, #eef0f4);color:var(--fb-text-muted, #667085);font-size:10px;font-variant-numeric:tabular-nums}.fb-prob__menu-empty{margin:6px;color:var(--fb-text-muted, #667085);font-size:11px}.fb-prob__reset{flex:none;padding:3px 8px;border:0;border-radius:6px;background:var(--fb-surface-sunken, #eef0f4);color:var(--fb-text-muted, #667085);font:inherit;font-size:11px;cursor:pointer}.fb-prob__reset:hover{background:var(--fb-border, #d6dae1)}\n"] }]
|
|
16674
|
+
}], ctorParameters: () => [], propDecorators: { elementFocused: [{ type: i0.Output, args: ["elementFocused"] }], elementOpened: [{ type: i0.Output, args: ["elementOpened"] }], closed: [{ type: i0.Output, args: ["closed"] }], codesRef: [{ type: i0.ViewChild, args: ['codes', { isSignal: true }] }] } });
|
|
16012
16675
|
|
|
16013
16676
|
/**
|
|
16014
16677
|
* Versioni e ciclo di vita — FRONTEND.md §8, §9.
|