@esfaenza/flow-builder 20.3.12 → 20.3.14
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 +24 -5
- package/fesm2022/esfaenza-flow-builder.mjs +405 -149
- package/fesm2022/esfaenza-flow-builder.mjs.map +1 -1
- package/index.d.ts +71 -25
- package/package.json +1 -1
|
@@ -1205,6 +1205,67 @@ function parseInvariantNumber(raw) {
|
|
|
1205
1205
|
return Number.isFinite(parsed) ? parsed : undefined;
|
|
1206
1206
|
}
|
|
1207
1207
|
|
|
1208
|
+
/**
|
|
1209
|
+
* Quale campo di un `FlowElementReferenceOrValue` porta davvero il valore — FRONTEND.md §4.2.
|
|
1210
|
+
*
|
|
1211
|
+
* La regola del contratto e' "uno e un solo campo valorizzato", e la §2 dice che cio' che non c'e'
|
|
1212
|
+
* si **omette**. Un backend che serializza tutte le proprieta' — `System.Text.Json` senza
|
|
1213
|
+
* `IgnoreNullValues` lo fa di default — rispetta il contratto nella sostanza e lo viola nella forma:
|
|
1214
|
+
* arriva `{"enumValue": "InIstruttoria", "formulaExpression": null, ...}`, cioe' un solo valore e
|
|
1215
|
+
* sette caselle piene di niente.
|
|
1216
|
+
*
|
|
1217
|
+
* Il campo si sceglieva con `!== undefined`, e un `null` passava: la prima casella letta —
|
|
1218
|
+
* `formulaExpression` — vinceva, l'editor apriva la formula (vuota) e il valore, pur presente nel
|
|
1219
|
+
* documento, spariva dalla vista. Da qui questa funzione sola: **`null` e' assente quanto
|
|
1220
|
+
* `undefined`**, e chi interpreta un valore la usa invece di confrontare con `undefined`.
|
|
1221
|
+
*
|
|
1222
|
+
* Sta in `core/` perche' i chiamanti sono piu' d'uno — l'editor del valore, l'editor delle
|
|
1223
|
+
* condizioni — e la regola non deve divergere fra loro.
|
|
1224
|
+
*/
|
|
1225
|
+
/**
|
|
1226
|
+
* I campi che possono portare il valore, **nell'ordine in cui si interpretano**: `formulaExpression`
|
|
1227
|
+
* per primo perche' e' l'unico a due campi, poi il riferimento, poi i letterali. `formulaDataType`
|
|
1228
|
+
* non c'e': accompagna l'espressione, non e' un valore da solo.
|
|
1229
|
+
*/
|
|
1230
|
+
const FLOW_VALUE_FIELDS = [
|
|
1231
|
+
'formulaExpression',
|
|
1232
|
+
'elementReference',
|
|
1233
|
+
'stringValue',
|
|
1234
|
+
'integerValue',
|
|
1235
|
+
'numberValue',
|
|
1236
|
+
'dateValue',
|
|
1237
|
+
'booleanValue',
|
|
1238
|
+
'enumValue',
|
|
1239
|
+
];
|
|
1240
|
+
/** I soli campi letterali: quelli che l'editor mostra sotto la modalita' «Valore». */
|
|
1241
|
+
const FLOW_VALUE_LITERAL_FIELDS = [
|
|
1242
|
+
'stringValue',
|
|
1243
|
+
'integerValue',
|
|
1244
|
+
'numberValue',
|
|
1245
|
+
'dateValue',
|
|
1246
|
+
'booleanValue',
|
|
1247
|
+
'enumValue',
|
|
1248
|
+
];
|
|
1249
|
+
/**
|
|
1250
|
+
* `true` se il campo porta un valore. Attenzione ai falsi legittimi: `booleanValue: false`,
|
|
1251
|
+
* `numberValue: 0` e `stringValue: ''` sono valori, quindi il confronto e' con `null`/`undefined`
|
|
1252
|
+
* e non una verifica di verita'.
|
|
1253
|
+
*/
|
|
1254
|
+
function isValued(field) {
|
|
1255
|
+
return field !== undefined && field !== null;
|
|
1256
|
+
}
|
|
1257
|
+
/** I campi valorizzati, nell'ordine di interpretazione: piu' di uno e' `VALUE_AMBIGUOUS` (§4.2). */
|
|
1258
|
+
function valuedFieldsOf(value) {
|
|
1259
|
+
if (!value) {
|
|
1260
|
+
return [];
|
|
1261
|
+
}
|
|
1262
|
+
return FLOW_VALUE_FIELDS.filter((field) => isValued(value[field]));
|
|
1263
|
+
}
|
|
1264
|
+
/** Il campo che l'editor considera **il** valore, cioe' il primo valorizzato nell'ordine sopra. */
|
|
1265
|
+
function valuedFieldOf(value) {
|
|
1266
|
+
return valuedFieldsOf(value)[0];
|
|
1267
|
+
}
|
|
1268
|
+
|
|
1208
1269
|
/**
|
|
1209
1270
|
* Gli step di uno stage di orchestrazione — FRONTEND.md §5.13.
|
|
1210
1271
|
*
|
|
@@ -3781,6 +3842,244 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.27", ngImpo
|
|
|
3781
3842
|
args: [{ selector: 'fb-element-palette', standalone: true, imports: [FFlowModule], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"fb-palette__search\">\r\n <input\r\n type=\"search\"\r\n placeholder=\"Cerca un elemento\"\r\n aria-label=\"Cerca un elemento\"\r\n (input)=\"onFilter($any($event.target).value)\"\r\n />\r\n</div>\r\n\r\n@if (isEmpty()) {\r\n <p class=\"fb-palette__empty\">\r\n Il dizionario degli elementi non e\u2019 ancora disponibile.\r\n </p>\r\n}\r\n\r\n<div class=\"fb-palette__groups\">\r\n @for (group of groups(); track group.category) {\r\n <section class=\"fb-palette__group\">\r\n <h3 class=\"fb-palette__category\">{{ group.category }}</h3>\r\n @for (item of group.items; track item.key) {\r\n <!--\r\n `fExternalItem` rende la voce trascinabile sul canvas: il rilascio emette\r\n `fCreateNode` con questo `fData` e la posizione, che diventa locationX/locationY.\r\n Il dato e' l'oggetto {type, variant}, non il solo tipo: due voci dello stesso tipo\r\n si distinguono soltanto per la variante.\r\n -->\r\n <button\r\n type=\"button\"\r\n class=\"fb-palette__item\"\r\n fExternalItem\r\n [fExternalItemId]=\"item.key\"\r\n [fData]=\"item.data\"\r\n [class.fb-palette__item--warn]=\"isIncompatible(item)\"\r\n [title]=\"hintOf(item)\"\r\n (click)=\"pick(item)\"\r\n >\r\n <span class=\"fb-palette__icon\" [class]=\"'fb-palette__icon ' + categoryClass(group.category)\">\r\n {{ iconOf(item) }}\r\n </span>\r\n <span class=\"fb-palette__text\">\r\n <span class=\"fb-palette__label\">{{ item.label }}</span>\r\n @if (item.entry.hasAutomaticOutput) {\r\n <span class=\"fb-palette__flag\" title=\"Espone un output automatico sotto il nome dell\u2019elemento\">\r\n output automatico\r\n </span>\r\n }\r\n @if (faultLabel(item)) {\r\n <span class=\"fb-palette__flag\" title=\"Ha un ramo che l\u2019autore puo\u2019 prevedere e disegnare\">\r\n {{ faultLabel(item) }}\r\n </span>\r\n }\r\n </span>\r\n @if (isIncompatible(item)) {\r\n <span class=\"fb-palette__warn\" aria-hidden=\"true\">!</span>\r\n }\r\n </button>\r\n }\r\n </section>\r\n }\r\n</div>\r\n", styles: [":host{display:flex;flex-direction:column;height:100%;min-height:0;background:var(--fb-surface, #fff);border-right:1px solid var(--fb-border, #d6dae1)}.fb-palette__search{padding:8px;border-bottom:1px solid var(--fb-border-subtle, #e6e9ee)}.fb-palette__search input{box-sizing:border-box;width:100%;padding:5px 8px;border:1px solid var(--fb-border, #d6dae1);border-radius:6px;background:var(--fb-surface, #fff);color:var(--fb-text, #1d2939);font:inherit;font-size:12px}.fb-palette__search input:focus-visible{outline:2px solid var(--fb-accent, #2f6feb);outline-offset:-1px}.fb-palette__groups{flex:1;min-height:0;overflow-y:auto;padding:4px 0 12px}.fb-palette__empty{margin:12px 10px;font-size:12px;color:var(--fb-text-muted, #667085)}.fb-palette__category{margin:10px 10px 4px;font-size:10px;font-weight:700;letter-spacing:.06em;text-transform:uppercase;color:var(--fb-text-subtle, #98a2b3)}.fb-palette__item{display:flex;align-items:center;gap:8px;box-sizing:border-box;width:calc(100% - 12px);margin:1px 6px;padding:5px 8px;border:0;border-radius:var(--fb-radius-xs, 6px);background:transparent;color:var(--fb-text, #1a1c23);font:inherit;text-align:left;cursor:grab;transition:background .12s ease}.fb-palette__item:hover{background:var(--fb-surface-alt, #f7f8fa)}.fb-palette__item:focus-visible{outline:2px solid var(--fb-accent, #2f6feb);outline-offset:-2px}.fb-palette__icon{display:grid;place-items:center;flex:0 0 auto;width:24px;height:24px;border-radius:var(--fb-radius-xs, 6px);background:var(--fb-icon-bg, #98a2b3);color:#fff;font-size:11px;font-weight:700}.cat-screen{--fb-icon-bg: #3b82f6}.cat-logic{--fb-icon-bg: #8b5cf6}.cat-data{--fb-icon-bg: #06b6d4}.cat-action{--fb-icon-bg: #f59e0b}.cat-flow{--fb-icon-bg: #14b8a6}.fb-palette__text{display:flex;flex-direction:column;min-width:0}.fb-palette__label{font-size:12px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.fb-palette__flag{font-size:9px;color:var(--fb-text-subtle, #98a2b3)}.fb-palette__item--warn .fb-palette__label{color:var(--fb-warning, #b7791f)}.fb-palette__warn{margin-left:auto;color:var(--fb-warning, #b7791f);font-weight:700}:host ::ng-deep .f-external-item-preview{padding:4px 8px;border:1px solid var(--fb-accent, #2f6feb);border-radius:6px;background:var(--fb-surface, #fff);box-shadow:0 4px 12px #1018282e;opacity:.95}\n"] }]
|
|
3782
3843
|
}], propDecorators: { processType: [{ type: i0.Input, args: [{ isSignal: true, alias: "processType", required: false }] }], elementPicked: [{ type: i0.Output, args: ["elementPicked"] }] } });
|
|
3783
3844
|
|
|
3845
|
+
/**
|
|
3846
|
+
* Il tipo di cio' che un riferimento designa — FRONTEND.md §4.1, §4.3, §4.4, §4.6, §4.7.1.
|
|
3847
|
+
*
|
|
3848
|
+
* `POST /flows/references` elenca le **radici**, non i percorsi: `Richiesta` c'e', `Richiesta.Stato`
|
|
3849
|
+
* no. Cercare la corrispondenza esatta del valore in quell'elenco basta per una risorsa e non basta
|
|
3850
|
+
* per un percorso, e la conseguenza non e' cosmetica: senza il tipo dell'ultimo segmento un membro
|
|
3851
|
+
* `Enum` diventa una casella di testo su un insieme chiuso (§4.6) e un confronto fra tipi
|
|
3852
|
+
* incompatibili non si vede prima del backend (§4.3).
|
|
3853
|
+
*
|
|
3854
|
+
* Il tipo di un percorso pero' si **ricava**: e' `core/path-navigation` che naviga la catena, e qui
|
|
3855
|
+
* c'e' l'innesto fra i due mondi — dall'elenco dei riferimenti alla tappa da cui partire, e dalla
|
|
3856
|
+
* tappa all'ultimo segmento. Sta in `core/` e non in un inspector perche' i chiamanti sono tre
|
|
3857
|
+
* (Assignment, condizioni, filtri) e la regola del **prefisso dichiarato piu' lungo** e' una sola:
|
|
3858
|
+
* duplicarla vorrebbe dire farla divergere.
|
|
3859
|
+
*
|
|
3860
|
+
* Le due asimmetrie da tenere a mente:
|
|
3861
|
+
*
|
|
3862
|
+
* - **Non ogni radice e' navigabile.** Un output automatico dichiara il proprio tipo, ma la
|
|
3863
|
+
* specifica tiene i percorsi che ne partono fuori dalla validazione (§7): dedurne il tipo qui
|
|
3864
|
+
* significherebbe far dire all'editor cio' che il backend non verifica. Stessa scelta del
|
|
3865
|
+
* `reference-picker`, e le due devono restare la stessa.
|
|
3866
|
+
* - **"Non lo so" resta "non lo so".** Catena interrotta, catalogo assente, segmento inesistente:
|
|
3867
|
+
* l'esito e' `null` e il chiamante ricade sul controllo generico. Un tipo indovinato e' peggio
|
|
3868
|
+
* di un tipo mancante, perche' fa comparire un elenco chiuso di valori sbagliati.
|
|
3869
|
+
*/
|
|
3870
|
+
/**
|
|
3871
|
+
* Il riferimento dichiarato piu' **lungo** che sia un prefisso del valore, sui confini del punto.
|
|
3872
|
+
*
|
|
3873
|
+
* Non e' il primo segmento: un nome dichiarato puo' contenere il punto, e le globali sono il caso
|
|
3874
|
+
* per cui la regola esiste — di `$User.Anagrafica.Citta` il dizionario dichiara `$User.Anagrafica`
|
|
3875
|
+
* (§4.1: vince il prefisso dichiarato piu' lungo).
|
|
3876
|
+
*/
|
|
3877
|
+
function declaredPrefixOf(references, value) {
|
|
3878
|
+
const trimmed = (value ?? '').trim();
|
|
3879
|
+
if (!trimmed) {
|
|
3880
|
+
return undefined;
|
|
3881
|
+
}
|
|
3882
|
+
let best;
|
|
3883
|
+
for (const candidate of references) {
|
|
3884
|
+
if (!candidate.name || (trimmed !== candidate.name && !trimmed.startsWith(`${candidate.name}.`))) {
|
|
3885
|
+
continue;
|
|
3886
|
+
}
|
|
3887
|
+
if (!best || candidate.name.length > best.name.length) {
|
|
3888
|
+
best = candidate;
|
|
3889
|
+
}
|
|
3890
|
+
}
|
|
3891
|
+
return best;
|
|
3892
|
+
}
|
|
3893
|
+
/**
|
|
3894
|
+
* La risorsa da cui il valore sta navigando, se e solo se il percorso e' verificabile: una
|
|
3895
|
+
* `Structure` con la sua classe, un record con la sua entita'. Un output automatico e' escluso di
|
|
3896
|
+
* proposito (vedi l'intestazione), e una collection non si naviga — il percorso designa l'insieme.
|
|
3897
|
+
*/
|
|
3898
|
+
function navigableRootOf(references, value, isStructure) {
|
|
3899
|
+
const reference = declaredPrefixOf(references, value);
|
|
3900
|
+
if (!reference || reference.kind === 'ElementOutput' || !reference.objectType || reference.isCollection) {
|
|
3901
|
+
return undefined;
|
|
3902
|
+
}
|
|
3903
|
+
const trimmed = (value ?? '').trim();
|
|
3904
|
+
const path = trimmed.length > reference.name.length ? trimmed.slice(reference.name.length + 1) : '';
|
|
3905
|
+
if (isStructure(reference.dataType)) {
|
|
3906
|
+
return { reference, container: { kind: 'structure', name: reference.objectType }, path };
|
|
3907
|
+
}
|
|
3908
|
+
return reference.dataType === 'Object'
|
|
3909
|
+
? { reference, container: { kind: 'object', name: reference.objectType }, path }
|
|
3910
|
+
: undefined;
|
|
3911
|
+
}
|
|
3912
|
+
/** Il tipo di un segmento risolto: campo o membro, ridotti al minimo comune (§4.7.1). */
|
|
3913
|
+
function typeOfEntry(entry) {
|
|
3914
|
+
return {
|
|
3915
|
+
dataType: entry.dataType,
|
|
3916
|
+
objectType: entry.objectType ?? undefined,
|
|
3917
|
+
isCollection: entry.isCollection,
|
|
3918
|
+
isWritable: entry.isWritable,
|
|
3919
|
+
origin: 'member',
|
|
3920
|
+
};
|
|
3921
|
+
}
|
|
3922
|
+
function typeOfReference(reference) {
|
|
3923
|
+
return {
|
|
3924
|
+
dataType: reference.dataType ?? undefined,
|
|
3925
|
+
objectType: reference.objectType ?? undefined,
|
|
3926
|
+
isCollection: reference.isCollection,
|
|
3927
|
+
isWritable: reference.isWritable,
|
|
3928
|
+
origin: 'resource',
|
|
3929
|
+
};
|
|
3930
|
+
}
|
|
3931
|
+
/**
|
|
3932
|
+
* Il tipo di un riferimento: la risorsa se il nome e' in elenco, altrimenti l'ultimo segmento del
|
|
3933
|
+
* percorso navigato. `null` = non si sa, e non autorizza a mostrare un elenco chiuso.
|
|
3934
|
+
*
|
|
3935
|
+
* `usage` vale solo per l'ultimo segmento quando la tappa e' un'entita' (§4.4): in un filtro serve
|
|
3936
|
+
* `filterable`, altrove `any` — chiederlo con l'uso sbagliato farebbe sparire un campo che esiste.
|
|
3937
|
+
*/
|
|
3938
|
+
async function resolveReferenceType(catalog, references, value, isStructure, usage) {
|
|
3939
|
+
const trimmed = (value ?? '').trim();
|
|
3940
|
+
if (!trimmed) {
|
|
3941
|
+
return null;
|
|
3942
|
+
}
|
|
3943
|
+
const exact = references.find((candidate) => candidate.name === trimmed);
|
|
3944
|
+
if (exact) {
|
|
3945
|
+
return typeOfReference(exact);
|
|
3946
|
+
}
|
|
3947
|
+
const root = navigableRootOf(references, trimmed, isStructure);
|
|
3948
|
+
if (!root?.path) {
|
|
3949
|
+
return null;
|
|
3950
|
+
}
|
|
3951
|
+
return resolvePathType(catalog, root.container, root.path, usage);
|
|
3952
|
+
}
|
|
3953
|
+
/** Come sopra, dentro una tappa già nota: e' il caso dei filtri, dove la radice e' l'entita'. */
|
|
3954
|
+
async function resolvePathType(catalog, container, path, usage) {
|
|
3955
|
+
const trimmed = (path ?? '').trim();
|
|
3956
|
+
if (!trimmed) {
|
|
3957
|
+
return null;
|
|
3958
|
+
}
|
|
3959
|
+
const resolution = await resolvePath(catalog, container, trimmed, { usage });
|
|
3960
|
+
// Solo `resolved` e' autorevole: `unknown` e `unverifiable` sono cio' che il picker segnala, non
|
|
3961
|
+
// un tipo su cui costruire un elenco di valori.
|
|
3962
|
+
return resolution.status === 'resolved' && resolution.last ? typeOfEntry(resolution.last) : null;
|
|
3963
|
+
}
|
|
3964
|
+
function sameType(a, b) {
|
|
3965
|
+
if (!a || !b) {
|
|
3966
|
+
return a === b;
|
|
3967
|
+
}
|
|
3968
|
+
return (a.dataType === b.dataType &&
|
|
3969
|
+
a.objectType === b.objectType &&
|
|
3970
|
+
!!a.isCollection === !!b.isCollection &&
|
|
3971
|
+
a.isWritable === b.isWritable &&
|
|
3972
|
+
a.origin === b.origin);
|
|
3973
|
+
}
|
|
3974
|
+
/**
|
|
3975
|
+
* Il motore: risolve in background e pubblica per lettura sincrona.
|
|
3976
|
+
*
|
|
3977
|
+
* Perche' non un `computed`: la risoluzione e' asincrona (una chiamata di catalogo per tappa) e i
|
|
3978
|
+
* valori sono **molti** — un'operazione di Assignment per riga. Quindi un `effect` che risolve e una
|
|
3979
|
+
* mappa da cui i template leggono. Da chiamare in un **contesto di iniezione**.
|
|
3980
|
+
*
|
|
3981
|
+
* Il token di sequenza per chiave non e' cosmetico: una risposta superata che arriva dopo
|
|
3982
|
+
* scriverebbe il tipo di un valore che l'utente ha già cambiato, e l'editor mostrerebbe l'elenco di
|
|
3983
|
+
* valori sbagliato senza che nulla lo segnali.
|
|
3984
|
+
*/
|
|
3985
|
+
function trackTypes(request) {
|
|
3986
|
+
const types = signal(new Map(), ...(ngDevMode ? [{ debugName: "types" }] : []));
|
|
3987
|
+
const scope = signal('', ...(ngDevMode ? [{ debugName: "scope" }] : []));
|
|
3988
|
+
/**
|
|
3989
|
+
* Fuori dai signal di proposito: e' il registro di cio' che e' già stato chiesto, non uno stato da
|
|
3990
|
+
* cui ridisegnare. Leggerlo dentro l'`effect` che lo scrive sarebbe un ciclo.
|
|
3991
|
+
*/
|
|
3992
|
+
const requested = new Map();
|
|
3993
|
+
const tokens = new Map();
|
|
3994
|
+
let sequence = 0;
|
|
3995
|
+
effect(() => {
|
|
3996
|
+
const current = request();
|
|
3997
|
+
scope.set(current?.scope ?? '');
|
|
3998
|
+
if (!current) {
|
|
3999
|
+
return;
|
|
4000
|
+
}
|
|
4001
|
+
for (const value of new Set(current.values)) {
|
|
4002
|
+
const trimmed = (value ?? '').trim();
|
|
4003
|
+
if (!trimmed) {
|
|
4004
|
+
continue;
|
|
4005
|
+
}
|
|
4006
|
+
const key = `${current.scope}${trimmed}`;
|
|
4007
|
+
if (requested.get(key) === current.version) {
|
|
4008
|
+
continue;
|
|
4009
|
+
}
|
|
4010
|
+
requested.set(key, current.version);
|
|
4011
|
+
const token = ++sequence;
|
|
4012
|
+
tokens.set(key, token);
|
|
4013
|
+
void current.resolve(trimmed).then((result) => {
|
|
4014
|
+
if (tokens.get(key) !== token) {
|
|
4015
|
+
return;
|
|
4016
|
+
}
|
|
4017
|
+
const known = types();
|
|
4018
|
+
// Riscrivere la mappa con lo stesso contenuto ridisegnerebbe l'inspector a ogni battuta.
|
|
4019
|
+
if (known.has(key) && sameType(known.get(key), result)) {
|
|
4020
|
+
return;
|
|
4021
|
+
}
|
|
4022
|
+
const next = new Map(known);
|
|
4023
|
+
next.set(key, result);
|
|
4024
|
+
types.set(next);
|
|
4025
|
+
});
|
|
4026
|
+
}
|
|
4027
|
+
});
|
|
4028
|
+
const typeOf = (value) => {
|
|
4029
|
+
const trimmed = (value ?? '').trim();
|
|
4030
|
+
if (!trimmed) {
|
|
4031
|
+
return undefined;
|
|
4032
|
+
}
|
|
4033
|
+
return types().get(`${scope()}${trimmed}`) ?? undefined;
|
|
4034
|
+
};
|
|
4035
|
+
return {
|
|
4036
|
+
typeOf,
|
|
4037
|
+
dataTypeOf: (value) => typeOf(value)?.dataType,
|
|
4038
|
+
objectTypeOf: (value) => typeOf(value)?.objectType,
|
|
4039
|
+
};
|
|
4040
|
+
}
|
|
4041
|
+
/**
|
|
4042
|
+
* I tipi di piu' riferimenti insieme, risolti in background. È cio' che sostituisce la ricerca per
|
|
4043
|
+
* corrispondenza esatta negli inspector: sulle risorse risponde come prima, sui percorsi risponde
|
|
4044
|
+
* dove prima taceva.
|
|
4045
|
+
*/
|
|
4046
|
+
function referenceTypes(catalog, isStructure, request) {
|
|
4047
|
+
return trackTypes(() => {
|
|
4048
|
+
const current = request();
|
|
4049
|
+
if (!current) {
|
|
4050
|
+
return null;
|
|
4051
|
+
}
|
|
4052
|
+
const references = current.references;
|
|
4053
|
+
const usage = current.usage ?? 'any';
|
|
4054
|
+
return {
|
|
4055
|
+
scope: `references|${usage}`,
|
|
4056
|
+
// L'elenco arriva da una richiesta: cambia identita' a ogni risposta, ed e' esattamente
|
|
4057
|
+
// quando le deduzioni vanno rifatte (una variabile rinominata, un tipo cambiato).
|
|
4058
|
+
version: references,
|
|
4059
|
+
values: current.values,
|
|
4060
|
+
resolve: (value) => resolveReferenceType(catalog, references, value, isStructure, usage),
|
|
4061
|
+
};
|
|
4062
|
+
});
|
|
4063
|
+
}
|
|
4064
|
+
/** I tipi di piu' percorsi dentro una tappa nota: i filtri su un'entita' (§4.4). */
|
|
4065
|
+
function pathTypes(catalog, request) {
|
|
4066
|
+
return trackTypes(() => {
|
|
4067
|
+
const current = request();
|
|
4068
|
+
const container = current?.container;
|
|
4069
|
+
if (!current || !container?.name) {
|
|
4070
|
+
return null;
|
|
4071
|
+
}
|
|
4072
|
+
const usage = current.usage ?? 'any';
|
|
4073
|
+
return {
|
|
4074
|
+
scope: `${container.kind}:${container.name}|${usage}`,
|
|
4075
|
+
// Tappa e uso stanno già nello scope: dentro lo stesso scope la risposta non cambia.
|
|
4076
|
+
version: 'container',
|
|
4077
|
+
values: current.paths,
|
|
4078
|
+
resolve: (path) => resolvePathType(catalog, container, path, usage),
|
|
4079
|
+
};
|
|
4080
|
+
});
|
|
4081
|
+
}
|
|
4082
|
+
|
|
3784
4083
|
/**
|
|
3785
4084
|
* Selettore di riferimento — FRONTEND.md §4.1, §4.5, §4.7, §5.2, §6.4, §13.3.
|
|
3786
4085
|
*
|
|
@@ -3941,57 +4240,18 @@ class ReferencePickerComponent {
|
|
|
3941
4240
|
});
|
|
3942
4241
|
}
|
|
3943
4242
|
/**
|
|
3944
|
-
* Il riferimento dichiarato piu' **lungo** che sia un prefisso del valore,
|
|
4243
|
+
* Il riferimento dichiarato piu' **lungo** che sia un prefisso del valore, e la tappa da cui
|
|
4244
|
+
* navigare il resto. La regola sta in `core/reference-path` perche' non e' solo del picker: gli
|
|
4245
|
+
* inspector ne ricavano il **tipo** dell'ultimo segmento con la stessa catena, e due copie di
|
|
4246
|
+
* «prefisso piu' lungo» divergerebbero (§4.1, §4.7.1).
|
|
3945
4247
|
*
|
|
3946
|
-
*
|
|
3947
|
-
*
|
|
3948
|
-
* e cercare la corrispondenza esatta di tutto cio' che segue lo scope segnalerebbe come rotto un
|
|
3949
|
-
* percorso giusto (§4.1: vince il prefisso dichiarato piu' lungo).
|
|
4248
|
+
* Fra **tutte** le radici, non fra quelle filtrate: la radice di `Richiesta.Ragione` in un campo
|
|
4249
|
+
* `String` sta solo nell'elenco non filtrato.
|
|
3950
4250
|
*/
|
|
3951
|
-
declaredPrefix = computed(() => {
|
|
3952
|
-
|
|
3953
|
-
if (!value) {
|
|
3954
|
-
return undefined;
|
|
3955
|
-
}
|
|
3956
|
-
let best;
|
|
3957
|
-
for (const candidate of this.roots()) {
|
|
3958
|
-
if (value !== candidate.name && !value.startsWith(`${candidate.name}.`)) {
|
|
3959
|
-
continue;
|
|
3960
|
-
}
|
|
3961
|
-
if (!best || candidate.name.length > best.name.length) {
|
|
3962
|
-
best = candidate;
|
|
3963
|
-
}
|
|
3964
|
-
}
|
|
3965
|
-
return best;
|
|
3966
|
-
}, ...(ngDevMode ? [{ debugName: "declaredPrefix" }] : []));
|
|
3967
|
-
/**
|
|
3968
|
-
* La risorsa da cui il valore corrente sta navigando, quando la sua forma e' **dichiarata**: una
|
|
3969
|
-
* `Structure` con la classe, un record con l'entita'. È cio' che rende il percorso verificabile.
|
|
3970
|
-
*
|
|
3971
|
-
* Un output automatico e' escluso di proposito: la primitiva ne dichiara il tipo, ma la
|
|
3972
|
-
* specifica tiene i percorsi che ne partono fuori dalla validazione (§7, ultimo capoverso), e
|
|
3973
|
-
* l'editor non deve accusare cio' che il backend non verifica.
|
|
3974
|
-
*/
|
|
3975
|
-
navigableRoot = computed(() => {
|
|
3976
|
-
// Fra tutte le radici: quella di `Richiesta.Ragione` in un campo `String` sta solo nell'elenco
|
|
3977
|
-
// non filtrato.
|
|
3978
|
-
const reference = this.declaredPrefix();
|
|
3979
|
-
if (!reference || reference.kind === 'ElementOutput' || !reference.objectType || reference.isCollection) {
|
|
3980
|
-
return undefined;
|
|
3981
|
-
}
|
|
3982
|
-
if (this.dictionaries.isStructure(reference.dataType)) {
|
|
3983
|
-
return { reference, container: { kind: 'structure', name: reference.objectType } };
|
|
3984
|
-
}
|
|
3985
|
-
return reference.dataType === 'Object'
|
|
3986
|
-
? { reference, container: { kind: 'object', name: reference.objectType } }
|
|
3987
|
-
: undefined;
|
|
3988
|
-
}, ...(ngDevMode ? [{ debugName: "navigableRoot" }] : []));
|
|
4251
|
+
declaredPrefix = computed(() => declaredPrefixOf(this.roots(), this.value()), ...(ngDevMode ? [{ debugName: "declaredPrefix" }] : []));
|
|
4252
|
+
navigableRoot = computed(() => navigableRootOf(this.roots(), this.value(), (dataType) => this.dictionaries.isStructure(dataType)), ...(ngDevMode ? [{ debugName: "navigableRoot" }] : []));
|
|
3989
4253
|
/** Il percorso dopo la radice: `Cliente.Email` di `Richiesta.Cliente.Email`. */
|
|
3990
|
-
navigatedPath = computed(() => {
|
|
3991
|
-
const root = this.navigableRoot()?.reference.name;
|
|
3992
|
-
const value = (this.value() ?? '')?.trim();
|
|
3993
|
-
return root && value.length > root.length ? value.slice(root.length + 1) : '';
|
|
3994
|
-
}, ...(ngDevMode ? [{ debugName: "navigatedPath" }] : []));
|
|
4254
|
+
navigatedPath = computed(() => this.navigableRoot()?.path ?? '', ...(ngDevMode ? [{ debugName: "navigatedPath" }] : []));
|
|
3995
4255
|
navigation = navigatePath(this.catalog, () => {
|
|
3996
4256
|
const root = this.navigableRoot();
|
|
3997
4257
|
return root ? { root: root.container, path: this.navigatedPath() } : null;
|
|
@@ -5207,18 +5467,16 @@ class ValueEditorComponent {
|
|
|
5207
5467
|
// valore che il modello non sa portare (§4.2, §4.7).
|
|
5208
5468
|
return this.dataType() && this.literalAllowed() ? 'literal' : 'reference';
|
|
5209
5469
|
}
|
|
5210
|
-
|
|
5470
|
+
// `isValued` e non `!== undefined`: un backend che serializza anche cio' che non c'e' manda
|
|
5471
|
+
// `formulaExpression: null`, e il primo controllo apriva la formula (vuota) su un valore che nel
|
|
5472
|
+
// documento c'era — un letterale che sparisce dalla vista senza che nulla lo segnali (§4.2).
|
|
5473
|
+
if (isValued(value.formulaExpression)) {
|
|
5211
5474
|
return 'formula';
|
|
5212
5475
|
}
|
|
5213
|
-
if (value.elementReference
|
|
5476
|
+
if (isValued(value.elementReference)) {
|
|
5214
5477
|
return value.elementReference.startsWith('$GlobalConstant.') ? 'globalConstant' : 'reference';
|
|
5215
5478
|
}
|
|
5216
|
-
if (
|
|
5217
|
-
value.integerValue !== undefined ||
|
|
5218
|
-
value.numberValue !== undefined ||
|
|
5219
|
-
value.dateValue !== undefined ||
|
|
5220
|
-
value.booleanValue !== undefined ||
|
|
5221
|
-
value.enumValue !== undefined) {
|
|
5479
|
+
if (FLOW_VALUE_LITERAL_FIELDS.some((field) => isValued(value[field]))) {
|
|
5222
5480
|
return 'literal';
|
|
5223
5481
|
}
|
|
5224
5482
|
return 'empty';
|
|
@@ -5228,23 +5486,7 @@ class ValueEditorComponent {
|
|
|
5228
5486
|
* Puo' capitare su un documento arrivato da un altro editor: si segnala e si offre
|
|
5229
5487
|
* di normalizzarlo, invece di nasconderlo.
|
|
5230
5488
|
*/
|
|
5231
|
-
filledFieldCount = computed(() => {
|
|
5232
|
-
const value = this.value();
|
|
5233
|
-
if (!value) {
|
|
5234
|
-
return 0;
|
|
5235
|
-
}
|
|
5236
|
-
const keys = [
|
|
5237
|
-
'elementReference',
|
|
5238
|
-
'stringValue',
|
|
5239
|
-
'integerValue',
|
|
5240
|
-
'numberValue',
|
|
5241
|
-
'dateValue',
|
|
5242
|
-
'booleanValue',
|
|
5243
|
-
'enumValue',
|
|
5244
|
-
'formulaExpression',
|
|
5245
|
-
];
|
|
5246
|
-
return keys.filter((key) => value[key] !== undefined).length;
|
|
5247
|
-
}, ...(ngDevMode ? [{ debugName: "filledFieldCount" }] : []));
|
|
5489
|
+
filledFieldCount = computed(() => valuedFieldsOf(this.value()).length, ...(ngDevMode ? [{ debugName: "filledFieldCount" }] : []));
|
|
5248
5490
|
isAmbiguous = computed(() => this.filledFieldCount() > 1, ...(ngDevMode ? [{ debugName: "isAmbiguous" }] : []));
|
|
5249
5491
|
/**
|
|
5250
5492
|
* Il campo del valore che corrisponde al `dataType` (§4.2, tabella degli otto tipi). `Object` e
|
|
@@ -5299,7 +5541,7 @@ class ValueEditorComponent {
|
|
|
5299
5541
|
return '';
|
|
5300
5542
|
}
|
|
5301
5543
|
const field = this.literalField();
|
|
5302
|
-
if (field === 'dateValue' && value.dateValue
|
|
5544
|
+
if (field === 'dateValue' && isValued(value.dateValue)) {
|
|
5303
5545
|
// `datetime-local` rifiuta un valore col fuso e resterebbe **vuoto** pur avendo un
|
|
5304
5546
|
// valore: il suffisso si toglie qui e si riscrive in uscita. Se il documento porta un
|
|
5305
5547
|
// campo diverso da quello atteso si passa al fallback qui sotto, che lo mostra comunque.
|
|
@@ -5459,7 +5701,13 @@ class ValueEditorComponent {
|
|
|
5459
5701
|
this.emit({ elementReference: value.elementReference });
|
|
5460
5702
|
return;
|
|
5461
5703
|
default: {
|
|
5462
|
-
|
|
5704
|
+
// Il campo del tipo atteso, ma solo se e' quello che porta il valore: dove il tipo non si
|
|
5705
|
+
// sa — o il documento usa un campo diverso — «normalizza» scriverebbe un valore vuoto e
|
|
5706
|
+
// butterebbe via l'unico che c'era.
|
|
5707
|
+
const expected = this.literalField();
|
|
5708
|
+
const field = isValued(value[expected])
|
|
5709
|
+
? expected
|
|
5710
|
+
: (FLOW_VALUE_LITERAL_FIELDS.find((candidate) => isValued(value[candidate])) ?? expected);
|
|
5463
5711
|
this.emit({ [field]: value[field] });
|
|
5464
5712
|
}
|
|
5465
5713
|
}
|
|
@@ -5505,6 +5753,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.27", ngImpo
|
|
|
5505
5753
|
*/
|
|
5506
5754
|
class ConditionEditorComponent {
|
|
5507
5755
|
dictionaries = inject(FlowDictionaryStore);
|
|
5756
|
+
catalog = inject(FlowCatalogStore);
|
|
5508
5757
|
api = inject(FlowBuilderApi);
|
|
5509
5758
|
store = inject(FlowDocumentStore);
|
|
5510
5759
|
/** Il contenitore: porta `conditions`, `conditionLogic` e, se serve, `formula`. */
|
|
@@ -5529,6 +5778,20 @@ class ConditionEditorComponent {
|
|
|
5529
5778
|
* fra testo e numero prima che lo dica la validazione (§4.3).
|
|
5530
5779
|
*/
|
|
5531
5780
|
references = signal([], ...(ngDevMode ? [{ debugName: "references" }] : []));
|
|
5781
|
+
/**
|
|
5782
|
+
* Il tipo dei due lati, risorsa o **percorso**. L'elenco porta le radici e non i percorsi (§6.4):
|
|
5783
|
+
* su `Richiesta.Stato` la corrispondenza esatta non trova niente, e il tipo si ricava navigando la
|
|
5784
|
+
* classe (§4.7.1). È cio' che fa comparire l'elenco dei valori su un membro `Enum` (§4.6) e che
|
|
5785
|
+
* rende visibile un confronto fra tipi incompatibili anche dentro un'istanza (§4.3).
|
|
5786
|
+
*/
|
|
5787
|
+
types = referenceTypes(this.catalog, (dataType) => this.dictionaries.isStructure(dataType), () => ({
|
|
5788
|
+
references: this.references(),
|
|
5789
|
+
// Entrambi i lati: il destro serve a `rightDataType` quando e' un riferimento.
|
|
5790
|
+
values: this.conditions().flatMap((condition) => [
|
|
5791
|
+
condition.leftValueReference,
|
|
5792
|
+
condition.rightValue?.elementReference,
|
|
5793
|
+
]),
|
|
5794
|
+
}));
|
|
5532
5795
|
constructor() {
|
|
5533
5796
|
effect(() => {
|
|
5534
5797
|
const definition = this.store.document();
|
|
@@ -5544,12 +5807,9 @@ class ConditionEditorComponent {
|
|
|
5544
5807
|
.catch(() => this.references.set([]));
|
|
5545
5808
|
});
|
|
5546
5809
|
}
|
|
5547
|
-
/** Il tipo di un riferimento, `undefined`
|
|
5810
|
+
/** Il tipo di un riferimento, `undefined` dove non si sa: lì non si segnala (§4.3). */
|
|
5548
5811
|
typeOfReference(reference) {
|
|
5549
|
-
|
|
5550
|
-
return undefined;
|
|
5551
|
-
}
|
|
5552
|
-
return this.references().find((entry) => entry.name === reference)?.dataType ?? undefined;
|
|
5812
|
+
return this.types.dataTypeOf(reference);
|
|
5553
5813
|
}
|
|
5554
5814
|
/** Il tipo del lato sinistro: guida il secondo operando. */
|
|
5555
5815
|
leftDataType(condition) {
|
|
@@ -5557,15 +5817,11 @@ class ConditionEditorComponent {
|
|
|
5557
5817
|
}
|
|
5558
5818
|
/**
|
|
5559
5819
|
* Il tipo concreto del lato sinistro quando e' un `Enum`: senza di questo il valore di confronto
|
|
5560
|
-
* resterebbe una casella di testo su un insieme chiuso (§4.6).
|
|
5561
|
-
* (`
|
|
5820
|
+
* resterebbe una casella di testo su un insieme chiuso (§4.6). Vale anche su un percorso navigato
|
|
5821
|
+
* (`Richiesta.Stato`, `$Record.Stato`): il tipo del membro o del campo lo ricava {@link types}.
|
|
5562
5822
|
*/
|
|
5563
5823
|
leftObjectType(condition) {
|
|
5564
|
-
|
|
5565
|
-
if (!reference) {
|
|
5566
|
-
return undefined;
|
|
5567
|
-
}
|
|
5568
|
-
return this.references().find((entry) => entry.name === reference)?.objectType ?? undefined;
|
|
5824
|
+
return this.types.objectTypeOf(condition.leftValueReference);
|
|
5569
5825
|
}
|
|
5570
5826
|
/** Il tipo del lato destro, dedotto dal campo valorizzato o dal riferimento scelto. */
|
|
5571
5827
|
rightDataType(condition) {
|
|
@@ -5573,31 +5829,29 @@ class ConditionEditorComponent {
|
|
|
5573
5829
|
if (!value) {
|
|
5574
5830
|
return undefined;
|
|
5575
5831
|
}
|
|
5576
|
-
|
|
5577
|
-
|
|
5578
|
-
|
|
5579
|
-
|
|
5580
|
-
|
|
5581
|
-
|
|
5582
|
-
|
|
5583
|
-
|
|
5584
|
-
|
|
5585
|
-
|
|
5586
|
-
|
|
5587
|
-
|
|
5588
|
-
|
|
5589
|
-
|
|
5590
|
-
|
|
5591
|
-
|
|
5592
|
-
|
|
5593
|
-
|
|
5594
|
-
|
|
5595
|
-
|
|
5596
|
-
|
|
5597
|
-
|
|
5598
|
-
return 'Enum';
|
|
5832
|
+
// Stessa regola dell'editor del valore: un campo a `null` e' assente, non valorizzato — con
|
|
5833
|
+
// `!== undefined` un backend che serializza tutto faceva risultare **formula** ogni letterale,
|
|
5834
|
+
// e il tipo dedotto era quello sbagliato (§4.2).
|
|
5835
|
+
switch (valuedFieldOf(value)) {
|
|
5836
|
+
case 'formulaExpression':
|
|
5837
|
+
return value.formulaDataType;
|
|
5838
|
+
case 'elementReference':
|
|
5839
|
+
return this.typeOfReference(value.elementReference);
|
|
5840
|
+
case 'stringValue':
|
|
5841
|
+
return 'String';
|
|
5842
|
+
case 'integerValue':
|
|
5843
|
+
return 'Integer';
|
|
5844
|
+
case 'numberValue':
|
|
5845
|
+
return 'Number';
|
|
5846
|
+
case 'dateValue':
|
|
5847
|
+
return 'Date';
|
|
5848
|
+
case 'booleanValue':
|
|
5849
|
+
return 'Boolean';
|
|
5850
|
+
case 'enumValue':
|
|
5851
|
+
return 'Enum';
|
|
5852
|
+
default:
|
|
5853
|
+
return undefined;
|
|
5599
5854
|
}
|
|
5600
|
-
return undefined;
|
|
5601
5855
|
}
|
|
5602
5856
|
/**
|
|
5603
5857
|
* `true` quando i due lati sono incompatibili: e' `CONDITION_TYPE_MISMATCH`, un errore che
|
|
@@ -5768,7 +6022,7 @@ class ConditionEditorComponent {
|
|
|
5768
6022
|
// "confronta con questo" ma "l'esito atteso e' questo": va riscritto, non tenuto.
|
|
5769
6023
|
condition.rightValue = { booleanValue: true };
|
|
5770
6024
|
}
|
|
5771
|
-
else if (condition.rightValue?.booleanValue
|
|
6025
|
+
else if (isValued(condition.rightValue?.booleanValue) && operator !== 'EqualTo') {
|
|
5772
6026
|
condition.rightValue = undefined;
|
|
5773
6027
|
}
|
|
5774
6028
|
});
|
|
@@ -5815,8 +6069,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.27", ngImpo
|
|
|
5815
6069
|
*
|
|
5816
6070
|
* - I campi proposti passano da `GET /schema/{object}/fields?usage=filterable`, così un
|
|
5817
6071
|
* campo non filtrabile non arriva nemmeno alla validazione (`FIELD_NOT_FILTERABLE`).
|
|
5818
|
-
* La scrittura libera resta possibile
|
|
5819
|
-
* (`Cliente.Citta`)
|
|
6072
|
+
* La scrittura libera resta possibile — un catalogo incompleto non deve bloccare il campo — ma i
|
|
6073
|
+
* percorsi di relazione (`Cliente.Citta`) **si navigano**: il tipo dell'ultimo segmento si ricava
|
|
6074
|
+
* con `core/reference-path`, ed e' cio' che fa comparire l'elenco dei valori su un `Enum` (§4.4).
|
|
5820
6075
|
*/
|
|
5821
6076
|
class RecordFilterEditorComponent {
|
|
5822
6077
|
catalog = inject(FlowCatalogStore);
|
|
@@ -5838,25 +6093,25 @@ class RecordFilterEditorComponent {
|
|
|
5838
6093
|
emptyWarning = input(null, ...(ngDevMode ? [{ debugName: "emptyWarning" }] : []));
|
|
5839
6094
|
emptyWarningSeverity = input('warn', ...(ngDevMode ? [{ debugName: "emptyWarningSeverity" }] : []));
|
|
5840
6095
|
changed = output();
|
|
5841
|
-
fields = signal([], ...(ngDevMode ? [{ debugName: "fields" }] : []));
|
|
5842
6096
|
/** Tutti i campi, filtrabili o no: serve solo a riconoscere le chiavi composte (§5.7). */
|
|
5843
6097
|
allFields = signal([], ...(ngDevMode ? [{ debugName: "allFields" }] : []));
|
|
5844
6098
|
filters = computed(() => this.holder().filters ?? [], ...(ngDevMode ? [{ debugName: "filters" }] : []));
|
|
5845
6099
|
operators = computed(() => this.dictionaries.recordFilterOperators(), ...(ngDevMode ? [{ debugName: "operators" }] : []));
|
|
6100
|
+
/**
|
|
6101
|
+
* Il tipo del campo filtrato, **compresi i percorsi di relazione**: `Cliente.Citta` non e' nella
|
|
6102
|
+
* lista piatta dei campi dell'entita' e il suo tipo si ricava navigando la catena (§4.4). Senza,
|
|
6103
|
+
* un campo `Enum` raggiunto per relazione tornava una casella di testo su un insieme chiuso (§4.6).
|
|
6104
|
+
*
|
|
6105
|
+
* L'`usage` e' quello del campo — `filterable` fra i filtri — e vale per l'**ultimo** segmento: i
|
|
6106
|
+
* segmenti intermedi li chiede `resolvePath` con `any`, perche' l'uso riguarda il campo confrontato
|
|
6107
|
+
* e non la relazione che porta a lui.
|
|
6108
|
+
*/
|
|
6109
|
+
types = pathTypes(this.catalog, () => ({
|
|
6110
|
+
container: this.object() ? { kind: 'object', name: this.object() } : null,
|
|
6111
|
+
paths: this.filters().map((filter) => filter.field),
|
|
6112
|
+
usage: this.usage(),
|
|
6113
|
+
}));
|
|
5846
6114
|
constructor() {
|
|
5847
|
-
effect(() => {
|
|
5848
|
-
const object = this.object();
|
|
5849
|
-
const usage = this.usage();
|
|
5850
|
-
if (!object) {
|
|
5851
|
-
this.fields.set([]);
|
|
5852
|
-
return;
|
|
5853
|
-
}
|
|
5854
|
-
void this.catalog
|
|
5855
|
-
.listFields(object, usage)
|
|
5856
|
-
.then((list) => this.fields.set(list ?? []))
|
|
5857
|
-
// Catalogo non disponibile: il campo resta scrivibile a mano.
|
|
5858
|
-
.catch(() => this.fields.set([]));
|
|
5859
|
-
});
|
|
5860
6115
|
effect(() => {
|
|
5861
6116
|
const object = this.object();
|
|
5862
6117
|
if (!object) {
|
|
@@ -5996,18 +6251,17 @@ class RecordFilterEditorComponent {
|
|
|
5996
6251
|
}
|
|
5997
6252
|
/**
|
|
5998
6253
|
* Il tipo del campo, per proporre il controllo giusto sul valore: e' il motivo per cui il
|
|
5999
|
-
* catalogo resta
|
|
6254
|
+
* catalogo resta interrogato qui, e non solo dentro il picker.
|
|
6000
6255
|
*/
|
|
6001
6256
|
fieldDataType(filter) {
|
|
6002
|
-
return this.
|
|
6257
|
+
return this.types.dataTypeOf(filter.field);
|
|
6003
6258
|
}
|
|
6004
6259
|
/**
|
|
6005
6260
|
* Il tipo concreto del campo: su un campo `Enum` e' cio' che permette di proporre i valori
|
|
6006
|
-
* ammessi invece di farli digitare (§4.6).
|
|
6007
|
-
* e resta `undefined`, come per il tipo.
|
|
6261
|
+
* ammessi invece di farli digitare (§4.6).
|
|
6008
6262
|
*/
|
|
6009
6263
|
fieldObjectType(filter) {
|
|
6010
|
-
return this.
|
|
6264
|
+
return this.types.objectTypeOf(filter.field);
|
|
6011
6265
|
}
|
|
6012
6266
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.27", ngImport: i0, type: RecordFilterEditorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
6013
6267
|
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.27", type: RecordFilterEditorComponent, isStandalone: true, selector: "fb-record-filter-editor", inputs: { holder: { classPropertyName: "holder", publicName: "holder", isSignal: true, isRequired: true, transformFunction: null }, object: { classPropertyName: "object", publicName: "object", isSignal: true, isRequired: false, transformFunction: null }, title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, usage: { classPropertyName: "usage", publicName: "usage", isSignal: true, isRequired: false, transformFunction: null }, supportsLogic: { classPropertyName: "supportsLogic", publicName: "supportsLogic", isSignal: true, isRequired: false, transformFunction: null }, supportsFormula: { classPropertyName: "supportsFormula", publicName: "supportsFormula", isSignal: true, isRequired: false, transformFunction: null }, emptyWarning: { classPropertyName: "emptyWarning", publicName: "emptyWarning", isSignal: true, isRequired: false, transformFunction: null }, emptyWarningSeverity: { classPropertyName: "emptyWarningSeverity", publicName: "emptyWarningSeverity", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { changed: "changed" }, ngImport: i0, template: "<fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">{{ title() }}</legend>\r\n\r\n @if (!object()) {\r\n <p class=\"fb-field__hint\">Scegli prima un oggetto per poter filtrare sui suoi campi.</p>\r\n }\r\n\r\n @if (supportsLogic()) {\r\n <div class=\"fb-filter__modes\" role=\"group\" aria-label=\"Logica dei filtri\">\r\n <button\r\n type=\"button\"\r\n class=\"fb-filter__mode\"\r\n [class.fb-filter__mode--active]=\"logicMode() === 'and'\"\r\n (click)=\"setLogicMode('and')\"\r\n >\r\n Tutti (AND)\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-filter__mode\"\r\n [class.fb-filter__mode--active]=\"logicMode() === 'or'\"\r\n (click)=\"setLogicMode('or')\"\r\n >\r\n Almeno uno (OR)\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-filter__mode\"\r\n [class.fb-filter__mode--active]=\"logicMode() === 'custom'\"\r\n (click)=\"setLogicMode('custom')\"\r\n >\r\n Espressione\r\n </button>\r\n @if (supportsFormula()) {\r\n <button\r\n type=\"button\"\r\n class=\"fb-filter__mode\"\r\n [class.fb-filter__mode--active]=\"logicMode() === 'formula'\"\r\n (click)=\"setLogicMode('formula')\"\r\n >\r\n Formula\r\n </button>\r\n }\r\n </div>\r\n } @else {\r\n <!-- Qui il modello non ha `filterLogic`: mostrarlo lo farebbe perdere al salvataggio. -->\r\n <p class=\"fb-field__hint\">Su questo elemento i filtri sono sempre combinati in AND.</p>\r\n }\r\n\r\n @if (supportsLogic() && logicMode() === 'custom') {\r\n <div class=\"fb-field\">\r\n <input\r\n class=\"fb-input fb-input--mono\"\r\n [value]=\"customLogic()\"\r\n placeholder=\"1 AND (2 OR 3)\"\r\n aria-label=\"Espressione sugli indici dei filtri\"\r\n (input)=\"setCustomLogic($any($event.target).value)\"\r\n />\r\n </div>\r\n }\r\n\r\n @if (supportsFormula() && logicMode() === 'formula') {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Formula di filtro</label>\r\n <textarea\r\n class=\"fb-textarea fb-input--mono\"\r\n [value]=\"holder().filterFormula || ''\"\r\n (input)=\"setFormula($any($event.target).value)\"\r\n ></textarea>\r\n <p class=\"fb-field__hint\">Valutata dal motore di regole, in alternativa ai filtri.</p>\r\n </div>\r\n }\r\n\r\n @if (identifierNotFilterable()) {\r\n <p class=\"fb-field__hint\">\r\n Su questo oggetto l\u2019identificativo non e\u2019 filtrabile: la chiave e\u2019 composta e porta la propria\r\n forma canonica. Filtra per le colonne della chiave, una per colonna.\r\n </p>\r\n }\r\n\r\n @if (showEmptyWarning()) {\r\n <p\r\n class=\"fb-callout\"\r\n [class.fb-callout--warn]=\"emptyWarningSeverity() === 'warn'\"\r\n [class.fb-callout--error]=\"emptyWarningSeverity() === 'error'\"\r\n >\r\n {{ emptyWarning() }}\r\n </p>\r\n }\r\n\r\n <div class=\"fb-list\">\r\n @for (filter of filters(); track $index) {\r\n <div class=\"fb-list__item\">\r\n <div class=\"fb-list__header\">\r\n <span class=\"fb-list__index\">{{ $index + 1 }}</span>\r\n <span class=\"fb-list__spacer\"></span>\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-btn--ghost fb-btn--icon\"\r\n aria-label=\"Rimuovi il filtro\"\r\n (click)=\"removeFilter($index)\"\r\n >\r\n \u00D7\r\n </button>\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Campo</label>\r\n <fb-field-picker\r\n [value]=\"filter.field\"\r\n [object]=\"object()\"\r\n [usage]=\"usage()\"\r\n placeholder=\"Scrivi o scegli un campo\"\r\n (valueChange)=\"setField($index, $event ?? '')\"\r\n />\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Operatore</label>\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"filter.operator || ''\"\r\n (change)=\"setOperator($index, $any($event.target).value)\"\r\n >\r\n @for (operator of operators(); track operator.value) {\r\n <option [value]=\"operator.value\">{{ operator.label }}</option>\r\n }\r\n </select>\r\n </div>\r\n\r\n @if (isNullOperator(filter)) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Esito atteso</label>\r\n <div class=\"fb-filter__modes\">\r\n <button\r\n type=\"button\"\r\n class=\"fb-filter__mode\"\r\n [class.fb-filter__mode--active]=\"nullExpectation(filter)\"\r\n (click)=\"setNullExpectation($index, true)\"\r\n >\r\n \u00E8 vuoto\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"fb-filter__mode\"\r\n [class.fb-filter__mode--active]=\"!nullExpectation(filter)\"\r\n (click)=\"setNullExpectation($index, false)\"\r\n >\r\n non \u00E8 vuoto\r\n </button>\r\n </div>\r\n </div>\r\n } @else {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Valore</label>\r\n <fb-value-editor\r\n [value]=\"filter.value\"\r\n [dataType]=\"fieldDataType(filter)\"\r\n [objectType]=\"fieldObjectType(filter)\"\r\n label=\"Valore del filtro\"\r\n [allowFormula]=\"false\"\r\n (valueChange)=\"setValue($index, $event)\"\r\n />\r\n </div>\r\n }\r\n </div>\r\n } @empty {\r\n <p class=\"fb-empty\">Nessun filtro.</p>\r\n }\r\n </div>\r\n\r\n <button type=\"button\" class=\"fb-btn\" (click)=\"addFilter()\">Aggiungi filtro</button>\r\n</fieldset>\r\n", styles: [":host{display:block}.fb-filter__modes{display:flex;flex-wrap:wrap;gap:3px;margin-bottom:8px}.fb-filter__mode{padding:3px 8px;border:1px solid var(--fb-border-subtle, #e6e9ee);border-radius:12px;background:transparent;color:var(--fb-text-muted, #667085);font:inherit;font-size:11px;cursor:pointer}.fb-filter__mode:hover{background:var(--fb-surface-alt, #f8f9fb)}.fb-filter__mode--active{border-color:var(--fb-accent, #2f6feb);background:color-mix(in srgb,var(--fb-accent, #2f6feb) 10%,transparent);color:var(--fb-accent, #2f6feb);font-weight:600}\n"], dependencies: [{ kind: "component", type: FieldPickerComponent, selector: "fb-field-picker", inputs: ["value", "object", "usage", "label", "placeholder", "disabled"], outputs: ["valueChange"] }, { kind: "component", type: ValueEditorComponent, selector: "fb-value-editor", inputs: ["value", "label", "dataType", "objectType", "isCollection", "disabled", "allowFormula"], outputs: ["valueChange"] }, { kind: "directive", type: SelectValueDirective, selector: "select[fbValue]", inputs: ["fbValue"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
@@ -6555,12 +6809,25 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.27", ngImpo
|
|
|
6555
6809
|
class AssignmentInspectorComponent extends NodeInspectorBase {
|
|
6556
6810
|
elementType = 'Assignment';
|
|
6557
6811
|
api = inject(FlowBuilderApi);
|
|
6812
|
+
catalog = inject(FlowCatalogStore);
|
|
6558
6813
|
/**
|
|
6559
6814
|
* I riferimenti **scrivibili**, per conoscere il tipo della destinazione. Non e' un doppione del
|
|
6560
6815
|
* picker: serve il tipo, ed e' cio' che permette all'editor del valore di mostrare il controllo
|
|
6561
6816
|
* giusto — e su un `Enum` di proporre i valori del tipo invece di farli digitare (§4.6).
|
|
6562
6817
|
*/
|
|
6563
6818
|
writableReferences = signal([], ...(ngDevMode ? [{ debugName: "writableReferences" }] : []));
|
|
6819
|
+
/**
|
|
6820
|
+
* Il tipo della destinazione, risorsa o **percorso**. L'elenco porta le radici e non i percorsi
|
|
6821
|
+
* (§6.4): su `Richiesta.Stato` la corrispondenza esatta non trova niente, e il tipo si ricava
|
|
6822
|
+
* navigando la classe — altrimenti un membro `Enum` resterebbe una casella di testo (§4.6, §4.7.1).
|
|
6823
|
+
*
|
|
6824
|
+
* `usage` resta quello di default (`any`): un campo di `$Record` si assegna se e' createable
|
|
6825
|
+
* **oppure** updateable, e qui il contesto non dice quale dei due (§5.7).
|
|
6826
|
+
*/
|
|
6827
|
+
targetTypes = referenceTypes(this.catalog, (dataType) => this.dictionaries.isStructure(dataType), () => ({
|
|
6828
|
+
references: this.writableReferences(),
|
|
6829
|
+
values: this.items().map((item) => item.assignToReference),
|
|
6830
|
+
}));
|
|
6564
6831
|
constructor() {
|
|
6565
6832
|
super();
|
|
6566
6833
|
effect(() => {
|
|
@@ -6576,22 +6843,11 @@ class AssignmentInspectorComponent extends NodeInspectorBase {
|
|
|
6576
6843
|
.catch(() => this.writableReferences.set([]));
|
|
6577
6844
|
});
|
|
6578
6845
|
}
|
|
6579
|
-
/**
|
|
6580
|
-
* Il tipo della destinazione, solo per corrispondenza **esatta** del nome: un percorso navigato
|
|
6581
|
-
* (`Cliente.Stato`) non e' in elenco, e lì il tipo resta ignoto invece di essere indovinato.
|
|
6582
|
-
*/
|
|
6583
|
-
targetOf(item) {
|
|
6584
|
-
const reference = item.assignToReference;
|
|
6585
|
-
if (!reference) {
|
|
6586
|
-
return undefined;
|
|
6587
|
-
}
|
|
6588
|
-
return this.writableReferences().find((entry) => entry.name === reference);
|
|
6589
|
-
}
|
|
6590
6846
|
targetDataType(item) {
|
|
6591
|
-
return this.
|
|
6847
|
+
return this.targetTypes.dataTypeOf(item.assignToReference);
|
|
6592
6848
|
}
|
|
6593
6849
|
targetObjectType(item) {
|
|
6594
|
-
return this.
|
|
6850
|
+
return this.targetTypes.objectTypeOf(item.assignToReference);
|
|
6595
6851
|
}
|
|
6596
6852
|
assignment = computed(() => this.node(), ...(ngDevMode ? [{ debugName: "assignment" }] : []));
|
|
6597
6853
|
items = computed(() => this.assignment().assignmentItems ?? [], ...(ngDevMode ? [{ debugName: "items" }] : []));
|
|
@@ -6679,11 +6935,11 @@ class AssignmentInspectorComponent extends NodeInspectorBase {
|
|
|
6679
6935
|
return !operator.startsWith('Remove') || operator === 'RemovePosition' || operator === 'RemoveUncommon';
|
|
6680
6936
|
}
|
|
6681
6937
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.27", ngImport: i0, type: AssignmentInspectorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
6682
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.27", type: AssignmentInspectorComponent, isStandalone: true, selector: "fb-assignment-inspector", usesInheritance: true, ngImport: i0, template: "<fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Operazioni</legend>\r\n <p class=\"fb-section__note\">\r\n Le operazioni sono eseguite <strong>nell\u2019ordine in cui compaiono</strong>: spostarne una cambia il\r\n risultato.\r\n </p>\r\n\r\n <div class=\"fb-list\">\r\n @for (item of items(); track $index) {\r\n <div class=\"fb-list__item\">\r\n <div class=\"fb-list__header\">\r\n <span class=\"fb-list__index\">{{ $index + 1 }}</span>\r\n <span class=\"fb-list__spacer\"></span>\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-btn--ghost fb-btn--icon\"\r\n aria-label=\"Sposta su\"\r\n [disabled]=\"$first\"\r\n (click)=\"moveItem($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=\"Sposta giu\u2019\"\r\n [disabled]=\"$last\"\r\n (click)=\"moveItem($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 l\u2019operazione\"\r\n (click)=\"removeItem($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 fb-field__label--required\">Destinazione</label>\r\n <!--\r\n Solo destinazioni scrivibili: una costante o una formula produrrebbero\r\n TARGET_NOT_WRITABLE. Le globali assegnabili \u2014 `$Flow.CurrentStage`,\r\n `$Flow.ActiveStages` e quelle dichiarate scrivibili dall'host \u2014 arrivano gi\u00E0\r\n dalla primitiva, non si aggiungono qui.\r\n -->\r\n <fb-reference-picker\r\n [value]=\"item.assignToReference\"\r\n [writableOnly]=\"true\"\r\n placeholder=\"Scegli una variabile\"\r\n (valueChange)=\"setTarget($index, $event)\"\r\n />\r\n <p class=\"fb-field__hint\">\r\n Puoi scrivere anche un campo di un record: <code>Cliente.Email</code>, <code>$Record.Stato</code>.\r\n </p>\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Operazione</label>\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"item.operator || ''\"\r\n (change)=\"setOperator($index, $any($event.target).value)\"\r\n >\r\n @for (operator of operators(); track operator.value) {\r\n <option [value]=\"operator.value\">{{ operator.label }}</option>\r\n }\r\n </select>\r\n @if (operatorDescription(item.operator)) {\r\n <p class=\"fb-field__hint\">{{ operatorDescription(item.operator) }}</p>\r\n }\r\n @if (addSemanticsHint(item)) {\r\n <p class=\"fb-field__hint\">{{ addSemanticsHint(item) }}</p>\r\n }\r\n @if (expectsCollection(item)) {\r\n <p class=\"fb-field__hint fb-field__hint--warn\">\r\n Questa operazione ha senso solo su una destinazione collection: su una variabile singola non\r\n fallisce, semplicemente non fa nulla.\r\n </p>\r\n }\r\n </div>\r\n\r\n @if (needsValue(item)) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Valore</label>\r\n <!--\r\n Il tipo della destinazione guida il controllo: su un Enum i valori proponibili sono\r\n quelli del suo tipo, e farli digitare e' il modo piu' facile di scriverne uno che il\r\n runtime non riconosce (\u00A74.6). Ignoto il tipo, il campo resta generico.\r\n -->\r\n <fb-value-editor\r\n [value]=\"item.value\"\r\n label=\"Valore\"\r\n [dataType]=\"
|
|
6938
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.27", type: AssignmentInspectorComponent, isStandalone: true, selector: "fb-assignment-inspector", usesInheritance: true, ngImport: i0, template: "<fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Operazioni</legend>\r\n <p class=\"fb-section__note\">\r\n Le operazioni sono eseguite <strong>nell\u2019ordine in cui compaiono</strong>: spostarne una cambia il\r\n risultato.\r\n </p>\r\n\r\n <div class=\"fb-list\">\r\n @for (item of items(); track $index) {\r\n <div class=\"fb-list__item\">\r\n <div class=\"fb-list__header\">\r\n <span class=\"fb-list__index\">{{ $index + 1 }}</span>\r\n <span class=\"fb-list__spacer\"></span>\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-btn--ghost fb-btn--icon\"\r\n aria-label=\"Sposta su\"\r\n [disabled]=\"$first\"\r\n (click)=\"moveItem($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=\"Sposta giu\u2019\"\r\n [disabled]=\"$last\"\r\n (click)=\"moveItem($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 l\u2019operazione\"\r\n (click)=\"removeItem($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 fb-field__label--required\">Destinazione</label>\r\n <!--\r\n Solo destinazioni scrivibili: una costante o una formula produrrebbero\r\n TARGET_NOT_WRITABLE. Le globali assegnabili \u2014 `$Flow.CurrentStage`,\r\n `$Flow.ActiveStages` e quelle dichiarate scrivibili dall'host \u2014 arrivano gi\u00E0\r\n dalla primitiva, non si aggiungono qui.\r\n -->\r\n <fb-reference-picker\r\n [value]=\"item.assignToReference\"\r\n [writableOnly]=\"true\"\r\n placeholder=\"Scegli una variabile\"\r\n (valueChange)=\"setTarget($index, $event)\"\r\n />\r\n <p class=\"fb-field__hint\">\r\n Puoi scrivere anche un campo di un record: <code>Cliente.Email</code>, <code>$Record.Stato</code>.\r\n </p>\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Operazione</label>\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"item.operator || ''\"\r\n (change)=\"setOperator($index, $any($event.target).value)\"\r\n >\r\n @for (operator of operators(); track operator.value) {\r\n <option [value]=\"operator.value\">{{ operator.label }}</option>\r\n }\r\n </select>\r\n @if (operatorDescription(item.operator)) {\r\n <p class=\"fb-field__hint\">{{ operatorDescription(item.operator) }}</p>\r\n }\r\n @if (addSemanticsHint(item)) {\r\n <p class=\"fb-field__hint\">{{ addSemanticsHint(item) }}</p>\r\n }\r\n @if (expectsCollection(item)) {\r\n <p class=\"fb-field__hint fb-field__hint--warn\">\r\n Questa operazione ha senso solo su una destinazione collection: su una variabile singola non\r\n fallisce, semplicemente non fa nulla.\r\n </p>\r\n }\r\n </div>\r\n\r\n @if (needsValue(item)) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Valore</label>\r\n <!--\r\n Il tipo della destinazione guida il controllo: su un Enum i valori proponibili sono\r\n quelli del suo tipo, e farli digitare e' il modo piu' facile di scriverne uno che il\r\n runtime non riconosce (\u00A74.6). Vale anche dentro una classe \u2014 \u00ABRichiesta.Stato\u00BB \u2014 perche'\r\n il tipo si ricava navigando i membri (\u00A74.7.1). Ignoto il tipo, il campo resta generico.\r\n -->\r\n <fb-value-editor\r\n [value]=\"item.value\"\r\n label=\"Valore\"\r\n [dataType]=\"targetDataType(item)\"\r\n [objectType]=\"targetObjectType(item)\"\r\n (valueChange)=\"setValue($index, $event)\"\r\n />\r\n </div>\r\n }\r\n </div>\r\n } @empty {\r\n <p class=\"fb-empty\">\r\n Nessuna operazione: un Assignment senza operazioni non fa nulla (ASSIGNMENT_WITHOUT_ITEMS).\r\n </p>\r\n }\r\n </div>\r\n\r\n <button type=\"button\" class=\"fb-btn\" (click)=\"addItem()\">Aggiungi operazione</button>\r\n</fieldset>\r\n\r\n<fb-connector-editor\r\n [nodeName]=\"name()\"\r\n [node]=\"node()\"\r\n [outlets]=\"outlets()\"\r\n (connectorChanged)=\"onConnectorChanged($event)\"\r\n/>\r\n", dependencies: [{ kind: "component", type: ConnectorEditorComponent, selector: "fb-connector-editor", inputs: ["nodeName", "node", "outlets", "title"], outputs: ["connectorChanged"] }, { kind: "component", type: ReferencePickerComponent, selector: "fb-reference-picker", inputs: ["value", "label", "placeholder", "disabled", "dataType", "isCollection", "objectType", "writableOnly", "elementsOnly"], outputs: ["valueChange"] }, { kind: "component", type: ValueEditorComponent, selector: "fb-value-editor", inputs: ["value", "label", "dataType", "objectType", "isCollection", "disabled", "allowFormula"], outputs: ["valueChange"] }, { kind: "directive", type: SelectValueDirective, selector: "select[fbValue]", inputs: ["fbValue"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
6683
6939
|
}
|
|
6684
6940
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.27", ngImport: i0, type: AssignmentInspectorComponent, decorators: [{
|
|
6685
6941
|
type: Component,
|
|
6686
|
-
args: [{ selector: 'fb-assignment-inspector', standalone: true, imports: [ConnectorEditorComponent, ReferencePickerComponent, ValueEditorComponent, SelectValueDirective], changeDetection: ChangeDetectionStrategy.OnPush, template: "<fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Operazioni</legend>\r\n <p class=\"fb-section__note\">\r\n Le operazioni sono eseguite <strong>nell\u2019ordine in cui compaiono</strong>: spostarne una cambia il\r\n risultato.\r\n </p>\r\n\r\n <div class=\"fb-list\">\r\n @for (item of items(); track $index) {\r\n <div class=\"fb-list__item\">\r\n <div class=\"fb-list__header\">\r\n <span class=\"fb-list__index\">{{ $index + 1 }}</span>\r\n <span class=\"fb-list__spacer\"></span>\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-btn--ghost fb-btn--icon\"\r\n aria-label=\"Sposta su\"\r\n [disabled]=\"$first\"\r\n (click)=\"moveItem($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=\"Sposta giu\u2019\"\r\n [disabled]=\"$last\"\r\n (click)=\"moveItem($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 l\u2019operazione\"\r\n (click)=\"removeItem($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 fb-field__label--required\">Destinazione</label>\r\n <!--\r\n Solo destinazioni scrivibili: una costante o una formula produrrebbero\r\n TARGET_NOT_WRITABLE. Le globali assegnabili \u2014 `$Flow.CurrentStage`,\r\n `$Flow.ActiveStages` e quelle dichiarate scrivibili dall'host \u2014 arrivano gi\u00E0\r\n dalla primitiva, non si aggiungono qui.\r\n -->\r\n <fb-reference-picker\r\n [value]=\"item.assignToReference\"\r\n [writableOnly]=\"true\"\r\n placeholder=\"Scegli una variabile\"\r\n (valueChange)=\"setTarget($index, $event)\"\r\n />\r\n <p class=\"fb-field__hint\">\r\n Puoi scrivere anche un campo di un record: <code>Cliente.Email</code>, <code>$Record.Stato</code>.\r\n </p>\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Operazione</label>\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"item.operator || ''\"\r\n (change)=\"setOperator($index, $any($event.target).value)\"\r\n >\r\n @for (operator of operators(); track operator.value) {\r\n <option [value]=\"operator.value\">{{ operator.label }}</option>\r\n }\r\n </select>\r\n @if (operatorDescription(item.operator)) {\r\n <p class=\"fb-field__hint\">{{ operatorDescription(item.operator) }}</p>\r\n }\r\n @if (addSemanticsHint(item)) {\r\n <p class=\"fb-field__hint\">{{ addSemanticsHint(item) }}</p>\r\n }\r\n @if (expectsCollection(item)) {\r\n <p class=\"fb-field__hint fb-field__hint--warn\">\r\n Questa operazione ha senso solo su una destinazione collection: su una variabile singola non\r\n fallisce, semplicemente non fa nulla.\r\n </p>\r\n }\r\n </div>\r\n\r\n @if (needsValue(item)) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Valore</label>\r\n <!--\r\n Il tipo della destinazione guida il controllo: su un Enum i valori proponibili sono\r\n quelli del suo tipo, e farli digitare e' il modo piu' facile di scriverne uno che il\r\n runtime non riconosce (\u00A74.6). Ignoto il tipo, il campo resta generico.\r\n -->\r\n <fb-value-editor\r\n [value]=\"item.value\"\r\n label=\"Valore\"\r\n [dataType]=\"
|
|
6942
|
+
args: [{ selector: 'fb-assignment-inspector', standalone: true, imports: [ConnectorEditorComponent, ReferencePickerComponent, ValueEditorComponent, SelectValueDirective], changeDetection: ChangeDetectionStrategy.OnPush, template: "<fieldset class=\"fb-section\">\r\n <legend class=\"fb-section__title\">Operazioni</legend>\r\n <p class=\"fb-section__note\">\r\n Le operazioni sono eseguite <strong>nell\u2019ordine in cui compaiono</strong>: spostarne una cambia il\r\n risultato.\r\n </p>\r\n\r\n <div class=\"fb-list\">\r\n @for (item of items(); track $index) {\r\n <div class=\"fb-list__item\">\r\n <div class=\"fb-list__header\">\r\n <span class=\"fb-list__index\">{{ $index + 1 }}</span>\r\n <span class=\"fb-list__spacer\"></span>\r\n <button\r\n type=\"button\"\r\n class=\"fb-btn fb-btn--ghost fb-btn--icon\"\r\n aria-label=\"Sposta su\"\r\n [disabled]=\"$first\"\r\n (click)=\"moveItem($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=\"Sposta giu\u2019\"\r\n [disabled]=\"$last\"\r\n (click)=\"moveItem($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 l\u2019operazione\"\r\n (click)=\"removeItem($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 fb-field__label--required\">Destinazione</label>\r\n <!--\r\n Solo destinazioni scrivibili: una costante o una formula produrrebbero\r\n TARGET_NOT_WRITABLE. Le globali assegnabili \u2014 `$Flow.CurrentStage`,\r\n `$Flow.ActiveStages` e quelle dichiarate scrivibili dall'host \u2014 arrivano gi\u00E0\r\n dalla primitiva, non si aggiungono qui.\r\n -->\r\n <fb-reference-picker\r\n [value]=\"item.assignToReference\"\r\n [writableOnly]=\"true\"\r\n placeholder=\"Scegli una variabile\"\r\n (valueChange)=\"setTarget($index, $event)\"\r\n />\r\n <p class=\"fb-field__hint\">\r\n Puoi scrivere anche un campo di un record: <code>Cliente.Email</code>, <code>$Record.Stato</code>.\r\n </p>\r\n </div>\r\n\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Operazione</label>\r\n <select\r\n class=\"fb-select\"\r\n [fbValue]=\"item.operator || ''\"\r\n (change)=\"setOperator($index, $any($event.target).value)\"\r\n >\r\n @for (operator of operators(); track operator.value) {\r\n <option [value]=\"operator.value\">{{ operator.label }}</option>\r\n }\r\n </select>\r\n @if (operatorDescription(item.operator)) {\r\n <p class=\"fb-field__hint\">{{ operatorDescription(item.operator) }}</p>\r\n }\r\n @if (addSemanticsHint(item)) {\r\n <p class=\"fb-field__hint\">{{ addSemanticsHint(item) }}</p>\r\n }\r\n @if (expectsCollection(item)) {\r\n <p class=\"fb-field__hint fb-field__hint--warn\">\r\n Questa operazione ha senso solo su una destinazione collection: su una variabile singola non\r\n fallisce, semplicemente non fa nulla.\r\n </p>\r\n }\r\n </div>\r\n\r\n @if (needsValue(item)) {\r\n <div class=\"fb-field\">\r\n <label class=\"fb-field__label\">Valore</label>\r\n <!--\r\n Il tipo della destinazione guida il controllo: su un Enum i valori proponibili sono\r\n quelli del suo tipo, e farli digitare e' il modo piu' facile di scriverne uno che il\r\n runtime non riconosce (\u00A74.6). Vale anche dentro una classe \u2014 \u00ABRichiesta.Stato\u00BB \u2014 perche'\r\n il tipo si ricava navigando i membri (\u00A74.7.1). Ignoto il tipo, il campo resta generico.\r\n -->\r\n <fb-value-editor\r\n [value]=\"item.value\"\r\n label=\"Valore\"\r\n [dataType]=\"targetDataType(item)\"\r\n [objectType]=\"targetObjectType(item)\"\r\n (valueChange)=\"setValue($index, $event)\"\r\n />\r\n </div>\r\n }\r\n </div>\r\n } @empty {\r\n <p class=\"fb-empty\">\r\n Nessuna operazione: un Assignment senza operazioni non fa nulla (ASSIGNMENT_WITHOUT_ITEMS).\r\n </p>\r\n }\r\n </div>\r\n\r\n <button type=\"button\" class=\"fb-btn\" (click)=\"addItem()\">Aggiungi operazione</button>\r\n</fieldset>\r\n\r\n<fb-connector-editor\r\n [nodeName]=\"name()\"\r\n [node]=\"node()\"\r\n [outlets]=\"outlets()\"\r\n (connectorChanged)=\"onConnectorChanged($event)\"\r\n/>\r\n" }]
|
|
6687
6943
|
}], ctorParameters: () => [] });
|
|
6688
6944
|
|
|
6689
6945
|
/**
|
|
@@ -10376,5 +10632,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.27", ngImpo
|
|
|
10376
10632
|
* Generated bundle index. Do not edit.
|
|
10377
10633
|
*/
|
|
10378
10634
|
|
|
10379
|
-
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, 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, 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, variantFieldOf, variantOf, variantPresetOf };
|
|
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 };
|
|
10380
10636
|
//# sourceMappingURL=esfaenza-flow-builder.mjs.map
|