@esfaenza/flow-builder 20.3.14 → 20.3.15
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 +40 -4
- package/fesm2022/esfaenza-flow-builder.mjs +880 -14
- package/fesm2022/esfaenza-flow-builder.mjs.map +1 -1
- package/index.d.ts +395 -15
- package/package.json +2 -1
- package/styles/flow-builder.css +33 -0
|
@@ -5,6 +5,7 @@ import { firstValueFrom } from 'rxjs';
|
|
|
5
5
|
import dagre from 'dagre';
|
|
6
6
|
import * as i1 from '@foblex/flow';
|
|
7
7
|
import { EFMarkerType, EFConnectableSide, FCanvasComponent, FFlowModule, provideFFlow, withA11y } from '@foblex/flow';
|
|
8
|
+
import { CdkDrag, CdkDragHandle, CdkDropList } from '@angular/cdk/drag-drop';
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* Modello del documento Flow — FRONTEND.md §3, §4, §5.
|
|
@@ -22,6 +23,7 @@ import { EFMarkerType, EFConnectableSide, FCanvasComponent, FFlowModule, provide
|
|
|
22
23
|
/** Le collection di node: `metadataProperty` del dizionario `elementTypes` (§3.2). */
|
|
23
24
|
const FLOW_NODE_COLLECTIONS = [
|
|
24
25
|
'screens',
|
|
26
|
+
'dynamicScreens',
|
|
25
27
|
'assignments',
|
|
26
28
|
'decisions',
|
|
27
29
|
'loops',
|
|
@@ -712,6 +714,7 @@ function outletsOf(type, node) {
|
|
|
712
714
|
case 'CustomError':
|
|
713
715
|
return [];
|
|
714
716
|
case 'Screen':
|
|
717
|
+
case 'DynamicScreen':
|
|
715
718
|
case 'Assignment':
|
|
716
719
|
case 'CollectionProcessor':
|
|
717
720
|
case 'RecordRollback':
|
|
@@ -733,6 +736,7 @@ function outletByKey(type, node, key) {
|
|
|
733
736
|
*/
|
|
734
737
|
const FALLBACK_COLLECTION_BY_TYPE = {
|
|
735
738
|
Screen: 'screens',
|
|
739
|
+
DynamicScreen: 'dynamicScreens',
|
|
736
740
|
Assignment: 'assignments',
|
|
737
741
|
Decision: 'decisions',
|
|
738
742
|
Loop: 'loops',
|
|
@@ -757,6 +761,7 @@ const TYPE_BY_COLLECTION = Object.fromEntries(Object.entries(FALLBACK_COLLECTION
|
|
|
757
761
|
/** Etichette di fallback, se il dizionario non e' caricato. */
|
|
758
762
|
const FALLBACK_TYPE_LABEL = {
|
|
759
763
|
Screen: 'Screen',
|
|
764
|
+
DynamicScreen: 'Screen dinamico',
|
|
760
765
|
Assignment: 'Assegnazione',
|
|
761
766
|
Decision: 'Decisione',
|
|
762
767
|
Loop: 'Ciclo',
|
|
@@ -799,6 +804,9 @@ const TYPES_WITH_AUTOMATIC_OUTPUT = new Set([
|
|
|
799
804
|
const FLOW_ELEMENT_ICONS = {
|
|
800
805
|
Start: '▶',
|
|
801
806
|
Screen: '▤',
|
|
807
|
+
// Diverso da quello dello Screen a form: sul canvas i due elementi devono distinguersi
|
|
808
|
+
// a colpo d'occhio, perche' chi decide il layout e' l'unica differenza fra loro (§5.2).
|
|
809
|
+
DynamicScreen: '▦',
|
|
802
810
|
Decision: '◆',
|
|
803
811
|
Assignment: '=',
|
|
804
812
|
Loop: '↻',
|
|
@@ -1315,6 +1323,198 @@ function stageStepOutputReferenced(reference, steps) {
|
|
|
1315
1323
|
*/
|
|
1316
1324
|
const ORCHESTRATION_CONDITION_OUTPUT = 'isOrchestrationConditionMet';
|
|
1317
1325
|
|
|
1326
|
+
/**
|
|
1327
|
+
* L'albero dei campi di uno screen dinamico — FRONTEND.md §5.2.
|
|
1328
|
+
*
|
|
1329
|
+
* Un campo puo' contenerne altri (`Region` dentro `RegionContainer`), quindi «il campo
|
|
1330
|
+
* selezionato» non e' un indice ma un **percorso di indici**: `[1, 0, 2]` e' il terzo campo
|
|
1331
|
+
* della prima colonna della seconda sezione. Tutto cio' che cammina l'albero sta qui, perche'
|
|
1332
|
+
* inspector, store e outline lo fanno con la stessa regola e sbagliarne una copia significa
|
|
1333
|
+
* scrivere in un campo diverso da quello che l'utente vede selezionato.
|
|
1334
|
+
*
|
|
1335
|
+
* L'altra ragione per cui questo file esiste: **i campi che raccolgono un valore sono risorse
|
|
1336
|
+
* del flow** (§5.2). I loro nomi vivono nello stesso spazio dei nomi di node e risorse (§3.3),
|
|
1337
|
+
* quindi `usedNames` dello store deve comprenderli — un campo omonimo di una variabile e'
|
|
1338
|
+
* `NAME_DUPLICATED`, non una stranezza tollerata.
|
|
1339
|
+
*/
|
|
1340
|
+
/** Uguaglianza fra percorsi: due array diversi con gli stessi indici sono lo stesso campo. */
|
|
1341
|
+
function samePath(a, b) {
|
|
1342
|
+
if (!a || !b || a.length !== b.length) {
|
|
1343
|
+
return false;
|
|
1344
|
+
}
|
|
1345
|
+
return a.every((value, index) => value === b[index]);
|
|
1346
|
+
}
|
|
1347
|
+
/** `true` se `parent` e' un antenato di `path` (o lo stesso campo). */
|
|
1348
|
+
function isPathInside(parent, path) {
|
|
1349
|
+
return parent.length <= path.length && parent.every((value, index) => value === path[index]);
|
|
1350
|
+
}
|
|
1351
|
+
/** Chiave stabile di un percorso: serve al `track` e agli id delle liste di trascinamento. */
|
|
1352
|
+
function pathKey(path) {
|
|
1353
|
+
return path.join('.');
|
|
1354
|
+
}
|
|
1355
|
+
/** Il campo a un percorso, o `undefined` se il percorso non esiste piu'. */
|
|
1356
|
+
function fieldAt(fields, path) {
|
|
1357
|
+
let list = fields;
|
|
1358
|
+
let field;
|
|
1359
|
+
for (const index of path) {
|
|
1360
|
+
field = list?.[index];
|
|
1361
|
+
if (!field) {
|
|
1362
|
+
return undefined;
|
|
1363
|
+
}
|
|
1364
|
+
list = field.fields;
|
|
1365
|
+
}
|
|
1366
|
+
return field;
|
|
1367
|
+
}
|
|
1368
|
+
/** La lista che **contiene** un percorso, creandola se serve: serve a inserire e rimuovere. */
|
|
1369
|
+
function listOf(screen, parentPath, create) {
|
|
1370
|
+
if (parentPath.length === 0) {
|
|
1371
|
+
if (create) {
|
|
1372
|
+
screen.fields ??= [];
|
|
1373
|
+
}
|
|
1374
|
+
return screen.fields;
|
|
1375
|
+
}
|
|
1376
|
+
const parent = fieldAt(screen.fields, parentPath);
|
|
1377
|
+
if (!parent) {
|
|
1378
|
+
return undefined;
|
|
1379
|
+
}
|
|
1380
|
+
if (create) {
|
|
1381
|
+
parent.fields ??= [];
|
|
1382
|
+
}
|
|
1383
|
+
return parent.fields;
|
|
1384
|
+
}
|
|
1385
|
+
/** L'albero appiattito in ordine di visita, con profondita': e' l'elenco che si disegna. */
|
|
1386
|
+
function flattenFields(fields, parentPath = []) {
|
|
1387
|
+
const result = [];
|
|
1388
|
+
(fields ?? []).forEach((field, index) => {
|
|
1389
|
+
const path = [...parentPath, index];
|
|
1390
|
+
result.push({ field, path, depth: parentPath.length });
|
|
1391
|
+
if (field.fields?.length) {
|
|
1392
|
+
result.push(...flattenFields(field.fields, path));
|
|
1393
|
+
}
|
|
1394
|
+
});
|
|
1395
|
+
return result;
|
|
1396
|
+
}
|
|
1397
|
+
/** Tutti i campi di uno screen, in ordine di visita. */
|
|
1398
|
+
function allFieldsOf(screen) {
|
|
1399
|
+
return flattenFields(screen?.fields).map((node) => node.field);
|
|
1400
|
+
}
|
|
1401
|
+
/**
|
|
1402
|
+
* I nomi dei campi di tutti gli screen dinamici del documento.
|
|
1403
|
+
*
|
|
1404
|
+
* Contenitori compresi: un `Region` non e' una risorsa — non raccoglie un valore — ma il suo
|
|
1405
|
+
* nome resta un nome, e due campi omonimi nella stessa schermata sono comunque un problema.
|
|
1406
|
+
* Chi vuole i **soli** riferimenti proponibili usa `isFieldResource`.
|
|
1407
|
+
*/
|
|
1408
|
+
function screenFieldNames(definition) {
|
|
1409
|
+
const names = [];
|
|
1410
|
+
for (const screen of definition?.dynamicScreens ?? []) {
|
|
1411
|
+
for (const field of allFieldsOf(screen)) {
|
|
1412
|
+
if (field.name) {
|
|
1413
|
+
names.push(field.name);
|
|
1414
|
+
}
|
|
1415
|
+
}
|
|
1416
|
+
}
|
|
1417
|
+
return names;
|
|
1418
|
+
}
|
|
1419
|
+
/**
|
|
1420
|
+
* §5.2 — il campo e' una risorsa del flow (`kind: "ScreenField"`), quindi referenziabile
|
|
1421
|
+
* per nome ovunque e **di sola lettura**.
|
|
1422
|
+
*
|
|
1423
|
+
* Un `ComponentInstance` non lo e' pur essendo un campo: non ha un valore proprio, ce l'hanno
|
|
1424
|
+
* i suoi output. Il flag `storesValue` arriva dal dizionario, che e' l'unica fonte autoritativa:
|
|
1425
|
+
* senza la voce del dizionario si risponde `false`, cioe' "non lo so" — proporre un riferimento
|
|
1426
|
+
* che il backend non conosce e' peggio che non proporlo.
|
|
1427
|
+
*/
|
|
1428
|
+
function isFieldResource(field, entry) {
|
|
1429
|
+
return !!field?.name && entry?.storesValue === true;
|
|
1430
|
+
}
|
|
1431
|
+
// ---------------------------------------------------------------------------
|
|
1432
|
+
// Mutazioni (lavorano sulla copia di lavoro dello store)
|
|
1433
|
+
// ---------------------------------------------------------------------------
|
|
1434
|
+
/** Inserisce un campo dentro `parentPath` alla posizione indicata (in fondo se assente). */
|
|
1435
|
+
function insertField(screen, parentPath, field, index) {
|
|
1436
|
+
const list = listOf(screen, parentPath, true);
|
|
1437
|
+
if (!list) {
|
|
1438
|
+
return undefined;
|
|
1439
|
+
}
|
|
1440
|
+
const position = index === undefined || index > list.length ? list.length : Math.max(0, index);
|
|
1441
|
+
list.splice(position, 0, field);
|
|
1442
|
+
return [...parentPath, position];
|
|
1443
|
+
}
|
|
1444
|
+
/** Rimuove il campo a un percorso, e con esso tutto cio' che conteneva. */
|
|
1445
|
+
function removeField(screen, path) {
|
|
1446
|
+
if (path.length === 0) {
|
|
1447
|
+
return;
|
|
1448
|
+
}
|
|
1449
|
+
const parentPath = path.slice(0, -1);
|
|
1450
|
+
const list = listOf(screen, parentPath, false);
|
|
1451
|
+
const index = path[path.length - 1];
|
|
1452
|
+
list?.splice(index, 1);
|
|
1453
|
+
if (list && list.length === 0) {
|
|
1454
|
+
// Liste vuote omesse, non scritte come `[]` (§2).
|
|
1455
|
+
if (parentPath.length === 0) {
|
|
1456
|
+
delete screen.fields;
|
|
1457
|
+
}
|
|
1458
|
+
else {
|
|
1459
|
+
delete fieldAt(screen.fields, parentPath)?.fields;
|
|
1460
|
+
}
|
|
1461
|
+
}
|
|
1462
|
+
}
|
|
1463
|
+
/**
|
|
1464
|
+
* Sposta un campo dentro un'altra lista, a una posizione precisa.
|
|
1465
|
+
*
|
|
1466
|
+
* `toIndex` e' l'indice **dopo** l'estrazione, cioe' la stessa convenzione di
|
|
1467
|
+
* `moveItemInArray` del CDK: e' l'indice che il drop del trascinamento consegna, e riallinearlo
|
|
1468
|
+
* qui sposterebbe di uno ogni trascinamento verso il basso nella stessa lista.
|
|
1469
|
+
*
|
|
1470
|
+
* Uno spostamento dentro se stesso e' rifiutato: sposterebbe un contenitore dentro il proprio
|
|
1471
|
+
* sottoalbero, e il sottoalbero sparirebbe con lui.
|
|
1472
|
+
*/
|
|
1473
|
+
function moveField(screen, from, toParent, toIndex) {
|
|
1474
|
+
if (from.length === 0 || isPathInside(from, toParent)) {
|
|
1475
|
+
return undefined;
|
|
1476
|
+
}
|
|
1477
|
+
const field = fieldAt(screen.fields, from);
|
|
1478
|
+
if (!field) {
|
|
1479
|
+
return undefined;
|
|
1480
|
+
}
|
|
1481
|
+
removeField(screen, from);
|
|
1482
|
+
return insertField(screen, shiftAfterRemoval(from, toParent), field, toIndex);
|
|
1483
|
+
}
|
|
1484
|
+
/**
|
|
1485
|
+
* Il percorso della destinazione **dopo** aver estratto il campo di partenza.
|
|
1486
|
+
*
|
|
1487
|
+
* Estrarre `[0]` fa scalare di uno i fratelli che seguivano: la sezione che era `[1]` ora e'
|
|
1488
|
+
* `[0]`, e reinserire nel percorso vecchio significa scrivere dentro un contenitore diverso da
|
|
1489
|
+
* quello su cui l'utente ha rilasciato. Riguarda solo i percorsi che **passano** dalla lista da
|
|
1490
|
+
* cui si estrae, e solo gli indici successivi a quello estratto.
|
|
1491
|
+
*/
|
|
1492
|
+
function shiftAfterRemoval(from, toParent) {
|
|
1493
|
+
const fromParent = from.slice(0, -1);
|
|
1494
|
+
const fromIndex = from[from.length - 1];
|
|
1495
|
+
if (toParent.length <= fromParent.length || !samePath(fromParent, toParent.slice(0, fromParent.length))) {
|
|
1496
|
+
return toParent;
|
|
1497
|
+
}
|
|
1498
|
+
const shifted = [...toParent];
|
|
1499
|
+
if (shifted[fromParent.length] > fromIndex) {
|
|
1500
|
+
shifted[fromParent.length] -= 1;
|
|
1501
|
+
}
|
|
1502
|
+
return shifted;
|
|
1503
|
+
}
|
|
1504
|
+
/** Duplica un campo accanto all'originale, rinominandolo: i nomi restano unici (§3.3). */
|
|
1505
|
+
function duplicateField(screen, path, newName) {
|
|
1506
|
+
const field = fieldAt(screen.fields, path);
|
|
1507
|
+
if (!field) {
|
|
1508
|
+
return undefined;
|
|
1509
|
+
}
|
|
1510
|
+
const copy = JSON.parse(JSON.stringify(field));
|
|
1511
|
+
copy.name = newName;
|
|
1512
|
+
// I figli non si rinominano: sarebbero nomi duplicati. Copiare un contenitore e' quindi un
|
|
1513
|
+
// gesto da completare a mano, e l'editor lo dice invece di inventare dieci nomi.
|
|
1514
|
+
delete copy.fields;
|
|
1515
|
+
return insertField(screen, path.slice(0, -1), copy, path[path.length - 1] + 1);
|
|
1516
|
+
}
|
|
1517
|
+
|
|
1318
1518
|
/**
|
|
1319
1519
|
* La gravita' di un rilievo, ridotta a tre secchi — FRONTEND.md §7.
|
|
1320
1520
|
*
|
|
@@ -1811,15 +2011,18 @@ class FlowDocumentStore {
|
|
|
1811
2011
|
return result;
|
|
1812
2012
|
}, ...(ngDevMode ? [{ debugName: "resources" }] : []));
|
|
1813
2013
|
/**
|
|
1814
|
-
* §3.3 — l'insieme dei nomi già usati: node, risorse
|
|
1815
|
-
* spazio di nomi. Il controllo di unicita' che guarda solo le
|
|
1816
|
-
* variabile omonima di un node (§13.5); quello che dimentica gli
|
|
1817
|
-
* step omonimo di una variabile
|
|
2014
|
+
* §3.3 — l'insieme dei nomi già usati: node, risorse, step di orchestrazione **e campi di
|
|
2015
|
+
* screen dinamico**, un unico spazio di nomi. Il controllo di unicita' che guarda solo le
|
|
2016
|
+
* variabili lascia passare una variabile omonima di un node (§13.5); quello che dimentica gli
|
|
2017
|
+
* step lascia passare uno step omonimo di una variabile (§5.13); quello che dimentica i campi
|
|
2018
|
+
* lascia passare un campo omonimo di una variabile, che e' `NAME_DUPLICATED` come gli altri
|
|
2019
|
+
* — e lì il danno e' peggiore, perche' un campo **e' un riferimento** (§5.2).
|
|
1818
2020
|
*/
|
|
1819
2021
|
usedNames = computed(() => [
|
|
1820
2022
|
...this.nodes().map((reference) => reference.name),
|
|
1821
2023
|
...this.resources().map((reference) => reference.name),
|
|
1822
2024
|
...stageStepNames(this._document()),
|
|
2025
|
+
...screenFieldNames(this._document()),
|
|
1823
2026
|
], ...(ngDevMode ? [{ debugName: "usedNames" }] : []));
|
|
1824
2027
|
/** Gli archi derivati dal documento, Start incluso. */
|
|
1825
2028
|
edges = computed(() => {
|
|
@@ -2051,6 +2254,37 @@ class FlowDocumentStore {
|
|
|
2051
2254
|
FlowDocumentStore.rewriteReferences(draft, oldName, newName);
|
|
2052
2255
|
});
|
|
2053
2256
|
}
|
|
2257
|
+
/**
|
|
2258
|
+
* §5.2 — rinomina un campo di screen dinamico riscrivendo i riferimenti che lo usano.
|
|
2259
|
+
*
|
|
2260
|
+
* Un campo che raccoglie un valore **e' una risorsa** referenziabile per nome: rinominarlo
|
|
2261
|
+
* senza riscrivere le condizioni e le assegnazioni che lo citano lascia dei
|
|
2262
|
+
* `REFERENCE_UNKNOWN` che si scoprono solo alla validazione. Vale la stessa regola dei node
|
|
2263
|
+
* (§13.8): nessuna primitiva del backend lo fa, lo fa l'editor.
|
|
2264
|
+
*/
|
|
2265
|
+
renameScreenField(nodeName, path, newName) {
|
|
2266
|
+
const reference = this.nodeByName().get(nodeName);
|
|
2267
|
+
if (!reference || !newName) {
|
|
2268
|
+
return;
|
|
2269
|
+
}
|
|
2270
|
+
// Una sola `update`, non `updateNode` piu' una riscrittura: annidare due update farebbe
|
|
2271
|
+
// ripartire la seconda dal documento **precedente**, buttando via la rinomina.
|
|
2272
|
+
this.update((draft) => {
|
|
2273
|
+
const list = draft[reference.collection];
|
|
2274
|
+
const screen = list?.[reference.index];
|
|
2275
|
+
const field = fieldAt(screen?.fields, path);
|
|
2276
|
+
if (!field || field.name === newName) {
|
|
2277
|
+
return;
|
|
2278
|
+
}
|
|
2279
|
+
const oldName = field.name;
|
|
2280
|
+
field.name = newName;
|
|
2281
|
+
if (oldName) {
|
|
2282
|
+
// La riscrittura cammina l'intero documento: il campo puo' essere referenziato da
|
|
2283
|
+
// qualunque elemento successivo, non solo da questo screen.
|
|
2284
|
+
FlowDocumentStore.rewriteReferences(draft, oldName, newName);
|
|
2285
|
+
}
|
|
2286
|
+
});
|
|
2287
|
+
}
|
|
2054
2288
|
/** Rinomina una risorsa, riscrivendo i riferimenti che la usano. */
|
|
2055
2289
|
renameResource(collection, index, newName) {
|
|
2056
2290
|
this.update((draft) => {
|
|
@@ -2286,6 +2520,42 @@ class FlowDictionaryStore {
|
|
|
2286
2520
|
stageStepTypes = computed(() => this._dictionaries().stageStepTypes ?? [], ...(ngDevMode ? [{ debugName: "stageStepTypes" }] : []));
|
|
2287
2521
|
assigneeTypes = computed(() => this._dictionaries().assigneeTypes ?? [], ...(ngDevMode ? [{ debugName: "assigneeTypes" }] : []));
|
|
2288
2522
|
conditionLogicModes = computed(() => this._dictionaries().conditionLogicModes ?? [], ...(ngDevMode ? [{ debugName: "conditionLogicModes" }] : []));
|
|
2523
|
+
regionContainerTypes = computed(() => this._dictionaries().regionContainerTypes ?? [], ...(ngDevMode ? [{ debugName: "regionContainerTypes" }] : []));
|
|
2524
|
+
screenFieldInputsRevisited = computed(() => this._dictionaries().screenFieldInputsRevisited ?? [], ...(ngDevMode ? [{ debugName: "screenFieldInputsRevisited" }] : []));
|
|
2525
|
+
/** §5.2 — i tipi di campo di uno screen dinamico, con i flag che guidano il form. */
|
|
2526
|
+
screenFieldTypes = computed(() => this._dictionaries().screenFieldTypes ?? [], ...(ngDevMode ? [{ debugName: "screenFieldTypes" }] : []));
|
|
2527
|
+
/**
|
|
2528
|
+
* La voce di un tipo di campo. **Non c'e' un fallback cablato**, di proposito: i flag sono gli
|
|
2529
|
+
* stessi che applica il runtime (§5.2, §6.4), e indovinarli qui vorrebbe dire proporre una
|
|
2530
|
+
* configurazione che il motore rifiuta. Voce assente = nessun flag, cioe' il form mostra i soli
|
|
2531
|
+
* campi comuni e lascia decidere al backend.
|
|
2532
|
+
*/
|
|
2533
|
+
screenFieldType(value) {
|
|
2534
|
+
if (!value) {
|
|
2535
|
+
return undefined;
|
|
2536
|
+
}
|
|
2537
|
+
return this.screenFieldTypes().find((entry) => entry.value === value);
|
|
2538
|
+
}
|
|
2539
|
+
/** §5.2 — il campo raccoglie un valore: e' una risorsa, e vuole tipo, default, … */
|
|
2540
|
+
screenFieldStoresValue(value) {
|
|
2541
|
+
return this.screenFieldType(value)?.storesValue ?? false;
|
|
2542
|
+
}
|
|
2543
|
+
/** §5.2 — il campo contiene altri campi: `Region`, `RegionContainer`. */
|
|
2544
|
+
screenFieldIsContainer(value) {
|
|
2545
|
+
return this.screenFieldType(value)?.isContainer ?? false;
|
|
2546
|
+
}
|
|
2547
|
+
/** §5.2 — il campo si configura con `choiceReferences`. */
|
|
2548
|
+
screenFieldAcceptsChoices(value) {
|
|
2549
|
+
return this.screenFieldType(value)?.acceptsChoices ?? false;
|
|
2550
|
+
}
|
|
2551
|
+
/** §5.2 — `dataType` obbligatorio: senza, `DATA_TYPE_MISSING`. */
|
|
2552
|
+
screenFieldRequiresDataType(value) {
|
|
2553
|
+
return this.screenFieldType(value)?.requiresDataType ?? false;
|
|
2554
|
+
}
|
|
2555
|
+
/** §5.2 — il valore raccolto e' una collection: contano gli operatori di collection. */
|
|
2556
|
+
screenFieldIsCollection(value) {
|
|
2557
|
+
return this.screenFieldType(value)?.isCollection ?? false;
|
|
2558
|
+
}
|
|
2289
2559
|
elementTypes = computed(() => this._dictionaries().elementTypes ?? [], ...(ngDevMode ? [{ debugName: "elementTypes" }] : []));
|
|
2290
2560
|
/**
|
|
2291
2561
|
* I tipi mostrabili nella palette: `isSupported: false` va nascosto (§3.2, §13.9).
|
|
@@ -3781,9 +4051,14 @@ class ElementPaletteComponent {
|
|
|
3781
4051
|
pick(item) {
|
|
3782
4052
|
this.elementPicked.emit(item.data);
|
|
3783
4053
|
}
|
|
3784
|
-
/**
|
|
4054
|
+
/**
|
|
4055
|
+
* Uno screen in un flow che non mostra nulla: si segnala, non si nasconde (§3.1). Vale per
|
|
4056
|
+
* entrambi i tipi di screen — quello a form e quello dinamico sono la stessa interazione con
|
|
4057
|
+
* l'utente, e in un flow `AutoLaunched` non c'e' nessuno a cui mostrarli.
|
|
4058
|
+
*/
|
|
3785
4059
|
isIncompatible(item) {
|
|
3786
|
-
|
|
4060
|
+
const isScreen = item.entry.value === 'Screen' || item.entry.value === 'DynamicScreen';
|
|
4061
|
+
return isScreen && this.processType() === 'AutoLaunched';
|
|
3787
4062
|
}
|
|
3788
4063
|
/**
|
|
3789
4064
|
* Il testo del tooltip. Su una voce che nasce da una variante ci va anche il nome del tipo:
|
|
@@ -4375,6 +4650,9 @@ class ReferencePickerComponent {
|
|
|
4375
4650
|
return 'Dynamic choice set';
|
|
4376
4651
|
case 'Stage':
|
|
4377
4652
|
return 'Stage';
|
|
4653
|
+
// §5.2 — nasce dichiarando un campo di screen dinamico, non da una collection di risorse.
|
|
4654
|
+
case 'ScreenField':
|
|
4655
|
+
return 'Campi di schermata';
|
|
4378
4656
|
case 'ElementOutput':
|
|
4379
4657
|
return 'Output di elementi';
|
|
4380
4658
|
case 'StructureMember':
|
|
@@ -7239,6 +7517,585 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.27", ngImpo
|
|
|
7239
7517
|
args: [{ selector: 'fb-decision-inspector', standalone: true, imports: [ConditionEditorComponent, ConnectorEditorComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<p class=\"fb-callout\">\r\n Le regole sono valutate <strong>dall\u2019alto verso il basso</strong> e si ferma alla prima vera. L\u2019ordine e\u2019\r\n parte del significato del flow: usa le frecce per cambiarlo.\r\n</p>\r\n\r\n<div class=\"fb-list\">\r\n @for (rule of rules(); track $index) {\r\n <div class=\"fb-list__item\">\r\n <div class=\"fb-list__header\">\r\n <span class=\"fb-list__index\" [title]=\"'Valutata per ' + ($index + 1) + '\u00AA'\">{{ $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=\"Valuta prima\"\r\n [disabled]=\"$first\"\r\n (click)=\"moveRule($index, -1)\"\r\n >\r\n \u2191\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-btn--ghost fb-btn--icon\"\r\n aria-label=\"Valuta dopo\"\r\n [disabled]=\"$last\"\r\n (click)=\"moveRule($index, 1)\"\r\n >\r\n \u2193\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-btn--ghost fb-btn--icon\"\r\n aria-label=\"Rimuovi la regola\"\r\n (click)=\"removeRule($index)\"\r\n >\r\n \u00D7\r\n </button>\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Etichetta del ramo</label>\r\n <input\r\n class=\"fb-input\"\r\n [value]=\"rule.label || ''\"\r\n placeholder=\"Approvato\"\r\n (input)=\"setRuleLabel($index, $any($event.target).value)\"\r\n />\r\n @if (!rule.label) {\r\n <p class=\"fb-field__hint\">Senza etichetta l\u2019arco resta senza nome sul canvas.</p>\r\n }\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Nome tecnico</label>\r\n <input\r\n class=\"fb-input fb-input--mono\"\r\n [class.fb-input--invalid]=\"!!ruleNameError($index)\"\r\n [value]=\"rule.name || ''\"\r\n (input)=\"setRuleName($index, $any($event.target).value)\"\r\n />\r\n @if (ruleNameError($index)) {\r\n <p class=\"fb-field__error\">{{ ruleNameError($index) }}</p>\r\n }\r\n </div>\r\n\r\n <fb-condition-editor\r\n [holder]=\"rule\"\r\n title=\"Quando prendere questo ramo\"\r\n [issuePath]=\"'rules[' + (rule.name || $index) + ']'\"\r\n (changed)=\"onRuleConditionsChanged($index, $event)\"\r\n />\r\n </div>\r\n } @empty {\r\n <p class=\"fb-empty\">\r\n Nessuna regola: una Decision senza regole prende sempre il ramo di default\r\n (DECISION_WITHOUT_RULES).\r\n </p>\r\n }\r\n</div>\r\n\r\n<button type=\"button\" class=\"fb-btn\" (click)=\"addRule()\">Aggiungi regola</button>\r\n\r\n<fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Se nessuna regola e\u2019 vera</legend>\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Etichetta del ramo di default</label>\r\n <input\r\n class=\"fb-input\"\r\n [value]=\"defaultLabel()\"\r\n placeholder=\"Rifiutato\"\r\n (input)=\"setDefaultLabel($any($event.target).value)\"\r\n />\r\n </div>\r\n @if (!hasDefaultTarget()) {\r\n <p class=\"fb-field__hint\">\r\n Nessuna destinazione di default: se nessuna regola e\u2019 vera l\u2019esecuzione finisce qui.\r\n </p>\r\n }\r\n</fieldset>\r\n\r\n<fb-connector-editor\r\n [nodeName]=\"name()\"\r\n [node]=\"node()\"\r\n [outlets]=\"outlets()\"\r\n title=\"Rami\"\r\n (connectorChanged)=\"onConnectorChanged($event)\"\r\n/>\r\n" }]
|
|
7240
7518
|
}] });
|
|
7241
7519
|
|
|
7520
|
+
/**
|
|
7521
|
+
* Screen dinamico — FRONTEND.md §5.2.
|
|
7522
|
+
*
|
|
7523
|
+
* A differenza dello Screen a form (§5.1), qui la schermata **la compone il flow**: l'inspector
|
|
7524
|
+
* e' quindi un compositore, non un form di quattro campi. Due pannelli: a sinistra l'albero dei
|
|
7525
|
+
* campi con la griglia, a destra le proprieta' del campo selezionato.
|
|
7526
|
+
*
|
|
7527
|
+
* Le cose che questo componente fa di proposito, e che un editor piu' ingenuo sbaglia:
|
|
7528
|
+
*
|
|
7529
|
+
* 1. **Quali campi mostrare lo decide il dizionario, non il codice.** `screenFieldTypes` porta
|
|
7530
|
+
* `storesValue`, `isCollection`, `acceptsChoices`, `isContainer`, `requiresDataType` (§6.4),
|
|
7531
|
+
* e sono gli **stessi** flag che applica il runtime: reimplementarli qui vorrebbe dire
|
|
7532
|
+
* proporre configurazioni che il motore rifiuta.
|
|
7533
|
+
* 2. **Un campo che raccoglie un valore e' una risorsa del flow** (§5.2): il suo nome vive
|
|
7534
|
+
* nello spazio dei nomi comune (§3.3) e rinominarlo riscrive i riferimenti — se ne occupa
|
|
7535
|
+
* `FlowDocumentStore.renameScreenField`, perche' nessuna primitiva del backend lo fa (§13.8).
|
|
7536
|
+
* Per questo il nome si applica sul `change` (uscita dal campo), non a ogni tasto: una
|
|
7537
|
+
* riscrittura per lettera digitata sarebbe una riscrittura del documento per lettera.
|
|
7538
|
+
* 3. **I campi sono di sola lettura per il flow**: non si offre nessuna destinazione su di
|
|
7539
|
+
* essi, e il default e' un valore, non un'assegnazione.
|
|
7540
|
+
* 4. **`choiceReferences` accetta solo `Choice` e `DynamicChoiceSet`** (§5.2): puntare a una
|
|
7541
|
+
* variabile e' `SCREEN_FIELD_CHOICE_UNKNOWN`, quindi l'elenco proposto e' filtrato sulle due
|
|
7542
|
+
* collection e non sui riferimenti in generale.
|
|
7543
|
+
* 5. **Il trascinamento e' del CDK.** L'annidamento previsto e' sezione → colonne → campi, e
|
|
7544
|
+
* lo spostamento dentro il proprio sottoalbero e' rifiutato da `moveField`: un contenitore
|
|
7545
|
+
* trascinato dentro se stesso porterebbe via tutto quello che contiene.
|
|
7546
|
+
*/
|
|
7547
|
+
class DynamicScreenInspectorComponent extends NodeInspectorBase {
|
|
7548
|
+
catalog = inject(FlowCatalogStore);
|
|
7549
|
+
dictionary = inject(FlowDictionaryStore);
|
|
7550
|
+
elementType = 'DynamicScreen';
|
|
7551
|
+
screen = computed(() => this.node(), ...(ngDevMode ? [{ debugName: "screen" }] : []));
|
|
7552
|
+
/** Il percorso del campo selezionato: `null` = nessuno, e il pannello destro lo dice. */
|
|
7553
|
+
selection = signal(null, ...(ngDevMode ? [{ debugName: "selection" }] : []));
|
|
7554
|
+
/** L'albero appiattito: e' anche cio' che serve per capire quali percorsi esistono ancora. */
|
|
7555
|
+
tree = computed(() => flattenFields(this.screen().fields), ...(ngDevMode ? [{ debugName: "tree" }] : []));
|
|
7556
|
+
selectedPath = computed(() => {
|
|
7557
|
+
const path = this.selection();
|
|
7558
|
+
if (!path) {
|
|
7559
|
+
return null;
|
|
7560
|
+
}
|
|
7561
|
+
// Il campo puo' essere sparito (annulla, eliminazione altrove): una selezione che punta a
|
|
7562
|
+
// un percorso inesistente mostrerebbe le proprieta' di un altro campo.
|
|
7563
|
+
return fieldAt(this.screen().fields, path) ? path : null;
|
|
7564
|
+
}, ...(ngDevMode ? [{ debugName: "selectedPath" }] : []));
|
|
7565
|
+
selectedField = computed(() => {
|
|
7566
|
+
const path = this.selectedPath();
|
|
7567
|
+
return path ? fieldAt(this.screen().fields, path) : undefined;
|
|
7568
|
+
}, ...(ngDevMode ? [{ debugName: "selectedField" }] : []));
|
|
7569
|
+
/** La voce di dizionario del campo selezionato: e' lei a decidere il form di destra. */
|
|
7570
|
+
selectedType = computed(() => this.dictionary.screenFieldType(this.selectedField()?.fieldType), ...(ngDevMode ? [{ debugName: "selectedType" }] : []));
|
|
7571
|
+
// -------------------------------------------------------------------------
|
|
7572
|
+
// Cataloghi
|
|
7573
|
+
// -------------------------------------------------------------------------
|
|
7574
|
+
/** Componenti e form sono la stessa domanda al frontend, e lo stesso catalogo (§5.2). */
|
|
7575
|
+
components = signal([], ...(ngDevMode ? [{ debugName: "components" }] : []));
|
|
7576
|
+
componentParameters = signal([], ...(ngDevMode ? [{ debugName: "componentParameters" }] : []));
|
|
7577
|
+
/** I tipi di enumerazione: dizionario chiuso, quindi `<select>` e non combo (§4.6). */
|
|
7578
|
+
enumTypes = signal([], ...(ngDevMode ? [{ debugName: "enumTypes" }] : []));
|
|
7579
|
+
constructor() {
|
|
7580
|
+
super();
|
|
7581
|
+
void this.catalog
|
|
7582
|
+
.listForms()
|
|
7583
|
+
.then((list) => this.components.set(list ?? []))
|
|
7584
|
+
.catch(() => this.components.set([]));
|
|
7585
|
+
void this.catalog
|
|
7586
|
+
.listEnumTypes()
|
|
7587
|
+
.then((list) => this.enumTypes.set(list ?? []))
|
|
7588
|
+
.catch(() => this.enumTypes.set([]));
|
|
7589
|
+
effect(() => {
|
|
7590
|
+
const extensionName = this.selectedField()?.extensionName;
|
|
7591
|
+
if (!extensionName) {
|
|
7592
|
+
this.componentParameters.set([]);
|
|
7593
|
+
return;
|
|
7594
|
+
}
|
|
7595
|
+
void this.catalog
|
|
7596
|
+
.listFormParameters(extensionName)
|
|
7597
|
+
.then((list) => this.componentParameters.set(list ?? []))
|
|
7598
|
+
.catch(() => this.componentParameters.set([]));
|
|
7599
|
+
});
|
|
7600
|
+
}
|
|
7601
|
+
componentOptions = computed(() => this.components(), ...(ngDevMode ? [{ debugName: "componentOptions" }] : []));
|
|
7602
|
+
enumOptions = computed(() => this.enumTypes(), ...(ngDevMode ? [{ debugName: "enumOptions" }] : []));
|
|
7603
|
+
componentParameterList = computed(() => this.componentParameters(), ...(ngDevMode ? [{ debugName: "componentParameterList" }] : []));
|
|
7604
|
+
/**
|
|
7605
|
+
* Le choice proponibili: **solo** `choices` e `dynamicChoiceSets` (§5.2). Un elenco costruito
|
|
7606
|
+
* dai riferimenti in generale farebbe scegliere una variabile, che e'
|
|
7607
|
+
* `SCREEN_FIELD_CHOICE_UNKNOWN`.
|
|
7608
|
+
*/
|
|
7609
|
+
choiceOptions = computed(() => {
|
|
7610
|
+
const document = this.store.document();
|
|
7611
|
+
const options = [];
|
|
7612
|
+
for (const choice of document.choices ?? []) {
|
|
7613
|
+
options.push({ name: choice.name ?? '', label: choice.choiceText ?? null, description: 'Choice' });
|
|
7614
|
+
}
|
|
7615
|
+
for (const set of document.dynamicChoiceSets ?? []) {
|
|
7616
|
+
options.push({
|
|
7617
|
+
name: set.name ?? '',
|
|
7618
|
+
label: set.object ? `da ${set.object}` : null,
|
|
7619
|
+
description: 'Dynamic choice set',
|
|
7620
|
+
});
|
|
7621
|
+
}
|
|
7622
|
+
return options.filter((option) => option.name);
|
|
7623
|
+
}, ...(ngDevMode ? [{ debugName: "choiceOptions" }] : []));
|
|
7624
|
+
/** Gli stage dichiarati: `stageReference` alimenta l'indicatore di avanzamento (§5.2). */
|
|
7625
|
+
stageOptions = computed(() => (this.store.document().stages ?? [])
|
|
7626
|
+
.filter((stage) => stage.name)
|
|
7627
|
+
.map((stage) => ({ name: stage.name, label: stage.label ?? null })), ...(ngDevMode ? [{ debugName: "stageOptions" }] : []));
|
|
7628
|
+
// -------------------------------------------------------------------------
|
|
7629
|
+
// Lettura dell'albero
|
|
7630
|
+
// -------------------------------------------------------------------------
|
|
7631
|
+
icon = elementIcon('DynamicScreen');
|
|
7632
|
+
isSelected(path) {
|
|
7633
|
+
return samePath(this.selectedPath(), path);
|
|
7634
|
+
}
|
|
7635
|
+
isContainer(field) {
|
|
7636
|
+
return this.dictionary.screenFieldIsContainer(field?.fieldType);
|
|
7637
|
+
}
|
|
7638
|
+
/** L'etichetta mostrata nell'albero: `fieldText`, poi il nome, poi un segnaposto. */
|
|
7639
|
+
captionOf(field) {
|
|
7640
|
+
return field.fieldText || field.name || '(senza nome)';
|
|
7641
|
+
}
|
|
7642
|
+
typeLabelOf(field) {
|
|
7643
|
+
return this.dictionary.screenFieldType(field.fieldType)?.label ?? field.fieldType ?? '—';
|
|
7644
|
+
}
|
|
7645
|
+
/** La larghezza in dodicesimi: assente = tutta la riga, che e' il comportamento di default. */
|
|
7646
|
+
widthOf(field) {
|
|
7647
|
+
return field.width ?? 12;
|
|
7648
|
+
}
|
|
7649
|
+
/** `true` dove il tipo non e' nel dizionario: il form di destra lo dice invece di indovinare. */
|
|
7650
|
+
isUnknownType(field) {
|
|
7651
|
+
return !!field?.fieldType && !this.dictionary.screenFieldType(field.fieldType);
|
|
7652
|
+
}
|
|
7653
|
+
/** Il campo raccoglie un valore, quindi e' referenziabile per nome nel resto del flow (§5.2). */
|
|
7654
|
+
selectedIsResource = computed(() => this.selectedType()?.storesValue === true, ...(ngDevMode ? [{ debugName: "selectedIsResource" }] : []));
|
|
7655
|
+
/**
|
|
7656
|
+
* §5.2 — uno screen senza campi non ha niente da mostrare (`SCREEN_WITHOUT_FIELDS`), e uno
|
|
7657
|
+
* senza destinazione che non consente «fine» e' un vicolo cieco (`SCREEN_DEAD_END`). Si dicono
|
|
7658
|
+
* qui prima che lo dica la validazione.
|
|
7659
|
+
*/
|
|
7660
|
+
isEmpty = computed(() => !this.screen().fields?.length, ...(ngDevMode ? [{ debugName: "isEmpty" }] : []));
|
|
7661
|
+
isDeadEnd = computed(() => {
|
|
7662
|
+
const screen = this.screen();
|
|
7663
|
+
return !screen.connector?.targetReference && screen.allowFinish === false;
|
|
7664
|
+
}, ...(ngDevMode ? [{ debugName: "isDeadEnd" }] : []));
|
|
7665
|
+
allowBack = computed(() => this.screen().allowBack !== false, ...(ngDevMode ? [{ debugName: "allowBack" }] : []));
|
|
7666
|
+
allowFinish = computed(() => this.screen().allowFinish !== false, ...(ngDevMode ? [{ debugName: "allowFinish" }] : []));
|
|
7667
|
+
allowPause = computed(() => this.screen().allowPause !== false, ...(ngDevMode ? [{ debugName: "allowPause" }] : []));
|
|
7668
|
+
showHeader = computed(() => this.screen().showHeader !== false, ...(ngDevMode ? [{ debugName: "showHeader" }] : []));
|
|
7669
|
+
showFooter = computed(() => this.screen().showFooter !== false, ...(ngDevMode ? [{ debugName: "showFooter" }] : []));
|
|
7670
|
+
/** §5.2 — `isEditable` ha default `true`: a `false` il runtime ignora cio' che torna. */
|
|
7671
|
+
selectedIsEditable = computed(() => this.selectedField()?.isEditable !== false, ...(ngDevMode ? [{ debugName: "selectedIsEditable" }] : []));
|
|
7672
|
+
selectedIsRequired = computed(() => this.selectedField()?.isRequired === true, ...(ngDevMode ? [{ debugName: "selectedIsRequired" }] : []));
|
|
7673
|
+
/**
|
|
7674
|
+
* §5.2 — `storeOutputAutomatically` **insieme** a `outputParameters` e'
|
|
7675
|
+
* `OUTPUT_CONFIGURATION_CONFLICT`: la coppia si mostra come esclusiva, non come due caselle.
|
|
7676
|
+
*/
|
|
7677
|
+
hasOutputConflict = computed(() => {
|
|
7678
|
+
const field = this.selectedField();
|
|
7679
|
+
return field?.storeOutputAutomatically === true && (field.outputParameters?.length ?? 0) > 0;
|
|
7680
|
+
}, ...(ngDevMode ? [{ debugName: "hasOutputConflict" }] : []));
|
|
7681
|
+
/** §5.2 — la choice di default fuori dall'elenco e' un avviso, non un errore. */
|
|
7682
|
+
defaultChoiceIsForeign = computed(() => {
|
|
7683
|
+
const field = this.selectedField();
|
|
7684
|
+
const value = field?.defaultSelectedChoiceReference;
|
|
7685
|
+
return !!value && !(field?.choiceReferences ?? []).includes(value);
|
|
7686
|
+
}, ...(ngDevMode ? [{ debugName: "defaultChoiceIsForeign" }] : []));
|
|
7687
|
+
/** §5.2 — `scale` su un campo non numerico e' `SCALE_NOT_APPLICABLE`. */
|
|
7688
|
+
scaleApplies = computed(() => this.dictionary.supportsScale(this.selectedField()?.dataType), ...(ngDevMode ? [{ debugName: "scaleApplies" }] : []));
|
|
7689
|
+
requiresObjectType = computed(() => this.dictionary.requiresObjectType(this.selectedField()?.dataType), ...(ngDevMode ? [{ debugName: "requiresObjectType" }] : []));
|
|
7690
|
+
objectTypeIsStructure = computed(() => this.dictionary.isStructure(this.selectedField()?.dataType), ...(ngDevMode ? [{ debugName: "objectTypeIsStructure" }] : []));
|
|
7691
|
+
/** L'oggetto di un `ObjectProvided`, cioe' la parte prima del punto (§5.2). */
|
|
7692
|
+
providedObject = computed(() => {
|
|
7693
|
+
const reference = this.selectedField()?.objectFieldReference ?? '';
|
|
7694
|
+
const dot = reference.indexOf('.');
|
|
7695
|
+
return dot < 0 ? reference : reference.slice(0, dot);
|
|
7696
|
+
}, ...(ngDevMode ? [{ debugName: "providedObject" }] : []));
|
|
7697
|
+
providedField = computed(() => {
|
|
7698
|
+
const reference = this.selectedField()?.objectFieldReference ?? '';
|
|
7699
|
+
const dot = reference.indexOf('.');
|
|
7700
|
+
return dot < 0 ? '' : reference.slice(dot + 1);
|
|
7701
|
+
}, ...(ngDevMode ? [{ debugName: "providedField" }] : []));
|
|
7702
|
+
// -------------------------------------------------------------------------
|
|
7703
|
+
// Mutazioni sull'albero
|
|
7704
|
+
// -------------------------------------------------------------------------
|
|
7705
|
+
select(path) {
|
|
7706
|
+
this.selection.set([...path]);
|
|
7707
|
+
}
|
|
7708
|
+
/** Aggiunge un campo dentro il contenitore selezionato, o in fondo alla schermata. */
|
|
7709
|
+
addField(type) {
|
|
7710
|
+
const parent = this.parentForNewField();
|
|
7711
|
+
const base = this.dictionary.screenFieldType(type)?.label ?? type;
|
|
7712
|
+
const name = uniqueFlowName(base, this.store.usedNames());
|
|
7713
|
+
const field = { name, fieldType: type };
|
|
7714
|
+
if (this.dictionary.screenFieldStoresValue(type) && this.dictionary.screenFieldRequiresDataType(type)) {
|
|
7715
|
+
// Il tipo e' obbligatorio (`DATA_TYPE_MISSING`): nascere con `String` evita di
|
|
7716
|
+
// presentare come completo un campo che non lo e'. Resta cambiabile.
|
|
7717
|
+
field.dataType = 'String';
|
|
7718
|
+
}
|
|
7719
|
+
if (!this.dictionary.screenFieldIsContainer(type)) {
|
|
7720
|
+
field.fieldText = base;
|
|
7721
|
+
}
|
|
7722
|
+
let created;
|
|
7723
|
+
this.patch((node) => {
|
|
7724
|
+
created = insertField(node, parent, field);
|
|
7725
|
+
});
|
|
7726
|
+
if (created) {
|
|
7727
|
+
this.selection.set(created);
|
|
7728
|
+
}
|
|
7729
|
+
}
|
|
7730
|
+
/**
|
|
7731
|
+
* Dove finisce un campo nuovo: dentro il contenitore selezionato (o dentro il contenitore
|
|
7732
|
+
* che contiene il campo selezionato), altrimenti in fondo alla schermata. È il gesto che
|
|
7733
|
+
* l'utente si aspetta dopo aver cliccato una colonna.
|
|
7734
|
+
*/
|
|
7735
|
+
parentForNewField() {
|
|
7736
|
+
const path = this.selectedPath();
|
|
7737
|
+
if (!path) {
|
|
7738
|
+
return [];
|
|
7739
|
+
}
|
|
7740
|
+
return this.isContainer(this.selectedField()) ? path : path.slice(0, -1);
|
|
7741
|
+
}
|
|
7742
|
+
removeSelected() {
|
|
7743
|
+
const path = this.selectedPath();
|
|
7744
|
+
if (!path) {
|
|
7745
|
+
return;
|
|
7746
|
+
}
|
|
7747
|
+
this.patch((node) => removeField(node, path));
|
|
7748
|
+
this.selection.set(null);
|
|
7749
|
+
}
|
|
7750
|
+
duplicateSelected() {
|
|
7751
|
+
const path = this.selectedPath();
|
|
7752
|
+
const field = this.selectedField();
|
|
7753
|
+
if (!path || !field) {
|
|
7754
|
+
return;
|
|
7755
|
+
}
|
|
7756
|
+
const name = uniqueFlowName(field.name || 'Campo', this.store.usedNames());
|
|
7757
|
+
let created;
|
|
7758
|
+
this.patch((node) => {
|
|
7759
|
+
created = duplicateField(node, path, name);
|
|
7760
|
+
});
|
|
7761
|
+
if (created) {
|
|
7762
|
+
this.selection.set(created);
|
|
7763
|
+
}
|
|
7764
|
+
}
|
|
7765
|
+
/**
|
|
7766
|
+
* Spostamento con i tasti: la stessa operazione del trascinamento, per chi non trascina e per
|
|
7767
|
+
* i casi in cui la lista e' piu' alta del pannello.
|
|
7768
|
+
*/
|
|
7769
|
+
moveSelected(direction) {
|
|
7770
|
+
const path = this.selectedPath();
|
|
7771
|
+
if (!path) {
|
|
7772
|
+
return;
|
|
7773
|
+
}
|
|
7774
|
+
const parent = path.slice(0, -1);
|
|
7775
|
+
const index = path[path.length - 1];
|
|
7776
|
+
const siblings = parent.length
|
|
7777
|
+
? (fieldAt(this.screen().fields, parent)?.fields ?? [])
|
|
7778
|
+
: (this.screen().fields ?? []);
|
|
7779
|
+
const target = index + direction;
|
|
7780
|
+
if (target < 0 || target >= siblings.length) {
|
|
7781
|
+
return;
|
|
7782
|
+
}
|
|
7783
|
+
this.applyMove(path, parent, target);
|
|
7784
|
+
}
|
|
7785
|
+
/**
|
|
7786
|
+
* Sposta il campo dentro il contenitore che lo precede. È la controparte esplicita del
|
|
7787
|
+
* trascinamento: dove l'albero e' lungo, mirare la riga giusta costa piu' di un clic.
|
|
7788
|
+
*/
|
|
7789
|
+
indentSelected() {
|
|
7790
|
+
const target = this.indentTarget();
|
|
7791
|
+
const path = this.selectedPath();
|
|
7792
|
+
if (!path || !target) {
|
|
7793
|
+
return;
|
|
7794
|
+
}
|
|
7795
|
+
this.applyMove(path, target, (fieldAt(this.screen().fields, target)?.fields?.length ?? 0));
|
|
7796
|
+
}
|
|
7797
|
+
/** Il contenitore in cui «porta dentro» metterebbe il campo, o `null` se non ce n'e' uno. */
|
|
7798
|
+
indentTarget = computed(() => {
|
|
7799
|
+
const path = this.selectedPath();
|
|
7800
|
+
if (!path || path[path.length - 1] === 0) {
|
|
7801
|
+
// Primo della sua lista: non ha un fratello precedente in cui entrare.
|
|
7802
|
+
return null;
|
|
7803
|
+
}
|
|
7804
|
+
const previous = [...path.slice(0, -1), path[path.length - 1] - 1];
|
|
7805
|
+
return this.isContainer(fieldAt(this.screen().fields, previous)) ? previous : null;
|
|
7806
|
+
}, ...(ngDevMode ? [{ debugName: "indentTarget" }] : []));
|
|
7807
|
+
/** Estrae il campo dal proprio contenitore e lo mette subito dopo di esso. */
|
|
7808
|
+
outdentSelected() {
|
|
7809
|
+
const path = this.selectedPath();
|
|
7810
|
+
if (!path || path.length < 2) {
|
|
7811
|
+
return;
|
|
7812
|
+
}
|
|
7813
|
+
const grandParent = path.slice(0, -2);
|
|
7814
|
+
const parentIndex = path[path.length - 2];
|
|
7815
|
+
this.applyMove(path, grandParent, parentIndex + 1);
|
|
7816
|
+
}
|
|
7817
|
+
/**
|
|
7818
|
+
* Rilascio del trascinamento sulla lista **piatta**.
|
|
7819
|
+
*
|
|
7820
|
+
* Il contenitore di arrivo non lo dice il CDK — c'e' una sola lista — ma si deduce dalla riga
|
|
7821
|
+
* che **precede** il punto di rilascio, che e' anche cio' che l'utente vede: rilasciare subito
|
|
7822
|
+
* sotto una sezione o una colonna significa «mettilo dentro»; rilasciare sotto un campo
|
|
7823
|
+
* normale significa «mettilo accanto». Sono le due sole intenzioni possibili.
|
|
7824
|
+
*/
|
|
7825
|
+
onDrop(event) {
|
|
7826
|
+
const nodes = this.tree();
|
|
7827
|
+
const dragged = nodes[event.previousIndex];
|
|
7828
|
+
if (!dragged) {
|
|
7829
|
+
return;
|
|
7830
|
+
}
|
|
7831
|
+
const from = dragged.path;
|
|
7832
|
+
// L'elenco come lo vede il CDK al momento del rilascio: senza la riga trascinata.
|
|
7833
|
+
const withoutDragged = nodes.filter((_, index) => index !== event.previousIndex);
|
|
7834
|
+
let before;
|
|
7835
|
+
for (let index = event.currentIndex - 1; index >= 0; index -= 1) {
|
|
7836
|
+
const candidate = withoutDragged[index];
|
|
7837
|
+
// Le righe del proprio sottoalbero non sono un ancoraggio valido: e' lì che il campo
|
|
7838
|
+
// finirebbe dentro se stesso.
|
|
7839
|
+
if (candidate && !isPathInside(from, candidate.path)) {
|
|
7840
|
+
before = candidate;
|
|
7841
|
+
break;
|
|
7842
|
+
}
|
|
7843
|
+
}
|
|
7844
|
+
if (!before) {
|
|
7845
|
+
// Rilasciato in cima: primo campo della schermata.
|
|
7846
|
+
this.applyMove(from, [], 0, true);
|
|
7847
|
+
return;
|
|
7848
|
+
}
|
|
7849
|
+
if (this.isContainer(before.field)) {
|
|
7850
|
+
this.applyMove(from, before.path, 0, true);
|
|
7851
|
+
return;
|
|
7852
|
+
}
|
|
7853
|
+
this.applyMove(from, before.path.slice(0, -1), before.path[before.path.length - 1] + 1, true);
|
|
7854
|
+
}
|
|
7855
|
+
/**
|
|
7856
|
+
* `preRemoval` distingue le due convenzioni sull'indice di arrivo: i comandi (su, giu',
|
|
7857
|
+
* porta fuori) ragionano sulla lista **dopo** l'estrazione, il rilascio ragiona su cio' che
|
|
7858
|
+
* si vedeva **prima**. Confonderle sposta di uno ogni trascinamento verso il basso.
|
|
7859
|
+
*/
|
|
7860
|
+
applyMove(from, toParent, toIndex, preRemoval = false) {
|
|
7861
|
+
const fromParent = from.slice(0, -1);
|
|
7862
|
+
const target = preRemoval && samePath(fromParent, toParent) && from[from.length - 1] < toIndex
|
|
7863
|
+
? toIndex - 1
|
|
7864
|
+
: toIndex;
|
|
7865
|
+
let moved;
|
|
7866
|
+
this.patch((node) => {
|
|
7867
|
+
moved = moveField(node, from, toParent, target);
|
|
7868
|
+
});
|
|
7869
|
+
if (moved) {
|
|
7870
|
+
this.selection.set(moved);
|
|
7871
|
+
}
|
|
7872
|
+
}
|
|
7873
|
+
// -------------------------------------------------------------------------
|
|
7874
|
+
// Mutazioni sul campo selezionato
|
|
7875
|
+
// -------------------------------------------------------------------------
|
|
7876
|
+
/** Scrive una proprieta' del campo selezionato; valore vuoto → chiave omessa (§2). */
|
|
7877
|
+
patchField(mutate) {
|
|
7878
|
+
const path = this.selectedPath();
|
|
7879
|
+
if (!path) {
|
|
7880
|
+
return;
|
|
7881
|
+
}
|
|
7882
|
+
this.patch((node) => {
|
|
7883
|
+
const field = fieldAt(node.fields, path);
|
|
7884
|
+
if (field) {
|
|
7885
|
+
mutate(field);
|
|
7886
|
+
}
|
|
7887
|
+
});
|
|
7888
|
+
}
|
|
7889
|
+
setFieldProperty(property, value) {
|
|
7890
|
+
this.patchField((field) => {
|
|
7891
|
+
if (value === undefined || value === null || value === '') {
|
|
7892
|
+
delete field[property];
|
|
7893
|
+
}
|
|
7894
|
+
else {
|
|
7895
|
+
field[property] = value;
|
|
7896
|
+
}
|
|
7897
|
+
});
|
|
7898
|
+
}
|
|
7899
|
+
/**
|
|
7900
|
+
* Il nome si applica sull'uscita dal campo e passa da `renameScreenField`: il campo e' una
|
|
7901
|
+
* risorsa, quindi la rinomina deve riscrivere i riferimenti che lo citano (§5.2, §13.8).
|
|
7902
|
+
*/
|
|
7903
|
+
setFieldName(value) {
|
|
7904
|
+
const path = this.selectedPath();
|
|
7905
|
+
const trimmed = value.trim();
|
|
7906
|
+
if (!path || !trimmed || trimmed === this.selectedField()?.name) {
|
|
7907
|
+
return;
|
|
7908
|
+
}
|
|
7909
|
+
this.store.renameScreenField(this.name(), path, trimmed);
|
|
7910
|
+
}
|
|
7911
|
+
/**
|
|
7912
|
+
* Cambiare tipo **ripulisce** cio' che il tipo nuovo non prevede: choice su una textarea o un
|
|
7913
|
+
* `dataType` su un `DisplayText` sono `SCREEN_FIELD_CONFIGURATION_INVALID`, e lasciarli nel
|
|
7914
|
+
* documento significa lasciare un errore invisibile nell'editor.
|
|
7915
|
+
*/
|
|
7916
|
+
setFieldType(type) {
|
|
7917
|
+
const entry = this.dictionary.screenFieldType(type);
|
|
7918
|
+
this.patchField((field) => {
|
|
7919
|
+
field.fieldType = type;
|
|
7920
|
+
if (!entry?.storesValue) {
|
|
7921
|
+
delete field.dataType;
|
|
7922
|
+
delete field.objectType;
|
|
7923
|
+
delete field.isRequired;
|
|
7924
|
+
delete field.isEditable;
|
|
7925
|
+
delete field.defaultValue;
|
|
7926
|
+
delete field.scale;
|
|
7927
|
+
delete field.maxLength;
|
|
7928
|
+
delete field.validationRule;
|
|
7929
|
+
delete field.inputsOnNextNavToAssocScrn;
|
|
7930
|
+
}
|
|
7931
|
+
if (!entry?.acceptsChoices) {
|
|
7932
|
+
delete field.choiceReferences;
|
|
7933
|
+
delete field.defaultSelectedChoiceReference;
|
|
7934
|
+
}
|
|
7935
|
+
if (!entry?.isContainer) {
|
|
7936
|
+
delete field.fields;
|
|
7937
|
+
delete field.regionContainerType;
|
|
7938
|
+
}
|
|
7939
|
+
if (type !== 'ComponentInstance') {
|
|
7940
|
+
delete field.extensionName;
|
|
7941
|
+
delete field.inputParameters;
|
|
7942
|
+
delete field.outputParameters;
|
|
7943
|
+
delete field.storeOutputAutomatically;
|
|
7944
|
+
}
|
|
7945
|
+
if (type !== 'ObjectProvided') {
|
|
7946
|
+
delete field.objectFieldReference;
|
|
7947
|
+
}
|
|
7948
|
+
});
|
|
7949
|
+
}
|
|
7950
|
+
setDefaultValue(value) {
|
|
7951
|
+
this.patchField((field) => {
|
|
7952
|
+
field.defaultValue = value;
|
|
7953
|
+
});
|
|
7954
|
+
}
|
|
7955
|
+
setNumberProperty(property, raw) {
|
|
7956
|
+
const parsed = Number(raw);
|
|
7957
|
+
this.setFieldProperty(property, raw === '' || Number.isNaN(parsed) ? undefined : parsed);
|
|
7958
|
+
}
|
|
7959
|
+
/** `isRequired` si scrive solo quando e' vero: il default e' facoltativo. */
|
|
7960
|
+
setRequired(value) {
|
|
7961
|
+
this.patchField((field) => {
|
|
7962
|
+
if (value) {
|
|
7963
|
+
field.isRequired = true;
|
|
7964
|
+
}
|
|
7965
|
+
else {
|
|
7966
|
+
delete field.isRequired;
|
|
7967
|
+
}
|
|
7968
|
+
});
|
|
7969
|
+
}
|
|
7970
|
+
/** `isEditable` ha default `true`: si scrive solo il `false`, che e' la scelta significativa. */
|
|
7971
|
+
setEditable(value) {
|
|
7972
|
+
this.patchField((field) => {
|
|
7973
|
+
if (value) {
|
|
7974
|
+
delete field.isEditable;
|
|
7975
|
+
}
|
|
7976
|
+
else {
|
|
7977
|
+
field.isEditable = false;
|
|
7978
|
+
}
|
|
7979
|
+
});
|
|
7980
|
+
}
|
|
7981
|
+
addChoice(name) {
|
|
7982
|
+
if (!name) {
|
|
7983
|
+
return;
|
|
7984
|
+
}
|
|
7985
|
+
this.patchField((field) => {
|
|
7986
|
+
field.choiceReferences ??= [];
|
|
7987
|
+
if (!field.choiceReferences.includes(name)) {
|
|
7988
|
+
// L'ordine e' quello di presentazione delle opzioni (§5.2): si aggiunge in fondo.
|
|
7989
|
+
field.choiceReferences.push(name);
|
|
7990
|
+
}
|
|
7991
|
+
});
|
|
7992
|
+
}
|
|
7993
|
+
removeChoice(index) {
|
|
7994
|
+
this.patchField((field) => {
|
|
7995
|
+
field.choiceReferences?.splice(index, 1);
|
|
7996
|
+
if (!field.choiceReferences?.length) {
|
|
7997
|
+
delete field.choiceReferences;
|
|
7998
|
+
}
|
|
7999
|
+
});
|
|
8000
|
+
}
|
|
8001
|
+
moveChoice(index, direction) {
|
|
8002
|
+
const target = index + direction;
|
|
8003
|
+
this.patchField((field) => {
|
|
8004
|
+
const list = field.choiceReferences;
|
|
8005
|
+
if (!list || target < 0 || target >= list.length) {
|
|
8006
|
+
return;
|
|
8007
|
+
}
|
|
8008
|
+
const [entry] = list.splice(index, 1);
|
|
8009
|
+
list.splice(target, 0, entry);
|
|
8010
|
+
});
|
|
8011
|
+
}
|
|
8012
|
+
setValidationRule(part, value) {
|
|
8013
|
+
this.patchField((field) => {
|
|
8014
|
+
const rule = { ...(field.validationRule ?? {}), [part]: value || undefined };
|
|
8015
|
+
if (!rule.formulaExpression && !rule.errorMessage) {
|
|
8016
|
+
delete field.validationRule;
|
|
8017
|
+
}
|
|
8018
|
+
else {
|
|
8019
|
+
field.validationRule = rule;
|
|
8020
|
+
}
|
|
8021
|
+
});
|
|
8022
|
+
}
|
|
8023
|
+
/** La regola di visibilita' e' un blocco condizioni come quello di una Decision (§5.2). */
|
|
8024
|
+
onVisibilityChanged(mutate) {
|
|
8025
|
+
this.patchField((field) => {
|
|
8026
|
+
field.visibilityRule ??= {};
|
|
8027
|
+
mutate(field.visibilityRule);
|
|
8028
|
+
if (!field.visibilityRule.conditions?.length && !field.visibilityRule.formula) {
|
|
8029
|
+
// Nessuna condizione = campo sempre visibile: meglio omettere la regola che scrivere
|
|
8030
|
+
// un contenitore vuoto (§2).
|
|
8031
|
+
delete field.visibilityRule;
|
|
8032
|
+
}
|
|
8033
|
+
});
|
|
8034
|
+
}
|
|
8035
|
+
/** Il blocco passato all'editor delle condizioni: mai `undefined`, altrimenti non si apre. */
|
|
8036
|
+
visibilityRule = computed(() => this.selectedField()?.visibilityRule ?? {}, ...(ngDevMode ? [{ debugName: "visibilityRule" }] : []));
|
|
8037
|
+
onComponentParametersChanged(mutate) {
|
|
8038
|
+
this.patchField((field) => mutate(field));
|
|
8039
|
+
}
|
|
8040
|
+
setStoreOutputAutomatically(value) {
|
|
8041
|
+
this.patchField((field) => {
|
|
8042
|
+
if (value) {
|
|
8043
|
+
field.storeOutputAutomatically = true;
|
|
8044
|
+
// Esclusivo con gli output espliciti (§5.2): scriverli entrambi e'
|
|
8045
|
+
// `OUTPUT_CONFIGURATION_CONFLICT`, quindi la scelta ne cancella l'altra.
|
|
8046
|
+
delete field.outputParameters;
|
|
8047
|
+
}
|
|
8048
|
+
else {
|
|
8049
|
+
delete field.storeOutputAutomatically;
|
|
8050
|
+
}
|
|
8051
|
+
});
|
|
8052
|
+
}
|
|
8053
|
+
/** `ObjectProvided`: oggetto e campo si scrivono in un unico `<Oggetto>.<Campo>` (§5.2). */
|
|
8054
|
+
setProvidedObject(value) {
|
|
8055
|
+
this.setFieldProperty('objectFieldReference', value ? `${value}.${this.providedField()}` : undefined);
|
|
8056
|
+
}
|
|
8057
|
+
setProvidedField(value) {
|
|
8058
|
+
const object = this.providedObject();
|
|
8059
|
+
this.setFieldProperty('objectFieldReference', object && value ? `${object}.${value}` : object || undefined);
|
|
8060
|
+
}
|
|
8061
|
+
// -------------------------------------------------------------------------
|
|
8062
|
+
// Schermata
|
|
8063
|
+
// -------------------------------------------------------------------------
|
|
8064
|
+
setScreenText(property, value) {
|
|
8065
|
+
this.setField(property, value);
|
|
8066
|
+
}
|
|
8067
|
+
/** I flag con default `true` si scrivono solo quando si nega, per non sporcare il diff. */
|
|
8068
|
+
setScreenFlag(flag, value) {
|
|
8069
|
+
this.patch((node) => {
|
|
8070
|
+
if (value) {
|
|
8071
|
+
delete node[flag];
|
|
8072
|
+
}
|
|
8073
|
+
else {
|
|
8074
|
+
node[flag] = false;
|
|
8075
|
+
}
|
|
8076
|
+
});
|
|
8077
|
+
}
|
|
8078
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.27", ngImport: i0, type: DynamicScreenInspectorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
8079
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.27", type: DynamicScreenInspectorComponent, isStandalone: true, selector: "fb-dynamic-screen-inspector", usesInheritance: true, ngImport: i0, template: "<p class=\"fb-callout\">\r\n Lo screen dinamico <strong>descrive i propri campi</strong>: la schermata la compone il flow, non un\r\n componente scritto a mano. Un campo che raccoglie un valore diventa una <strong>risorsa</strong> del flow,\r\n referenziabile per nome ovunque e di sola lettura.\r\n</p>\r\n\r\n<div class=\"fb-dyn\">\r\n <!-- ------------------------------------------------------------- albero -->\r\n <section class=\"fb-dyn__pane fb-dyn__pane--tree\" aria-label=\"Campi della schermata\">\r\n <header class=\"fb-dyn__pane-head\">\r\n <h3 class=\"fb-dyn__pane-title\">Campi</h3>\r\n <div class=\"fb-dyn__add\">\r\n <select class=\"fb-select fb-select--sm\" #newType [disabled]=\"!dictionary.screenFieldTypes().length\">\r\n @for (entry of dictionary.screenFieldTypes(); track entry.value) {\r\n <option [value]=\"entry.value\">{{ entry.label }}</option>\r\n }\r\n </select>\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-btn--primary fb-btn--sm\"\r\n [disabled]=\"!dictionary.screenFieldTypes().length\"\r\n (click)=\"addField(newType.value)\"\r\n >\r\n Aggiungi\r\n </button>\r\n </div>\r\n </header>\r\n\r\n @if (!dictionary.screenFieldTypes().length) {\r\n <p class=\"fb-callout fb-callout--warn\">\r\n Il dizionario <code>screenFieldTypes</code> non e\u2019 disponibile: senza i suoi flag l\u2019editor non sa\r\n quali campi ammette ciascun tipo, e non li inventa.\r\n </p>\r\n }\r\n\r\n <p class=\"fb-dyn__hint\">\r\n Trascina per riordinare e per spostare un campo dentro una sezione o una colonna. L\u2019annidamento\r\n previsto e\u2019 <strong>sezione \u2192 colonne \u2192 campi</strong>.\r\n </p>\r\n\r\n <!--\r\n **Una sola** lista di rilascio sull'albero appiattito, non una per contenitore.\r\n Le drop list annidate del CDK sono ambigue proprio qui: il rettangolo di una sezione\r\n contiene quello delle sue colonne, e chi riceve il rilascio e' la lista registrata per\r\n prima \u2014 cioe' sempre la radice. Con la lista piatta il contenitore di arrivo si deduce\r\n dalla riga che precede il punto di rilascio, che e' anche cio' che si vede.\r\n -->\r\n <div\r\n cdkDropList\r\n class=\"fb-dyn__tree\"\r\n [cdkDropListData]=\"tree()\"\r\n (cdkDropListDropped)=\"onDrop($event)\"\r\n >\r\n @for (item of tree(); track $index) {\r\n <div cdkDrag class=\"fb-dyn__item\" [style.margin-left.px]=\"item.depth * 16\">\r\n <div\r\n class=\"fb-dyn__row\"\r\n [class.fb-dyn__row--selected]=\"isSelected(item.path)\"\r\n [class.fb-dyn__row--container]=\"isContainer(item.field)\"\r\n role=\"button\"\r\n tabindex=\"0\"\r\n (click)=\"select(item.path)\"\r\n (keydown.enter)=\"select(item.path)\"\r\n (keydown.space)=\"select(item.path)\"\r\n >\r\n <span class=\"fb-dyn__grip\" cdkDragHandle aria-hidden=\"true\">\u283F</span>\r\n <span class=\"fb-dyn__caption\">{{ captionOf(item.field) }}</span>\r\n <span class=\"fb-dyn__type\">{{ typeLabelOf(item.field) }}</span>\r\n @if (item.field.isRequired) {\r\n <span class=\"fb-dyn__badge fb-dyn__badge--required\" title=\"Obbligatorio\">*</span>\r\n }\r\n @if (item.field.visibilityRule?.conditions?.length) {\r\n <span class=\"fb-dyn__badge\" title=\"Ha una regola di visibilita\u2019\">\u25D0</span>\r\n }\r\n @if (!isContainer(item.field)) {\r\n <!-- La larghezza in dodicesimi, disegnata: e' l'unica parte di layout che il\r\n documento porta, e leggerla come numero non dice niente. -->\r\n <span class=\"fb-dyn__width\" [title]=\"widthOf(item.field) + '/12'\">\r\n <span class=\"fb-dyn__width-fill\" [style.width.%]=\"(widthOf(item.field) / 12) * 100\"></span>\r\n </span>\r\n }\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n\r\n @if (isEmpty()) {\r\n <p class=\"fb-callout fb-callout--warn\">\r\n La schermata non ha campi: non c\u2019e\u2019 niente da mostrare all\u2019utente (SCREEN_WITHOUT_FIELDS).\r\n </p>\r\n }\r\n </section>\r\n\r\n <!-- --------------------------------------------------------- proprieta' -->\r\n <section class=\"fb-dyn__pane fb-dyn__pane--props\" aria-label=\"Proprieta\u2019 del campo\">\r\n @if (!selectedField()) {\r\n <p class=\"fb-empty\">Seleziona un campo a sinistra per configurarlo.</p>\r\n } @else {\r\n <header class=\"fb-dyn__pane-head\">\r\n <h3 class=\"fb-dyn__pane-title\">{{ captionOf(selectedField()!) }}</h3>\r\n <div class=\"fb-dyn__pane-actions\">\r\n <button type=\"button\" class=\"fb-btn fb-btn--sm\" title=\"Sposta su\" (click)=\"moveSelected(-1)\">\u2191</button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--sm\" title=\"Sposta giu\u2019\" (click)=\"moveSelected(1)\">\u2193</button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-btn--sm\"\r\n title=\"Porta dentro il contenitore precedente\"\r\n [disabled]=\"!indentTarget()\"\r\n (click)=\"indentSelected()\"\r\n >\r\n \u21E5\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-btn--sm\"\r\n title=\"Porta fuori dal contenitore\"\r\n [disabled]=\"(selectedPath()?.length || 0) < 2\"\r\n (click)=\"outdentSelected()\"\r\n >\r\n \u21E4\r\n </button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--sm\" (click)=\"duplicateSelected()\">Duplica</button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--danger fb-btn--sm\" (click)=\"removeSelected()\">Elimina</button>\r\n </div>\r\n </header>\r\n\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]=\"selectedField()!.fieldType || ''\"\r\n (change)=\"setFieldType($any($event.target).value)\"\r\n >\r\n <option value=\"\">\u2014 scegli \u2014</option>\r\n @for (entry of dictionary.screenFieldTypes(); track entry.value) {\r\n <option [value]=\"entry.value\">{{ entry.label }}</option>\r\n }\r\n </select>\r\n @if (!selectedField()!.fieldType) {\r\n <p class=\"fb-field__error\">Il tipo e\u2019 obbligatorio: senza, SCREEN_FIELD_TYPE_MISSING.</p>\r\n } @else if (isUnknownType(selectedField())) {\r\n <p class=\"fb-field__hint fb-field__hint--warn\">\r\n Questo tipo non e\u2019 nel dizionario: l\u2019editor non sa quali campi ammetta e mostra solo i comuni.\r\n </p>\r\n } @else if (selectedType()?.description) {\r\n <p class=\"fb-field__hint\">{{ selectedType()?.description }}</p>\r\n }\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Nome</label>\r\n <!-- Sul `change` e non sull\u2019`input`: la rinomina riscrive i riferimenti nel documento,\r\n e farlo a ogni tasto significherebbe riscriverlo per ogni lettera. -->\r\n <input\r\n class=\"fb-input fb-input--mono\"\r\n [value]=\"selectedField()!.name || ''\"\r\n (change)=\"setFieldName($any($event.target).value)\"\r\n />\r\n @if (selectedIsResource()) {\r\n <p class=\"fb-field__hint\">\r\n Questo campo e\u2019 una <strong>risorsa</strong>: lo referenzi come\r\n <code>{{ selectedField()!.name || 'Nome' }}</code> in condizioni, formule e parametri. \u00C8 di sola\r\n lettura per il flow \u2014 un Assignment che ci scrive e\u2019 TARGET_NOT_WRITABLE.\r\n </p>\r\n }\r\n </div>\r\n\r\n @if (!selectedType()?.isContainer && selectedField()!.fieldType !== 'ComponentInstance') {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">\r\n {{ selectedField()!.fieldType === 'DisplayText' ? 'Testo' : 'Etichetta' }}\r\n </label>\r\n <textarea\r\n class=\"fb-textarea\"\r\n [value]=\"selectedField()!.fieldText || ''\"\r\n (input)=\"setFieldProperty('fieldText', $any($event.target).value)\"\r\n ></textarea>\r\n <p class=\"fb-field__hint\">Supporta i merge field <code>{!Riferimento}</code>.</p>\r\n </div>\r\n }\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Testo di aiuto</label>\r\n <input\r\n class=\"fb-input\"\r\n [value]=\"selectedField()!.helpText || ''\"\r\n (input)=\"setFieldProperty('helpText', $any($event.target).value)\"\r\n />\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Larghezza</label>\r\n <div class=\"fb-dyn__width-editor\">\r\n <input\r\n type=\"range\"\r\n min=\"1\"\r\n max=\"12\"\r\n step=\"1\"\r\n [value]=\"widthOf(selectedField()!)\"\r\n (input)=\"setNumberProperty('width', $any($event.target).value)\"\r\n />\r\n <span class=\"fb-dyn__width-value\">{{ widthOf(selectedField()!) }}/12</span>\r\n </div>\r\n <p class=\"fb-field__hint\">\r\n Colonne della griglia della schermata. \u00C8 un\u2019indicazione: il frontend puo\u2019 ignorarla.\r\n </p>\r\n </div>\r\n\r\n <!-- ------------------------------------------------ campi che raccolgono un valore -->\r\n @if (selectedType()?.storesValue) {\r\n <fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Valore</legend>\r\n\r\n @if (selectedField()!.fieldType !== 'ObjectProvided') {\r\n <div class=\"fb-field\">\r\n <label\r\n class=\"fb-field__label\"\r\n [class.fb-field__label--required]=\"!!selectedType()?.requiresDataType\"\r\n >\r\n Tipo di dato\r\n </label>\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"selectedField()!.dataType || ''\"\r\n (change)=\"setFieldProperty('dataType', $any($event.target).value)\"\r\n >\r\n <option value=\"\">\u2014 scegli \u2014</option>\r\n @for (entry of dictionary.dataTypes(); track entry.value) {\r\n <option [value]=\"entry.value\">{{ entry.label }}</option>\r\n }\r\n </select>\r\n @if (selectedType()?.requiresDataType && !selectedField()!.dataType) {\r\n <p class=\"fb-field__error\">Obbligatorio per questo tipo di campo (DATA_TYPE_MISSING).</p>\r\n }\r\n @if (selectedType()?.isCollection) {\r\n <p class=\"fb-field__hint\">\r\n Il valore raccolto e\u2019 una <strong>collection</strong>, non una stringa con i valori\r\n separati: nelle condizioni si usano gli operatori di collection.\r\n </p>\r\n }\r\n </div>\r\n\r\n @if (requiresObjectType()) {\r\n <div class=\"fb-field\">\r\n <label\r\n class=\"fb-field__label\"\r\n [class.fb-field__label--required]=\"objectTypeIsStructure()\"\r\n >\r\n {{ objectTypeIsStructure() ? 'Classe' : selectedField()!.dataType === 'Enum' ? 'Tipo di enumerazione' : 'Oggetto' }}\r\n </label>\r\n @if (selectedField()!.dataType === 'Enum') {\r\n <!-- Dizionario chiuso: si sceglie, non si scrive. -->\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"selectedField()!.objectType || ''\"\r\n (change)=\"setFieldProperty('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 @if (!selectedField()!.objectType) {\r\n <p class=\"fb-field__hint fb-field__hint--warn\">\r\n Senza il tipo concreto l\u2019editor non puo\u2019 proporre i valori dell\u2019enumerazione.\r\n </p>\r\n }\r\n } @else if (objectTypeIsStructure()) {\r\n <fb-structure-picker\r\n [value]=\"selectedField()!.objectType\"\r\n label=\"Classe\"\r\n (valueChange)=\"setFieldProperty('objectType', $event ?? '')\"\r\n />\r\n @if (!selectedField()!.objectType) {\r\n <p class=\"fb-field__error\">\r\n La classe e\u2019 obbligatoria: senza, il runtime non ha nulla da istanziare\r\n (OBJECT_TYPE_MISSING).\r\n </p>\r\n }\r\n } @else {\r\n <fb-object-picker\r\n [value]=\"selectedField()!.objectType\"\r\n label=\"Oggetto\"\r\n (valueChange)=\"setFieldProperty('objectType', $event ?? '')\"\r\n />\r\n }\r\n </div>\r\n }\r\n } @else {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Campo dell\u2019entita\u2019</label>\r\n <p class=\"fb-field__hint\">\r\n Tipo, label, obbligatorieta\u2019, scala e \u2014 se il campo e\u2019 una picklist \u2014 le opzioni arrivano\r\n dallo schema dati: qui non si ridichiarano.\r\n </p>\r\n <fb-object-picker\r\n [value]=\"providedObject() || undefined\"\r\n label=\"Oggetto\"\r\n (valueChange)=\"setProvidedObject($event)\"\r\n />\r\n <fb-field-picker\r\n [value]=\"providedField() || undefined\"\r\n [object]=\"providedObject() || undefined\"\r\n label=\"Campo\"\r\n (valueChange)=\"setProvidedField($event)\"\r\n />\r\n </div>\r\n }\r\n\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"selectedIsRequired()\"\r\n (change)=\"setRequired($any($event.target).checked)\"\r\n />\r\n Obbligatorio\r\n </label>\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"selectedIsEditable()\"\r\n (change)=\"setEditable($any($event.target).checked)\"\r\n />\r\n Modificabile\r\n </label>\r\n @if (!selectedIsEditable()) {\r\n <p class=\"fb-field__hint\">\r\n A <code>false</code> il runtime <strong>ignora</strong> cio\u2019 che il client rimanda indietro:\r\n e\u2019 l\u2019unico modo di rendere un campo davvero di sola lettura.\r\n </p>\r\n }\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Valore di default</label>\r\n <fb-value-editor\r\n [value]=\"selectedField()!.defaultValue\"\r\n [dataType]=\"selectedField()!.dataType\"\r\n [objectType]=\"selectedField()!.objectType\"\r\n [isCollection]=\"selectedType()?.isCollection\"\r\n label=\"Valore di default\"\r\n (valueChange)=\"setDefaultValue($event)\"\r\n />\r\n </div>\r\n\r\n <div class=\"fb-dyn__grid\">\r\n @if (scaleApplies()) {\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]=\"selectedField()!.scale ?? ''\"\r\n (change)=\"setNumberProperty('scale', $any($event.target).value)\"\r\n />\r\n </div>\r\n } @else if (selectedField()!.scale !== undefined) {\r\n <p class=\"fb-field__hint fb-field__hint--warn\">\r\n <code>scale</code> su un campo non numerico e\u2019 SCALE_NOT_APPLICABLE.\r\n </p>\r\n }\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Lunghezza massima</label>\r\n <input\r\n class=\"fb-input\"\r\n type=\"number\"\r\n min=\"1\"\r\n [value]=\"selectedField()!.maxLength ?? ''\"\r\n (change)=\"setNumberProperty('maxLength', $any($event.target).value)\"\r\n />\r\n <p class=\"fb-field__hint\">Verificata anche dal runtime (TOO_LONG).</p>\r\n </div>\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Tornando sulla schermata</label>\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"selectedField()!.inputsOnNextNavToAssocScrn || ''\"\r\n (change)=\"setFieldProperty('inputsOnNextNavToAssocScrn', $any($event.target).value)\"\r\n >\r\n <option value=\"\">Predefinito (mantieni i valori)</option>\r\n @for (entry of dictionary.screenFieldInputsRevisited(); track entry.value) {\r\n <option [value]=\"entry.value\">{{ entry.label }}</option>\r\n }\r\n </select>\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Regola di validazione</label>\r\n <input\r\n class=\"fb-input fb-input--mono\"\r\n placeholder=\"Espressione, es. LEN(Nome) > 3\"\r\n [value]=\"selectedField()!.validationRule?.formulaExpression || ''\"\r\n (change)=\"setValidationRule('formulaExpression', $any($event.target).value)\"\r\n />\r\n <input\r\n class=\"fb-input\"\r\n placeholder=\"Messaggio mostrato quando l\u2019espressione e\u2019 falsa\"\r\n [value]=\"selectedField()!.validationRule?.errorMessage || ''\"\r\n (change)=\"setValidationRule('errorMessage', $any($event.target).value)\"\r\n />\r\n <p class=\"fb-field__hint\">\r\n L\u2019espressione la valuta il motore di regole: il backend non la interpreta e non la verifica.\r\n </p>\r\n </div>\r\n </fieldset>\r\n }\r\n\r\n <!-- --------------------------------------------------------------- choice -->\r\n @if (selectedType()?.acceptsChoices) {\r\n <fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Opzioni</legend>\r\n <p class=\"fb-section__note\">\r\n Accetta <strong>Choice</strong> e <strong>Dynamic choice set</strong>, nell\u2019ordine in cui le\r\n opzioni compaiono. Puntare a una variabile e\u2019 SCREEN_FIELD_CHOICE_UNKNOWN.\r\n </p>\r\n\r\n @if (!selectedField()!.choiceReferences?.length) {\r\n <p class=\"fb-callout fb-callout--warn\">\r\n Nessuna opzione: il campo non ha niente da mostrare (SCREEN_FIELD_CHOICES_MISSING).\r\n </p>\r\n }\r\n\r\n <div class=\"fb-list\">\r\n @for (choice of selectedField()!.choiceReferences || []; 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__title\">{{ choice }}</span>\r\n <span class=\"fb-list__spacer\"></span>\r\n <button type=\"button\" class=\"fb-btn fb-btn--sm\" (click)=\"moveChoice($index, -1)\">\u2191</button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--sm\" (click)=\"moveChoice($index, 1)\">\u2193</button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-btn--ghost fb-btn--sm\"\r\n aria-label=\"Rimuovi\"\r\n (click)=\"removeChoice($index)\"\r\n >\r\n \u00D7\r\n </button>\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Aggiungi un\u2019opzione</label>\r\n <fb-name-picker\r\n [options]=\"choiceOptions()\"\r\n label=\"Choice\"\r\n placeholder=\"Scegli una choice o un choice set\"\r\n unknownMessage=\"Questo nome non e\u2019 una choice ne\u2019 un choice set: e\u2019 SCREEN_FIELD_CHOICE_UNKNOWN.\"\r\n unknownSeverity=\"error\"\r\n emptyMessage=\"Il flow non dichiara nessuna choice: creale nel pannello delle risorse.\"\r\n (valueChange)=\"addChoice($event)\"\r\n />\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Opzione selezionata all\u2019apertura</label>\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"selectedField()!.defaultSelectedChoiceReference || ''\"\r\n (change)=\"setFieldProperty('defaultSelectedChoiceReference', $any($event.target).value)\"\r\n >\r\n <option value=\"\">\u2014 nessuna \u2014</option>\r\n @for (choice of selectedField()!.choiceReferences || []; track $index) {\r\n <option [value]=\"choice\">{{ choice }}</option>\r\n }\r\n </select>\r\n @if (defaultChoiceIsForeign()) {\r\n <p class=\"fb-field__hint fb-field__hint--warn\">\r\n \u00AB{{ selectedField()!.defaultSelectedChoiceReference }}\u00BB non e\u2019 fra le opzioni elencate qui\r\n sopra.\r\n </p>\r\n }\r\n </div>\r\n </fieldset>\r\n }\r\n\r\n <!-- ------------------------------------------------------- contenitori -->\r\n @if (selectedType()?.isContainer) {\r\n <fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Contenitore</legend>\r\n @if (selectedField()!.fieldType === 'RegionContainer') {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Tipo di sezione</label>\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"selectedField()!.regionContainerType || ''\"\r\n (change)=\"setFieldProperty('regionContainerType', $any($event.target).value)\"\r\n >\r\n <option value=\"\">\u2014 predefinito \u2014</option>\r\n @for (entry of dictionary.regionContainerTypes(); track entry.value) {\r\n <option [value]=\"entry.value\">{{ entry.label }}</option>\r\n }\r\n </select>\r\n </div>\r\n }\r\n @if (!selectedField()!.fields?.length) {\r\n <p class=\"fb-callout fb-callout--warn\">\r\n Il contenitore e\u2019 vuoto: non produce niente sulla schermata (SCREEN_CONTAINER_EMPTY).\r\n </p>\r\n }\r\n </fieldset>\r\n }\r\n\r\n <!-- --------------------------------------------------- ComponentInstance -->\r\n @if (selectedField()!.fieldType === 'ComponentInstance') {\r\n <fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Componente</legend>\r\n <p class=\"fb-section__note\">\r\n Componenti e form sono la stessa domanda al frontend \u2014 cosa sa rendere, e con quali parametri \u2014\r\n e passano dallo stesso catalogo. Un <code>ComponentInstance</code> non ha un valore proprio: lo\r\n hanno i suoi output.\r\n </p>\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Componente</label>\r\n <fb-name-picker\r\n [value]=\"selectedField()!.extensionName\"\r\n [options]=\"componentOptions()\"\r\n label=\"Componente\"\r\n placeholder=\"Scrivi o scegli un componente\"\r\n [isMono]=\"false\"\r\n unknownMessage=\"Questo componente non esiste nel catalogo: e\u2019 SCREEN_COMPONENT_UNKNOWN.\"\r\n unknownSeverity=\"error\"\r\n emptyMessage=\"Il catalogo dei componenti non e\u2019 popolato: il nome non viene verificato.\"\r\n (valueChange)=\"setFieldProperty('extensionName', $event ?? '')\"\r\n />\r\n @if (!selectedField()!.extensionName) {\r\n <p class=\"fb-field__error\">Obbligatorio: senza, SCREEN_COMPONENT_MISSING.</p>\r\n }\r\n </div>\r\n\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"selectedField()!.storeOutputAutomatically === true\"\r\n (change)=\"setStoreOutputAutomatically($any($event.target).checked)\"\r\n />\r\n Rendi gli output referenziabili automaticamente\r\n </label>\r\n @if (selectedField()!.storeOutputAutomatically) {\r\n <p class=\"fb-field__hint\">\r\n Gli output si referenziano come\r\n <code>{{ selectedField()!.name || 'Campo' }}.nomeOutput</code>, senza dichiarare variabili.\r\n </p>\r\n }\r\n @if (hasOutputConflict()) {\r\n <p class=\"fb-callout fb-callout--error\">\r\n Output automatici <strong>e</strong> parametri di uscita insieme:\r\n OUTPUT_CONFIGURATION_CONFLICT.\r\n </p>\r\n }\r\n\r\n <fb-parameter-editor\r\n [holder]=\"$any(selectedField())\"\r\n [catalogParameters]=\"componentParameterList()\"\r\n inputTitle=\"Valori passati al componente\"\r\n outputTitle=\"Valori raccolti dal componente\"\r\n [showOutputs]=\"!selectedField()!.storeOutputAutomatically\"\r\n outputsDisabledReason=\"Gli output sono automatici: disattivalo per assegnarli a variabili.\"\r\n (changed)=\"onComponentParametersChanged($event)\"\r\n />\r\n </fieldset>\r\n }\r\n\r\n <!-- ------------------------------------------------------- visibilita' -->\r\n <fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Visibilita\u2019</legend>\r\n <p class=\"fb-section__note\">\r\n Le regole si rivalutano <strong>sui valori appena inviati</strong>: e\u2019 cos\u00EC che un campo compare in\r\n funzione di un altro campo della stessa schermata. Un campo risultato nascosto viene\r\n <strong>azzerato</strong> e non viene validato.\r\n </p>\r\n <fb-condition-editor\r\n [holder]=\"visibilityRule()\"\r\n title=\"Mostra il campo quando\"\r\n [allowFormula]=\"false\"\r\n [issuePath]=\"'fields[' + (selectedField()!.name || '') + '].visibilityRule'\"\r\n (changed)=\"onVisibilityChanged($event)\"\r\n />\r\n </fieldset>\r\n }\r\n </section>\r\n</div>\r\n\r\n<!-- --------------------------------------------------------------- schermata -->\r\n<fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Schermata</legend>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Testo di aiuto</label>\r\n <textarea\r\n class=\"fb-textarea\"\r\n [value]=\"screen().helpText || ''\"\r\n (input)=\"setScreenText('helpText', $any($event.target).value)\"\r\n ></textarea>\r\n </div>\r\n\r\n <p class=\"fb-section__note\">\r\n Questi flag sono un\u2019intenzione, non la verita\u2019 finale: a runtime il motore comunica\r\n <code>canGoBack</code>, <code>canFinish</code> e <code>canPause</code> nella richiesta della schermata.\r\n </p>\r\n <label class=\"fb-check\">\r\n <input type=\"checkbox\" [checked]=\"allowBack()\" (change)=\"setScreenFlag('allowBack', $any($event.target).checked)\" />\r\n Consenti \u00ABindietro\u00BB\r\n </label>\r\n <label class=\"fb-check\">\r\n <input type=\"checkbox\" [checked]=\"allowFinish()\" (change)=\"setScreenFlag('allowFinish', $any($event.target).checked)\" />\r\n Consenti \u00ABfine\u00BB\r\n </label>\r\n <label class=\"fb-check\">\r\n <input type=\"checkbox\" [checked]=\"allowPause()\" (change)=\"setScreenFlag('allowPause', $any($event.target).checked)\" />\r\n Consenti \u00ABpausa\u00BB\r\n </label>\r\n <label class=\"fb-check\">\r\n <input type=\"checkbox\" [checked]=\"showHeader()\" (change)=\"setScreenFlag('showHeader', $any($event.target).checked)\" />\r\n Mostra l\u2019intestazione\r\n </label>\r\n <label class=\"fb-check\">\r\n <input type=\"checkbox\" [checked]=\"showFooter()\" (change)=\"setScreenFlag('showFooter', $any($event.target).checked)\" />\r\n Mostra il piede\r\n </label>\r\n\r\n @if (allowPause()) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Testo mostrato alla pausa</label>\r\n <input\r\n class=\"fb-input\"\r\n [value]=\"screen().pausedText || ''\"\r\n (input)=\"setScreenText('pausedText', $any($event.target).value)\"\r\n />\r\n </div>\r\n }\r\n\r\n <div class=\"fb-dyn__grid\">\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Etichetta \u00ABindietro\u00BB</label>\r\n <input\r\n class=\"fb-input\"\r\n [value]=\"screen().backButtonLabel || ''\"\r\n (input)=\"setScreenText('backButtonLabel', $any($event.target).value)\"\r\n />\r\n </div>\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Etichetta \u00ABavanti / fine\u00BB</label>\r\n <input\r\n class=\"fb-input\"\r\n [value]=\"screen().nextOrFinishButtonLabel || ''\"\r\n (input)=\"setScreenText('nextOrFinishButtonLabel', $any($event.target).value)\"\r\n />\r\n </div>\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Etichetta \u00ABpausa\u00BB</label>\r\n <input\r\n class=\"fb-input\"\r\n [value]=\"screen().pauseButtonLabel || ''\"\r\n (input)=\"setScreenText('pauseButtonLabel', $any($event.target).value)\"\r\n />\r\n </div>\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Stage mostrato nell\u2019avanzamento</label>\r\n <fb-name-picker\r\n [value]=\"screen().stageReference\"\r\n [options]=\"stageOptions()\"\r\n label=\"Stage\"\r\n placeholder=\"Scegli uno stage\"\r\n unknownMessage=\"Questo stage non e\u2019 dichiarato dal flow.\"\r\n emptyMessage=\"Il flow non dichiara stage: creali nel pannello delle risorse.\"\r\n (valueChange)=\"setScreenText('stageReference', $event ?? '')\"\r\n />\r\n </div>\r\n\r\n @if (isDeadEnd()) {\r\n <p class=\"fb-callout fb-callout--error\">\r\n Questa schermata non ha una destinazione e non consente \u00ABfine\u00BB: e\u2019 un vicolo cieco, e l\u2019utente\r\n resterebbe bloccato (SCREEN_DEAD_END).\r\n </p>\r\n }\r\n</fieldset>\r\n\r\n<fb-connector-editor\r\n [nodeName]=\"name()\"\r\n [node]=\"node()\"\r\n [outlets]=\"outlets()\"\r\n (connectorChanged)=\"onConnectorChanged($event)\"\r\n/>\r\n", styles: [".fb-dyn{display:grid;grid-template-columns:minmax(240px,320px) minmax(0,1fr);gap:14px;align-items:start;margin-bottom:14px}@media(max-width:900px){.fb-dyn{grid-template-columns:minmax(0,1fr)}}.fb-dyn__pane{min-width:0;padding:10px;border:1px solid var(--fb-border, #e2e5eb);border-radius:var(--fb-radius, 10px);background:var(--fb-surface-alt, #f7f8fa)}.fb-dyn__pane--tree{position:sticky;top:0}.fb-dyn__pane--props{background:var(--fb-surface, #fff)}.fb-dyn__pane-head{display:flex;align-items:center;gap:8px;margin-bottom:8px}.fb-dyn__pane-title{flex:1;min-width:0;margin:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:12px;font-weight:700;text-transform:uppercase;letter-spacing:.04em;color:var(--fb-text-muted, #6b7086)}.fb-dyn__pane-actions,.fb-dyn__add{display:flex;flex-wrap:wrap;gap:4px}.fb-dyn__add .fb-select--sm{width:auto;max-width:150px}.fb-btn--sm{padding:3px 7px;font-size:11px}.fb-select--sm{padding:3px 6px;font-size:11px}.fb-dyn__hint{margin:0 0 8px;font-size:11px;color:var(--fb-text-muted, #6b7086)}.fb-dyn__tree{max-height:420px;overflow-y:auto;overscroll-behavior:contain}.fb-dyn__item{background:transparent}.fb-dyn__row{display:flex;align-items:center;gap:6px;padding:4px 6px;margin-bottom:3px;border:1px solid var(--fb-border, #e2e5eb);border-radius:var(--fb-radius-xs, 6px);background:var(--fb-surface, #fff);cursor:pointer}.fb-dyn__row:hover{border-color:var(--fb-border-strong, #cfd4de)}.fb-dyn__row--selected{border-color:var(--fb-accent, #4f6ef7);box-shadow:inset 0 0 0 1px var(--fb-accent, #4f6ef7)}.fb-dyn__row--container{background:var(--fb-surface-sunken, #eef0f4);font-weight:600}.fb-dyn__grip{color:var(--fb-text-subtle, #98a2b3);cursor:grab}.fb-dyn__caption{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:12px;color:var(--fb-text, #1a1c23)}.fb-dyn__type{font-size:10px;color:var(--fb-text-subtle, #98a2b3)}.fb-dyn__badge{font-size:11px;color:var(--fb-text-muted, #6b7086)}.fb-dyn__badge--required{color:var(--fb-error, #dc2626);font-weight:700}.fb-dyn__width{flex:none;width:34px;height:6px;border-radius:3px;background:var(--fb-surface-sunken, #eef0f4);overflow:hidden}.fb-dyn__width-fill{display:block;height:100%;background:var(--fb-accent, #4f6ef7)}.fb-dyn__grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:0 10px}.fb-dyn__width-editor{display:flex;align-items:center;gap:8px}.fb-dyn__width-editor input[type=range]{flex:1;min-width:0}.fb-dyn__width-value{font-size:11px;color:var(--fb-text-muted, #6b7086)}.fb-dyn__item.cdk-drag-placeholder{opacity:.4}.fb-dyn__tree.cdk-drop-list-dragging .fb-dyn__row{transition:transform .12s cubic-bezier(0,0,.2,1)}\n"], dependencies: [{ kind: "directive", type: CdkDrag, selector: "[cdkDrag]", inputs: ["cdkDragData", "cdkDragLockAxis", "cdkDragRootElement", "cdkDragBoundary", "cdkDragStartDelay", "cdkDragFreeDragPosition", "cdkDragDisabled", "cdkDragConstrainPosition", "cdkDragPreviewClass", "cdkDragPreviewContainer", "cdkDragScale"], outputs: ["cdkDragStarted", "cdkDragReleased", "cdkDragEnded", "cdkDragEntered", "cdkDragExited", "cdkDragDropped", "cdkDragMoved"], exportAs: ["cdkDrag"] }, { kind: "directive", type: CdkDragHandle, selector: "[cdkDragHandle]", inputs: ["cdkDragHandleDisabled"] }, { kind: "directive", type: CdkDropList, selector: "[cdkDropList], cdk-drop-list", inputs: ["cdkDropListConnectedTo", "cdkDropListData", "cdkDropListOrientation", "id", "cdkDropListLockAxis", "cdkDropListDisabled", "cdkDropListSortingDisabled", "cdkDropListEnterPredicate", "cdkDropListSortPredicate", "cdkDropListAutoScrollDisabled", "cdkDropListAutoScrollStep", "cdkDropListElementContainer", "cdkDropListHasAnchor"], outputs: ["cdkDropListDropped", "cdkDropListEntered", "cdkDropListExited", "cdkDropListSorted"], exportAs: ["cdkDropList"] }, { kind: "component", type: ConditionEditorComponent, selector: "fb-condition-editor", inputs: ["holder", "title", "allowFormula", "allowLogic", "issuePath"], outputs: ["changed"] }, { kind: "component", type: ConnectorEditorComponent, selector: "fb-connector-editor", inputs: ["nodeName", "node", "outlets", "title"], outputs: ["connectorChanged"] }, { kind: "component", type: FieldPickerComponent, selector: "fb-field-picker", inputs: ["value", "object", "usage", "label", "placeholder", "disabled"], outputs: ["valueChange"] }, { kind: "component", type: NamePickerComponent, selector: "fb-name-picker", inputs: ["value", "options", "label", "placeholder", "disabled", "unknownMessage", "unknownSeverity", "emptyMessage", "isMono"], outputs: ["valueChange"] }, { kind: "component", type: ObjectPickerComponent, selector: "fb-object-picker", inputs: ["value", "label", "placeholder", "disabled"], outputs: ["valueChange"] }, { kind: "component", type: ParameterEditorComponent, selector: "fb-parameter-editor", inputs: ["holder", "catalogParameters", "inputTitle", "outputTitle", "showInputs", "showOutputs", "outputsDisabledReason"], outputs: ["changed"] }, { kind: "directive", type: SelectValueDirective, selector: "select[fbValue]", inputs: ["fbValue"] }, { 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", "disabled", "allowFormula"], outputs: ["valueChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
8080
|
+
}
|
|
8081
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.27", ngImport: i0, type: DynamicScreenInspectorComponent, decorators: [{
|
|
8082
|
+
type: Component,
|
|
8083
|
+
args: [{ selector: 'fb-dynamic-screen-inspector', standalone: true, imports: [
|
|
8084
|
+
CdkDrag,
|
|
8085
|
+
CdkDragHandle,
|
|
8086
|
+
CdkDropList,
|
|
8087
|
+
ConditionEditorComponent,
|
|
8088
|
+
ConnectorEditorComponent,
|
|
8089
|
+
FieldPickerComponent,
|
|
8090
|
+
NamePickerComponent,
|
|
8091
|
+
ObjectPickerComponent,
|
|
8092
|
+
ParameterEditorComponent,
|
|
8093
|
+
SelectValueDirective,
|
|
8094
|
+
StructurePickerComponent,
|
|
8095
|
+
ValueEditorComponent,
|
|
8096
|
+
], changeDetection: ChangeDetectionStrategy.OnPush, template: "<p class=\"fb-callout\">\r\n Lo screen dinamico <strong>descrive i propri campi</strong>: la schermata la compone il flow, non un\r\n componente scritto a mano. Un campo che raccoglie un valore diventa una <strong>risorsa</strong> del flow,\r\n referenziabile per nome ovunque e di sola lettura.\r\n</p>\r\n\r\n<div class=\"fb-dyn\">\r\n <!-- ------------------------------------------------------------- albero -->\r\n <section class=\"fb-dyn__pane fb-dyn__pane--tree\" aria-label=\"Campi della schermata\">\r\n <header class=\"fb-dyn__pane-head\">\r\n <h3 class=\"fb-dyn__pane-title\">Campi</h3>\r\n <div class=\"fb-dyn__add\">\r\n <select class=\"fb-select fb-select--sm\" #newType [disabled]=\"!dictionary.screenFieldTypes().length\">\r\n @for (entry of dictionary.screenFieldTypes(); track entry.value) {\r\n <option [value]=\"entry.value\">{{ entry.label }}</option>\r\n }\r\n </select>\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-btn--primary fb-btn--sm\"\r\n [disabled]=\"!dictionary.screenFieldTypes().length\"\r\n (click)=\"addField(newType.value)\"\r\n >\r\n Aggiungi\r\n </button>\r\n </div>\r\n </header>\r\n\r\n @if (!dictionary.screenFieldTypes().length) {\r\n <p class=\"fb-callout fb-callout--warn\">\r\n Il dizionario <code>screenFieldTypes</code> non e\u2019 disponibile: senza i suoi flag l\u2019editor non sa\r\n quali campi ammette ciascun tipo, e non li inventa.\r\n </p>\r\n }\r\n\r\n <p class=\"fb-dyn__hint\">\r\n Trascina per riordinare e per spostare un campo dentro una sezione o una colonna. L\u2019annidamento\r\n previsto e\u2019 <strong>sezione \u2192 colonne \u2192 campi</strong>.\r\n </p>\r\n\r\n <!--\r\n **Una sola** lista di rilascio sull'albero appiattito, non una per contenitore.\r\n Le drop list annidate del CDK sono ambigue proprio qui: il rettangolo di una sezione\r\n contiene quello delle sue colonne, e chi riceve il rilascio e' la lista registrata per\r\n prima \u2014 cioe' sempre la radice. Con la lista piatta il contenitore di arrivo si deduce\r\n dalla riga che precede il punto di rilascio, che e' anche cio' che si vede.\r\n -->\r\n <div\r\n cdkDropList\r\n class=\"fb-dyn__tree\"\r\n [cdkDropListData]=\"tree()\"\r\n (cdkDropListDropped)=\"onDrop($event)\"\r\n >\r\n @for (item of tree(); track $index) {\r\n <div cdkDrag class=\"fb-dyn__item\" [style.margin-left.px]=\"item.depth * 16\">\r\n <div\r\n class=\"fb-dyn__row\"\r\n [class.fb-dyn__row--selected]=\"isSelected(item.path)\"\r\n [class.fb-dyn__row--container]=\"isContainer(item.field)\"\r\n role=\"button\"\r\n tabindex=\"0\"\r\n (click)=\"select(item.path)\"\r\n (keydown.enter)=\"select(item.path)\"\r\n (keydown.space)=\"select(item.path)\"\r\n >\r\n <span class=\"fb-dyn__grip\" cdkDragHandle aria-hidden=\"true\">\u283F</span>\r\n <span class=\"fb-dyn__caption\">{{ captionOf(item.field) }}</span>\r\n <span class=\"fb-dyn__type\">{{ typeLabelOf(item.field) }}</span>\r\n @if (item.field.isRequired) {\r\n <span class=\"fb-dyn__badge fb-dyn__badge--required\" title=\"Obbligatorio\">*</span>\r\n }\r\n @if (item.field.visibilityRule?.conditions?.length) {\r\n <span class=\"fb-dyn__badge\" title=\"Ha una regola di visibilita\u2019\">\u25D0</span>\r\n }\r\n @if (!isContainer(item.field)) {\r\n <!-- La larghezza in dodicesimi, disegnata: e' l'unica parte di layout che il\r\n documento porta, e leggerla come numero non dice niente. -->\r\n <span class=\"fb-dyn__width\" [title]=\"widthOf(item.field) + '/12'\">\r\n <span class=\"fb-dyn__width-fill\" [style.width.%]=\"(widthOf(item.field) / 12) * 100\"></span>\r\n </span>\r\n }\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n\r\n @if (isEmpty()) {\r\n <p class=\"fb-callout fb-callout--warn\">\r\n La schermata non ha campi: non c\u2019e\u2019 niente da mostrare all\u2019utente (SCREEN_WITHOUT_FIELDS).\r\n </p>\r\n }\r\n </section>\r\n\r\n <!-- --------------------------------------------------------- proprieta' -->\r\n <section class=\"fb-dyn__pane fb-dyn__pane--props\" aria-label=\"Proprieta\u2019 del campo\">\r\n @if (!selectedField()) {\r\n <p class=\"fb-empty\">Seleziona un campo a sinistra per configurarlo.</p>\r\n } @else {\r\n <header class=\"fb-dyn__pane-head\">\r\n <h3 class=\"fb-dyn__pane-title\">{{ captionOf(selectedField()!) }}</h3>\r\n <div class=\"fb-dyn__pane-actions\">\r\n <button type=\"button\" class=\"fb-btn fb-btn--sm\" title=\"Sposta su\" (click)=\"moveSelected(-1)\">\u2191</button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--sm\" title=\"Sposta giu\u2019\" (click)=\"moveSelected(1)\">\u2193</button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-btn--sm\"\r\n title=\"Porta dentro il contenitore precedente\"\r\n [disabled]=\"!indentTarget()\"\r\n (click)=\"indentSelected()\"\r\n >\r\n \u21E5\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-btn--sm\"\r\n title=\"Porta fuori dal contenitore\"\r\n [disabled]=\"(selectedPath()?.length || 0) < 2\"\r\n (click)=\"outdentSelected()\"\r\n >\r\n \u21E4\r\n </button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--sm\" (click)=\"duplicateSelected()\">Duplica</button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--danger fb-btn--sm\" (click)=\"removeSelected()\">Elimina</button>\r\n </div>\r\n </header>\r\n\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]=\"selectedField()!.fieldType || ''\"\r\n (change)=\"setFieldType($any($event.target).value)\"\r\n >\r\n <option value=\"\">\u2014 scegli \u2014</option>\r\n @for (entry of dictionary.screenFieldTypes(); track entry.value) {\r\n <option [value]=\"entry.value\">{{ entry.label }}</option>\r\n }\r\n </select>\r\n @if (!selectedField()!.fieldType) {\r\n <p class=\"fb-field__error\">Il tipo e\u2019 obbligatorio: senza, SCREEN_FIELD_TYPE_MISSING.</p>\r\n } @else if (isUnknownType(selectedField())) {\r\n <p class=\"fb-field__hint fb-field__hint--warn\">\r\n Questo tipo non e\u2019 nel dizionario: l\u2019editor non sa quali campi ammetta e mostra solo i comuni.\r\n </p>\r\n } @else if (selectedType()?.description) {\r\n <p class=\"fb-field__hint\">{{ selectedType()?.description }}</p>\r\n }\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Nome</label>\r\n <!-- Sul `change` e non sull\u2019`input`: la rinomina riscrive i riferimenti nel documento,\r\n e farlo a ogni tasto significherebbe riscriverlo per ogni lettera. -->\r\n <input\r\n class=\"fb-input fb-input--mono\"\r\n [value]=\"selectedField()!.name || ''\"\r\n (change)=\"setFieldName($any($event.target).value)\"\r\n />\r\n @if (selectedIsResource()) {\r\n <p class=\"fb-field__hint\">\r\n Questo campo e\u2019 una <strong>risorsa</strong>: lo referenzi come\r\n <code>{{ selectedField()!.name || 'Nome' }}</code> in condizioni, formule e parametri. \u00C8 di sola\r\n lettura per il flow \u2014 un Assignment che ci scrive e\u2019 TARGET_NOT_WRITABLE.\r\n </p>\r\n }\r\n </div>\r\n\r\n @if (!selectedType()?.isContainer && selectedField()!.fieldType !== 'ComponentInstance') {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">\r\n {{ selectedField()!.fieldType === 'DisplayText' ? 'Testo' : 'Etichetta' }}\r\n </label>\r\n <textarea\r\n class=\"fb-textarea\"\r\n [value]=\"selectedField()!.fieldText || ''\"\r\n (input)=\"setFieldProperty('fieldText', $any($event.target).value)\"\r\n ></textarea>\r\n <p class=\"fb-field__hint\">Supporta i merge field <code>{!Riferimento}</code>.</p>\r\n </div>\r\n }\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Testo di aiuto</label>\r\n <input\r\n class=\"fb-input\"\r\n [value]=\"selectedField()!.helpText || ''\"\r\n (input)=\"setFieldProperty('helpText', $any($event.target).value)\"\r\n />\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Larghezza</label>\r\n <div class=\"fb-dyn__width-editor\">\r\n <input\r\n type=\"range\"\r\n min=\"1\"\r\n max=\"12\"\r\n step=\"1\"\r\n [value]=\"widthOf(selectedField()!)\"\r\n (input)=\"setNumberProperty('width', $any($event.target).value)\"\r\n />\r\n <span class=\"fb-dyn__width-value\">{{ widthOf(selectedField()!) }}/12</span>\r\n </div>\r\n <p class=\"fb-field__hint\">\r\n Colonne della griglia della schermata. \u00C8 un\u2019indicazione: il frontend puo\u2019 ignorarla.\r\n </p>\r\n </div>\r\n\r\n <!-- ------------------------------------------------ campi che raccolgono un valore -->\r\n @if (selectedType()?.storesValue) {\r\n <fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Valore</legend>\r\n\r\n @if (selectedField()!.fieldType !== 'ObjectProvided') {\r\n <div class=\"fb-field\">\r\n <label\r\n class=\"fb-field__label\"\r\n [class.fb-field__label--required]=\"!!selectedType()?.requiresDataType\"\r\n >\r\n Tipo di dato\r\n </label>\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"selectedField()!.dataType || ''\"\r\n (change)=\"setFieldProperty('dataType', $any($event.target).value)\"\r\n >\r\n <option value=\"\">\u2014 scegli \u2014</option>\r\n @for (entry of dictionary.dataTypes(); track entry.value) {\r\n <option [value]=\"entry.value\">{{ entry.label }}</option>\r\n }\r\n </select>\r\n @if (selectedType()?.requiresDataType && !selectedField()!.dataType) {\r\n <p class=\"fb-field__error\">Obbligatorio per questo tipo di campo (DATA_TYPE_MISSING).</p>\r\n }\r\n @if (selectedType()?.isCollection) {\r\n <p class=\"fb-field__hint\">\r\n Il valore raccolto e\u2019 una <strong>collection</strong>, non una stringa con i valori\r\n separati: nelle condizioni si usano gli operatori di collection.\r\n </p>\r\n }\r\n </div>\r\n\r\n @if (requiresObjectType()) {\r\n <div class=\"fb-field\">\r\n <label\r\n class=\"fb-field__label\"\r\n [class.fb-field__label--required]=\"objectTypeIsStructure()\"\r\n >\r\n {{ objectTypeIsStructure() ? 'Classe' : selectedField()!.dataType === 'Enum' ? 'Tipo di enumerazione' : 'Oggetto' }}\r\n </label>\r\n @if (selectedField()!.dataType === 'Enum') {\r\n <!-- Dizionario chiuso: si sceglie, non si scrive. -->\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"selectedField()!.objectType || ''\"\r\n (change)=\"setFieldProperty('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 @if (!selectedField()!.objectType) {\r\n <p class=\"fb-field__hint fb-field__hint--warn\">\r\n Senza il tipo concreto l\u2019editor non puo\u2019 proporre i valori dell\u2019enumerazione.\r\n </p>\r\n }\r\n } @else if (objectTypeIsStructure()) {\r\n <fb-structure-picker\r\n [value]=\"selectedField()!.objectType\"\r\n label=\"Classe\"\r\n (valueChange)=\"setFieldProperty('objectType', $event ?? '')\"\r\n />\r\n @if (!selectedField()!.objectType) {\r\n <p class=\"fb-field__error\">\r\n La classe e\u2019 obbligatoria: senza, il runtime non ha nulla da istanziare\r\n (OBJECT_TYPE_MISSING).\r\n </p>\r\n }\r\n } @else {\r\n <fb-object-picker\r\n [value]=\"selectedField()!.objectType\"\r\n label=\"Oggetto\"\r\n (valueChange)=\"setFieldProperty('objectType', $event ?? '')\"\r\n />\r\n }\r\n </div>\r\n }\r\n } @else {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Campo dell\u2019entita\u2019</label>\r\n <p class=\"fb-field__hint\">\r\n Tipo, label, obbligatorieta\u2019, scala e \u2014 se il campo e\u2019 una picklist \u2014 le opzioni arrivano\r\n dallo schema dati: qui non si ridichiarano.\r\n </p>\r\n <fb-object-picker\r\n [value]=\"providedObject() || undefined\"\r\n label=\"Oggetto\"\r\n (valueChange)=\"setProvidedObject($event)\"\r\n />\r\n <fb-field-picker\r\n [value]=\"providedField() || undefined\"\r\n [object]=\"providedObject() || undefined\"\r\n label=\"Campo\"\r\n (valueChange)=\"setProvidedField($event)\"\r\n />\r\n </div>\r\n }\r\n\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"selectedIsRequired()\"\r\n (change)=\"setRequired($any($event.target).checked)\"\r\n />\r\n Obbligatorio\r\n </label>\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"selectedIsEditable()\"\r\n (change)=\"setEditable($any($event.target).checked)\"\r\n />\r\n Modificabile\r\n </label>\r\n @if (!selectedIsEditable()) {\r\n <p class=\"fb-field__hint\">\r\n A <code>false</code> il runtime <strong>ignora</strong> cio\u2019 che il client rimanda indietro:\r\n e\u2019 l\u2019unico modo di rendere un campo davvero di sola lettura.\r\n </p>\r\n }\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Valore di default</label>\r\n <fb-value-editor\r\n [value]=\"selectedField()!.defaultValue\"\r\n [dataType]=\"selectedField()!.dataType\"\r\n [objectType]=\"selectedField()!.objectType\"\r\n [isCollection]=\"selectedType()?.isCollection\"\r\n label=\"Valore di default\"\r\n (valueChange)=\"setDefaultValue($event)\"\r\n />\r\n </div>\r\n\r\n <div class=\"fb-dyn__grid\">\r\n @if (scaleApplies()) {\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]=\"selectedField()!.scale ?? ''\"\r\n (change)=\"setNumberProperty('scale', $any($event.target).value)\"\r\n />\r\n </div>\r\n } @else if (selectedField()!.scale !== undefined) {\r\n <p class=\"fb-field__hint fb-field__hint--warn\">\r\n <code>scale</code> su un campo non numerico e\u2019 SCALE_NOT_APPLICABLE.\r\n </p>\r\n }\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Lunghezza massima</label>\r\n <input\r\n class=\"fb-input\"\r\n type=\"number\"\r\n min=\"1\"\r\n [value]=\"selectedField()!.maxLength ?? ''\"\r\n (change)=\"setNumberProperty('maxLength', $any($event.target).value)\"\r\n />\r\n <p class=\"fb-field__hint\">Verificata anche dal runtime (TOO_LONG).</p>\r\n </div>\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Tornando sulla schermata</label>\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"selectedField()!.inputsOnNextNavToAssocScrn || ''\"\r\n (change)=\"setFieldProperty('inputsOnNextNavToAssocScrn', $any($event.target).value)\"\r\n >\r\n <option value=\"\">Predefinito (mantieni i valori)</option>\r\n @for (entry of dictionary.screenFieldInputsRevisited(); track entry.value) {\r\n <option [value]=\"entry.value\">{{ entry.label }}</option>\r\n }\r\n </select>\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Regola di validazione</label>\r\n <input\r\n class=\"fb-input fb-input--mono\"\r\n placeholder=\"Espressione, es. LEN(Nome) > 3\"\r\n [value]=\"selectedField()!.validationRule?.formulaExpression || ''\"\r\n (change)=\"setValidationRule('formulaExpression', $any($event.target).value)\"\r\n />\r\n <input\r\n class=\"fb-input\"\r\n placeholder=\"Messaggio mostrato quando l\u2019espressione e\u2019 falsa\"\r\n [value]=\"selectedField()!.validationRule?.errorMessage || ''\"\r\n (change)=\"setValidationRule('errorMessage', $any($event.target).value)\"\r\n />\r\n <p class=\"fb-field__hint\">\r\n L\u2019espressione la valuta il motore di regole: il backend non la interpreta e non la verifica.\r\n </p>\r\n </div>\r\n </fieldset>\r\n }\r\n\r\n <!-- --------------------------------------------------------------- choice -->\r\n @if (selectedType()?.acceptsChoices) {\r\n <fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Opzioni</legend>\r\n <p class=\"fb-section__note\">\r\n Accetta <strong>Choice</strong> e <strong>Dynamic choice set</strong>, nell\u2019ordine in cui le\r\n opzioni compaiono. Puntare a una variabile e\u2019 SCREEN_FIELD_CHOICE_UNKNOWN.\r\n </p>\r\n\r\n @if (!selectedField()!.choiceReferences?.length) {\r\n <p class=\"fb-callout fb-callout--warn\">\r\n Nessuna opzione: il campo non ha niente da mostrare (SCREEN_FIELD_CHOICES_MISSING).\r\n </p>\r\n }\r\n\r\n <div class=\"fb-list\">\r\n @for (choice of selectedField()!.choiceReferences || []; 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__title\">{{ choice }}</span>\r\n <span class=\"fb-list__spacer\"></span>\r\n <button type=\"button\" class=\"fb-btn fb-btn--sm\" (click)=\"moveChoice($index, -1)\">\u2191</button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--sm\" (click)=\"moveChoice($index, 1)\">\u2193</button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-btn--ghost fb-btn--sm\"\r\n aria-label=\"Rimuovi\"\r\n (click)=\"removeChoice($index)\"\r\n >\r\n \u00D7\r\n </button>\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Aggiungi un\u2019opzione</label>\r\n <fb-name-picker\r\n [options]=\"choiceOptions()\"\r\n label=\"Choice\"\r\n placeholder=\"Scegli una choice o un choice set\"\r\n unknownMessage=\"Questo nome non e\u2019 una choice ne\u2019 un choice set: e\u2019 SCREEN_FIELD_CHOICE_UNKNOWN.\"\r\n unknownSeverity=\"error\"\r\n emptyMessage=\"Il flow non dichiara nessuna choice: creale nel pannello delle risorse.\"\r\n (valueChange)=\"addChoice($event)\"\r\n />\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Opzione selezionata all\u2019apertura</label>\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"selectedField()!.defaultSelectedChoiceReference || ''\"\r\n (change)=\"setFieldProperty('defaultSelectedChoiceReference', $any($event.target).value)\"\r\n >\r\n <option value=\"\">\u2014 nessuna \u2014</option>\r\n @for (choice of selectedField()!.choiceReferences || []; track $index) {\r\n <option [value]=\"choice\">{{ choice }}</option>\r\n }\r\n </select>\r\n @if (defaultChoiceIsForeign()) {\r\n <p class=\"fb-field__hint fb-field__hint--warn\">\r\n \u00AB{{ selectedField()!.defaultSelectedChoiceReference }}\u00BB non e\u2019 fra le opzioni elencate qui\r\n sopra.\r\n </p>\r\n }\r\n </div>\r\n </fieldset>\r\n }\r\n\r\n <!-- ------------------------------------------------------- contenitori -->\r\n @if (selectedType()?.isContainer) {\r\n <fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Contenitore</legend>\r\n @if (selectedField()!.fieldType === 'RegionContainer') {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Tipo di sezione</label>\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"selectedField()!.regionContainerType || ''\"\r\n (change)=\"setFieldProperty('regionContainerType', $any($event.target).value)\"\r\n >\r\n <option value=\"\">\u2014 predefinito \u2014</option>\r\n @for (entry of dictionary.regionContainerTypes(); track entry.value) {\r\n <option [value]=\"entry.value\">{{ entry.label }}</option>\r\n }\r\n </select>\r\n </div>\r\n }\r\n @if (!selectedField()!.fields?.length) {\r\n <p class=\"fb-callout fb-callout--warn\">\r\n Il contenitore e\u2019 vuoto: non produce niente sulla schermata (SCREEN_CONTAINER_EMPTY).\r\n </p>\r\n }\r\n </fieldset>\r\n }\r\n\r\n <!-- --------------------------------------------------- ComponentInstance -->\r\n @if (selectedField()!.fieldType === 'ComponentInstance') {\r\n <fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Componente</legend>\r\n <p class=\"fb-section__note\">\r\n Componenti e form sono la stessa domanda al frontend \u2014 cosa sa rendere, e con quali parametri \u2014\r\n e passano dallo stesso catalogo. Un <code>ComponentInstance</code> non ha un valore proprio: lo\r\n hanno i suoi output.\r\n </p>\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label fb-field__label--required\">Componente</label>\r\n <fb-name-picker\r\n [value]=\"selectedField()!.extensionName\"\r\n [options]=\"componentOptions()\"\r\n label=\"Componente\"\r\n placeholder=\"Scrivi o scegli un componente\"\r\n [isMono]=\"false\"\r\n unknownMessage=\"Questo componente non esiste nel catalogo: e\u2019 SCREEN_COMPONENT_UNKNOWN.\"\r\n unknownSeverity=\"error\"\r\n emptyMessage=\"Il catalogo dei componenti non e\u2019 popolato: il nome non viene verificato.\"\r\n (valueChange)=\"setFieldProperty('extensionName', $event ?? '')\"\r\n />\r\n @if (!selectedField()!.extensionName) {\r\n <p class=\"fb-field__error\">Obbligatorio: senza, SCREEN_COMPONENT_MISSING.</p>\r\n }\r\n </div>\r\n\r\n <label class=\"fb-check\">\r\n <input\r\n type=\"checkbox\"\r\n [checked]=\"selectedField()!.storeOutputAutomatically === true\"\r\n (change)=\"setStoreOutputAutomatically($any($event.target).checked)\"\r\n />\r\n Rendi gli output referenziabili automaticamente\r\n </label>\r\n @if (selectedField()!.storeOutputAutomatically) {\r\n <p class=\"fb-field__hint\">\r\n Gli output si referenziano come\r\n <code>{{ selectedField()!.name || 'Campo' }}.nomeOutput</code>, senza dichiarare variabili.\r\n </p>\r\n }\r\n @if (hasOutputConflict()) {\r\n <p class=\"fb-callout fb-callout--error\">\r\n Output automatici <strong>e</strong> parametri di uscita insieme:\r\n OUTPUT_CONFIGURATION_CONFLICT.\r\n </p>\r\n }\r\n\r\n <fb-parameter-editor\r\n [holder]=\"$any(selectedField())\"\r\n [catalogParameters]=\"componentParameterList()\"\r\n inputTitle=\"Valori passati al componente\"\r\n outputTitle=\"Valori raccolti dal componente\"\r\n [showOutputs]=\"!selectedField()!.storeOutputAutomatically\"\r\n outputsDisabledReason=\"Gli output sono automatici: disattivalo per assegnarli a variabili.\"\r\n (changed)=\"onComponentParametersChanged($event)\"\r\n />\r\n </fieldset>\r\n }\r\n\r\n <!-- ------------------------------------------------------- visibilita' -->\r\n <fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Visibilita\u2019</legend>\r\n <p class=\"fb-section__note\">\r\n Le regole si rivalutano <strong>sui valori appena inviati</strong>: e\u2019 cos\u00EC che un campo compare in\r\n funzione di un altro campo della stessa schermata. Un campo risultato nascosto viene\r\n <strong>azzerato</strong> e non viene validato.\r\n </p>\r\n <fb-condition-editor\r\n [holder]=\"visibilityRule()\"\r\n title=\"Mostra il campo quando\"\r\n [allowFormula]=\"false\"\r\n [issuePath]=\"'fields[' + (selectedField()!.name || '') + '].visibilityRule'\"\r\n (changed)=\"onVisibilityChanged($event)\"\r\n />\r\n </fieldset>\r\n }\r\n </section>\r\n</div>\r\n\r\n<!-- --------------------------------------------------------------- schermata -->\r\n<fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Schermata</legend>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Testo di aiuto</label>\r\n <textarea\r\n class=\"fb-textarea\"\r\n [value]=\"screen().helpText || ''\"\r\n (input)=\"setScreenText('helpText', $any($event.target).value)\"\r\n ></textarea>\r\n </div>\r\n\r\n <p class=\"fb-section__note\">\r\n Questi flag sono un\u2019intenzione, non la verita\u2019 finale: a runtime il motore comunica\r\n <code>canGoBack</code>, <code>canFinish</code> e <code>canPause</code> nella richiesta della schermata.\r\n </p>\r\n <label class=\"fb-check\">\r\n <input type=\"checkbox\" [checked]=\"allowBack()\" (change)=\"setScreenFlag('allowBack', $any($event.target).checked)\" />\r\n Consenti \u00ABindietro\u00BB\r\n </label>\r\n <label class=\"fb-check\">\r\n <input type=\"checkbox\" [checked]=\"allowFinish()\" (change)=\"setScreenFlag('allowFinish', $any($event.target).checked)\" />\r\n Consenti \u00ABfine\u00BB\r\n </label>\r\n <label class=\"fb-check\">\r\n <input type=\"checkbox\" [checked]=\"allowPause()\" (change)=\"setScreenFlag('allowPause', $any($event.target).checked)\" />\r\n Consenti \u00ABpausa\u00BB\r\n </label>\r\n <label class=\"fb-check\">\r\n <input type=\"checkbox\" [checked]=\"showHeader()\" (change)=\"setScreenFlag('showHeader', $any($event.target).checked)\" />\r\n Mostra l\u2019intestazione\r\n </label>\r\n <label class=\"fb-check\">\r\n <input type=\"checkbox\" [checked]=\"showFooter()\" (change)=\"setScreenFlag('showFooter', $any($event.target).checked)\" />\r\n Mostra il piede\r\n </label>\r\n\r\n @if (allowPause()) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Testo mostrato alla pausa</label>\r\n <input\r\n class=\"fb-input\"\r\n [value]=\"screen().pausedText || ''\"\r\n (input)=\"setScreenText('pausedText', $any($event.target).value)\"\r\n />\r\n </div>\r\n }\r\n\r\n <div class=\"fb-dyn__grid\">\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Etichetta \u00ABindietro\u00BB</label>\r\n <input\r\n class=\"fb-input\"\r\n [value]=\"screen().backButtonLabel || ''\"\r\n (input)=\"setScreenText('backButtonLabel', $any($event.target).value)\"\r\n />\r\n </div>\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Etichetta \u00ABavanti / fine\u00BB</label>\r\n <input\r\n class=\"fb-input\"\r\n [value]=\"screen().nextOrFinishButtonLabel || ''\"\r\n (input)=\"setScreenText('nextOrFinishButtonLabel', $any($event.target).value)\"\r\n />\r\n </div>\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Etichetta \u00ABpausa\u00BB</label>\r\n <input\r\n class=\"fb-input\"\r\n [value]=\"screen().pauseButtonLabel || ''\"\r\n (input)=\"setScreenText('pauseButtonLabel', $any($event.target).value)\"\r\n />\r\n </div>\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Stage mostrato nell\u2019avanzamento</label>\r\n <fb-name-picker\r\n [value]=\"screen().stageReference\"\r\n [options]=\"stageOptions()\"\r\n label=\"Stage\"\r\n placeholder=\"Scegli uno stage\"\r\n unknownMessage=\"Questo stage non e\u2019 dichiarato dal flow.\"\r\n emptyMessage=\"Il flow non dichiara stage: creali nel pannello delle risorse.\"\r\n (valueChange)=\"setScreenText('stageReference', $event ?? '')\"\r\n />\r\n </div>\r\n\r\n @if (isDeadEnd()) {\r\n <p class=\"fb-callout fb-callout--error\">\r\n Questa schermata non ha una destinazione e non consente \u00ABfine\u00BB: e\u2019 un vicolo cieco, e l\u2019utente\r\n resterebbe bloccato (SCREEN_DEAD_END).\r\n </p>\r\n }\r\n</fieldset>\r\n\r\n<fb-connector-editor\r\n [nodeName]=\"name()\"\r\n [node]=\"node()\"\r\n [outlets]=\"outlets()\"\r\n (connectorChanged)=\"onConnectorChanged($event)\"\r\n/>\r\n", styles: [".fb-dyn{display:grid;grid-template-columns:minmax(240px,320px) minmax(0,1fr);gap:14px;align-items:start;margin-bottom:14px}@media(max-width:900px){.fb-dyn{grid-template-columns:minmax(0,1fr)}}.fb-dyn__pane{min-width:0;padding:10px;border:1px solid var(--fb-border, #e2e5eb);border-radius:var(--fb-radius, 10px);background:var(--fb-surface-alt, #f7f8fa)}.fb-dyn__pane--tree{position:sticky;top:0}.fb-dyn__pane--props{background:var(--fb-surface, #fff)}.fb-dyn__pane-head{display:flex;align-items:center;gap:8px;margin-bottom:8px}.fb-dyn__pane-title{flex:1;min-width:0;margin:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:12px;font-weight:700;text-transform:uppercase;letter-spacing:.04em;color:var(--fb-text-muted, #6b7086)}.fb-dyn__pane-actions,.fb-dyn__add{display:flex;flex-wrap:wrap;gap:4px}.fb-dyn__add .fb-select--sm{width:auto;max-width:150px}.fb-btn--sm{padding:3px 7px;font-size:11px}.fb-select--sm{padding:3px 6px;font-size:11px}.fb-dyn__hint{margin:0 0 8px;font-size:11px;color:var(--fb-text-muted, #6b7086)}.fb-dyn__tree{max-height:420px;overflow-y:auto;overscroll-behavior:contain}.fb-dyn__item{background:transparent}.fb-dyn__row{display:flex;align-items:center;gap:6px;padding:4px 6px;margin-bottom:3px;border:1px solid var(--fb-border, #e2e5eb);border-radius:var(--fb-radius-xs, 6px);background:var(--fb-surface, #fff);cursor:pointer}.fb-dyn__row:hover{border-color:var(--fb-border-strong, #cfd4de)}.fb-dyn__row--selected{border-color:var(--fb-accent, #4f6ef7);box-shadow:inset 0 0 0 1px var(--fb-accent, #4f6ef7)}.fb-dyn__row--container{background:var(--fb-surface-sunken, #eef0f4);font-weight:600}.fb-dyn__grip{color:var(--fb-text-subtle, #98a2b3);cursor:grab}.fb-dyn__caption{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:12px;color:var(--fb-text, #1a1c23)}.fb-dyn__type{font-size:10px;color:var(--fb-text-subtle, #98a2b3)}.fb-dyn__badge{font-size:11px;color:var(--fb-text-muted, #6b7086)}.fb-dyn__badge--required{color:var(--fb-error, #dc2626);font-weight:700}.fb-dyn__width{flex:none;width:34px;height:6px;border-radius:3px;background:var(--fb-surface-sunken, #eef0f4);overflow:hidden}.fb-dyn__width-fill{display:block;height:100%;background:var(--fb-accent, #4f6ef7)}.fb-dyn__grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:0 10px}.fb-dyn__width-editor{display:flex;align-items:center;gap:8px}.fb-dyn__width-editor input[type=range]{flex:1;min-width:0}.fb-dyn__width-value{font-size:11px;color:var(--fb-text-muted, #6b7086)}.fb-dyn__item.cdk-drag-placeholder{opacity:.4}.fb-dyn__tree.cdk-drop-list-dragging .fb-dyn__row{transition:transform .12s cubic-bezier(0,0,.2,1)}\n"] }]
|
|
8097
|
+
}], ctorParameters: () => [] });
|
|
8098
|
+
|
|
7242
8099
|
/**
|
|
7243
8100
|
* Loop — FRONTEND.md §5.4.
|
|
7244
8101
|
*
|
|
@@ -9057,7 +9914,7 @@ class ElementInspectorComponent {
|
|
|
9057
9914
|
return { x: node?.locationX ?? 0, y: node?.locationY ?? 0 };
|
|
9058
9915
|
}, ...(ngDevMode ? [{ debugName: "position" }] : []));
|
|
9059
9916
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.27", ngImport: i0, type: ElementInspectorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
9060
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.27", 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 } }, outputs: { closed: "closed", removeRequested: "removeRequested", duplicateRequested: "duplicateRequested" }, 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 issues(); 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 }\r\n\r\n @if (isUnsupported()) {\r\n <p class=\"fb-callout fb-callout--error\">\r\n Questo tipo di elemento non e\u2019 supportato dal motore: il flow che lo contiene non parte\r\n (ELEMENT_NOT_SUPPORTED). Non e\u2019 creabile dalla palette; se e\u2019 arrivato da un documento importato,\r\n va rimosso.\r\n </p>\r\n }\r\n\r\n @if (!isStart()) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Etichetta</label>\r\n <input\r\n class=\"fb-input\"\r\n [value]=\"$any(node()?.label) || ''\"\r\n placeholder=\"Nome mostrato sul canvas\"\r\n (input)=\"setLabel($any($event.target).value)\"\r\n />\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Nome tecnico</label>\r\n @if (pendingName() === null) {\r\n <div class=\"fb-field__row\">\r\n <input class=\"fb-input fb-input--mono\" [value]=\"selectedName() || ''\" readonly />\r\n <button type=\"button\" class=\"fb-btn\" (click)=\"startRename()\">Rinomina</button>\r\n </div>\r\n <p class=\"fb-field__hint\">\r\n \u00C8 l\u2019identificatore con cui i riferimenti raggiungono questo elemento.\r\n @if (referenceCount() > 1) {\r\n Compare {{ referenceCount() }} volte nel documento.\r\n }\r\n </p>\r\n } @else {\r\n <input\r\n class=\"fb-input fb-input--mono\"\r\n [class.fb-input--invalid]=\"!!nameError()\"\r\n [value]=\"pendingName() || ''\"\r\n (input)=\"onPendingNameInput($any($event.target).value)\"\r\n />\r\n @if (nameError()) {\r\n <p class=\"fb-field__error\">{{ nameError() }}</p>\r\n } @else {\r\n <p class=\"fb-field__hint fb-field__hint--warn\">\r\n La rinomina riscrive tutti i riferimenti che puntano a questo elemento\r\n ({{ referenceCount() }} occorrenze): nessuna primitiva del backend lo fa, lo fa l\u2019editor.\r\n </p>\r\n }\r\n <div class=\"fb-field__row\">\r\n <button type=\"button\" class=\"fb-btn fb-btn--primary\" [disabled]=\"!canRename()\" (click)=\"applyRename()\">\r\n Applica\r\n </button>\r\n <button type=\"button\" class=\"fb-btn\" (click)=\"suggestNameFromLabel()\">Genera dalla label</button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--ghost\" (click)=\"cancelRename()\">Annulla</button>\r\n </div>\r\n }\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Descrizione</label>\r\n <textarea\r\n class=\"fb-textarea\"\r\n [value]=\"$any(node()?.description) || ''\"\r\n (input)=\"setDescription($any($event.target).value)\"\r\n ></textarea>\r\n </div>\r\n }\r\n\r\n <p class=\"fb-inspector__position\">\r\n Posizione sul canvas: {{ position().x }}, {{ position().y }}\r\n </p>\r\n\r\n <hr class=\"fb-inspector__divider\" />\r\n\r\n @if (isStart()) {\r\n <fb-start-inspector />\r\n } @else if (node()) {\r\n @switch (type()) {\r\n @case ('Screen') {\r\n <fb-screen-inspector [name]=\"selectedName()!\" [node]=\"node()!\" />\r\n }\r\n @case ('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)}\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: 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 });
|
|
9917
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.27", 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 } }, outputs: { closed: "closed", removeRequested: "removeRequested", duplicateRequested: "duplicateRequested" }, 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 issues(); 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 }\r\n\r\n @if (isUnsupported()) {\r\n <p class=\"fb-callout fb-callout--error\">\r\n Questo tipo di elemento non e\u2019 supportato dal motore: il flow che lo contiene non parte\r\n (ELEMENT_NOT_SUPPORTED). Non e\u2019 creabile dalla palette; se e\u2019 arrivato da un documento importato,\r\n va rimosso.\r\n </p>\r\n }\r\n\r\n @if (!isStart()) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Etichetta</label>\r\n <input\r\n class=\"fb-input\"\r\n [value]=\"$any(node()?.label) || ''\"\r\n placeholder=\"Nome mostrato sul canvas\"\r\n (input)=\"setLabel($any($event.target).value)\"\r\n />\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Nome tecnico</label>\r\n @if (pendingName() === null) {\r\n <div class=\"fb-field__row\">\r\n <input class=\"fb-input fb-input--mono\" [value]=\"selectedName() || ''\" readonly />\r\n <button type=\"button\" class=\"fb-btn\" (click)=\"startRename()\">Rinomina</button>\r\n </div>\r\n <p class=\"fb-field__hint\">\r\n \u00C8 l\u2019identificatore con cui i riferimenti raggiungono questo elemento.\r\n @if (referenceCount() > 1) {\r\n Compare {{ referenceCount() }} volte nel documento.\r\n }\r\n </p>\r\n } @else {\r\n <input\r\n class=\"fb-input fb-input--mono\"\r\n [class.fb-input--invalid]=\"!!nameError()\"\r\n [value]=\"pendingName() || ''\"\r\n (input)=\"onPendingNameInput($any($event.target).value)\"\r\n />\r\n @if (nameError()) {\r\n <p class=\"fb-field__error\">{{ nameError() }}</p>\r\n } @else {\r\n <p class=\"fb-field__hint fb-field__hint--warn\">\r\n La rinomina riscrive tutti i riferimenti che puntano a questo elemento\r\n ({{ referenceCount() }} occorrenze): nessuna primitiva del backend lo fa, lo fa l\u2019editor.\r\n </p>\r\n }\r\n <div class=\"fb-field__row\">\r\n <button type=\"button\" class=\"fb-btn fb-btn--primary\" [disabled]=\"!canRename()\" (click)=\"applyRename()\">\r\n Applica\r\n </button>\r\n <button type=\"button\" class=\"fb-btn\" (click)=\"suggestNameFromLabel()\">Genera dalla label</button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--ghost\" (click)=\"cancelRename()\">Annulla</button>\r\n </div>\r\n }\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Descrizione</label>\r\n <textarea\r\n class=\"fb-textarea\"\r\n [value]=\"$any(node()?.description) || ''\"\r\n (input)=\"setDescription($any($event.target).value)\"\r\n ></textarea>\r\n </div>\r\n }\r\n\r\n <p class=\"fb-inspector__position\">\r\n Posizione sul canvas: {{ position().x }}, {{ position().y }}\r\n </p>\r\n\r\n <hr class=\"fb-inspector__divider\" />\r\n\r\n @if (isStart()) {\r\n <fb-start-inspector />\r\n } @else if (node()) {\r\n @switch (type()) {\r\n @case ('Screen') {\r\n <fb-screen-inspector [name]=\"selectedName()!\" [node]=\"node()!\" />\r\n }\r\n @case ('DynamicScreen') {\r\n <fb-dynamic-screen-inspector [name]=\"selectedName()!\" [node]=\"node()!\" />\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)}\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" }, { 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 });
|
|
9061
9918
|
}
|
|
9062
9919
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.27", ngImport: i0, type: ElementInspectorComponent, decorators: [{
|
|
9063
9920
|
type: Component,
|
|
@@ -9067,6 +9924,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.27", ngImpo
|
|
|
9067
9924
|
CollectionProcessorInspectorComponent,
|
|
9068
9925
|
CustomErrorInspectorComponent,
|
|
9069
9926
|
DecisionInspectorComponent,
|
|
9927
|
+
DynamicScreenInspectorComponent,
|
|
9070
9928
|
LoopInspectorComponent,
|
|
9071
9929
|
OrchestratedStageInspectorComponent,
|
|
9072
9930
|
RecordLookupInspectorComponent,
|
|
@@ -9078,7 +9936,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.27", ngImpo
|
|
|
9078
9936
|
SubflowInspectorComponent,
|
|
9079
9937
|
TransformInspectorComponent,
|
|
9080
9938
|
WaitInspectorComponent,
|
|
9081
|
-
], 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 issues(); 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 }\r\n\r\n @if (isUnsupported()) {\r\n <p class=\"fb-callout fb-callout--error\">\r\n Questo tipo di elemento non e\u2019 supportato dal motore: il flow che lo contiene non parte\r\n (ELEMENT_NOT_SUPPORTED). Non e\u2019 creabile dalla palette; se e\u2019 arrivato da un documento importato,\r\n va rimosso.\r\n </p>\r\n }\r\n\r\n @if (!isStart()) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Etichetta</label>\r\n <input\r\n class=\"fb-input\"\r\n [value]=\"$any(node()?.label) || ''\"\r\n placeholder=\"Nome mostrato sul canvas\"\r\n (input)=\"setLabel($any($event.target).value)\"\r\n />\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Nome tecnico</label>\r\n @if (pendingName() === null) {\r\n <div class=\"fb-field__row\">\r\n <input class=\"fb-input fb-input--mono\" [value]=\"selectedName() || ''\" readonly />\r\n <button type=\"button\" class=\"fb-btn\" (click)=\"startRename()\">Rinomina</button>\r\n </div>\r\n <p class=\"fb-field__hint\">\r\n \u00C8 l\u2019identificatore con cui i riferimenti raggiungono questo elemento.\r\n @if (referenceCount() > 1) {\r\n Compare {{ referenceCount() }} volte nel documento.\r\n }\r\n </p>\r\n } @else {\r\n <input\r\n class=\"fb-input fb-input--mono\"\r\n [class.fb-input--invalid]=\"!!nameError()\"\r\n [value]=\"pendingName() || ''\"\r\n (input)=\"onPendingNameInput($any($event.target).value)\"\r\n />\r\n @if (nameError()) {\r\n <p class=\"fb-field__error\">{{ nameError() }}</p>\r\n } @else {\r\n <p class=\"fb-field__hint fb-field__hint--warn\">\r\n La rinomina riscrive tutti i riferimenti che puntano a questo elemento\r\n ({{ referenceCount() }} occorrenze): nessuna primitiva del backend lo fa, lo fa l\u2019editor.\r\n </p>\r\n }\r\n <div class=\"fb-field__row\">\r\n <button type=\"button\" class=\"fb-btn fb-btn--primary\" [disabled]=\"!canRename()\" (click)=\"applyRename()\">\r\n Applica\r\n </button>\r\n <button type=\"button\" class=\"fb-btn\" (click)=\"suggestNameFromLabel()\">Genera dalla label</button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--ghost\" (click)=\"cancelRename()\">Annulla</button>\r\n </div>\r\n }\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Descrizione</label>\r\n <textarea\r\n class=\"fb-textarea\"\r\n [value]=\"$any(node()?.description) || ''\"\r\n (input)=\"setDescription($any($event.target).value)\"\r\n ></textarea>\r\n </div>\r\n }\r\n\r\n <p class=\"fb-inspector__position\">\r\n Posizione sul canvas: {{ position().x }}, {{ position().y }}\r\n </p>\r\n\r\n <hr class=\"fb-inspector__divider\" />\r\n\r\n @if (isStart()) {\r\n <fb-start-inspector />\r\n } @else if (node()) {\r\n @switch (type()) {\r\n @case ('Screen') {\r\n <fb-screen-inspector [name]=\"selectedName()!\" [node]=\"node()!\" />\r\n }\r\n @case ('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)}\n"] }]
|
|
9939
|
+
], 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 issues(); 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 }\r\n\r\n @if (isUnsupported()) {\r\n <p class=\"fb-callout fb-callout--error\">\r\n Questo tipo di elemento non e\u2019 supportato dal motore: il flow che lo contiene non parte\r\n (ELEMENT_NOT_SUPPORTED). Non e\u2019 creabile dalla palette; se e\u2019 arrivato da un documento importato,\r\n va rimosso.\r\n </p>\r\n }\r\n\r\n @if (!isStart()) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Etichetta</label>\r\n <input\r\n class=\"fb-input\"\r\n [value]=\"$any(node()?.label) || ''\"\r\n placeholder=\"Nome mostrato sul canvas\"\r\n (input)=\"setLabel($any($event.target).value)\"\r\n />\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Nome tecnico</label>\r\n @if (pendingName() === null) {\r\n <div class=\"fb-field__row\">\r\n <input class=\"fb-input fb-input--mono\" [value]=\"selectedName() || ''\" readonly />\r\n <button type=\"button\" class=\"fb-btn\" (click)=\"startRename()\">Rinomina</button>\r\n </div>\r\n <p class=\"fb-field__hint\">\r\n \u00C8 l\u2019identificatore con cui i riferimenti raggiungono questo elemento.\r\n @if (referenceCount() > 1) {\r\n Compare {{ referenceCount() }} volte nel documento.\r\n }\r\n </p>\r\n } @else {\r\n <input\r\n class=\"fb-input fb-input--mono\"\r\n [class.fb-input--invalid]=\"!!nameError()\"\r\n [value]=\"pendingName() || ''\"\r\n (input)=\"onPendingNameInput($any($event.target).value)\"\r\n />\r\n @if (nameError()) {\r\n <p class=\"fb-field__error\">{{ nameError() }}</p>\r\n } @else {\r\n <p class=\"fb-field__hint fb-field__hint--warn\">\r\n La rinomina riscrive tutti i riferimenti che puntano a questo elemento\r\n ({{ referenceCount() }} occorrenze): nessuna primitiva del backend lo fa, lo fa l\u2019editor.\r\n </p>\r\n }\r\n <div class=\"fb-field__row\">\r\n <button type=\"button\" class=\"fb-btn fb-btn--primary\" [disabled]=\"!canRename()\" (click)=\"applyRename()\">\r\n Applica\r\n </button>\r\n <button type=\"button\" class=\"fb-btn\" (click)=\"suggestNameFromLabel()\">Genera dalla label</button>\r\n <button type=\"button\" class=\"fb-btn fb-btn--ghost\" (click)=\"cancelRename()\">Annulla</button>\r\n </div>\r\n }\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Descrizione</label>\r\n <textarea\r\n class=\"fb-textarea\"\r\n [value]=\"$any(node()?.description) || ''\"\r\n (input)=\"setDescription($any($event.target).value)\"\r\n ></textarea>\r\n </div>\r\n }\r\n\r\n <p class=\"fb-inspector__position\">\r\n Posizione sul canvas: {{ position().x }}, {{ position().y }}\r\n </p>\r\n\r\n <hr class=\"fb-inspector__divider\" />\r\n\r\n @if (isStart()) {\r\n <fb-start-inspector />\r\n } @else if (node()) {\r\n @switch (type()) {\r\n @case ('Screen') {\r\n <fb-screen-inspector [name]=\"selectedName()!\" [node]=\"node()!\" />\r\n }\r\n @case ('DynamicScreen') {\r\n <fb-dynamic-screen-inspector [name]=\"selectedName()!\" [node]=\"node()!\" />\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)}\n"] }]
|
|
9082
9940
|
}], propDecorators: { selectedName: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectedName", required: false }] }], showHeader: [{ type: i0.Input, args: [{ isSignal: true, alias: "showHeader", required: false }] }], closed: [{ type: i0.Output, args: ["closed"] }], removeRequested: [{ type: i0.Output, args: ["removeRequested"] }], duplicateRequested: [{ type: i0.Output, args: ["duplicateRequested"] }] } });
|
|
9083
9941
|
|
|
9084
9942
|
/**
|
|
@@ -9142,11 +10000,11 @@ class ElementDialogComponent {
|
|
|
9142
10000
|
this.duplicateRequested.emit(name);
|
|
9143
10001
|
}
|
|
9144
10002
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.27", ngImport: i0, type: ElementDialogComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
9145
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "20.3.27", type: ElementDialogComponent, isStandalone: true, selector: "fb-element-dialog", inputs: { selectedName: { classPropertyName: "selectedName", publicName: "selectedName", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { closed: "closed", removeRequested: "removeRequested", duplicateRequested: "duplicateRequested" }, viewQueries: [{ propertyName: "panel", first: true, predicate: ["panel"], descendants: true, isSignal: true }], ngImport: i0, template: "<!--\r\n Il backdrop chiude: e' il gesto che tutti si aspettano. Il pannello ferma la propagazione\r\n del click, altrimenti configurare un campo chiuderebbe la dialog.\r\n-->\r\n<div class=\"fb-dialog__backdrop\" (click)=\"close()\"></div>\r\n\r\n<div\r\n #panel\r\n class=\"fb-dialog__panel\"\r\n role=\"dialog\"\r\n aria-modal=\"true\"\r\n [attr.aria-label]=\"typeLabel() + ': ' + title()\"\r\n tabindex=\"-1\"\r\n (click)=\"$event.stopPropagation()\"\r\n (keydown.escape)=\"close()\"\r\n>\r\n <header class=\"fb-dialog__head\">\r\n <div class=\"fb-dialog__identity\">\r\n <span class=\"fb-dialog__type\">{{ typeLabel() }}</span>\r\n <h2 class=\"fb-dialog__title\">{{ title() }}</h2>\r\n </div>\r\n <button type=\"button\" class=\"fb-btn fb-btn--ghost fb-btn--icon\" aria-label=\"Chiudi\" (click)=\"close()\">\u00D7</button>\r\n </header>\r\n\r\n <div class=\"fb-dialog__body\">\r\n <!-- L'intestazione dell'inspector qui e' quella della dialog: non si ripete. -->\r\n <fb-element-inspector\r\n [selectedName]=\"selectedName()\"\r\n [showHeader]=\"false\"\r\n (removeRequested)=\"onRemoveRequested($event)\"\r\n (duplicateRequested)=\"onDuplicateRequested($event)\"\r\n (closed)=\"close()\"\r\n />\r\n </div>\r\n\r\n <footer class=\"fb-dialog__foot\">\r\n <span class=\"fb-dialog__note\">\r\n Le modifiche sono gi\u00E0 nel documento: per tornare indietro c\u2019\u00E8 l\u2019annulla dell\u2019editor.\r\n </span>\r\n <button type=\"button\" class=\"fb-btn fb-btn--primary\" (click)=\"close()\">Fatto</button>\r\n </footer>\r\n</div>\r\n", styles: [":host{position:absolute;inset:0;z-index:30;display:grid;place-items:center;padding:24px}.fb-dialog__backdrop{position:absolute;inset:0;background:#10182852;-webkit-backdrop-filter:blur(1px);backdrop-filter:blur(1px)}.fb-dialog__panel{position:relative;display:flex;flex-direction:column;width:min(
|
|
10003
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "20.3.27", type: ElementDialogComponent, isStandalone: true, selector: "fb-element-dialog", inputs: { selectedName: { classPropertyName: "selectedName", publicName: "selectedName", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { closed: "closed", removeRequested: "removeRequested", duplicateRequested: "duplicateRequested" }, viewQueries: [{ propertyName: "panel", first: true, predicate: ["panel"], descendants: true, isSignal: true }], ngImport: i0, template: "<!--\r\n Il backdrop chiude: e' il gesto che tutti si aspettano. Il pannello ferma la propagazione\r\n del click, altrimenti configurare un campo chiuderebbe la dialog.\r\n-->\r\n<div class=\"fb-dialog__backdrop\" (click)=\"close()\"></div>\r\n\r\n<div\r\n #panel\r\n class=\"fb-dialog__panel\"\r\n role=\"dialog\"\r\n aria-modal=\"true\"\r\n [attr.aria-label]=\"typeLabel() + ': ' + title()\"\r\n tabindex=\"-1\"\r\n (click)=\"$event.stopPropagation()\"\r\n (keydown.escape)=\"close()\"\r\n>\r\n <header class=\"fb-dialog__head\">\r\n <div class=\"fb-dialog__identity\">\r\n <span class=\"fb-dialog__type\">{{ typeLabel() }}</span>\r\n <h2 class=\"fb-dialog__title\">{{ title() }}</h2>\r\n </div>\r\n <button type=\"button\" class=\"fb-btn fb-btn--ghost fb-btn--icon\" aria-label=\"Chiudi\" (click)=\"close()\">\u00D7</button>\r\n </header>\r\n\r\n <div class=\"fb-dialog__body\">\r\n <!-- L'intestazione dell'inspector qui e' quella della dialog: non si ripete. -->\r\n <fb-element-inspector\r\n [selectedName]=\"selectedName()\"\r\n [showHeader]=\"false\"\r\n (removeRequested)=\"onRemoveRequested($event)\"\r\n (duplicateRequested)=\"onDuplicateRequested($event)\"\r\n (closed)=\"close()\"\r\n />\r\n </div>\r\n\r\n <footer class=\"fb-dialog__foot\">\r\n <span class=\"fb-dialog__note\">\r\n Le modifiche sono gi\u00E0 nel documento: per tornare indietro c\u2019\u00E8 l\u2019annulla dell\u2019editor.\r\n </span>\r\n <button type=\"button\" class=\"fb-btn fb-btn--primary\" (click)=\"close()\">Fatto</button>\r\n </footer>\r\n</div>\r\n", styles: [":host{position:absolute;inset:0;z-index:30;display:grid;place-items:center;padding:24px}.fb-dialog__backdrop{position:absolute;inset:0;background:#10182852;-webkit-backdrop-filter:blur(1px);backdrop-filter:blur(1px)}.fb-dialog__panel{position:relative;display:flex;flex-direction:column;width:min(var(--fb-dialog-width, 1400px),100%);height:min(var(--fb-dialog-height, 860px),100%);border:1px solid var(--fb-border, #e2e5eb);border-radius:var(--fb-radius-lg, 12px);background:var(--fb-surface, #fff);box-shadow:var(--fb-shadow-lg, 0 18px 44px rgb(16 24 40 / 18%));outline:none;overflow:hidden}.fb-dialog__head{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:12px 14px;border-bottom:1px solid var(--fb-border-subtle, #eef0f4)}.fb-dialog__identity{min-width:0}.fb-dialog__type{display:block;font-size:10px;font-weight:700;letter-spacing:.06em;text-transform:uppercase;color:var(--fb-text-subtle, #98a2b3)}.fb-dialog__title{margin:1px 0 0;font-size:15px;font-weight:600;color:var(--fb-text, #1a1c23);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fb-dialog__body{flex:1;min-height:0;overflow-y:auto;overscroll-behavior:contain}.fb-dialog__foot{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:10px 14px;border-top:1px solid var(--fb-border-subtle, #eef0f4);background:var(--fb-surface-alt, #f7f8fa)}.fb-dialog__note{font-size:11px;color:var(--fb-text-muted, #6b7086)}@media(max-height:620px){:host{padding:10px}}\n"], dependencies: [{ kind: "component", type: ElementInspectorComponent, selector: "fb-element-inspector", inputs: ["selectedName", "showHeader"], outputs: ["closed", "removeRequested", "duplicateRequested"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
9146
10004
|
}
|
|
9147
10005
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.27", ngImport: i0, type: ElementDialogComponent, decorators: [{
|
|
9148
10006
|
type: Component,
|
|
9149
|
-
args: [{ selector: 'fb-element-dialog', standalone: true, imports: [ElementInspectorComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<!--\r\n Il backdrop chiude: e' il gesto che tutti si aspettano. Il pannello ferma la propagazione\r\n del click, altrimenti configurare un campo chiuderebbe la dialog.\r\n-->\r\n<div class=\"fb-dialog__backdrop\" (click)=\"close()\"></div>\r\n\r\n<div\r\n #panel\r\n class=\"fb-dialog__panel\"\r\n role=\"dialog\"\r\n aria-modal=\"true\"\r\n [attr.aria-label]=\"typeLabel() + ': ' + title()\"\r\n tabindex=\"-1\"\r\n (click)=\"$event.stopPropagation()\"\r\n (keydown.escape)=\"close()\"\r\n>\r\n <header class=\"fb-dialog__head\">\r\n <div class=\"fb-dialog__identity\">\r\n <span class=\"fb-dialog__type\">{{ typeLabel() }}</span>\r\n <h2 class=\"fb-dialog__title\">{{ title() }}</h2>\r\n </div>\r\n <button type=\"button\" class=\"fb-btn fb-btn--ghost fb-btn--icon\" aria-label=\"Chiudi\" (click)=\"close()\">\u00D7</button>\r\n </header>\r\n\r\n <div class=\"fb-dialog__body\">\r\n <!-- L'intestazione dell'inspector qui e' quella della dialog: non si ripete. -->\r\n <fb-element-inspector\r\n [selectedName]=\"selectedName()\"\r\n [showHeader]=\"false\"\r\n (removeRequested)=\"onRemoveRequested($event)\"\r\n (duplicateRequested)=\"onDuplicateRequested($event)\"\r\n (closed)=\"close()\"\r\n />\r\n </div>\r\n\r\n <footer class=\"fb-dialog__foot\">\r\n <span class=\"fb-dialog__note\">\r\n Le modifiche sono gi\u00E0 nel documento: per tornare indietro c\u2019\u00E8 l\u2019annulla dell\u2019editor.\r\n </span>\r\n <button type=\"button\" class=\"fb-btn fb-btn--primary\" (click)=\"close()\">Fatto</button>\r\n </footer>\r\n</div>\r\n", styles: [":host{position:absolute;inset:0;z-index:30;display:grid;place-items:center;padding:24px}.fb-dialog__backdrop{position:absolute;inset:0;background:#10182852;-webkit-backdrop-filter:blur(1px);backdrop-filter:blur(1px)}.fb-dialog__panel{position:relative;display:flex;flex-direction:column;width:min(
|
|
10007
|
+
args: [{ selector: 'fb-element-dialog', standalone: true, imports: [ElementInspectorComponent], changeDetection: ChangeDetectionStrategy.OnPush, template: "<!--\r\n Il backdrop chiude: e' il gesto che tutti si aspettano. Il pannello ferma la propagazione\r\n del click, altrimenti configurare un campo chiuderebbe la dialog.\r\n-->\r\n<div class=\"fb-dialog__backdrop\" (click)=\"close()\"></div>\r\n\r\n<div\r\n #panel\r\n class=\"fb-dialog__panel\"\r\n role=\"dialog\"\r\n aria-modal=\"true\"\r\n [attr.aria-label]=\"typeLabel() + ': ' + title()\"\r\n tabindex=\"-1\"\r\n (click)=\"$event.stopPropagation()\"\r\n (keydown.escape)=\"close()\"\r\n>\r\n <header class=\"fb-dialog__head\">\r\n <div class=\"fb-dialog__identity\">\r\n <span class=\"fb-dialog__type\">{{ typeLabel() }}</span>\r\n <h2 class=\"fb-dialog__title\">{{ title() }}</h2>\r\n </div>\r\n <button type=\"button\" class=\"fb-btn fb-btn--ghost fb-btn--icon\" aria-label=\"Chiudi\" (click)=\"close()\">\u00D7</button>\r\n </header>\r\n\r\n <div class=\"fb-dialog__body\">\r\n <!-- L'intestazione dell'inspector qui e' quella della dialog: non si ripete. -->\r\n <fb-element-inspector\r\n [selectedName]=\"selectedName()\"\r\n [showHeader]=\"false\"\r\n (removeRequested)=\"onRemoveRequested($event)\"\r\n (duplicateRequested)=\"onDuplicateRequested($event)\"\r\n (closed)=\"close()\"\r\n />\r\n </div>\r\n\r\n <footer class=\"fb-dialog__foot\">\r\n <span class=\"fb-dialog__note\">\r\n Le modifiche sono gi\u00E0 nel documento: per tornare indietro c\u2019\u00E8 l\u2019annulla dell\u2019editor.\r\n </span>\r\n <button type=\"button\" class=\"fb-btn fb-btn--primary\" (click)=\"close()\">Fatto</button>\r\n </footer>\r\n</div>\r\n", styles: [":host{position:absolute;inset:0;z-index:30;display:grid;place-items:center;padding:24px}.fb-dialog__backdrop{position:absolute;inset:0;background:#10182852;-webkit-backdrop-filter:blur(1px);backdrop-filter:blur(1px)}.fb-dialog__panel{position:relative;display:flex;flex-direction:column;width:min(var(--fb-dialog-width, 1400px),100%);height:min(var(--fb-dialog-height, 860px),100%);border:1px solid var(--fb-border, #e2e5eb);border-radius:var(--fb-radius-lg, 12px);background:var(--fb-surface, #fff);box-shadow:var(--fb-shadow-lg, 0 18px 44px rgb(16 24 40 / 18%));outline:none;overflow:hidden}.fb-dialog__head{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:12px 14px;border-bottom:1px solid var(--fb-border-subtle, #eef0f4)}.fb-dialog__identity{min-width:0}.fb-dialog__type{display:block;font-size:10px;font-weight:700;letter-spacing:.06em;text-transform:uppercase;color:var(--fb-text-subtle, #98a2b3)}.fb-dialog__title{margin:1px 0 0;font-size:15px;font-weight:600;color:var(--fb-text, #1a1c23);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fb-dialog__body{flex:1;min-height:0;overflow-y:auto;overscroll-behavior:contain}.fb-dialog__foot{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:10px 14px;border-top:1px solid var(--fb-border-subtle, #eef0f4);background:var(--fb-surface-alt, #f7f8fa)}.fb-dialog__note{font-size:11px;color:var(--fb-text-muted, #6b7086)}@media(max-height:620px){:host{padding:10px}}\n"] }]
|
|
9150
10008
|
}], ctorParameters: () => [], propDecorators: { selectedName: [{ type: i0.Input, args: [{ isSignal: true, alias: "selectedName", required: false }] }], closed: [{ type: i0.Output, args: ["closed"] }], removeRequested: [{ type: i0.Output, args: ["removeRequested"] }], duplicateRequested: [{ type: i0.Output, args: ["duplicateRequested"] }], panel: [{ type: i0.ViewChild, args: ['panel', { isSignal: true }] }] } });
|
|
9151
10009
|
|
|
9152
10010
|
/**
|
|
@@ -10265,11 +11123,19 @@ class FlowBuilderComponent {
|
|
|
10265
11123
|
const current = this.store.document().processType;
|
|
10266
11124
|
return !!current && !this.processTypes().some((type) => type.value === current);
|
|
10267
11125
|
}, ...(ngDevMode ? [{ debugName: "isProcessTypeOutOfCatalog" }] : []));
|
|
11126
|
+
/**
|
|
11127
|
+
* Quante schermate ha il flow. **Entrambi** i tipi di screen contano: quello a form e quello
|
|
11128
|
+
* dinamico sono la stessa interazione con l'utente, e contarne uno solo faceva comparire
|
|
11129
|
+
* «questo flow Screen non ha screen» su un flow fatto di soli screen dinamici (§5.2).
|
|
11130
|
+
*/
|
|
11131
|
+
screenCount = computed(() => {
|
|
11132
|
+
const document = this.store.document();
|
|
11133
|
+
return (document.screens?.length ?? 0) + (document.dynamicScreens?.length ?? 0);
|
|
11134
|
+
}, ...(ngDevMode ? [{ debugName: "screenCount" }] : []));
|
|
10268
11135
|
/** `AutoLaunched` + screen e' un errore di validazione: si segnala subito (§13.6). */
|
|
10269
|
-
hasScreensInAutoLaunched = computed(() => this.store.document().processType === 'AutoLaunched' &&
|
|
10270
|
-
(this.store.document().screens?.length ?? 0) > 0, ...(ngDevMode ? [{ debugName: "hasScreensInAutoLaunched" }] : []));
|
|
11136
|
+
hasScreensInAutoLaunched = computed(() => this.store.document().processType === 'AutoLaunched' && this.screenCount() > 0, ...(ngDevMode ? [{ debugName: "hasScreensInAutoLaunched" }] : []));
|
|
10271
11137
|
/** Un flow `Screen` senza screen e' `SCREEN_FLOW_WITHOUT_SCREENS`. */
|
|
10272
|
-
hasNoScreensInScreenFlow = computed(() => this.store.document().processType === 'Screen' &&
|
|
11138
|
+
hasNoScreensInScreenFlow = computed(() => this.store.document().processType === 'Screen' && this.screenCount() === 0, ...(ngDevMode ? [{ debugName: "hasNoScreensInScreenFlow" }] : []));
|
|
10273
11139
|
/**
|
|
10274
11140
|
* Un flow `Orchestration` senza stage di orchestrazione e' un **avviso**
|
|
10275
11141
|
* (`ORCHESTRATION_WITHOUT_STAGES`): il tipo dichiara un'orchestrazione che non c'e' (§3.1).
|
|
@@ -10632,5 +11498,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.27", ngImpo
|
|
|
10632
11498
|
* Generated bundle index. Do not edit.
|
|
10633
11499
|
*/
|
|
10634
11500
|
|
|
10635
|
-
export { ConditionEditorComponent, ConnectorEditorComponent, DebugPanelComponent, ElementDialogComponent, ElementInspectorComponent, ElementPaletteComponent, EnumValuePickerComponent, FALLBACK_COLLECTION_BY_TYPE, FALLBACK_TYPE_LABEL, FLOW_BUILDER_HTTP_CONFIG, FLOW_ELEMENT_ICONS, FLOW_ELEMENT_VARIANT_FIELDS, FLOW_ELEMENT_VARIANT_ICONS, FLOW_ERROR_FALLBACK_MESSAGE, FLOW_ERROR_HTTP_STATUS, FLOW_NAME_PATTERN, FLOW_NODE_COLLECTIONS, FLOW_NODE_HEIGHT, FLOW_NODE_WIDTH, FLOW_RESOURCE_COLLECTIONS, FLOW_VALUE_FIELDS, FLOW_VALUE_LITERAL_FIELDS, FieldAssignmentEditorComponent, FieldPickerComponent, FlowApiError, FlowBuilderApi, FlowBuilderComponent, FlowCanvasComponent, FlowCatalogStore, FlowDictionaryStore, FlowDocumentStore, FlowEditorSession, FlowLayoutService, FlowValidationStore, HttpFlowBuilderApi, NamePickerComponent, NodeInspectorBase, ORCHESTRATION_CONDITION_OUTPUT, ObjectPickerComponent, OrchestratedStageInspectorComponent, ParameterEditorComponent, ProblemsPanelComponent, RecordFilterEditorComponent, ReferencePickerComponent, ResourcePanelComponent, SEVERITY_BUCKETS, SEVERITY_ICON, SEVERITY_LABEL, START_NODE_NAME, SelectValueDirective, StartInspectorComponent, StructureMemberPickerComponent, StructurePickerComponent, TYPES_WITH_AUTOMATIC_OUTPUT, TYPE_BY_COLLECTION, UNSUPPORTED_TYPES, ValueEditorComponent, VersionPanelComponent, areTypesComparable, canvasNodeId, checkConditionLogic, checkFlowName, describePathEntry, elementIcon, emptyFlowDefinition, filterReferences, flowNodeWidth, flowNodeWidthClass, isCustomConditionLogic, isEmptyReferenceFilter, isGlobalReference, isNumericType, isTypeCheckedOperator, isValidFlowName, isValued, loadPathLevel, matchesReferenceFilter, moveCondition, navigatePath, outletByKey, outletsOf, parseCanvasNodeId, parseInvariantNumber, parseSourceConnectorId, parseTargetConnectorId, pathAvailableNames, pathContainerLabel, pathNotVerifiableMessage, referenceRoot, remapConditionLogic, removeCondition, resolvePath, severityBucket, slugifyFlowName, sourceConnectorId, stageStepNames, stageStepOutputReferenced, stepsOf, targetConnectorId, uniqueFlowName, valuedFieldOf, valuedFieldsOf, variantFieldOf, variantOf, variantPresetOf };
|
|
11501
|
+
export { ConditionEditorComponent, ConnectorEditorComponent, DebugPanelComponent, DynamicScreenInspectorComponent, ElementDialogComponent, ElementInspectorComponent, ElementPaletteComponent, EnumValuePickerComponent, FALLBACK_COLLECTION_BY_TYPE, FALLBACK_TYPE_LABEL, FLOW_BUILDER_HTTP_CONFIG, FLOW_ELEMENT_ICONS, FLOW_ELEMENT_VARIANT_FIELDS, FLOW_ELEMENT_VARIANT_ICONS, FLOW_ERROR_FALLBACK_MESSAGE, FLOW_ERROR_HTTP_STATUS, FLOW_NAME_PATTERN, FLOW_NODE_COLLECTIONS, FLOW_NODE_HEIGHT, FLOW_NODE_WIDTH, FLOW_RESOURCE_COLLECTIONS, FLOW_VALUE_FIELDS, FLOW_VALUE_LITERAL_FIELDS, FieldAssignmentEditorComponent, FieldPickerComponent, FlowApiError, FlowBuilderApi, FlowBuilderComponent, FlowCanvasComponent, FlowCatalogStore, FlowDictionaryStore, FlowDocumentStore, FlowEditorSession, FlowLayoutService, FlowValidationStore, HttpFlowBuilderApi, NamePickerComponent, NodeInspectorBase, ORCHESTRATION_CONDITION_OUTPUT, ObjectPickerComponent, OrchestratedStageInspectorComponent, ParameterEditorComponent, ProblemsPanelComponent, RecordFilterEditorComponent, ReferencePickerComponent, ResourcePanelComponent, SEVERITY_BUCKETS, SEVERITY_ICON, SEVERITY_LABEL, START_NODE_NAME, SelectValueDirective, StartInspectorComponent, StructureMemberPickerComponent, StructurePickerComponent, TYPES_WITH_AUTOMATIC_OUTPUT, TYPE_BY_COLLECTION, UNSUPPORTED_TYPES, ValueEditorComponent, VersionPanelComponent, allFieldsOf, areTypesComparable, canvasNodeId, checkConditionLogic, checkFlowName, describePathEntry, duplicateField, elementIcon, emptyFlowDefinition, fieldAt, filterReferences, flattenFields, flowNodeWidth, flowNodeWidthClass, insertField, isCustomConditionLogic, isEmptyReferenceFilter, isFieldResource, isGlobalReference, isNumericType, isPathInside, isTypeCheckedOperator, isValidFlowName, isValued, loadPathLevel, matchesReferenceFilter, moveCondition, moveField, navigatePath, outletByKey, outletsOf, parseCanvasNodeId, parseInvariantNumber, parseSourceConnectorId, parseTargetConnectorId, pathAvailableNames, pathContainerLabel, pathKey, pathNotVerifiableMessage, referenceRoot, remapConditionLogic, removeCondition, removeField, resolvePath, samePath, screenFieldNames, severityBucket, slugifyFlowName, sourceConnectorId, stageStepNames, stageStepOutputReferenced, stepsOf, targetConnectorId, uniqueFlowName, valuedFieldOf, valuedFieldsOf, variantFieldOf, variantOf, variantPresetOf };
|
|
10636
11502
|
//# sourceMappingURL=esfaenza-flow-builder.mjs.map
|