@esfaenza/flow-builder 20.3.12 → 20.3.13
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 +12 -4
- package/fesm2022/esfaenza-flow-builder.mjs +305 -96
- package/fesm2022/esfaenza-flow-builder.mjs.map +1 -1
- package/index.d.ts +33 -24
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -334,10 +334,18 @@ vuoto resta "non lo so" — primitiva non esposta, tipo senza valori dichiarati,
|
|
|
334
334
|
da scegliere — e allora il valore si digita; con l'elenco popolato un valore fuori elenco e' un
|
|
335
335
|
**avviso**, perche' nessun codice della §7 lo blocca ed e' l'editor ad accorgersene prima del runtime.
|
|
336
336
|
|
|
337
|
-
Perche' l'editor conosca il tipo della destinazione serve saperlo
|
|
338
|
-
`
|
|
339
|
-
|
|
340
|
-
|
|
337
|
+
Perche' l'editor conosca il tipo della destinazione serve saperlo, e i percorsi non fanno eccezione:
|
|
338
|
+
`POST /flows/references` elenca le **radici** e non i percorsi, quindi su `Richiesta.Stato` la
|
|
339
|
+
corrispondenza esatta del nome non trova niente. Il tipo si ricava allora navigando la catena —
|
|
340
|
+
`core/reference-path` innesta l'elenco dei riferimenti su `core/path-navigation`: trova il prefisso
|
|
341
|
+
dichiarato piu' lungo (§4.1), naviga il resto un segmento alla volta e prende il tipo dell'**ultimo**
|
|
342
|
+
segmento. Lo usano l'Assignment (su `getWritableReferences`), le condizioni e i filtri sui record —
|
|
343
|
+
dove la radice e' l'entita', quindi `Cliente.Citta` si risolve come il campo che e'. In un Transform
|
|
344
|
+
il tipo viene dal **membro** della classe di destinazione, che l'inspector già descrive.
|
|
345
|
+
|
|
346
|
+
Dove il tipo non si sa — catena interrotta, catalogo assente, segmento inesistente, percorso radicato
|
|
347
|
+
sull'output automatico di un elemento (che il backend non verifica, §7) — il campo resta generico
|
|
348
|
+
invece di indovinare: un elenco chiuso di valori sbagliati e' peggio di una casella di testo.
|
|
341
349
|
|
|
342
350
|
Lo stesso controllo, nella variante `<fb-name-picker>`, vale per **ogni altro nome che il flow
|
|
343
351
|
prende da fuori**: tipo di action e action, script, form, i nomi dei loro parametri di ingresso
|
|
@@ -3781,6 +3781,244 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.27", ngImpo
|
|
|
3781
3781
|
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
3782
|
}], propDecorators: { processType: [{ type: i0.Input, args: [{ isSignal: true, alias: "processType", required: false }] }], elementPicked: [{ type: i0.Output, args: ["elementPicked"] }] } });
|
|
3783
3783
|
|
|
3784
|
+
/**
|
|
3785
|
+
* Il tipo di cio' che un riferimento designa — FRONTEND.md §4.1, §4.3, §4.4, §4.6, §4.7.1.
|
|
3786
|
+
*
|
|
3787
|
+
* `POST /flows/references` elenca le **radici**, non i percorsi: `Richiesta` c'e', `Richiesta.Stato`
|
|
3788
|
+
* no. Cercare la corrispondenza esatta del valore in quell'elenco basta per una risorsa e non basta
|
|
3789
|
+
* per un percorso, e la conseguenza non e' cosmetica: senza il tipo dell'ultimo segmento un membro
|
|
3790
|
+
* `Enum` diventa una casella di testo su un insieme chiuso (§4.6) e un confronto fra tipi
|
|
3791
|
+
* incompatibili non si vede prima del backend (§4.3).
|
|
3792
|
+
*
|
|
3793
|
+
* Il tipo di un percorso pero' si **ricava**: e' `core/path-navigation` che naviga la catena, e qui
|
|
3794
|
+
* c'e' l'innesto fra i due mondi — dall'elenco dei riferimenti alla tappa da cui partire, e dalla
|
|
3795
|
+
* tappa all'ultimo segmento. Sta in `core/` e non in un inspector perche' i chiamanti sono tre
|
|
3796
|
+
* (Assignment, condizioni, filtri) e la regola del **prefisso dichiarato piu' lungo** e' una sola:
|
|
3797
|
+
* duplicarla vorrebbe dire farla divergere.
|
|
3798
|
+
*
|
|
3799
|
+
* Le due asimmetrie da tenere a mente:
|
|
3800
|
+
*
|
|
3801
|
+
* - **Non ogni radice e' navigabile.** Un output automatico dichiara il proprio tipo, ma la
|
|
3802
|
+
* specifica tiene i percorsi che ne partono fuori dalla validazione (§7): dedurne il tipo qui
|
|
3803
|
+
* significherebbe far dire all'editor cio' che il backend non verifica. Stessa scelta del
|
|
3804
|
+
* `reference-picker`, e le due devono restare la stessa.
|
|
3805
|
+
* - **"Non lo so" resta "non lo so".** Catena interrotta, catalogo assente, segmento inesistente:
|
|
3806
|
+
* l'esito e' `null` e il chiamante ricade sul controllo generico. Un tipo indovinato e' peggio
|
|
3807
|
+
* di un tipo mancante, perche' fa comparire un elenco chiuso di valori sbagliati.
|
|
3808
|
+
*/
|
|
3809
|
+
/**
|
|
3810
|
+
* Il riferimento dichiarato piu' **lungo** che sia un prefisso del valore, sui confini del punto.
|
|
3811
|
+
*
|
|
3812
|
+
* Non e' il primo segmento: un nome dichiarato puo' contenere il punto, e le globali sono il caso
|
|
3813
|
+
* per cui la regola esiste — di `$User.Anagrafica.Citta` il dizionario dichiara `$User.Anagrafica`
|
|
3814
|
+
* (§4.1: vince il prefisso dichiarato piu' lungo).
|
|
3815
|
+
*/
|
|
3816
|
+
function declaredPrefixOf(references, value) {
|
|
3817
|
+
const trimmed = (value ?? '').trim();
|
|
3818
|
+
if (!trimmed) {
|
|
3819
|
+
return undefined;
|
|
3820
|
+
}
|
|
3821
|
+
let best;
|
|
3822
|
+
for (const candidate of references) {
|
|
3823
|
+
if (!candidate.name || (trimmed !== candidate.name && !trimmed.startsWith(`${candidate.name}.`))) {
|
|
3824
|
+
continue;
|
|
3825
|
+
}
|
|
3826
|
+
if (!best || candidate.name.length > best.name.length) {
|
|
3827
|
+
best = candidate;
|
|
3828
|
+
}
|
|
3829
|
+
}
|
|
3830
|
+
return best;
|
|
3831
|
+
}
|
|
3832
|
+
/**
|
|
3833
|
+
* La risorsa da cui il valore sta navigando, se e solo se il percorso e' verificabile: una
|
|
3834
|
+
* `Structure` con la sua classe, un record con la sua entita'. Un output automatico e' escluso di
|
|
3835
|
+
* proposito (vedi l'intestazione), e una collection non si naviga — il percorso designa l'insieme.
|
|
3836
|
+
*/
|
|
3837
|
+
function navigableRootOf(references, value, isStructure) {
|
|
3838
|
+
const reference = declaredPrefixOf(references, value);
|
|
3839
|
+
if (!reference || reference.kind === 'ElementOutput' || !reference.objectType || reference.isCollection) {
|
|
3840
|
+
return undefined;
|
|
3841
|
+
}
|
|
3842
|
+
const trimmed = (value ?? '').trim();
|
|
3843
|
+
const path = trimmed.length > reference.name.length ? trimmed.slice(reference.name.length + 1) : '';
|
|
3844
|
+
if (isStructure(reference.dataType)) {
|
|
3845
|
+
return { reference, container: { kind: 'structure', name: reference.objectType }, path };
|
|
3846
|
+
}
|
|
3847
|
+
return reference.dataType === 'Object'
|
|
3848
|
+
? { reference, container: { kind: 'object', name: reference.objectType }, path }
|
|
3849
|
+
: undefined;
|
|
3850
|
+
}
|
|
3851
|
+
/** Il tipo di un segmento risolto: campo o membro, ridotti al minimo comune (§4.7.1). */
|
|
3852
|
+
function typeOfEntry(entry) {
|
|
3853
|
+
return {
|
|
3854
|
+
dataType: entry.dataType,
|
|
3855
|
+
objectType: entry.objectType ?? undefined,
|
|
3856
|
+
isCollection: entry.isCollection,
|
|
3857
|
+
isWritable: entry.isWritable,
|
|
3858
|
+
origin: 'member',
|
|
3859
|
+
};
|
|
3860
|
+
}
|
|
3861
|
+
function typeOfReference(reference) {
|
|
3862
|
+
return {
|
|
3863
|
+
dataType: reference.dataType ?? undefined,
|
|
3864
|
+
objectType: reference.objectType ?? undefined,
|
|
3865
|
+
isCollection: reference.isCollection,
|
|
3866
|
+
isWritable: reference.isWritable,
|
|
3867
|
+
origin: 'resource',
|
|
3868
|
+
};
|
|
3869
|
+
}
|
|
3870
|
+
/**
|
|
3871
|
+
* Il tipo di un riferimento: la risorsa se il nome e' in elenco, altrimenti l'ultimo segmento del
|
|
3872
|
+
* percorso navigato. `null` = non si sa, e non autorizza a mostrare un elenco chiuso.
|
|
3873
|
+
*
|
|
3874
|
+
* `usage` vale solo per l'ultimo segmento quando la tappa e' un'entita' (§4.4): in un filtro serve
|
|
3875
|
+
* `filterable`, altrove `any` — chiederlo con l'uso sbagliato farebbe sparire un campo che esiste.
|
|
3876
|
+
*/
|
|
3877
|
+
async function resolveReferenceType(catalog, references, value, isStructure, usage) {
|
|
3878
|
+
const trimmed = (value ?? '').trim();
|
|
3879
|
+
if (!trimmed) {
|
|
3880
|
+
return null;
|
|
3881
|
+
}
|
|
3882
|
+
const exact = references.find((candidate) => candidate.name === trimmed);
|
|
3883
|
+
if (exact) {
|
|
3884
|
+
return typeOfReference(exact);
|
|
3885
|
+
}
|
|
3886
|
+
const root = navigableRootOf(references, trimmed, isStructure);
|
|
3887
|
+
if (!root?.path) {
|
|
3888
|
+
return null;
|
|
3889
|
+
}
|
|
3890
|
+
return resolvePathType(catalog, root.container, root.path, usage);
|
|
3891
|
+
}
|
|
3892
|
+
/** Come sopra, dentro una tappa già nota: e' il caso dei filtri, dove la radice e' l'entita'. */
|
|
3893
|
+
async function resolvePathType(catalog, container, path, usage) {
|
|
3894
|
+
const trimmed = (path ?? '').trim();
|
|
3895
|
+
if (!trimmed) {
|
|
3896
|
+
return null;
|
|
3897
|
+
}
|
|
3898
|
+
const resolution = await resolvePath(catalog, container, trimmed, { usage });
|
|
3899
|
+
// Solo `resolved` e' autorevole: `unknown` e `unverifiable` sono cio' che il picker segnala, non
|
|
3900
|
+
// un tipo su cui costruire un elenco di valori.
|
|
3901
|
+
return resolution.status === 'resolved' && resolution.last ? typeOfEntry(resolution.last) : null;
|
|
3902
|
+
}
|
|
3903
|
+
function sameType(a, b) {
|
|
3904
|
+
if (!a || !b) {
|
|
3905
|
+
return a === b;
|
|
3906
|
+
}
|
|
3907
|
+
return (a.dataType === b.dataType &&
|
|
3908
|
+
a.objectType === b.objectType &&
|
|
3909
|
+
!!a.isCollection === !!b.isCollection &&
|
|
3910
|
+
a.isWritable === b.isWritable &&
|
|
3911
|
+
a.origin === b.origin);
|
|
3912
|
+
}
|
|
3913
|
+
/**
|
|
3914
|
+
* Il motore: risolve in background e pubblica per lettura sincrona.
|
|
3915
|
+
*
|
|
3916
|
+
* Perche' non un `computed`: la risoluzione e' asincrona (una chiamata di catalogo per tappa) e i
|
|
3917
|
+
* valori sono **molti** — un'operazione di Assignment per riga. Quindi un `effect` che risolve e una
|
|
3918
|
+
* mappa da cui i template leggono. Da chiamare in un **contesto di iniezione**.
|
|
3919
|
+
*
|
|
3920
|
+
* Il token di sequenza per chiave non e' cosmetico: una risposta superata che arriva dopo
|
|
3921
|
+
* scriverebbe il tipo di un valore che l'utente ha già cambiato, e l'editor mostrerebbe l'elenco di
|
|
3922
|
+
* valori sbagliato senza che nulla lo segnali.
|
|
3923
|
+
*/
|
|
3924
|
+
function trackTypes(request) {
|
|
3925
|
+
const types = signal(new Map(), ...(ngDevMode ? [{ debugName: "types" }] : []));
|
|
3926
|
+
const scope = signal('', ...(ngDevMode ? [{ debugName: "scope" }] : []));
|
|
3927
|
+
/**
|
|
3928
|
+
* Fuori dai signal di proposito: e' il registro di cio' che e' già stato chiesto, non uno stato da
|
|
3929
|
+
* cui ridisegnare. Leggerlo dentro l'`effect` che lo scrive sarebbe un ciclo.
|
|
3930
|
+
*/
|
|
3931
|
+
const requested = new Map();
|
|
3932
|
+
const tokens = new Map();
|
|
3933
|
+
let sequence = 0;
|
|
3934
|
+
effect(() => {
|
|
3935
|
+
const current = request();
|
|
3936
|
+
scope.set(current?.scope ?? '');
|
|
3937
|
+
if (!current) {
|
|
3938
|
+
return;
|
|
3939
|
+
}
|
|
3940
|
+
for (const value of new Set(current.values)) {
|
|
3941
|
+
const trimmed = (value ?? '').trim();
|
|
3942
|
+
if (!trimmed) {
|
|
3943
|
+
continue;
|
|
3944
|
+
}
|
|
3945
|
+
const key = `${current.scope}${trimmed}`;
|
|
3946
|
+
if (requested.get(key) === current.version) {
|
|
3947
|
+
continue;
|
|
3948
|
+
}
|
|
3949
|
+
requested.set(key, current.version);
|
|
3950
|
+
const token = ++sequence;
|
|
3951
|
+
tokens.set(key, token);
|
|
3952
|
+
void current.resolve(trimmed).then((result) => {
|
|
3953
|
+
if (tokens.get(key) !== token) {
|
|
3954
|
+
return;
|
|
3955
|
+
}
|
|
3956
|
+
const known = types();
|
|
3957
|
+
// Riscrivere la mappa con lo stesso contenuto ridisegnerebbe l'inspector a ogni battuta.
|
|
3958
|
+
if (known.has(key) && sameType(known.get(key), result)) {
|
|
3959
|
+
return;
|
|
3960
|
+
}
|
|
3961
|
+
const next = new Map(known);
|
|
3962
|
+
next.set(key, result);
|
|
3963
|
+
types.set(next);
|
|
3964
|
+
});
|
|
3965
|
+
}
|
|
3966
|
+
});
|
|
3967
|
+
const typeOf = (value) => {
|
|
3968
|
+
const trimmed = (value ?? '').trim();
|
|
3969
|
+
if (!trimmed) {
|
|
3970
|
+
return undefined;
|
|
3971
|
+
}
|
|
3972
|
+
return types().get(`${scope()}${trimmed}`) ?? undefined;
|
|
3973
|
+
};
|
|
3974
|
+
return {
|
|
3975
|
+
typeOf,
|
|
3976
|
+
dataTypeOf: (value) => typeOf(value)?.dataType,
|
|
3977
|
+
objectTypeOf: (value) => typeOf(value)?.objectType,
|
|
3978
|
+
};
|
|
3979
|
+
}
|
|
3980
|
+
/**
|
|
3981
|
+
* I tipi di piu' riferimenti insieme, risolti in background. È cio' che sostituisce la ricerca per
|
|
3982
|
+
* corrispondenza esatta negli inspector: sulle risorse risponde come prima, sui percorsi risponde
|
|
3983
|
+
* dove prima taceva.
|
|
3984
|
+
*/
|
|
3985
|
+
function referenceTypes(catalog, isStructure, request) {
|
|
3986
|
+
return trackTypes(() => {
|
|
3987
|
+
const current = request();
|
|
3988
|
+
if (!current) {
|
|
3989
|
+
return null;
|
|
3990
|
+
}
|
|
3991
|
+
const references = current.references;
|
|
3992
|
+
const usage = current.usage ?? 'any';
|
|
3993
|
+
return {
|
|
3994
|
+
scope: `references|${usage}`,
|
|
3995
|
+
// L'elenco arriva da una richiesta: cambia identita' a ogni risposta, ed e' esattamente
|
|
3996
|
+
// quando le deduzioni vanno rifatte (una variabile rinominata, un tipo cambiato).
|
|
3997
|
+
version: references,
|
|
3998
|
+
values: current.values,
|
|
3999
|
+
resolve: (value) => resolveReferenceType(catalog, references, value, isStructure, usage),
|
|
4000
|
+
};
|
|
4001
|
+
});
|
|
4002
|
+
}
|
|
4003
|
+
/** I tipi di piu' percorsi dentro una tappa nota: i filtri su un'entita' (§4.4). */
|
|
4004
|
+
function pathTypes(catalog, request) {
|
|
4005
|
+
return trackTypes(() => {
|
|
4006
|
+
const current = request();
|
|
4007
|
+
const container = current?.container;
|
|
4008
|
+
if (!current || !container?.name) {
|
|
4009
|
+
return null;
|
|
4010
|
+
}
|
|
4011
|
+
const usage = current.usage ?? 'any';
|
|
4012
|
+
return {
|
|
4013
|
+
scope: `${container.kind}:${container.name}|${usage}`,
|
|
4014
|
+
// Tappa e uso stanno già nello scope: dentro lo stesso scope la risposta non cambia.
|
|
4015
|
+
version: 'container',
|
|
4016
|
+
values: current.paths,
|
|
4017
|
+
resolve: (path) => resolvePathType(catalog, container, path, usage),
|
|
4018
|
+
};
|
|
4019
|
+
});
|
|
4020
|
+
}
|
|
4021
|
+
|
|
3784
4022
|
/**
|
|
3785
4023
|
* Selettore di riferimento — FRONTEND.md §4.1, §4.5, §4.7, §5.2, §6.4, §13.3.
|
|
3786
4024
|
*
|
|
@@ -3941,57 +4179,18 @@ class ReferencePickerComponent {
|
|
|
3941
4179
|
});
|
|
3942
4180
|
}
|
|
3943
4181
|
/**
|
|
3944
|
-
* Il riferimento dichiarato piu' **lungo** che sia un prefisso del valore,
|
|
4182
|
+
* Il riferimento dichiarato piu' **lungo** che sia un prefisso del valore, e la tappa da cui
|
|
4183
|
+
* navigare il resto. La regola sta in `core/reference-path` perche' non e' solo del picker: gli
|
|
4184
|
+
* inspector ne ricavano il **tipo** dell'ultimo segmento con la stessa catena, e due copie di
|
|
4185
|
+
* «prefisso piu' lungo» divergerebbero (§4.1, §4.7.1).
|
|
3945
4186
|
*
|
|
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).
|
|
4187
|
+
* Fra **tutte** le radici, non fra quelle filtrate: la radice di `Richiesta.Ragione` in un campo
|
|
4188
|
+
* `String` sta solo nell'elenco non filtrato.
|
|
3950
4189
|
*/
|
|
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" }] : []));
|
|
4190
|
+
declaredPrefix = computed(() => declaredPrefixOf(this.roots(), this.value()), ...(ngDevMode ? [{ debugName: "declaredPrefix" }] : []));
|
|
4191
|
+
navigableRoot = computed(() => navigableRootOf(this.roots(), this.value(), (dataType) => this.dictionaries.isStructure(dataType)), ...(ngDevMode ? [{ debugName: "navigableRoot" }] : []));
|
|
3989
4192
|
/** 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" }] : []));
|
|
4193
|
+
navigatedPath = computed(() => this.navigableRoot()?.path ?? '', ...(ngDevMode ? [{ debugName: "navigatedPath" }] : []));
|
|
3995
4194
|
navigation = navigatePath(this.catalog, () => {
|
|
3996
4195
|
const root = this.navigableRoot();
|
|
3997
4196
|
return root ? { root: root.container, path: this.navigatedPath() } : null;
|
|
@@ -5505,6 +5704,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.27", ngImpo
|
|
|
5505
5704
|
*/
|
|
5506
5705
|
class ConditionEditorComponent {
|
|
5507
5706
|
dictionaries = inject(FlowDictionaryStore);
|
|
5707
|
+
catalog = inject(FlowCatalogStore);
|
|
5508
5708
|
api = inject(FlowBuilderApi);
|
|
5509
5709
|
store = inject(FlowDocumentStore);
|
|
5510
5710
|
/** Il contenitore: porta `conditions`, `conditionLogic` e, se serve, `formula`. */
|
|
@@ -5529,6 +5729,20 @@ class ConditionEditorComponent {
|
|
|
5529
5729
|
* fra testo e numero prima che lo dica la validazione (§4.3).
|
|
5530
5730
|
*/
|
|
5531
5731
|
references = signal([], ...(ngDevMode ? [{ debugName: "references" }] : []));
|
|
5732
|
+
/**
|
|
5733
|
+
* Il tipo dei due lati, risorsa o **percorso**. L'elenco porta le radici e non i percorsi (§6.4):
|
|
5734
|
+
* su `Richiesta.Stato` la corrispondenza esatta non trova niente, e il tipo si ricava navigando la
|
|
5735
|
+
* classe (§4.7.1). È cio' che fa comparire l'elenco dei valori su un membro `Enum` (§4.6) e che
|
|
5736
|
+
* rende visibile un confronto fra tipi incompatibili anche dentro un'istanza (§4.3).
|
|
5737
|
+
*/
|
|
5738
|
+
types = referenceTypes(this.catalog, (dataType) => this.dictionaries.isStructure(dataType), () => ({
|
|
5739
|
+
references: this.references(),
|
|
5740
|
+
// Entrambi i lati: il destro serve a `rightDataType` quando e' un riferimento.
|
|
5741
|
+
values: this.conditions().flatMap((condition) => [
|
|
5742
|
+
condition.leftValueReference,
|
|
5743
|
+
condition.rightValue?.elementReference,
|
|
5744
|
+
]),
|
|
5745
|
+
}));
|
|
5532
5746
|
constructor() {
|
|
5533
5747
|
effect(() => {
|
|
5534
5748
|
const definition = this.store.document();
|
|
@@ -5544,12 +5758,9 @@ class ConditionEditorComponent {
|
|
|
5544
5758
|
.catch(() => this.references.set([]));
|
|
5545
5759
|
});
|
|
5546
5760
|
}
|
|
5547
|
-
/** Il tipo di un riferimento, `undefined`
|
|
5761
|
+
/** Il tipo di un riferimento, `undefined` dove non si sa: lì non si segnala (§4.3). */
|
|
5548
5762
|
typeOfReference(reference) {
|
|
5549
|
-
|
|
5550
|
-
return undefined;
|
|
5551
|
-
}
|
|
5552
|
-
return this.references().find((entry) => entry.name === reference)?.dataType ?? undefined;
|
|
5763
|
+
return this.types.dataTypeOf(reference);
|
|
5553
5764
|
}
|
|
5554
5765
|
/** Il tipo del lato sinistro: guida il secondo operando. */
|
|
5555
5766
|
leftDataType(condition) {
|
|
@@ -5557,15 +5768,11 @@ class ConditionEditorComponent {
|
|
|
5557
5768
|
}
|
|
5558
5769
|
/**
|
|
5559
5770
|
* 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
|
-
* (`
|
|
5771
|
+
* resterebbe una casella di testo su un insieme chiuso (§4.6). Vale anche su un percorso navigato
|
|
5772
|
+
* (`Richiesta.Stato`, `$Record.Stato`): il tipo del membro o del campo lo ricava {@link types}.
|
|
5562
5773
|
*/
|
|
5563
5774
|
leftObjectType(condition) {
|
|
5564
|
-
|
|
5565
|
-
if (!reference) {
|
|
5566
|
-
return undefined;
|
|
5567
|
-
}
|
|
5568
|
-
return this.references().find((entry) => entry.name === reference)?.objectType ?? undefined;
|
|
5775
|
+
return this.types.objectTypeOf(condition.leftValueReference);
|
|
5569
5776
|
}
|
|
5570
5777
|
/** Il tipo del lato destro, dedotto dal campo valorizzato o dal riferimento scelto. */
|
|
5571
5778
|
rightDataType(condition) {
|
|
@@ -5815,8 +6022,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.27", ngImpo
|
|
|
5815
6022
|
*
|
|
5816
6023
|
* - I campi proposti passano da `GET /schema/{object}/fields?usage=filterable`, così un
|
|
5817
6024
|
* campo non filtrabile non arriva nemmeno alla validazione (`FIELD_NOT_FILTERABLE`).
|
|
5818
|
-
* La scrittura libera resta possibile
|
|
5819
|
-
* (`Cliente.Citta`)
|
|
6025
|
+
* La scrittura libera resta possibile — un catalogo incompleto non deve bloccare il campo — ma i
|
|
6026
|
+
* percorsi di relazione (`Cliente.Citta`) **si navigano**: il tipo dell'ultimo segmento si ricava
|
|
6027
|
+
* con `core/reference-path`, ed e' cio' che fa comparire l'elenco dei valori su un `Enum` (§4.4).
|
|
5820
6028
|
*/
|
|
5821
6029
|
class RecordFilterEditorComponent {
|
|
5822
6030
|
catalog = inject(FlowCatalogStore);
|
|
@@ -5838,25 +6046,25 @@ class RecordFilterEditorComponent {
|
|
|
5838
6046
|
emptyWarning = input(null, ...(ngDevMode ? [{ debugName: "emptyWarning" }] : []));
|
|
5839
6047
|
emptyWarningSeverity = input('warn', ...(ngDevMode ? [{ debugName: "emptyWarningSeverity" }] : []));
|
|
5840
6048
|
changed = output();
|
|
5841
|
-
fields = signal([], ...(ngDevMode ? [{ debugName: "fields" }] : []));
|
|
5842
6049
|
/** Tutti i campi, filtrabili o no: serve solo a riconoscere le chiavi composte (§5.7). */
|
|
5843
6050
|
allFields = signal([], ...(ngDevMode ? [{ debugName: "allFields" }] : []));
|
|
5844
6051
|
filters = computed(() => this.holder().filters ?? [], ...(ngDevMode ? [{ debugName: "filters" }] : []));
|
|
5845
6052
|
operators = computed(() => this.dictionaries.recordFilterOperators(), ...(ngDevMode ? [{ debugName: "operators" }] : []));
|
|
6053
|
+
/**
|
|
6054
|
+
* Il tipo del campo filtrato, **compresi i percorsi di relazione**: `Cliente.Citta` non e' nella
|
|
6055
|
+
* lista piatta dei campi dell'entita' e il suo tipo si ricava navigando la catena (§4.4). Senza,
|
|
6056
|
+
* un campo `Enum` raggiunto per relazione tornava una casella di testo su un insieme chiuso (§4.6).
|
|
6057
|
+
*
|
|
6058
|
+
* L'`usage` e' quello del campo — `filterable` fra i filtri — e vale per l'**ultimo** segmento: i
|
|
6059
|
+
* segmenti intermedi li chiede `resolvePath` con `any`, perche' l'uso riguarda il campo confrontato
|
|
6060
|
+
* e non la relazione che porta a lui.
|
|
6061
|
+
*/
|
|
6062
|
+
types = pathTypes(this.catalog, () => ({
|
|
6063
|
+
container: this.object() ? { kind: 'object', name: this.object() } : null,
|
|
6064
|
+
paths: this.filters().map((filter) => filter.field),
|
|
6065
|
+
usage: this.usage(),
|
|
6066
|
+
}));
|
|
5846
6067
|
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
6068
|
effect(() => {
|
|
5861
6069
|
const object = this.object();
|
|
5862
6070
|
if (!object) {
|
|
@@ -5996,18 +6204,17 @@ class RecordFilterEditorComponent {
|
|
|
5996
6204
|
}
|
|
5997
6205
|
/**
|
|
5998
6206
|
* Il tipo del campo, per proporre il controllo giusto sul valore: e' il motivo per cui il
|
|
5999
|
-
* catalogo resta
|
|
6207
|
+
* catalogo resta interrogato qui, e non solo dentro il picker.
|
|
6000
6208
|
*/
|
|
6001
6209
|
fieldDataType(filter) {
|
|
6002
|
-
return this.
|
|
6210
|
+
return this.types.dataTypeOf(filter.field);
|
|
6003
6211
|
}
|
|
6004
6212
|
/**
|
|
6005
6213
|
* 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.
|
|
6214
|
+
* ammessi invece di farli digitare (§4.6).
|
|
6008
6215
|
*/
|
|
6009
6216
|
fieldObjectType(filter) {
|
|
6010
|
-
return this.
|
|
6217
|
+
return this.types.objectTypeOf(filter.field);
|
|
6011
6218
|
}
|
|
6012
6219
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.27", ngImport: i0, type: RecordFilterEditorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
6013
6220
|
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 +6762,25 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.27", ngImpo
|
|
|
6555
6762
|
class AssignmentInspectorComponent extends NodeInspectorBase {
|
|
6556
6763
|
elementType = 'Assignment';
|
|
6557
6764
|
api = inject(FlowBuilderApi);
|
|
6765
|
+
catalog = inject(FlowCatalogStore);
|
|
6558
6766
|
/**
|
|
6559
6767
|
* I riferimenti **scrivibili**, per conoscere il tipo della destinazione. Non e' un doppione del
|
|
6560
6768
|
* picker: serve il tipo, ed e' cio' che permette all'editor del valore di mostrare il controllo
|
|
6561
6769
|
* giusto — e su un `Enum` di proporre i valori del tipo invece di farli digitare (§4.6).
|
|
6562
6770
|
*/
|
|
6563
6771
|
writableReferences = signal([], ...(ngDevMode ? [{ debugName: "writableReferences" }] : []));
|
|
6772
|
+
/**
|
|
6773
|
+
* Il tipo della destinazione, risorsa o **percorso**. L'elenco porta le radici e non i percorsi
|
|
6774
|
+
* (§6.4): su `Richiesta.Stato` la corrispondenza esatta non trova niente, e il tipo si ricava
|
|
6775
|
+
* navigando la classe — altrimenti un membro `Enum` resterebbe una casella di testo (§4.6, §4.7.1).
|
|
6776
|
+
*
|
|
6777
|
+
* `usage` resta quello di default (`any`): un campo di `$Record` si assegna se e' createable
|
|
6778
|
+
* **oppure** updateable, e qui il contesto non dice quale dei due (§5.7).
|
|
6779
|
+
*/
|
|
6780
|
+
targetTypes = referenceTypes(this.catalog, (dataType) => this.dictionaries.isStructure(dataType), () => ({
|
|
6781
|
+
references: this.writableReferences(),
|
|
6782
|
+
values: this.items().map((item) => item.assignToReference),
|
|
6783
|
+
}));
|
|
6564
6784
|
constructor() {
|
|
6565
6785
|
super();
|
|
6566
6786
|
effect(() => {
|
|
@@ -6576,22 +6796,11 @@ class AssignmentInspectorComponent extends NodeInspectorBase {
|
|
|
6576
6796
|
.catch(() => this.writableReferences.set([]));
|
|
6577
6797
|
});
|
|
6578
6798
|
}
|
|
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
6799
|
targetDataType(item) {
|
|
6591
|
-
return this.
|
|
6800
|
+
return this.targetTypes.dataTypeOf(item.assignToReference);
|
|
6592
6801
|
}
|
|
6593
6802
|
targetObjectType(item) {
|
|
6594
|
-
return this.
|
|
6803
|
+
return this.targetTypes.objectTypeOf(item.assignToReference);
|
|
6595
6804
|
}
|
|
6596
6805
|
assignment = computed(() => this.node(), ...(ngDevMode ? [{ debugName: "assignment" }] : []));
|
|
6597
6806
|
items = computed(() => this.assignment().assignmentItems ?? [], ...(ngDevMode ? [{ debugName: "items" }] : []));
|
|
@@ -6679,11 +6888,11 @@ class AssignmentInspectorComponent extends NodeInspectorBase {
|
|
|
6679
6888
|
return !operator.startsWith('Remove') || operator === 'RemovePosition' || operator === 'RemoveUncommon';
|
|
6680
6889
|
}
|
|
6681
6890
|
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]=\"
|
|
6891
|
+
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
6892
|
}
|
|
6684
6893
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.27", ngImport: i0, type: AssignmentInspectorComponent, decorators: [{
|
|
6685
6894
|
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]=\"
|
|
6895
|
+
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
6896
|
}], ctorParameters: () => [] });
|
|
6688
6897
|
|
|
6689
6898
|
/**
|