@esfaenza/dpe-builder 20.3.6 → 20.3.8

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.
@@ -1,4 +1,4 @@
1
- import { throwError, defer, catchError, map, tap, EMPTY, switchMap, of, debounceTime } from 'rxjs';
1
+ import { throwError, defer, catchError, map, tap, EMPTY, switchMap, of, filter, debounceTime } from 'rxjs';
2
2
  import { HttpClient, HttpErrorResponse } from '@angular/common/http';
3
3
  import * as i0 from '@angular/core';
4
4
  import { InjectionToken, inject, Injectable, makeEnvironmentProviders, signal, computed, DestroyRef, input, ChangeDetectionStrategy, Component, ElementRef, output, viewChild, effect, Directive, untracked, Injector, afterNextRender } from '@angular/core';
@@ -1446,7 +1446,20 @@ function dpeNodeSubtitle(collection, node) {
1446
1446
  */
1447
1447
  /** Profondita' della cronologia: oltre, le istantanee piu' vecchie si buttano. */
1448
1448
  const HISTORY_LIMIT = 100;
1449
- /** Una definizione vuota ma **leggibile**: `formatVersion` e `dialect` non sono opzionali. */
1449
+ /**
1450
+ * Una definizione vuota ma **leggibile**: `formatVersion` e `dialect` non sono opzionali.
1451
+ *
1452
+ * **Serve a `createDefinition`, non a montare l'editor.** E' il documento minimo che si manda al
1453
+ * backend per far nascere una definizione nel catalogo (ADR 0019), e da li' in poi quella che si
1454
+ * edita e' la definizione **caricata con `load(nome)`**.
1455
+ *
1456
+ * Montare l'editor su un documento costruito qui, o su uno costruito a mano senza nome, produce due
1457
+ * sintomi che sembrano scollegati e sono la stessa cosa: la validazione risponde
1458
+ * `DPE_MODEL_REQUIRED_MISSING` su `name` e `label` — per il backend una stringa vuota e un campo
1459
+ * assente sono lo stesso caso — e il salvataggio non ha una chiave per la rotta
1460
+ * `PUT /definitions/{name}`. L'editor lo dichiara in cima con un banner e disabilita «Salva», ma la
1461
+ * correzione e' a monte: creare dal catalogo e caricare.
1462
+ */
1450
1463
  function dpeEmptyDefinition(name, label, dialect) {
1451
1464
  return { formatVersion: 1, name, label, dialect };
1452
1465
  }
@@ -1461,6 +1474,16 @@ class DpeDocumentStore {
1461
1474
  * sulla selezione.
1462
1475
  */
1463
1476
  _selectedNodeNames = signal([], ...(ngDevMode ? [{ debugName: "_selectedNodeNames" }] : []));
1477
+ /**
1478
+ * La dialog di modifica del nodo e' aperta. **Un booleano**, non un secondo nome di nodo: il
1479
+ * nodo su cui si sta agendo resta `selectedNodeName()`, e due registrazioni dello stesso fatto
1480
+ * sono esattamente cio' che questo store dichiara di non voler avere (ci ha gia' rimesso una
1481
+ * volta con la doppia selezione di @foblex/flow).
1482
+ *
1483
+ * Non entra nella cronologia: aprire una finestra non e' una modifica del documento, e un undo
1484
+ * che aprisse o chiudesse finestre sarebbe un comando che fa due cose.
1485
+ */
1486
+ _isNodeEditorOpen = signal(false, ...(ngDevMode ? [{ debugName: "_isNodeEditorOpen" }] : []));
1464
1487
  _past = signal([], ...(ngDevMode ? [{ debugName: "_past" }] : []));
1465
1488
  _future = signal([], ...(ngDevMode ? [{ debugName: "_future" }] : []));
1466
1489
  /**
@@ -1471,8 +1494,18 @@ class DpeDocumentStore {
1471
1494
  * — e tornare a "no" quando si annulla fino allo stato di partenza.
1472
1495
  */
1473
1496
  _savedSnapshot = signal(JSON.stringify(dpeEmptyDefinition('', '', 'Sqlite')), ...(ngDevMode ? [{ debugName: "_savedSnapshot" }] : []));
1497
+ /**
1498
+ * Vero da quando un documento e' stato **caricato**. Distingue il segnaposto del costruttore da
1499
+ * una definizione vera, e serve a chi non deve lavorare sul segnaposto: la validazione non lo
1500
+ * manda al backend (`DpeValidationService`), perche' la risposta sarebbero i quattro rilievi di
1501
+ * una definizione vuota — `name`, `label`, `dataSources` e nessun writeback — che descrivono il
1502
+ * segnaposto e non il lavoro dell'utente, e che dall'editor nessuno puo' correggere.
1503
+ */
1504
+ _hasDocument = signal(false, ...(ngDevMode ? [{ debugName: "_hasDocument" }] : []));
1474
1505
  /** La definizione corrente: unica fonte di verita' per canvas, ispettori e pannelli. */
1475
1506
  definition = this._definition.asReadonly();
1507
+ /** Vero da quando `load` ha portato un documento: prima c'e' solo il segnaposto. */
1508
+ hasDocument = this._hasDocument.asReadonly();
1476
1509
  /** Nodi e archi derivati dalla definizione. Ricalcolati solo quando la definizione cambia. */
1477
1510
  graph = computed(() => dpeBuildGraph(this._definition()), ...(ngDevMode ? [{ debugName: "graph" }] : []));
1478
1511
  selectedNodeNames = this._selectedNodeNames.asReadonly();
@@ -1487,6 +1520,11 @@ class DpeDocumentStore {
1487
1520
  const name = this.selectedNodeName();
1488
1521
  return name === undefined ? undefined : dpeFindNode(this._definition(), name)?.node;
1489
1522
  }, ...(ngDevMode ? [{ debugName: "selectedNode" }] : []));
1523
+ /**
1524
+ * La dialog di modifica del nodo e' aperta. Non e' una seconda identita': il nodo e'
1525
+ * `selectedNodeName()`.
1526
+ */
1527
+ isNodeEditorOpen = this._isNodeEditorOpen.asReadonly();
1490
1528
  // --- cio' che arriva dal backend ------------------------------------------
1491
1529
  validateResult = this._validateResult.asReadonly();
1492
1530
  compileResult = this._compileResult.asReadonly();
@@ -1543,11 +1581,16 @@ class DpeDocumentStore {
1543
1581
  */
1544
1582
  load(definition) {
1545
1583
  this._definition.set(definition);
1584
+ this._hasDocument.set(true);
1546
1585
  // Cio' che e' appena arrivato **e'** lo stato salvato: aprire un documento non lo sporca.
1547
1586
  this._savedSnapshot.set(JSON.stringify(definition));
1548
1587
  this._past.set([]);
1549
1588
  this._future.set([]);
1550
1589
  this._selectedNodeNames.set([]);
1590
+ // Una dialog rimasta aperta guarderebbe un nodo di **un'altra** definizione: il nome potrebbe
1591
+ // anche esistere qui, e allora si starebbe modificando un nodo che nessuno ha aperto. E' il
1592
+ // caso che si dimentica, perche' non fallisce — mostra la cosa sbagliata.
1593
+ this._isNodeEditorOpen.set(false);
1551
1594
  this._validateResult.set(undefined);
1552
1595
  this._compileResult.set(undefined);
1553
1596
  }
@@ -1583,6 +1626,24 @@ class DpeDocumentStore {
1583
1626
  selectMany(nodeNames) {
1584
1627
  this._selectedNodeNames.set(Array.from(new Set(nodeNames)));
1585
1628
  }
1629
+ /**
1630
+ * Seleziona **solo** quel nodo e apre la dialog di modifica.
1631
+ *
1632
+ * La selezione **collassa a un nodo solo**: il gesto «apri questo» dichiara su chi si sta
1633
+ * agendo, e la regola gia' in vigore e' che con piu' di un nodo selezionato non si mostrano i
1634
+ * campi di uno.
1635
+ *
1636
+ * Le due scritture sono **esplicite** e non conseguenza di un `effect`: un effect che scrive
1637
+ * signal renderebbe l'ordine di questi due fatti dipendente dalla schedulazione, cioe' non
1638
+ * testabile.
1639
+ */
1640
+ openNodeEditor(nodeName) {
1641
+ this.select(nodeName);
1642
+ this._isNodeEditorOpen.set(true);
1643
+ }
1644
+ closeNodeEditor() {
1645
+ this._isNodeEditorOpen.set(false);
1646
+ }
1586
1647
  // -------------------------------------------------------------------------
1587
1648
  // Mutazioni — sincrone e granulari
1588
1649
  // -------------------------------------------------------------------------
@@ -1635,6 +1696,9 @@ class DpeDocumentStore {
1635
1696
  if (!located) {
1636
1697
  return;
1637
1698
  }
1699
+ // Si legge **prima** della potatura della selezione: dopo, `selectedNodeName()` e' gia' un
1700
+ // altro nodo (o nessuno) e non si saprebbe piu' se la dialog stava guardando questo.
1701
+ const wasOpenOnIt = this._isNodeEditorOpen() && this.selectedNodeName() === nodeName;
1638
1702
  const existing = (definition[located.collection] ?? []);
1639
1703
  const next = existing.filter((node) => node.name !== nodeName);
1640
1704
  this.commit(this.withCollection(definition, located.collection, next));
@@ -1645,6 +1709,13 @@ class DpeDocumentStore {
1645
1709
  if (selected.includes(nodeName)) {
1646
1710
  this._selectedNodeNames.set(selected.filter((name) => name !== nodeName));
1647
1711
  }
1712
+ // Il bersaglio della dialog e' svanito: dopo l'eliminazione non c'e' piu' niente da
1713
+ // modificare. La chiusura sta qui e non nel componente perche' «Elimina il nodo» sta nel
1714
+ // piede della dialog, e un componente che si chiude da se' dopo aver chiesto una mutazione
1715
+ // sarebbe una seconda regola sullo stesso fatto.
1716
+ if (wasOpenOnIt) {
1717
+ this._isNodeEditorOpen.set(false);
1718
+ }
1648
1719
  }
1649
1720
  /**
1650
1721
  * L'**unico** punto che rinomina un nodo (**I5**): delega alla funzione pura, che aggiorna
@@ -2313,7 +2384,20 @@ class DpeValidationService {
2313
2384
  hasResult = computed(() => this.store.validateResult() !== undefined, ...(ngDevMode ? [{ debugName: "hasResult" }] : []));
2314
2385
  constructor() {
2315
2386
  toObservable(this.store.definition)
2316
- .pipe(debounceTime(VALIDATE_DEBOUNCE_MS), tap(() => {
2387
+ .pipe(
2388
+ /*
2389
+ * **Non si valida il segnaposto.** Lo store nasce con `dpeEmptyDefinition('', '', 'Sqlite')`
2390
+ * e questo servizio si iscrive alla costruzione: se il documento vero arriva dopo la
2391
+ * finestra di debounce — un ospite che monta l'editor mentre `load(nome)` e' in volo, cioe'
2392
+ * il caso normale con una rete vera — la finestra si chiudeva sul segnaposto e l'editor
2393
+ * validava un documento che nessuno aveva fornito. La risposta era esatta e inutile:
2394
+ * `DPE_MODEL_REQUIRED_MISSING` su `name`, `label` e `dataSources` piu'
2395
+ * `DPE_GRAPH_NO_WRITEBACK` — quattro rilievi del segnaposto, che l'utente non puo'
2396
+ * correggere da qui e che sembrano riguardare la sua definizione appena creata.
2397
+ *
2398
+ * Il filtro sta **prima** del debounce: il segnaposto non deve nemmeno aprire la finestra.
2399
+ */
2400
+ filter(() => this.store.hasDocument()), debounceTime(VALIDATE_DEBOUNCE_MS), tap(() => {
2317
2401
  this._isValidating.set(true);
2318
2402
  this._error.set(undefined);
2319
2403
  }),
@@ -3185,6 +3269,165 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.27", ngImpo
3185
3269
  }]
3186
3270
  }], propDecorators: { navigate: [{ type: i0.Output, args: ["navigate"] }] } });
3187
3271
 
3272
+ /**
3273
+ * I **fatti dal backend** su un nodo: schema in uscita e rilievi che lo riguardano.
3274
+ *
3275
+ * Sono in un componente a se' perche' nella dialog di modifica (ADR 0026) non stanno dove stanno
3276
+ * i campi: i campi prendono la colonna larga, questi la colonna stretta di destra. Metterli **di
3277
+ * fianco** e non sopra il form evita in anticipo il muro di avvisi che spinge il primo campo
3278
+ * fuori dallo schermo — con i rilievi in colonna il muro non si forma, quindi si mostrano
3279
+ * **tutti** (**I6**: l'elenco della validazione e' esaustivo, e troncarlo sarebbe mentire).
3280
+ *
3281
+ * In testa sta lo stato **valido ma non compilabile** (**I10**), perche' e' il fatto che spiega gli
3282
+ * altri due: senza di esso una colonna che elenca i rilievi tace proprio sul motivo per cui da
3283
+ * questo nodo non uscira' nessun SQL. «Non compilabile» e «non valido» sono due significati
3284
+ * distinti, e l'editor li tiene distinti per decisione (ADR 0011).
3285
+ *
3286
+ * Non calcola niente. Lo schema arriva da `validate` (**I4**: la propagazione e' del backend) e i
3287
+ * rilievi arrivano gia' raggruppati per nodo: qui si legge e si rende.
3288
+ */
3289
+ class DpeNodeFactsComponent {
3290
+ store = inject(DpeDocumentStore);
3291
+ /**
3292
+ * Il nodo di cui si mostrano i fatti. Arriva per input e non dalla selezione: chi monta questo
3293
+ * componente sa quale nodo sta guardando, e leggere qui la selezione lo farebbe cambiare
3294
+ * bersaglio sotto la dialog che lo contiene.
3295
+ */
3296
+ nodeName = input.required(...(ngDevMode ? [{ debugName: "nodeName" }] : []));
3297
+ /**
3298
+ * Il nodo e' valido ma **non compilabile** (**I10**). Due rami, ed e' la **stessa espressione**
3299
+ * che `DpeNodeInspectorBase.isNotCompilable` usa gia': il codice diagnostico
3300
+ * `DPE_NODE_NOT_COMPILABLE` raggruppato per nodo, **oppure** lo schema che dichiara
3301
+ * `isCompilable: false`. Servono entrambi perche' il backend puo' dirlo per due vie — un rilievo
3302
+ * sul nodo, o la riga di schema — e riconoscerne una sola vorrebbe dire tacere nell'altro caso.
3303
+ *
3304
+ * Qui non si giudica niente: la decisione e' del compilatore, questa e' la sua risposta.
3305
+ */
3306
+ isNotCompilable = computed(() => (this.store.diagnosticsByNode().get(this.nodeName())?.isNotCompilable ?? false) ||
3307
+ this.store.schemaByNode().get(this.nodeName())?.isCompilable === false, ...(ngDevMode ? [{ debugName: "isNotCompilable" }] : []));
3308
+ columns = computed(() => this.store.schemaByNode().get(this.nodeName())?.columns ?? [], ...(ngDevMode ? [{ debugName: "columns" }] : []));
3309
+ diagnostics = computed(() => this.store.diagnosticsByNode().get(this.nodeName())?.diagnostics ?? [], ...(ngDevMode ? [{ debugName: "diagnostics" }] : []));
3310
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.27", ngImport: i0, type: DpeNodeFactsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
3311
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.27", type: DpeNodeFactsComponent, isStandalone: true, selector: "dpe-node-facts", inputs: { nodeName: { classPropertyName: "nodeName", publicName: "nodeName", isSignal: true, isRequired: true, transformFunction: null } }, ngImport: i0, template: `
3312
+ @if (isNotCompilable()) {
3313
+ <!--
3314
+ Tono deliberatamente **non** da errore: colore proprio (ne’ rosso ne’ giallo) e le stesse
3315
+ parole degli ispettori di Forecast e CustomNode, perche’ e’ lo stesso fatto detto nello
3316
+ stesso linguaggio. Chiedere di correggere qualcosa qui sarebbe una bugia.
3317
+ -->
3318
+ <p class="dpe-note dpe-note--not-compilable">
3319
+ <span class="dpe-badge dpe-badge--not-compilable">non compilabile</span>
3320
+ Il nodo e’ <strong>valido</strong>: non c’e’ niente da correggere. Il compilatore non lo
3321
+ traduce in questa versione, quindi per questo nodo
3322
+ <strong>non verra’ generato SQL</strong>.
3323
+ </p>
3324
+ }
3325
+
3326
+ <section class="dpe-section">
3327
+ <h4 class="dpe-section__title">Schema in uscita</h4>
3328
+ @if (columns().length === 0) {
3329
+ <p class="dpe-empty">
3330
+ Nessuna colonna: il backend non ha (ancora) propagato uno schema per questo nodo.
3331
+ </p>
3332
+ } @else {
3333
+ <div class="dpe-columns">
3334
+ @for (column of columns(); track $index) {
3335
+ <span
3336
+ class="dpe-column"
3337
+ [class.dpe-column--nullable]="column.isNullable"
3338
+ [title]="column.type.kind + (column.isNullable ? ' · nullabile' : ' · non nulla')"
3339
+ >
3340
+ {{ column.name }}
3341
+ </span>
3342
+ }
3343
+ </div>
3344
+ <p class="dpe-field__hint">
3345
+ Tratteggiate le colonne nullabili. Lo schema arriva da <code>validate</code>: non lo
3346
+ calcoliamo qui.
3347
+ </p>
3348
+ }
3349
+ </section>
3350
+
3351
+ @if (diagnostics().length > 0) {
3352
+ <section class="dpe-section">
3353
+ <h4 class="dpe-section__title">Rilievi su questo nodo</h4>
3354
+ @for (diagnostic of diagnostics(); track $index) {
3355
+ <p
3356
+ class="dpe-field__issue"
3357
+ [class.dpe-field__issue--warning]="diagnostic.severity === 'Warning'"
3358
+ [class.dpe-field__issue--info]="diagnostic.severity === 'Info'"
3359
+ >
3360
+ <code>{{ diagnostic.code }}</code> — {{ diagnostic.message }}
3361
+ </p>
3362
+ }
3363
+ </section>
3364
+ }
3365
+ `, isInline: true, changeDetection: i0.ChangeDetectionStrategy.OnPush });
3366
+ }
3367
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.27", ngImport: i0, type: DpeNodeFactsComponent, decorators: [{
3368
+ type: Component,
3369
+ args: [{
3370
+ selector: 'dpe-node-facts',
3371
+ standalone: true,
3372
+ changeDetection: ChangeDetectionStrategy.OnPush,
3373
+ template: `
3374
+ @if (isNotCompilable()) {
3375
+ <!--
3376
+ Tono deliberatamente **non** da errore: colore proprio (ne’ rosso ne’ giallo) e le stesse
3377
+ parole degli ispettori di Forecast e CustomNode, perche’ e’ lo stesso fatto detto nello
3378
+ stesso linguaggio. Chiedere di correggere qualcosa qui sarebbe una bugia.
3379
+ -->
3380
+ <p class="dpe-note dpe-note--not-compilable">
3381
+ <span class="dpe-badge dpe-badge--not-compilable">non compilabile</span>
3382
+ Il nodo e’ <strong>valido</strong>: non c’e’ niente da correggere. Il compilatore non lo
3383
+ traduce in questa versione, quindi per questo nodo
3384
+ <strong>non verra’ generato SQL</strong>.
3385
+ </p>
3386
+ }
3387
+
3388
+ <section class="dpe-section">
3389
+ <h4 class="dpe-section__title">Schema in uscita</h4>
3390
+ @if (columns().length === 0) {
3391
+ <p class="dpe-empty">
3392
+ Nessuna colonna: il backend non ha (ancora) propagato uno schema per questo nodo.
3393
+ </p>
3394
+ } @else {
3395
+ <div class="dpe-columns">
3396
+ @for (column of columns(); track $index) {
3397
+ <span
3398
+ class="dpe-column"
3399
+ [class.dpe-column--nullable]="column.isNullable"
3400
+ [title]="column.type.kind + (column.isNullable ? ' · nullabile' : ' · non nulla')"
3401
+ >
3402
+ {{ column.name }}
3403
+ </span>
3404
+ }
3405
+ </div>
3406
+ <p class="dpe-field__hint">
3407
+ Tratteggiate le colonne nullabili. Lo schema arriva da <code>validate</code>: non lo
3408
+ calcoliamo qui.
3409
+ </p>
3410
+ }
3411
+ </section>
3412
+
3413
+ @if (diagnostics().length > 0) {
3414
+ <section class="dpe-section">
3415
+ <h4 class="dpe-section__title">Rilievi su questo nodo</h4>
3416
+ @for (diagnostic of diagnostics(); track $index) {
3417
+ <p
3418
+ class="dpe-field__issue"
3419
+ [class.dpe-field__issue--warning]="diagnostic.severity === 'Warning'"
3420
+ [class.dpe-field__issue--info]="diagnostic.severity === 'Info'"
3421
+ >
3422
+ <code>{{ diagnostic.code }}</code> — {{ diagnostic.message }}
3423
+ </p>
3424
+ }
3425
+ </section>
3426
+ }
3427
+ `,
3428
+ }]
3429
+ }], propDecorators: { nodeName: [{ type: i0.Input, args: [{ isSignal: true, alias: "nodeName", required: true }] }] } });
3430
+
3188
3431
  /**
3189
3432
  * `[dpeValue]` su un `<select>`.
3190
3433
  *
@@ -7491,48 +7734,27 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.27", ngImpo
7491
7734
  }] });
7492
7735
 
7493
7736
  /**
7494
- * Il contenitore degli ispettori: campi comuni, poi l'ispettore della categoria.
7737
+ * **Solo** i campi modificabili di **un** nodo: i campi comuni, poi l'ispettore della categoria.
7738
+ *
7739
+ * Cio' che non c'e' piu', e dove e' andato (ADR 0026): la testata di identita' e' della dialog che
7740
+ * lo contiene, le due sezioni di fatti in sola lettura sono di `dpe-node-facts`, e il caso «piu' di
7741
+ * un nodo selezionato» e' una striscia contestuale del guscio. Non c'e' **nessun** booleano di
7742
+ * modalita' al loro posto: si e' spostato contenuto, non aggiunto un interruttore che cambia il
7743
+ * significato del componente.
7495
7744
  *
7496
7745
  * La **rinomina** sta qui e non nei singoli ispettori, per due ragioni. E' identica per tutte le
7497
7746
  * categorie, e soprattutto e' un refactoring globale (**I5**): passa dall'unico punto che
7498
7747
  * riscrive i riferimenti a valle, e prima di applicarla **dice quante occorrenze tocchera'**.
7499
- * Rinominare non e' modificare un campo, e non deve sembrarlo.
7500
- *
7501
- * Con **piu' di un nodo selezionato** non si mostra l'editor del primo: si dichiara quanti sono e
7502
- * si offrono solo le operazioni valide su molti. Mostrare i campi di uno mentre N sono evidenziati
7503
- * e' la stessa bugia della doppia evidenziazione, detta piu' piano — l'utente crede di stare
7504
- * agendo su cio' che vede selezionato.
7748
+ * Rinominare non e' modificare un campo, e non deve sembrarlo. E' anche l'unica eccezione al
7749
+ * commit dal vivo: ha una bozza locale perche' ha un bottone, non perche' sia in una dialog.
7505
7750
  */
7506
7751
  class DpeNodeInspectorComponent {
7507
7752
  store = inject(DpeDocumentStore);
7508
- /**
7509
- * Il riordino richiede le misure delle card, che le conosce il canvas: l'ispettore **chiede**,
7510
- * non calcola. Componente stupido, orchestrazione fuori.
7511
- */
7512
- layoutRequested = output();
7513
- selectedNames = computed(() => this.store.selectedNodeNames(), ...(ngDevMode ? [{ debugName: "selectedNames" }] : []));
7514
7753
  located = computed(() => {
7515
7754
  const name = this.store.selectedNodeName();
7516
7755
  return name === undefined ? undefined : dpeFindNode(this.store.definition(), name);
7517
7756
  }, ...(ngDevMode ? [{ debugName: "located" }] : []));
7518
7757
  nameDraft = signal('', ...(ngDevMode ? [{ debugName: "nameDraft" }] : []));
7519
- categoryLabel = computed(() => {
7520
- const entry = this.located();
7521
- return entry ? dpeNodeCategory(entry.collection).label : '';
7522
- }, ...(ngDevMode ? [{ debugName: "categoryLabel" }] : []));
7523
- /** La classe di famiglia dell'icona: la stessa funzione che usano palette e canvas. */
7524
- familyClass = computed(() => {
7525
- const entry = this.located();
7526
- return entry ? dpeNodeIconFamilyClass(entry.collection) : '';
7527
- }, ...(ngDevMode ? [{ debugName: "familyClass" }] : []));
7528
- columns = computed(() => {
7529
- const entry = this.located();
7530
- return entry ? (this.store.schemaByNode().get(entry.node.name)?.columns ?? []) : [];
7531
- }, ...(ngDevMode ? [{ debugName: "columns" }] : []));
7532
- diagnostics = computed(() => {
7533
- const entry = this.located();
7534
- return entry ? (this.store.diagnosticsByNode().get(entry.node.name)?.diagnostics ?? []) : [];
7535
- }, ...(ngDevMode ? [{ debugName: "diagnostics" }] : []));
7536
7758
  referenceCount = computed(() => {
7537
7759
  const entry = this.located();
7538
7760
  return entry ? this.store.countReferences(entry.node.name) : 0;
@@ -7550,15 +7772,6 @@ class DpeNodeInspectorComponent {
7550
7772
  this.nameDraft.set(entry?.node.name ?? '');
7551
7773
  });
7552
7774
  }
7553
- /**
7554
- * Elimina i nodi selezionati. Si itera su una **copia** dei nomi: `removeNode` pota la selezione
7555
- * a ogni giro, e scorrere il signal mentre lo si svuota salterebbe la meta' dei nodi.
7556
- */
7557
- removeSelected() {
7558
- for (const name of [...this.selectedNames()]) {
7559
- this.store.removeNode(name);
7560
- }
7561
- }
7562
7775
  rename(currentName) {
7563
7776
  this.store.renameNode(currentName, this.nameDraft().trim());
7564
7777
  }
@@ -7611,59 +7824,8 @@ class DpeNodeInspectorComponent {
7611
7824
  return node;
7612
7825
  }
7613
7826
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.27", ngImport: i0, type: DpeNodeInspectorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
7614
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.27", type: DpeNodeInspectorComponent, isStandalone: true, selector: "dpe-node-inspector", outputs: { layoutRequested: "layoutRequested" }, ngImport: i0, template: `
7615
- @if (selectedNames().length > 1) {
7616
- <!--
7617
- Selezione multipla: si dice quanti sono e si offre solo cio' che ha senso su molti. Nessun
7618
- campo del primo nodo, perche' non e' su quello che l’utente sta agendo.
7619
- -->
7620
- <div class="dpe-inspector__head">
7621
- <strong>{{ selectedNames().length }} nodi selezionati</strong>
7622
- <span class="dpe-inspector__category">selezione</span>
7623
- </div>
7624
-
7625
- <ul class="dpe-multi__names">
7626
- @for (name of selectedNames(); track $index) {
7627
- <li class="dpe-multi__name">{{ name }}</li>
7628
- }
7629
- </ul>
7630
-
7631
- <p class="dpe-note">
7632
- I campi di un singolo nodo si modificano con un nodo solo selezionato: mostrarli qui
7633
- farebbe credere di agire su tutti.
7634
- </p>
7635
-
7636
- <div class="dpe-row">
7637
- <button type="button" class="dpe-btn" (click)="removeSelected()">
7638
- Elimina i {{ selectedNames().length }} nodi
7639
- </button>
7640
- <!--
7641
- Il riordino e’ dichiaratamente **dell’intero grafo**: dagre su un sottografo sarebbe una
7642
- funzione nuova, e chiamarla «riordina i selezionati» mentre riordina tutto sarebbe una
7643
- bugia — la stessa classe di difetto che questo pannello esiste per evitare.
7644
- -->
7645
- <button type="button" class="dpe-btn" (click)="layoutRequested.emit()">
7646
- Riordina il grafo
7647
- </button>
7648
- </div>
7649
-
7650
- <p class="dpe-note dpe-note--warning">
7651
- L’eliminazione lascia i riferimenti a valle così come sono: diventano riferimenti rotti, e
7652
- il backend li segnala. Si annulla con ↶.
7653
- </p>
7654
- } @else if (located(); as entry) {
7655
- <div class="dpe-inspector__head">
7656
- <!--
7657
- La stessa icona della palette e della card: il terzo posto in cui compare il tipo, e
7658
- deve avere lo stesso aspetto degli altri due.
7659
- -->
7660
- <span class="dpe-inspector__icon" [class]="'dpe-inspector__icon ' + familyClass()">
7661
- <dpe-node-icon [collection]="entry.collection" />
7662
- </span>
7663
- <strong class="dpe-inspector__name">{{ entry.node.label || entry.node.name }}</strong>
7664
- <span class="dpe-inspector__category">{{ categoryLabel() }}</span>
7665
- </div>
7666
-
7827
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.27", type: DpeNodeInspectorComponent, isStandalone: true, selector: "dpe-node-inspector", ngImport: i0, template: `
7828
+ @if (located(); as entry) {
7667
7829
  <div class="dpe-form">
7668
7830
  <div class="dpe-field">
7669
7831
  <label class="dpe-field__label" for="node-name">Nome tecnico</label>
@@ -7751,50 +7913,10 @@ class DpeNodeInspectorComponent {
7751
7913
  }
7752
7914
  }
7753
7915
  </section>
7754
-
7755
- <section class="dpe-section">
7756
- <h4 class="dpe-section__title">Schema in uscita</h4>
7757
- @if (columns().length === 0) {
7758
- <p class="dpe-empty">
7759
- Nessuna colonna: il backend non ha (ancora) propagato uno schema per questo nodo.
7760
- </p>
7761
- } @else {
7762
- <div class="dpe-columns">
7763
- @for (column of columns(); track $index) {
7764
- <span
7765
- class="dpe-column"
7766
- [class.dpe-column--nullable]="column.isNullable"
7767
- [title]="column.type.kind + (column.isNullable ? ' · nullabile' : ' · non nulla')"
7768
- >
7769
- {{ column.name }}
7770
- </span>
7771
- }
7772
- </div>
7773
- <p class="dpe-field__hint">
7774
- Tratteggiate le colonne nullabili. Lo schema arriva da <code>validate</code>: non lo
7775
- calcoliamo qui.
7776
- </p>
7777
- }
7778
- </section>
7779
-
7780
- @if (diagnostics().length > 0) {
7781
- <section class="dpe-section">
7782
- <h4 class="dpe-section__title">Rilievi su questo nodo</h4>
7783
- @for (diagnostic of diagnostics(); track $index) {
7784
- <p
7785
- class="dpe-field__issue"
7786
- [class.dpe-field__issue--warning]="diagnostic.severity === 'Warning'"
7787
- [class.dpe-field__issue--info]="diagnostic.severity === 'Info'"
7788
- >
7789
- <code>{{ diagnostic.code }}</code> — {{ diagnostic.message }}
7790
- </p>
7791
- }
7792
- </section>
7793
- }
7794
7916
  } @else {
7795
7917
  <p class="dpe-empty">Nessun nodo selezionato.</p>
7796
7918
  }
7797
- `, isInline: true, dependencies: [{ kind: "component", type: DpeAggregateInspectorComponent, selector: "dpe-aggregate-inspector" }, { kind: "component", type: DpeAppendInspectorComponent, selector: "dpe-append-inspector" }, { kind: "component", type: DpeAtomicWritebackInspectorComponent, selector: "dpe-atomic-writeback-inspector" }, { kind: "component", type: DpeCustomNodeInspectorComponent, selector: "dpe-custom-node-inspector" }, { kind: "component", type: DpeDataSourceInspectorComponent, selector: "dpe-data-source-inspector" }, { kind: "component", type: DpeFilterInspectorComponent, selector: "dpe-filter-inspector" }, { kind: "component", type: DpeForecastInspectorComponent, selector: "dpe-forecast-inspector" }, { kind: "component", type: DpeHierarchyPathInspectorComponent, selector: "dpe-hierarchy-path-inspector" }, { kind: "component", type: DpeJoinInspectorComponent, selector: "dpe-join-inspector" }, { kind: "component", type: DpeTransformInspectorComponent, selector: "dpe-transform-inspector" }, { kind: "component", type: DpeWritebackInspectorComponent, selector: "dpe-writeback-inspector" }, { kind: "component", type: DpeNodeIconComponent, selector: "dpe-node-icon", inputs: ["collection"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
7919
+ `, isInline: true, dependencies: [{ kind: "component", type: DpeAggregateInspectorComponent, selector: "dpe-aggregate-inspector" }, { kind: "component", type: DpeAppendInspectorComponent, selector: "dpe-append-inspector" }, { kind: "component", type: DpeAtomicWritebackInspectorComponent, selector: "dpe-atomic-writeback-inspector" }, { kind: "component", type: DpeCustomNodeInspectorComponent, selector: "dpe-custom-node-inspector" }, { kind: "component", type: DpeDataSourceInspectorComponent, selector: "dpe-data-source-inspector" }, { kind: "component", type: DpeFilterInspectorComponent, selector: "dpe-filter-inspector" }, { kind: "component", type: DpeForecastInspectorComponent, selector: "dpe-forecast-inspector" }, { kind: "component", type: DpeHierarchyPathInspectorComponent, selector: "dpe-hierarchy-path-inspector" }, { kind: "component", type: DpeJoinInspectorComponent, selector: "dpe-join-inspector" }, { kind: "component", type: DpeTransformInspectorComponent, selector: "dpe-transform-inspector" }, { kind: "component", type: DpeWritebackInspectorComponent, selector: "dpe-writeback-inspector" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
7798
7920
  }
7799
7921
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.27", ngImport: i0, type: DpeNodeInspectorComponent, decorators: [{
7800
7922
  type: Component,
@@ -7813,62 +7935,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.27", ngImpo
7813
7935
  DpeJoinInspectorComponent,
7814
7936
  DpeTransformInspectorComponent,
7815
7937
  DpeWritebackInspectorComponent,
7816
- DpeNodeIconComponent,
7817
7938
  ],
7818
7939
  changeDetection: ChangeDetectionStrategy.OnPush,
7819
7940
  template: `
7820
- @if (selectedNames().length > 1) {
7821
- <!--
7822
- Selezione multipla: si dice quanti sono e si offre solo cio' che ha senso su molti. Nessun
7823
- campo del primo nodo, perche' non e' su quello che l’utente sta agendo.
7824
- -->
7825
- <div class="dpe-inspector__head">
7826
- <strong>{{ selectedNames().length }} nodi selezionati</strong>
7827
- <span class="dpe-inspector__category">selezione</span>
7828
- </div>
7829
-
7830
- <ul class="dpe-multi__names">
7831
- @for (name of selectedNames(); track $index) {
7832
- <li class="dpe-multi__name">{{ name }}</li>
7833
- }
7834
- </ul>
7835
-
7836
- <p class="dpe-note">
7837
- I campi di un singolo nodo si modificano con un nodo solo selezionato: mostrarli qui
7838
- farebbe credere di agire su tutti.
7839
- </p>
7840
-
7841
- <div class="dpe-row">
7842
- <button type="button" class="dpe-btn" (click)="removeSelected()">
7843
- Elimina i {{ selectedNames().length }} nodi
7844
- </button>
7845
- <!--
7846
- Il riordino e’ dichiaratamente **dell’intero grafo**: dagre su un sottografo sarebbe una
7847
- funzione nuova, e chiamarla «riordina i selezionati» mentre riordina tutto sarebbe una
7848
- bugia — la stessa classe di difetto che questo pannello esiste per evitare.
7849
- -->
7850
- <button type="button" class="dpe-btn" (click)="layoutRequested.emit()">
7851
- Riordina il grafo
7852
- </button>
7853
- </div>
7854
-
7855
- <p class="dpe-note dpe-note--warning">
7856
- L’eliminazione lascia i riferimenti a valle così come sono: diventano riferimenti rotti, e
7857
- il backend li segnala. Si annulla con ↶.
7858
- </p>
7859
- } @else if (located(); as entry) {
7860
- <div class="dpe-inspector__head">
7861
- <!--
7862
- La stessa icona della palette e della card: il terzo posto in cui compare il tipo, e
7863
- deve avere lo stesso aspetto degli altri due.
7864
- -->
7865
- <span class="dpe-inspector__icon" [class]="'dpe-inspector__icon ' + familyClass()">
7866
- <dpe-node-icon [collection]="entry.collection" />
7867
- </span>
7868
- <strong class="dpe-inspector__name">{{ entry.node.label || entry.node.name }}</strong>
7869
- <span class="dpe-inspector__category">{{ categoryLabel() }}</span>
7870
- </div>
7871
-
7941
+ @if (located(); as entry) {
7872
7942
  <div class="dpe-form">
7873
7943
  <div class="dpe-field">
7874
7944
  <label class="dpe-field__label" for="node-name">Nome tecnico</label>
@@ -7956,52 +8026,302 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.27", ngImpo
7956
8026
  }
7957
8027
  }
7958
8028
  </section>
8029
+ } @else {
8030
+ <p class="dpe-empty">Nessun nodo selezionato.</p>
8031
+ }
8032
+ `,
8033
+ }]
8034
+ }], ctorParameters: () => [] });
8035
+
8036
+ /**
8037
+ * La finestra di modifica di un nodo: elemento `<dialog>` **nativo**, aperto con `showModal()`
8038
+ * (ADR 0026).
8039
+ *
8040
+ * Perche' l'elemento nativo e non un overlay scritto a mano: **Esc**, il **focus trap** (con la
8041
+ * finestra aperta il Tab non esce), il **fondo inerte** per puntatore *e* tastiera, `::backdrop`
8042
+ * come pseudo-elemento standard e nessun `z-index` — il top layer sta sopra ogni stacking context
8043
+ * senza partecipare alla gara. Sono tutte cose che un overlay dovrebbe reimplementare, e il focus
8044
+ * trap in particolare non si scrive in due righe.
8045
+ *
8046
+ * **Una sola istanza** nel guscio, fratello di `.dpe-shell__body`. Resta nel sottoalbero DOM del
8047
+ * componente e non si trapianta in `document.body`: il top layer e' una promozione **visiva**, non
8048
+ * un trapianto, quindi gli eventi risalgono l'albero e il `Ctrl+S` del guscio continua a
8049
+ * funzionare.
8050
+ *
8051
+ * **Le modifiche si applicano dal vivo**: gli ispettori scrivono nello store a ogni `(change)`,
8052
+ * qui non esiste nessuna bozza locale e il piede ha «Chiudi», non «OK / Annulla». Una bozza
8053
+ * produrrebbe due nozioni di annullamento in disaccordo con ↶ e rilievi che descrivono un
8054
+ * documento diverso da quello mostrato. Conseguenza dichiarata in interfaccia: **chiudere non
8055
+ * annulla**.
8056
+ *
8057
+ * Il nodo **non arriva per input**: si rideriva da `selectedNodeName()`, cosi' se cambia sotto
8058
+ * (una rinomina) la dialog lo segue invece di guardare un nome che non esiste piu'.
8059
+ */
8060
+ /**
8061
+ * Contatore di istanza per l'id dell'etichetta. Serve perche' `aria-labelledby` punta a un id, e
8062
+ * due editor nella stessa pagina sono due dialog: con un id fisso il documento avrebbe due
8063
+ * elementi con lo stesso id e il nome accessibile della seconda finestra sarebbe quello della
8064
+ * prima. Non e' una preferenza di stile — un id duplicato e' un difetto che solo uno screen
8065
+ * reader mostra.
8066
+ */
8067
+ let nextLabelId = 0;
8068
+ class DpeNodeDialogComponent {
8069
+ store = inject(DpeDocumentStore);
8070
+ dialogRef = viewChild.required('dlg');
8071
+ labelId = `dpe-node-dialog-label-${(nextLabelId += 1)}`;
8072
+ /**
8073
+ * Il nodo corrente, **riderivato** dallo store come fa il dispatcher degli ispettori. Non si
8074
+ * conserva il nome: se il nodo viene rinominato, `selectedNodeName()` porta gia' il nome nuovo.
8075
+ */
8076
+ located = computed(() => {
8077
+ const name = this.store.selectedNodeName();
8078
+ return name === undefined ? undefined : dpeFindNode(this.store.definition(), name);
8079
+ }, ...(ngDevMode ? [{ debugName: "located" }] : []));
8080
+ categoryLabel = computed(() => {
8081
+ const entry = this.located();
8082
+ return entry ? dpeNodeCategory(entry.collection).label : '';
8083
+ }, ...(ngDevMode ? [{ debugName: "categoryLabel" }] : []));
8084
+ familyClass = computed(() => {
8085
+ const entry = this.located();
8086
+ return entry ? dpeNodeIconFamilyClass(entry.collection) : '';
8087
+ }, ...(ngDevMode ? [{ debugName: "familyClass" }] : []));
8088
+ /**
8089
+ * Vero quando il fondo ha ricevuto il `pointerdown` di questo clic. Vedi `onDialogClick`: la
8090
+ * verifica di coppia non e' prudenza, e' la correzione di un difetto reale.
8091
+ */
8092
+ pointerDownWasOnBackdrop = false;
8093
+ constructor() {
8094
+ effect(() => {
8095
+ const el = this.dialogRef().nativeElement;
8096
+ const shouldBeOpen = this.store.isNodeEditorOpen() && this.located() !== undefined;
8097
+ // La guardia su `el.open` **non e' cosmetica**: `showModal()` su una dialog gia' aperta
8098
+ // lancia `InvalidStateError`, e questo effect riesegue a ogni mutazione del documento
8099
+ // (legge `located()`). Senza, aprire e modificare un campo basterebbe a far comparire
8100
+ // l'eccezione in console.
8101
+ if (shouldBeOpen && !el.open) {
8102
+ el.showModal();
8103
+ }
8104
+ else if (!shouldBeOpen && el.open) {
8105
+ el.close();
8106
+ }
8107
+ });
8108
+ }
8109
+ /**
8110
+ * **Esc.** Il browser chiede di chiudere e la finestra e' ancora aperta: la richiesta riguarda
8111
+ * per definizione la sessione corrente, quindi si chiude e non serve nessuna verifica.
8112
+ *
8113
+ * Perche' `cancel` e' legato, contro cio' che sembra sufficiente: in Chrome 151 l'evento `close`
8114
+ * **arriva molto in ritardo** (vedi `onClose`), quindi con il solo `close` lo store restava
8115
+ * «aperta» dopo Esc — e il sintomo era che **riaprire lo stesso nodo non faceva niente**: il
8116
+ * signal non cambiava, l'effect non rieseguiva, `showModal()` non veniva mai chiamato. Sul
8117
+ * bottone del piede non si vedeva, perche' quella via passa dallo store.
8118
+ *
8119
+ * `cancel` sarebbe annullabile: non si annulla. La chiusura dell'elemento arriva comunque dal
8120
+ * browser, e la guardia su `el.open` nell'effect regge in entrambi gli ordini.
8121
+ */
8122
+ onCancel() {
8123
+ this.store.closeNodeEditor();
8124
+ }
8125
+ /**
8126
+ * **L'elemento si e' chiuso.** La guardia non e' prudenza: Chrome accoda `close` come task
8127
+ * dell'elemento e lo consegna anche **dopo** una riapertura — misurato nella demo, la traccia
8128
+ * degli eventi mostra `close` con `open: true` subito dopo un `showModal()`. Senza la guardia
8129
+ * quel `close` in ritardo chiudeva la finestra appena aperta, e il sintomo era una dialog che
8130
+ * «non si apre a volte»: sparisce da sola dopo essere comparsa, e con la finestra aperta si
8131
+ * riapriva solo alla prima modifica del documento (l'unica cosa che faceva rieseguire l'effect).
8132
+ *
8133
+ * Se l'elemento risulta **aperto**, questo `close` riguarda una sessione precedente e si ignora.
8134
+ * Se risulta chiuso, la notifica e' di questa sessione — e allineare qui lo store e' anche cio'
8135
+ * che ripara una divergenza, se il browser chiudesse la finestra per vie sue.
8136
+ */
8137
+ onClose() {
8138
+ if (this.dialogRef().nativeElement.open) {
8139
+ return;
8140
+ }
8141
+ this.store.closeNodeEditor();
8142
+ }
8143
+ onPointerDown(event) {
8144
+ this.pointerDownWasOnBackdrop = event.target === this.dialogRef().nativeElement;
8145
+ }
8146
+ /**
8147
+ * Chiusura al clic sul fondo, con la **verifica di coppia** e non con il solo controllo sul
8148
+ * click. I clic sul velo sono recapitati all'elemento `<dialog>` stesso — ma anche un
8149
+ * trascinamento di selezione del testo che **inizia** dentro una casella e **finisce** fuori
8150
+ * produce un `click` con `target` uguale alla dialog. Con la sola verifica sul click, selezionare
8151
+ * del testo chiuderebbe la finestra.
8152
+ */
8153
+ onDialogClick(event) {
8154
+ if (this.pointerDownWasOnBackdrop && event.target === this.dialogRef().nativeElement) {
8155
+ this.store.closeNodeEditor();
8156
+ }
8157
+ }
8158
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.27", ngImport: i0, type: DpeNodeDialogComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
8159
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.27", type: DpeNodeDialogComponent, isStandalone: true, selector: "dpe-node-dialog", viewQueries: [{ propertyName: "dialogRef", first: true, predicate: ["dlg"], descendants: true, isSignal: true }], ngImport: i0, template: `
8160
+ <!--
8161
+ L’elemento dialog sta **sempre** nel template, e solo il contenuto e’ condizionato: cosi’
8162
+ viewChild.required non puo’ fallire e l’effect che apre non deve difendersi da un riferimento
8163
+ assente. Una dialog vuota e chiusa non rende niente — il foglio dell’agente utente le da’
8164
+ display: none.
8165
+ -->
8166
+ <dialog
8167
+ #dlg
8168
+ class="dpe-dialog"
8169
+ [attr.aria-labelledby]="labelId"
8170
+ (close)="onClose()"
8171
+ (cancel)="onCancel()"
8172
+ (click)="onDialogClick($event)"
8173
+ (pointerdown)="onPointerDown($event)"
8174
+ >
8175
+ @if (located(); as entry) {
8176
+ <header class="dpe-dialog__head">
8177
+ <!--
8178
+ Quarto posto in cui compare il tipo di nodo (palette, card del canvas, ispettore, qui):
8179
+ stessa icona e stessa classe di famiglia, calcolate dalle stesse funzioni. Un tipo che
8180
+ cambia aspetto da un posto all’altro e’ un tipo che non si riconosce.
8181
+ -->
8182
+ <span class="dpe-dialog__icon" [class]="'dpe-dialog__icon ' + familyClass()">
8183
+ <dpe-node-icon [collection]="entry.collection" />
8184
+ </span>
8185
+ <strong class="dpe-dialog__label" [id]="labelId">
8186
+ {{ entry.node.label || entry.node.name }}
8187
+ </strong>
8188
+ <span class="dpe-dialog__category">{{ categoryLabel() }}</span>
8189
+ <code class="dpe-dialog__name">{{ entry.node.name }}</code>
8190
+ <button
8191
+ type="button"
8192
+ class="dpe-dialog__btn dpe-dialog__btn--icon"
8193
+ aria-label="Chiudi la finestra di modifica"
8194
+ title="Chiudi (Esc). Le modifiche sono già nel documento."
8195
+ (click)="store.closeNodeEditor()"
8196
+ >
8197
+ ✕
8198
+ </button>
8199
+ </header>
8200
+
8201
+ <div class="dpe-dialog__body">
8202
+ <!-- Colonna larga: i campi. E’ la ragione per cui questa finestra esiste. -->
8203
+ <div class="dpe-dialog__main">
8204
+ <dpe-node-inspector />
8205
+ </div>
7959
8206
 
7960
- <section class="dpe-section">
7961
- <h4 class="dpe-section__title">Schema in uscita</h4>
7962
- @if (columns().length === 0) {
7963
- <p class="dpe-empty">
7964
- Nessuna colonna: il backend non ha (ancora) propagato uno schema per questo nodo.
8207
+ <!--
8208
+ Colonna stretta: i fatti dal backend su **questo** nodo. Non entra cio’ che riguarda il
8209
+ grafo — diagnostiche di tutta la definizione, schema di tutti i nodi e SQL generato
8210
+ hanno gia’ le loro linguette nel pannello inferiore.
8211
+ -->
8212
+ <aside class="dpe-dialog__side">
8213
+ <dpe-node-facts [nodeName]="entry.node.name" />
8214
+ </aside>
8215
+ </div>
8216
+
8217
+ <footer class="dpe-dialog__foot">
8218
+ <p class="dpe-dialog__note">
8219
+ Le modifiche sono <strong>già nel documento</strong>: chiudere non le annulla. Si torna
8220
+ indietro con ↶ nella barra.
7965
8221
  </p>
7966
- } @else {
7967
- <div class="dpe-columns">
7968
- @for (column of columns(); track $index) {
7969
- <span
7970
- class="dpe-column"
7971
- [class.dpe-column--nullable]="column.isNullable"
7972
- [title]="column.type.kind + (column.isNullable ? ' · nullabile' : ' · non nulla')"
7973
- >
7974
- {{ column.name }}
7975
- </span>
7976
- }
8222
+ <button
8223
+ type="button"
8224
+ class="dpe-dialog__btn dpe-dialog__btn--danger"
8225
+ title="Elimina il nodo: i riferimenti a valle restano e diventano riferimenti rotti. Si annulla con ↶."
8226
+ (click)="store.removeNode(entry.node.name)"
8227
+ >
8228
+ Elimina il nodo
8229
+ </button>
8230
+ <button type="button" class="dpe-dialog__btn" (click)="store.closeNodeEditor()">
8231
+ Chiudi
8232
+ </button>
8233
+ </footer>
8234
+ }
8235
+ </dialog>
8236
+ `, isInline: true, dependencies: [{ kind: "component", type: DpeNodeFactsComponent, selector: "dpe-node-facts", inputs: ["nodeName"] }, { kind: "component", type: DpeNodeInspectorComponent, selector: "dpe-node-inspector" }, { kind: "component", type: DpeNodeIconComponent, selector: "dpe-node-icon", inputs: ["collection"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
8237
+ }
8238
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.27", ngImport: i0, type: DpeNodeDialogComponent, decorators: [{
8239
+ type: Component,
8240
+ args: [{
8241
+ selector: 'dpe-node-dialog',
8242
+ standalone: true,
8243
+ imports: [DpeNodeFactsComponent, DpeNodeInspectorComponent, DpeNodeIconComponent],
8244
+ changeDetection: ChangeDetectionStrategy.OnPush,
8245
+ template: `
8246
+ <!--
8247
+ L’elemento dialog sta **sempre** nel template, e solo il contenuto e’ condizionato: cosi’
8248
+ viewChild.required non puo’ fallire e l’effect che apre non deve difendersi da un riferimento
8249
+ assente. Una dialog vuota e chiusa non rende niente — il foglio dell’agente utente le da’
8250
+ display: none.
8251
+ -->
8252
+ <dialog
8253
+ #dlg
8254
+ class="dpe-dialog"
8255
+ [attr.aria-labelledby]="labelId"
8256
+ (close)="onClose()"
8257
+ (cancel)="onCancel()"
8258
+ (click)="onDialogClick($event)"
8259
+ (pointerdown)="onPointerDown($event)"
8260
+ >
8261
+ @if (located(); as entry) {
8262
+ <header class="dpe-dialog__head">
8263
+ <!--
8264
+ Quarto posto in cui compare il tipo di nodo (palette, card del canvas, ispettore, qui):
8265
+ stessa icona e stessa classe di famiglia, calcolate dalle stesse funzioni. Un tipo che
8266
+ cambia aspetto da un posto all’altro e’ un tipo che non si riconosce.
8267
+ -->
8268
+ <span class="dpe-dialog__icon" [class]="'dpe-dialog__icon ' + familyClass()">
8269
+ <dpe-node-icon [collection]="entry.collection" />
8270
+ </span>
8271
+ <strong class="dpe-dialog__label" [id]="labelId">
8272
+ {{ entry.node.label || entry.node.name }}
8273
+ </strong>
8274
+ <span class="dpe-dialog__category">{{ categoryLabel() }}</span>
8275
+ <code class="dpe-dialog__name">{{ entry.node.name }}</code>
8276
+ <button
8277
+ type="button"
8278
+ class="dpe-dialog__btn dpe-dialog__btn--icon"
8279
+ aria-label="Chiudi la finestra di modifica"
8280
+ title="Chiudi (Esc). Le modifiche sono già nel documento."
8281
+ (click)="store.closeNodeEditor()"
8282
+ >
8283
+ ✕
8284
+ </button>
8285
+ </header>
8286
+
8287
+ <div class="dpe-dialog__body">
8288
+ <!-- Colonna larga: i campi. E’ la ragione per cui questa finestra esiste. -->
8289
+ <div class="dpe-dialog__main">
8290
+ <dpe-node-inspector />
7977
8291
  </div>
7978
- <p class="dpe-field__hint">
7979
- Tratteggiate le colonne nullabili. Lo schema arriva da <code>validate</code>: non lo
7980
- calcoliamo qui.
7981
- </p>
7982
- }
7983
- </section>
7984
8292
 
7985
- @if (diagnostics().length > 0) {
7986
- <section class="dpe-section">
7987
- <h4 class="dpe-section__title">Rilievi su questo nodo</h4>
7988
- @for (diagnostic of diagnostics(); track $index) {
7989
- <p
7990
- class="dpe-field__issue"
7991
- [class.dpe-field__issue--warning]="diagnostic.severity === 'Warning'"
7992
- [class.dpe-field__issue--info]="diagnostic.severity === 'Info'"
7993
- >
7994
- <code>{{ diagnostic.code }}</code> — {{ diagnostic.message }}
7995
- </p>
7996
- }
7997
- </section>
8293
+ <!--
8294
+ Colonna stretta: i fatti dal backend su **questo** nodo. Non entra cio’ che riguarda il
8295
+ grafo — diagnostiche di tutta la definizione, schema di tutti i nodi e SQL generato
8296
+ hanno gia’ le loro linguette nel pannello inferiore.
8297
+ -->
8298
+ <aside class="dpe-dialog__side">
8299
+ <dpe-node-facts [nodeName]="entry.node.name" />
8300
+ </aside>
8301
+ </div>
8302
+
8303
+ <footer class="dpe-dialog__foot">
8304
+ <p class="dpe-dialog__note">
8305
+ Le modifiche sono <strong>già nel documento</strong>: chiudere non le annulla. Si torna
8306
+ indietro con ↶ nella barra.
8307
+ </p>
8308
+ <button
8309
+ type="button"
8310
+ class="dpe-dialog__btn dpe-dialog__btn--danger"
8311
+ title="Elimina il nodo: i riferimenti a valle restano e diventano riferimenti rotti. Si annulla con ↶."
8312
+ (click)="store.removeNode(entry.node.name)"
8313
+ >
8314
+ Elimina il nodo
8315
+ </button>
8316
+ <button type="button" class="dpe-dialog__btn" (click)="store.closeNodeEditor()">
8317
+ Chiudi
8318
+ </button>
8319
+ </footer>
7998
8320
  }
7999
- } @else {
8000
- <p class="dpe-empty">Nessun nodo selezionato.</p>
8001
- }
8321
+ </dialog>
8002
8322
  `,
8003
8323
  }]
8004
- }], ctorParameters: () => [], propDecorators: { layoutRequested: [{ type: i0.Output, args: ["layoutRequested"] }] } });
8324
+ }], ctorParameters: () => [], propDecorators: { dialogRef: [{ type: i0.ViewChild, args: ['dlg', { isSignal: true }] }] } });
8005
8325
 
8006
8326
  /**
8007
8327
  * La palette dei nodi.
@@ -8649,6 +8969,8 @@ class DpeBuilderComponent {
8649
8969
  this.store.isDirty() &&
8650
8970
  this.hasKey(), ...(ngDevMode ? [{ debugName: "canSave" }] : []));
8651
8971
  nodeCount = computed(() => dpeAllNodes(this.store.definition()).length, ...(ngDevMode ? [{ debugName: "nodeCount" }] : []));
8972
+ /** I nomi selezionati: la striscia contestuale compare da due in su. */
8973
+ selectedNames = computed(() => this.store.selectedNodeNames(), ...(ngDevMode ? [{ debugName: "selectedNames" }] : []));
8652
8974
  constructor() {
8653
8975
  // Caricamento: si reagisce all'**identita'** dell'input, non al suo contenuto. Lo store
8654
8976
  // diventa la fonte di verita' da qui in poi.
@@ -8709,6 +9031,28 @@ class DpeBuilderComponent {
8709
9031
  // mouse subito dopo averlo rilasciato e' un passaggio inutile.
8710
9032
  this.store.select(name);
8711
9033
  }
9034
+ /**
9035
+ * Doppio clic sul nodo, o bottone `✎` sulla card: i due gesti che il canvas emetteva su
9036
+ * `nodeOpened` e che **nessuno ascoltava** — il bottone non produceva nemmeno la selezione.
9037
+ *
9038
+ * Due cose, in quest'ordine. Si apre la dialog (che collassa la selezione a quel nodo) e si
9039
+ * **inquadra** il nodo: sotto il velo non si vede, ma alla chiusura si torna guardando il nodo su
9040
+ * cui si e' lavorato invece del punto dov'era il grafo prima.
9041
+ */
9042
+ onNodeOpened(name) {
9043
+ this.store.openNodeEditor(name);
9044
+ this.canvas()?.revealNode(name);
9045
+ }
9046
+ /**
9047
+ * Elimina i nodi selezionati (striscia della selezione multipla). Si itera su una **copia** dei
9048
+ * nomi: `removeNode` pota la selezione a ogni giro, e scorrere il signal mentre lo si svuota
9049
+ * salterebbe la meta' dei nodi.
9050
+ */
9051
+ removeSelectedNodes() {
9052
+ for (const name of [...this.selectedNames()]) {
9053
+ this.store.removeNode(name);
9054
+ }
9055
+ }
8712
9056
  /** Clic sulla palette (via tastiera compresa): il nodo nasce in un punto libero e prevedibile. */
8713
9057
  onNodeRequested(collection) {
8714
9058
  const existing = this.store.positions();
@@ -8762,18 +9106,18 @@ class DpeBuilderComponent {
8762
9106
  this.store.markSaved();
8763
9107
  }
8764
9108
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.27", ngImport: i0, type: DpeBuilderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
8765
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.27", type: DpeBuilderComponent, isStandalone: true, selector: "dpe-builder", inputs: { definition: { classPropertyName: "definition", publicName: "definition", isSignal: true, isRequired: true, transformFunction: null }, isEditable: { classPropertyName: "isEditable", publicName: "isEditable", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { definitionChange: "definitionChange" }, host: { listeners: { "keydown": "onKeyDown($event)" } }, providers: [DpeDocumentStore, DpeLayoutService, DpeValidationService, DpeMetadataStore], viewQueries: [{ propertyName: "canvas", first: true, predicate: DpeCanvasComponent, descendants: true, isSignal: true }], ngImport: i0, template: "<!--\r\n `.dpe-builder` porta le variabili del tema: e\u2019 la classe radice su cui l\u2019app ospite le\r\n sovrascrive, e `data-dpe-theme=\"dark\"` sul contenitore ridefinisce i soli token.\r\n-->\r\n<div class=\"dpe-builder dpe-shell\">\r\n <header class=\"dpe-shell__bar\">\r\n <div class=\"dpe-shell__title\">\r\n <strong>{{ store.definition().label || store.definition().name || 'Definizione DPE' }}</strong>\r\n <span class=\"dpe-shell__sub\">{{ nodeCount() }} nodi</span>\r\n <!--\r\n Stato **sporco** rispetto all\u2019ultimo salvataggio, non un conteggio di operazioni: serve a\r\n decidere se salvare, e un contatore che sale quando si annulla risponde alla domanda\r\n sbagliata. Annullare fino allo stato di partenza torna qui a \u00ABnessuna modifica\u00BB.\r\n -->\r\n <span\r\n class=\"dpe-shell__dirty\"\r\n [class.dpe-shell__dirty--on]=\"store.isDirty()\"\r\n [title]=\"\r\n store.isDirty()\r\n ? 'Il documento differisce dall\u2019ultimo salvataggio'\r\n : 'Il documento coincide con l\u2019ultimo salvataggio'\r\n \"\r\n >\r\n @if (store.isDirty()) {\r\n \u25CF modificata\r\n } @else {\r\n \u25CB nessuna modifica\r\n }\r\n </span>\r\n </div>\r\n\r\n <!--\r\n Badge in **sola lettura** (ADR 0017): il dialetto e\u2019 dichiarato dall\u2019ambiente, non scelto\r\n qui. Mostra quello della definizione, che e\u2019 quello con cui il backend compilera\u2019 (ADR\r\n 0009). Il titolo dice le due cose che contano \u2014 chi lo decide, e che le espressioni sono\r\n SQL nativo di quel dialetto (ADR 0006).\r\n -->\r\n <span\r\n class=\"dpe-shell__dialect\"\r\n title=\"Il dialetto e\u2019 dichiarato dall\u2019ambiente e non si modifica dall\u2019editor. Le espressioni sono SQL nativo di questo dialetto: la definizione gli appartiene.\"\r\n >\r\n Dialetto \u00B7 <strong>{{ dialectLabels[dialect()] }}</strong>\r\n </span>\r\n\r\n <div class=\"dpe-shell__group\">\r\n <button\r\n type=\"button\"\r\n class=\"dpe-shell__btn\"\r\n [disabled]=\"!store.canUndo()\"\r\n title=\"Annulla (la cronologia e\u2019 una conseguenza dell\u2019immutabilita\u2019 dello stato)\"\r\n (click)=\"store.undo()\"\r\n >\r\n \u21B6 Annulla\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"dpe-shell__btn\"\r\n [disabled]=\"!store.canRedo()\"\r\n title=\"Ripeti\"\r\n (click)=\"store.redo()\"\r\n >\r\n \u21B7 Ripeti\r\n </button>\r\n <button type=\"button\" class=\"dpe-shell__btn\" title=\"Riordina il grafo\" (click)=\"runAutoLayout()\">\r\n \u2317 Riordina\r\n </button>\r\n </div>\r\n\r\n <!--\r\n Salvataggio attraverso il contratto (`DpeBuilderApi.save`, opzionale). E\u2019 il comando\r\n **primario**, separato dal gruppo annulla/ripeti/riordina: quelli disfano, questo scrive.\r\n\r\n **Sempre presente, mai nascosto.** Un bottone assente dice \u00ABquesto editor non salva\u00BB; un\r\n bottone disabilitato con il motivo dice \u00ABqui non si salva, ed ecco perche\u2019\u00BB \u2014 ed e\u2019 la\r\n verita\u2019 quando l\u2019host non espone `save()`. La disponibilita\u2019 si sa **prima del clic**\r\n (`core/dpe-capabilities.ts`), non dal primo fallimento.\r\n -->\r\n <div class=\"dpe-shell__group dpe-shell__group--primary\">\r\n <button\r\n type=\"button\"\r\n class=\"dpe-shell__btn dpe-shell__btn--primary\"\r\n [disabled]=\"!canSave()\"\r\n [title]=\"\r\n !validation.isSaveAvailable()\r\n ? 'Salvataggio non disponibile su questo ambiente: l\u2019host non espone save()'\r\n : validation.isSaving()\r\n ? 'Salvataggio in corso'\r\n : !hasKey()\r\n ? 'Il documento non ha un nome: e\u2019 la chiave del catalogo, e va creato da la\u2019 (l\u2019aggiornamento e\u2019 PUT /definitions/{nome})'\r\n : store.isDirty()\r\n ? 'Salva la definizione (Ctrl+S)'\r\n : 'Nessuna modifica da salvare'\r\n \"\r\n (click)=\"validation.save()\"\r\n >\r\n @if (validation.isSaving()) {\r\n \u2913 Salvataggio\u2026\r\n } @else {\r\n \u2913 Salva\r\n }\r\n </button>\r\n @if (validation.justSaved()) {\r\n <!-- Avviso transitorio: il fatto durevole e\u2019 \u00ABnessuna modifica\u00BB nell\u2019indicatore. -->\r\n <span class=\"dpe-shell__toast\" role=\"status\">\u2713 Salvata</span>\r\n }\r\n </div>\r\n\r\n <div class=\"dpe-shell__status\">\r\n @if (validation.isValidating()) {\r\n <span class=\"dpe-shell__pill dpe-shell__pill--busy\">Validazione in corso\u2026</span>\r\n } @else if (validation.error(); as error) {\r\n <!--\r\n Un guasto di trasporto non cancella le diagnostiche precedenti: dire \u00ABva tutto bene\u00BB\r\n quando non lo sappiamo piu\u2019 e\u2019 peggio che mostrare un risultato vecchio dichiarandolo.\r\n -->\r\n <span class=\"dpe-shell__pill dpe-shell__pill--error\" [title]=\"error.message\">\r\n Backend non raggiungibile ({{ error.category }})\r\n </span>\r\n } @else if (store.validateResult()) {\r\n <span\r\n class=\"dpe-shell__pill\"\r\n [class.dpe-shell__pill--error]=\"store.validateResult()!.hasErrors\"\r\n [class.dpe-shell__pill--ok]=\"!store.validateResult()!.hasErrors\"\r\n >\r\n @if (store.validateResult()!.hasErrors) {\r\n Con errori\r\n } @else {\r\n Nessun errore\r\n }\r\n </span>\r\n }\r\n </div>\r\n </header>\r\n\r\n @if (dialectMismatch(); as declared) {\r\n <!--\r\n Disallineamento fra la dichiarazione dell\u2019ambiente e la definizione (ADR 0017). Si nominano\r\n **entrambi** i dialetti e non si offre di correggere: correggere significherebbe migrare le\r\n espressioni SQL native, ed e\u2019 dell\u2019host. Non e\u2019 una regola di dominio duplicata \u2014 non giudica\r\n la definizione, riporta il disaccordo fra due fatti che l\u2019ospite ha fornito.\r\n -->\r\n <div class=\"dpe-shell__warn\" role=\"alert\">\r\n <strong>\r\n L\u2019ambiente dichiara {{ dialectLabels[declared] }}, la definizione e\u2019 scritta per\r\n {{ dialectLabels[dialect()] }}.\r\n </strong>\r\n <p>\r\n Il backend compilera\u2019 con il dialetto <strong>della definizione</strong>. Allinearli e\u2019 una\r\n migrazione delle espressioni SQL native e non si fa da qui.\r\n </p>\r\n </div>\r\n }\r\n\r\n <!--\r\n Il documento ricevuto non e' una definizione. Banner **persistente** e in cima: e' un difetto\r\n dell'integrazione, non dell'utente, e un canvas vuoto da solo non lo dice \u2014 sembra una\r\n definizione senza nodi. Non si nasconde l'editor: cio' che c'e' resta visibile, e il salvataggio\r\n e' disabilitato da se' perche' la chiave manca.\r\n -->\r\n @if (documentProblem(); as problem) {\r\n <div class=\"dpe-shell__warn dpe-shell__warn--error\" role=\"alert\">\r\n <strong>Il documento ricevuto non e\u2019 una definizione: {{ problem }}.</strong>\r\n <p>\r\n L\u2019editor riceve il documento dall\u2019applicazione ospite, e questo non ha la forma di una\r\n <code>DpeDefinition</code>. Le tre cause reali: il backend restituisce l\u2019involucro di lettura\r\n (<code>{{ '{' }} definition, diagnostics {{ '}' }}</code>) invece del documento; l\u2019ospite\r\n passa il <strong>sommario</strong> del catalogo al posto della definizione caricata con\r\n <code>load(nome)</code>; la risposta e\u2019 in PascalCase (<code>Name</code> non e\u2019\r\n <code>name</code>).\r\n </p>\r\n </div>\r\n }\r\n\r\n @if (validation.saveError(); as error) {\r\n <!--\r\n Banner **persistente**: un salvataggio fallito in silenzio e\u2019 peggio di nessun salvataggio,\r\n perche\u2019 l\u2019indicatore \u00ABmodificata\u00BB sparirebbe per un\u2019operazione che non e\u2019 avvenuta. Qui non\r\n sparisce: `markSaved()` viene chiamato solo al successo.\r\n -->\r\n <div class=\"dpe-shell__warn dpe-shell__warn--error\" role=\"alert\">\r\n <strong>Salvataggio non riuscito ({{ error.category }})</strong>\r\n <p>{{ error.message }}</p>\r\n <div class=\"dpe-shell__group\">\r\n <button type=\"button\" class=\"dpe-shell__btn\" (click)=\"validation.dismissSaveError()\">\r\n Ho capito\r\n </button>\r\n </div>\r\n </div>\r\n }\r\n\r\n <div class=\"dpe-shell__body\">\r\n <dpe-node-palette class=\"dpe-shell__palette\" (nodeRequested)=\"onNodeRequested($event)\" />\r\n\r\n <dpe-canvas\r\n class=\"dpe-shell__canvas\"\r\n [isEditable]=\"isEditable()\"\r\n (nodeDropped)=\"onNodeDropped($event)\"\r\n />\r\n\r\n <dpe-node-inspector class=\"dpe-shell__inspector\" (layoutRequested)=\"runAutoLayout()\" />\r\n </div>\r\n\r\n <div class=\"dpe-shell__bottom\">\r\n <div class=\"dpe-shell__tabs\" role=\"tablist\" aria-label=\"Pannelli\">\r\n <button\r\n type=\"button\"\r\n role=\"tab\"\r\n class=\"dpe-shell__tab\"\r\n [class.dpe-shell__tab--active]=\"bottomTab() === 'diagnostics'\"\r\n [attr.aria-selected]=\"bottomTab() === 'diagnostics'\"\r\n (click)=\"bottomTab.set('diagnostics')\"\r\n >\r\n Diagnostiche\r\n </button>\r\n <button\r\n type=\"button\"\r\n role=\"tab\"\r\n class=\"dpe-shell__tab\"\r\n [class.dpe-shell__tab--active]=\"bottomTab() === 'schema'\"\r\n [attr.aria-selected]=\"bottomTab() === 'schema'\"\r\n (click)=\"bottomTab.set('schema')\"\r\n >\r\n Schema\r\n </button>\r\n <button\r\n type=\"button\"\r\n role=\"tab\"\r\n class=\"dpe-shell__tab\"\r\n [class.dpe-shell__tab--active]=\"bottomTab() === 'sql'\"\r\n [attr.aria-selected]=\"bottomTab() === 'sql'\"\r\n (click)=\"bottomTab.set('sql')\"\r\n >\r\n SQL generato\r\n </button>\r\n </div>\r\n\r\n @switch (bottomTab()) {\r\n @case ('diagnostics') {\r\n <dpe-diagnostics-panel class=\"dpe-shell__panel\" (navigate)=\"onNavigate($event)\" />\r\n }\r\n @case ('schema') {\r\n <dpe-schema-panel class=\"dpe-shell__panel\" />\r\n }\r\n @case ('sql') {\r\n <dpe-sql-preview class=\"dpe-shell__panel\" />\r\n }\r\n }\r\n </div>\r\n</div>\r\n", dependencies: [{ kind: "component", type: DpeCanvasComponent, selector: "dpe-canvas", inputs: ["isEditable"], outputs: ["nodeOpened", "nodeDropped"] }, { kind: "component", type: DpeDiagnosticsPanelComponent, selector: "dpe-diagnostics-panel", outputs: ["navigate"] }, { kind: "component", type: DpeNodeInspectorComponent, selector: "dpe-node-inspector", outputs: ["layoutRequested"] }, { kind: "component", type: DpeNodePaletteComponent, selector: "dpe-node-palette", outputs: ["nodeRequested"] }, { kind: "component", type: DpeSchemaPanelComponent, selector: "dpe-schema-panel" }, { kind: "component", type: DpeSqlPreviewComponent, selector: "dpe-sql-preview" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
9109
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.27", type: DpeBuilderComponent, isStandalone: true, selector: "dpe-builder", inputs: { definition: { classPropertyName: "definition", publicName: "definition", isSignal: true, isRequired: true, transformFunction: null }, isEditable: { classPropertyName: "isEditable", publicName: "isEditable", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { definitionChange: "definitionChange" }, host: { listeners: { "keydown": "onKeyDown($event)" } }, providers: [DpeDocumentStore, DpeLayoutService, DpeValidationService, DpeMetadataStore], viewQueries: [{ propertyName: "canvas", first: true, predicate: DpeCanvasComponent, descendants: true, isSignal: true }], ngImport: i0, template: "<!--\r\n `.dpe-builder` porta le variabili del tema: e\u2019 la classe radice su cui l\u2019app ospite le\r\n sovrascrive, e `data-dpe-theme=\"dark\"` sul contenitore ridefinisce i soli token.\r\n-->\r\n<div class=\"dpe-builder dpe-shell\">\r\n <header class=\"dpe-shell__bar\">\r\n <div class=\"dpe-shell__title\">\r\n <strong>{{ store.definition().label || store.definition().name || 'Definizione DPE' }}</strong>\r\n <span class=\"dpe-shell__sub\">{{ nodeCount() }} nodi</span>\r\n <!--\r\n Stato **sporco** rispetto all\u2019ultimo salvataggio, non un conteggio di operazioni: serve a\r\n decidere se salvare, e un contatore che sale quando si annulla risponde alla domanda\r\n sbagliata. Annullare fino allo stato di partenza torna qui a \u00ABnessuna modifica\u00BB.\r\n -->\r\n <span\r\n class=\"dpe-shell__dirty\"\r\n [class.dpe-shell__dirty--on]=\"store.isDirty()\"\r\n [title]=\"\r\n store.isDirty()\r\n ? 'Il documento differisce dall\u2019ultimo salvataggio'\r\n : 'Il documento coincide con l\u2019ultimo salvataggio'\r\n \"\r\n >\r\n @if (store.isDirty()) {\r\n \u25CF modificata\r\n } @else {\r\n \u25CB nessuna modifica\r\n }\r\n </span>\r\n </div>\r\n\r\n <!--\r\n Badge in **sola lettura** (ADR 0017): il dialetto e\u2019 dichiarato dall\u2019ambiente, non scelto\r\n qui. Mostra quello della definizione, che e\u2019 quello con cui il backend compilera\u2019 (ADR\r\n 0009). Il titolo dice le due cose che contano \u2014 chi lo decide, e che le espressioni sono\r\n SQL nativo di quel dialetto (ADR 0006).\r\n -->\r\n <span\r\n class=\"dpe-shell__dialect\"\r\n title=\"Il dialetto e\u2019 dichiarato dall\u2019ambiente e non si modifica dall\u2019editor. Le espressioni sono SQL nativo di questo dialetto: la definizione gli appartiene.\"\r\n >\r\n Dialetto \u00B7 <strong>{{ dialectLabels[dialect()] }}</strong>\r\n </span>\r\n\r\n <div class=\"dpe-shell__group\">\r\n <button\r\n type=\"button\"\r\n class=\"dpe-shell__btn\"\r\n [disabled]=\"!store.canUndo()\"\r\n title=\"Annulla (la cronologia e\u2019 una conseguenza dell\u2019immutabilita\u2019 dello stato)\"\r\n (click)=\"store.undo()\"\r\n >\r\n \u21B6 Annulla\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"dpe-shell__btn\"\r\n [disabled]=\"!store.canRedo()\"\r\n title=\"Ripeti\"\r\n (click)=\"store.redo()\"\r\n >\r\n \u21B7 Ripeti\r\n </button>\r\n <button type=\"button\" class=\"dpe-shell__btn\" title=\"Riordina il grafo\" (click)=\"runAutoLayout()\">\r\n \u2317 Riordina\r\n </button>\r\n </div>\r\n\r\n <!--\r\n Salvataggio attraverso il contratto (`DpeBuilderApi.save`, opzionale). E\u2019 il comando\r\n **primario**, separato dal gruppo annulla/ripeti/riordina: quelli disfano, questo scrive.\r\n\r\n **Sempre presente, mai nascosto.** Un bottone assente dice \u00ABquesto editor non salva\u00BB; un\r\n bottone disabilitato con il motivo dice \u00ABqui non si salva, ed ecco perche\u2019\u00BB \u2014 ed e\u2019 la\r\n verita\u2019 quando l\u2019host non espone `save()`. La disponibilita\u2019 si sa **prima del clic**\r\n (`core/dpe-capabilities.ts`), non dal primo fallimento.\r\n -->\r\n <div class=\"dpe-shell__group dpe-shell__group--primary\">\r\n <button\r\n type=\"button\"\r\n class=\"dpe-shell__btn dpe-shell__btn--primary\"\r\n [disabled]=\"!canSave()\"\r\n [title]=\"\r\n !validation.isSaveAvailable()\r\n ? 'Salvataggio non disponibile su questo ambiente: l\u2019host non espone save()'\r\n : validation.isSaving()\r\n ? 'Salvataggio in corso'\r\n : !hasKey()\r\n ? 'Il documento non ha un nome: e\u2019 la chiave del catalogo, e va creato da la\u2019 (l\u2019aggiornamento e\u2019 PUT /definitions/{nome})'\r\n : store.isDirty()\r\n ? 'Salva la definizione (Ctrl+S)'\r\n : 'Nessuna modifica da salvare'\r\n \"\r\n (click)=\"validation.save()\"\r\n >\r\n @if (validation.isSaving()) {\r\n \u2913 Salvataggio\u2026\r\n } @else {\r\n \u2913 Salva\r\n }\r\n </button>\r\n @if (validation.justSaved()) {\r\n <!-- Avviso transitorio: il fatto durevole e\u2019 \u00ABnessuna modifica\u00BB nell\u2019indicatore. -->\r\n <span class=\"dpe-shell__toast\" role=\"status\">\u2713 Salvata</span>\r\n }\r\n </div>\r\n\r\n <div class=\"dpe-shell__status\">\r\n @if (validation.isValidating()) {\r\n <span class=\"dpe-shell__pill dpe-shell__pill--busy\">Validazione in corso\u2026</span>\r\n } @else if (validation.error(); as error) {\r\n <!--\r\n Un guasto di trasporto non cancella le diagnostiche precedenti: dire \u00ABva tutto bene\u00BB\r\n quando non lo sappiamo piu\u2019 e\u2019 peggio che mostrare un risultato vecchio dichiarandolo.\r\n -->\r\n <span class=\"dpe-shell__pill dpe-shell__pill--error\" [title]=\"error.message\">\r\n Backend non raggiungibile ({{ error.category }})\r\n </span>\r\n } @else if (store.validateResult()) {\r\n <span\r\n class=\"dpe-shell__pill\"\r\n [class.dpe-shell__pill--error]=\"store.validateResult()!.hasErrors\"\r\n [class.dpe-shell__pill--ok]=\"!store.validateResult()!.hasErrors\"\r\n >\r\n @if (store.validateResult()!.hasErrors) {\r\n Con errori\r\n } @else {\r\n Nessun errore\r\n }\r\n </span>\r\n }\r\n </div>\r\n </header>\r\n\r\n @if (dialectMismatch(); as declared) {\r\n <!--\r\n Disallineamento fra la dichiarazione dell\u2019ambiente e la definizione (ADR 0017). Si nominano\r\n **entrambi** i dialetti e non si offre di correggere: correggere significherebbe migrare le\r\n espressioni SQL native, ed e\u2019 dell\u2019host. Non e\u2019 una regola di dominio duplicata \u2014 non giudica\r\n la definizione, riporta il disaccordo fra due fatti che l\u2019ospite ha fornito.\r\n -->\r\n <div class=\"dpe-shell__warn\" role=\"alert\">\r\n <strong>\r\n L\u2019ambiente dichiara {{ dialectLabels[declared] }}, la definizione e\u2019 scritta per\r\n {{ dialectLabels[dialect()] }}.\r\n </strong>\r\n <p>\r\n Il backend compilera\u2019 con il dialetto <strong>della definizione</strong>. Allinearli e\u2019 una\r\n migrazione delle espressioni SQL native e non si fa da qui.\r\n </p>\r\n </div>\r\n }\r\n\r\n <!--\r\n Il documento ricevuto non e' una definizione. Banner **persistente** e in cima: e' un difetto\r\n dell'integrazione, non dell'utente, e un canvas vuoto da solo non lo dice \u2014 sembra una\r\n definizione senza nodi. Non si nasconde l'editor: cio' che c'e' resta visibile, e il salvataggio\r\n e' disabilitato da se' perche' la chiave manca.\r\n -->\r\n @if (documentProblem(); as problem) {\r\n <div class=\"dpe-shell__warn dpe-shell__warn--error\" role=\"alert\">\r\n <strong>Il documento ricevuto non e\u2019 una definizione: {{ problem }}.</strong>\r\n <p>\r\n L\u2019editor riceve il documento dall\u2019applicazione ospite, e questo non ha la forma di una\r\n <code>DpeDefinition</code>. Le tre cause reali: il backend restituisce l\u2019involucro di lettura\r\n (<code>{{ '{' }} definition, diagnostics {{ '}' }}</code>) invece del documento; l\u2019ospite\r\n passa il <strong>sommario</strong> del catalogo al posto della definizione caricata con\r\n <code>load(nome)</code>; la risposta e\u2019 in PascalCase (<code>Name</code> non e\u2019\r\n <code>name</code>).\r\n </p>\r\n </div>\r\n }\r\n\r\n @if (validation.saveError(); as error) {\r\n <!--\r\n Banner **persistente**: un salvataggio fallito in silenzio e\u2019 peggio di nessun salvataggio,\r\n perche\u2019 l\u2019indicatore \u00ABmodificata\u00BB sparirebbe per un\u2019operazione che non e\u2019 avvenuta. Qui non\r\n sparisce: `markSaved()` viene chiamato solo al successo.\r\n -->\r\n <div class=\"dpe-shell__warn dpe-shell__warn--error\" role=\"alert\">\r\n <strong>Salvataggio non riuscito ({{ error.category }})</strong>\r\n <p>{{ error.message }}</p>\r\n <div class=\"dpe-shell__group\">\r\n <button type=\"button\" class=\"dpe-shell__btn\" (click)=\"validation.dismissSaveError()\">\r\n Ho capito\r\n </button>\r\n </div>\r\n </div>\r\n }\r\n\r\n @if (selectedNames().length > 1) {\r\n <!--\r\n Striscia contestuale della **selezione multipla**. Sta qui, fra i banner di documento e il\r\n corpo, perche' e' un fatto che riguarda l\u2019insieme e non un nodo \u2014 ed e' dove l\u2019occhio guarda\r\n per i fatti dell\u2019insieme. Non mostra i campi di un nodo: mostrarli mentre N sono evidenziati\r\n e' la stessa bugia della doppia evidenziazione, detta piu' piano.\r\n -->\r\n <div class=\"dpe-shell__multi\">\r\n <div class=\"dpe-shell__multi-head\">\r\n <strong>{{ selectedNames().length }} nodi selezionati</strong>\r\n <ul class=\"dpe-multi__names\">\r\n @for (name of selectedNames(); track $index) {\r\n <li class=\"dpe-multi__name\">{{ name }}</li>\r\n }\r\n </ul>\r\n </div>\r\n\r\n <div class=\"dpe-shell__group\">\r\n <button type=\"button\" class=\"dpe-shell__btn\" (click)=\"removeSelectedNodes()\">\r\n Elimina i {{ selectedNames().length }} nodi\r\n </button>\r\n <!--\r\n Il riordino e\u2019 dichiaratamente **dell\u2019intero grafo**: dagre su un sottografo sarebbe una\r\n funzione nuova, e chiamarla \u00ABriordina i selezionati\u00BB mentre riordina tutto sarebbe una\r\n bugia \u2014 la stessa classe di difetto che questa striscia esiste per evitare.\r\n -->\r\n <button type=\"button\" class=\"dpe-shell__btn\" (click)=\"runAutoLayout()\">\r\n Riordina il grafo\r\n </button>\r\n </div>\r\n\r\n <p class=\"dpe-note dpe-note--warning\">\r\n L\u2019eliminazione lascia i riferimenti a valle cos\u00EC come sono: diventano riferimenti rotti, e\r\n il backend li segnala. Si annulla con \u21B6.\r\n </p>\r\n </div>\r\n }\r\n\r\n <div class=\"dpe-shell__body\">\r\n <dpe-node-palette class=\"dpe-shell__palette\" (nodeRequested)=\"onNodeRequested($event)\" />\r\n\r\n <dpe-canvas\r\n class=\"dpe-shell__canvas\"\r\n [isEditable]=\"isEditable()\"\r\n (nodeDropped)=\"onNodeDropped($event)\"\r\n (nodeOpened)=\"onNodeOpened($event)\"\r\n />\r\n </div>\r\n\r\n <!--\r\n **Una sola istanza**, fratello di `.dpe-shell__body`: non dentro il canvas (finirebbe nella\r\n trasformazione CSS di `f-canvas`) e non dentro il corpo (partecipererebbe alla griglia). Non\r\n partecipa al layout: `showModal()` la promuove nel top layer, quindi aprirla non produce\r\n nessun reflow del canvas e non c'e' nessuna inquadratura da rifare.\r\n -->\r\n <dpe-node-dialog />\r\n\r\n <div class=\"dpe-shell__bottom\">\r\n <div class=\"dpe-shell__tabs\" role=\"tablist\" aria-label=\"Pannelli\">\r\n <button\r\n type=\"button\"\r\n role=\"tab\"\r\n class=\"dpe-shell__tab\"\r\n [class.dpe-shell__tab--active]=\"bottomTab() === 'diagnostics'\"\r\n [attr.aria-selected]=\"bottomTab() === 'diagnostics'\"\r\n (click)=\"bottomTab.set('diagnostics')\"\r\n >\r\n Diagnostiche\r\n </button>\r\n <button\r\n type=\"button\"\r\n role=\"tab\"\r\n class=\"dpe-shell__tab\"\r\n [class.dpe-shell__tab--active]=\"bottomTab() === 'schema'\"\r\n [attr.aria-selected]=\"bottomTab() === 'schema'\"\r\n (click)=\"bottomTab.set('schema')\"\r\n >\r\n Schema\r\n </button>\r\n <button\r\n type=\"button\"\r\n role=\"tab\"\r\n class=\"dpe-shell__tab\"\r\n [class.dpe-shell__tab--active]=\"bottomTab() === 'sql'\"\r\n [attr.aria-selected]=\"bottomTab() === 'sql'\"\r\n (click)=\"bottomTab.set('sql')\"\r\n >\r\n SQL generato\r\n </button>\r\n </div>\r\n\r\n @switch (bottomTab()) {\r\n @case ('diagnostics') {\r\n <dpe-diagnostics-panel class=\"dpe-shell__panel\" (navigate)=\"onNavigate($event)\" />\r\n }\r\n @case ('schema') {\r\n <dpe-schema-panel class=\"dpe-shell__panel\" />\r\n }\r\n @case ('sql') {\r\n <dpe-sql-preview class=\"dpe-shell__panel\" />\r\n }\r\n }\r\n </div>\r\n</div>\r\n", dependencies: [{ kind: "component", type: DpeCanvasComponent, selector: "dpe-canvas", inputs: ["isEditable"], outputs: ["nodeOpened", "nodeDropped"] }, { kind: "component", type: DpeDiagnosticsPanelComponent, selector: "dpe-diagnostics-panel", outputs: ["navigate"] }, { kind: "component", type: DpeNodeDialogComponent, selector: "dpe-node-dialog" }, { kind: "component", type: DpeNodePaletteComponent, selector: "dpe-node-palette", outputs: ["nodeRequested"] }, { kind: "component", type: DpeSchemaPanelComponent, selector: "dpe-schema-panel" }, { kind: "component", type: DpeSqlPreviewComponent, selector: "dpe-sql-preview" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
8766
9110
  }
8767
9111
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.27", ngImport: i0, type: DpeBuilderComponent, decorators: [{
8768
9112
  type: Component,
8769
9113
  args: [{ selector: 'dpe-builder', standalone: true, imports: [
8770
9114
  DpeCanvasComponent,
8771
9115
  DpeDiagnosticsPanelComponent,
8772
- DpeNodeInspectorComponent,
9116
+ DpeNodeDialogComponent,
8773
9117
  DpeNodePaletteComponent,
8774
9118
  DpeSchemaPanelComponent,
8775
9119
  DpeSqlPreviewComponent,
8776
- ], changeDetection: ChangeDetectionStrategy.OnPush, providers: [DpeDocumentStore, DpeLayoutService, DpeValidationService, DpeMetadataStore], host: { '(keydown)': 'onKeyDown($event)' }, template: "<!--\r\n `.dpe-builder` porta le variabili del tema: e\u2019 la classe radice su cui l\u2019app ospite le\r\n sovrascrive, e `data-dpe-theme=\"dark\"` sul contenitore ridefinisce i soli token.\r\n-->\r\n<div class=\"dpe-builder dpe-shell\">\r\n <header class=\"dpe-shell__bar\">\r\n <div class=\"dpe-shell__title\">\r\n <strong>{{ store.definition().label || store.definition().name || 'Definizione DPE' }}</strong>\r\n <span class=\"dpe-shell__sub\">{{ nodeCount() }} nodi</span>\r\n <!--\r\n Stato **sporco** rispetto all\u2019ultimo salvataggio, non un conteggio di operazioni: serve a\r\n decidere se salvare, e un contatore che sale quando si annulla risponde alla domanda\r\n sbagliata. Annullare fino allo stato di partenza torna qui a \u00ABnessuna modifica\u00BB.\r\n -->\r\n <span\r\n class=\"dpe-shell__dirty\"\r\n [class.dpe-shell__dirty--on]=\"store.isDirty()\"\r\n [title]=\"\r\n store.isDirty()\r\n ? 'Il documento differisce dall\u2019ultimo salvataggio'\r\n : 'Il documento coincide con l\u2019ultimo salvataggio'\r\n \"\r\n >\r\n @if (store.isDirty()) {\r\n \u25CF modificata\r\n } @else {\r\n \u25CB nessuna modifica\r\n }\r\n </span>\r\n </div>\r\n\r\n <!--\r\n Badge in **sola lettura** (ADR 0017): il dialetto e\u2019 dichiarato dall\u2019ambiente, non scelto\r\n qui. Mostra quello della definizione, che e\u2019 quello con cui il backend compilera\u2019 (ADR\r\n 0009). Il titolo dice le due cose che contano \u2014 chi lo decide, e che le espressioni sono\r\n SQL nativo di quel dialetto (ADR 0006).\r\n -->\r\n <span\r\n class=\"dpe-shell__dialect\"\r\n title=\"Il dialetto e\u2019 dichiarato dall\u2019ambiente e non si modifica dall\u2019editor. Le espressioni sono SQL nativo di questo dialetto: la definizione gli appartiene.\"\r\n >\r\n Dialetto \u00B7 <strong>{{ dialectLabels[dialect()] }}</strong>\r\n </span>\r\n\r\n <div class=\"dpe-shell__group\">\r\n <button\r\n type=\"button\"\r\n class=\"dpe-shell__btn\"\r\n [disabled]=\"!store.canUndo()\"\r\n title=\"Annulla (la cronologia e\u2019 una conseguenza dell\u2019immutabilita\u2019 dello stato)\"\r\n (click)=\"store.undo()\"\r\n >\r\n \u21B6 Annulla\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"dpe-shell__btn\"\r\n [disabled]=\"!store.canRedo()\"\r\n title=\"Ripeti\"\r\n (click)=\"store.redo()\"\r\n >\r\n \u21B7 Ripeti\r\n </button>\r\n <button type=\"button\" class=\"dpe-shell__btn\" title=\"Riordina il grafo\" (click)=\"runAutoLayout()\">\r\n \u2317 Riordina\r\n </button>\r\n </div>\r\n\r\n <!--\r\n Salvataggio attraverso il contratto (`DpeBuilderApi.save`, opzionale). E\u2019 il comando\r\n **primario**, separato dal gruppo annulla/ripeti/riordina: quelli disfano, questo scrive.\r\n\r\n **Sempre presente, mai nascosto.** Un bottone assente dice \u00ABquesto editor non salva\u00BB; un\r\n bottone disabilitato con il motivo dice \u00ABqui non si salva, ed ecco perche\u2019\u00BB \u2014 ed e\u2019 la\r\n verita\u2019 quando l\u2019host non espone `save()`. La disponibilita\u2019 si sa **prima del clic**\r\n (`core/dpe-capabilities.ts`), non dal primo fallimento.\r\n -->\r\n <div class=\"dpe-shell__group dpe-shell__group--primary\">\r\n <button\r\n type=\"button\"\r\n class=\"dpe-shell__btn dpe-shell__btn--primary\"\r\n [disabled]=\"!canSave()\"\r\n [title]=\"\r\n !validation.isSaveAvailable()\r\n ? 'Salvataggio non disponibile su questo ambiente: l\u2019host non espone save()'\r\n : validation.isSaving()\r\n ? 'Salvataggio in corso'\r\n : !hasKey()\r\n ? 'Il documento non ha un nome: e\u2019 la chiave del catalogo, e va creato da la\u2019 (l\u2019aggiornamento e\u2019 PUT /definitions/{nome})'\r\n : store.isDirty()\r\n ? 'Salva la definizione (Ctrl+S)'\r\n : 'Nessuna modifica da salvare'\r\n \"\r\n (click)=\"validation.save()\"\r\n >\r\n @if (validation.isSaving()) {\r\n \u2913 Salvataggio\u2026\r\n } @else {\r\n \u2913 Salva\r\n }\r\n </button>\r\n @if (validation.justSaved()) {\r\n <!-- Avviso transitorio: il fatto durevole e\u2019 \u00ABnessuna modifica\u00BB nell\u2019indicatore. -->\r\n <span class=\"dpe-shell__toast\" role=\"status\">\u2713 Salvata</span>\r\n }\r\n </div>\r\n\r\n <div class=\"dpe-shell__status\">\r\n @if (validation.isValidating()) {\r\n <span class=\"dpe-shell__pill dpe-shell__pill--busy\">Validazione in corso\u2026</span>\r\n } @else if (validation.error(); as error) {\r\n <!--\r\n Un guasto di trasporto non cancella le diagnostiche precedenti: dire \u00ABva tutto bene\u00BB\r\n quando non lo sappiamo piu\u2019 e\u2019 peggio che mostrare un risultato vecchio dichiarandolo.\r\n -->\r\n <span class=\"dpe-shell__pill dpe-shell__pill--error\" [title]=\"error.message\">\r\n Backend non raggiungibile ({{ error.category }})\r\n </span>\r\n } @else if (store.validateResult()) {\r\n <span\r\n class=\"dpe-shell__pill\"\r\n [class.dpe-shell__pill--error]=\"store.validateResult()!.hasErrors\"\r\n [class.dpe-shell__pill--ok]=\"!store.validateResult()!.hasErrors\"\r\n >\r\n @if (store.validateResult()!.hasErrors) {\r\n Con errori\r\n } @else {\r\n Nessun errore\r\n }\r\n </span>\r\n }\r\n </div>\r\n </header>\r\n\r\n @if (dialectMismatch(); as declared) {\r\n <!--\r\n Disallineamento fra la dichiarazione dell\u2019ambiente e la definizione (ADR 0017). Si nominano\r\n **entrambi** i dialetti e non si offre di correggere: correggere significherebbe migrare le\r\n espressioni SQL native, ed e\u2019 dell\u2019host. Non e\u2019 una regola di dominio duplicata \u2014 non giudica\r\n la definizione, riporta il disaccordo fra due fatti che l\u2019ospite ha fornito.\r\n -->\r\n <div class=\"dpe-shell__warn\" role=\"alert\">\r\n <strong>\r\n L\u2019ambiente dichiara {{ dialectLabels[declared] }}, la definizione e\u2019 scritta per\r\n {{ dialectLabels[dialect()] }}.\r\n </strong>\r\n <p>\r\n Il backend compilera\u2019 con il dialetto <strong>della definizione</strong>. Allinearli e\u2019 una\r\n migrazione delle espressioni SQL native e non si fa da qui.\r\n </p>\r\n </div>\r\n }\r\n\r\n <!--\r\n Il documento ricevuto non e' una definizione. Banner **persistente** e in cima: e' un difetto\r\n dell'integrazione, non dell'utente, e un canvas vuoto da solo non lo dice \u2014 sembra una\r\n definizione senza nodi. Non si nasconde l'editor: cio' che c'e' resta visibile, e il salvataggio\r\n e' disabilitato da se' perche' la chiave manca.\r\n -->\r\n @if (documentProblem(); as problem) {\r\n <div class=\"dpe-shell__warn dpe-shell__warn--error\" role=\"alert\">\r\n <strong>Il documento ricevuto non e\u2019 una definizione: {{ problem }}.</strong>\r\n <p>\r\n L\u2019editor riceve il documento dall\u2019applicazione ospite, e questo non ha la forma di una\r\n <code>DpeDefinition</code>. Le tre cause reali: il backend restituisce l\u2019involucro di lettura\r\n (<code>{{ '{' }} definition, diagnostics {{ '}' }}</code>) invece del documento; l\u2019ospite\r\n passa il <strong>sommario</strong> del catalogo al posto della definizione caricata con\r\n <code>load(nome)</code>; la risposta e\u2019 in PascalCase (<code>Name</code> non e\u2019\r\n <code>name</code>).\r\n </p>\r\n </div>\r\n }\r\n\r\n @if (validation.saveError(); as error) {\r\n <!--\r\n Banner **persistente**: un salvataggio fallito in silenzio e\u2019 peggio di nessun salvataggio,\r\n perche\u2019 l\u2019indicatore \u00ABmodificata\u00BB sparirebbe per un\u2019operazione che non e\u2019 avvenuta. Qui non\r\n sparisce: `markSaved()` viene chiamato solo al successo.\r\n -->\r\n <div class=\"dpe-shell__warn dpe-shell__warn--error\" role=\"alert\">\r\n <strong>Salvataggio non riuscito ({{ error.category }})</strong>\r\n <p>{{ error.message }}</p>\r\n <div class=\"dpe-shell__group\">\r\n <button type=\"button\" class=\"dpe-shell__btn\" (click)=\"validation.dismissSaveError()\">\r\n Ho capito\r\n </button>\r\n </div>\r\n </div>\r\n }\r\n\r\n <div class=\"dpe-shell__body\">\r\n <dpe-node-palette class=\"dpe-shell__palette\" (nodeRequested)=\"onNodeRequested($event)\" />\r\n\r\n <dpe-canvas\r\n class=\"dpe-shell__canvas\"\r\n [isEditable]=\"isEditable()\"\r\n (nodeDropped)=\"onNodeDropped($event)\"\r\n />\r\n\r\n <dpe-node-inspector class=\"dpe-shell__inspector\" (layoutRequested)=\"runAutoLayout()\" />\r\n </div>\r\n\r\n <div class=\"dpe-shell__bottom\">\r\n <div class=\"dpe-shell__tabs\" role=\"tablist\" aria-label=\"Pannelli\">\r\n <button\r\n type=\"button\"\r\n role=\"tab\"\r\n class=\"dpe-shell__tab\"\r\n [class.dpe-shell__tab--active]=\"bottomTab() === 'diagnostics'\"\r\n [attr.aria-selected]=\"bottomTab() === 'diagnostics'\"\r\n (click)=\"bottomTab.set('diagnostics')\"\r\n >\r\n Diagnostiche\r\n </button>\r\n <button\r\n type=\"button\"\r\n role=\"tab\"\r\n class=\"dpe-shell__tab\"\r\n [class.dpe-shell__tab--active]=\"bottomTab() === 'schema'\"\r\n [attr.aria-selected]=\"bottomTab() === 'schema'\"\r\n (click)=\"bottomTab.set('schema')\"\r\n >\r\n Schema\r\n </button>\r\n <button\r\n type=\"button\"\r\n role=\"tab\"\r\n class=\"dpe-shell__tab\"\r\n [class.dpe-shell__tab--active]=\"bottomTab() === 'sql'\"\r\n [attr.aria-selected]=\"bottomTab() === 'sql'\"\r\n (click)=\"bottomTab.set('sql')\"\r\n >\r\n SQL generato\r\n </button>\r\n </div>\r\n\r\n @switch (bottomTab()) {\r\n @case ('diagnostics') {\r\n <dpe-diagnostics-panel class=\"dpe-shell__panel\" (navigate)=\"onNavigate($event)\" />\r\n }\r\n @case ('schema') {\r\n <dpe-schema-panel class=\"dpe-shell__panel\" />\r\n }\r\n @case ('sql') {\r\n <dpe-sql-preview class=\"dpe-shell__panel\" />\r\n }\r\n }\r\n </div>\r\n</div>\r\n" }]
9120
+ ], changeDetection: ChangeDetectionStrategy.OnPush, providers: [DpeDocumentStore, DpeLayoutService, DpeValidationService, DpeMetadataStore], host: { '(keydown)': 'onKeyDown($event)' }, template: "<!--\r\n `.dpe-builder` porta le variabili del tema: e\u2019 la classe radice su cui l\u2019app ospite le\r\n sovrascrive, e `data-dpe-theme=\"dark\"` sul contenitore ridefinisce i soli token.\r\n-->\r\n<div class=\"dpe-builder dpe-shell\">\r\n <header class=\"dpe-shell__bar\">\r\n <div class=\"dpe-shell__title\">\r\n <strong>{{ store.definition().label || store.definition().name || 'Definizione DPE' }}</strong>\r\n <span class=\"dpe-shell__sub\">{{ nodeCount() }} nodi</span>\r\n <!--\r\n Stato **sporco** rispetto all\u2019ultimo salvataggio, non un conteggio di operazioni: serve a\r\n decidere se salvare, e un contatore che sale quando si annulla risponde alla domanda\r\n sbagliata. Annullare fino allo stato di partenza torna qui a \u00ABnessuna modifica\u00BB.\r\n -->\r\n <span\r\n class=\"dpe-shell__dirty\"\r\n [class.dpe-shell__dirty--on]=\"store.isDirty()\"\r\n [title]=\"\r\n store.isDirty()\r\n ? 'Il documento differisce dall\u2019ultimo salvataggio'\r\n : 'Il documento coincide con l\u2019ultimo salvataggio'\r\n \"\r\n >\r\n @if (store.isDirty()) {\r\n \u25CF modificata\r\n } @else {\r\n \u25CB nessuna modifica\r\n }\r\n </span>\r\n </div>\r\n\r\n <!--\r\n Badge in **sola lettura** (ADR 0017): il dialetto e\u2019 dichiarato dall\u2019ambiente, non scelto\r\n qui. Mostra quello della definizione, che e\u2019 quello con cui il backend compilera\u2019 (ADR\r\n 0009). Il titolo dice le due cose che contano \u2014 chi lo decide, e che le espressioni sono\r\n SQL nativo di quel dialetto (ADR 0006).\r\n -->\r\n <span\r\n class=\"dpe-shell__dialect\"\r\n title=\"Il dialetto e\u2019 dichiarato dall\u2019ambiente e non si modifica dall\u2019editor. Le espressioni sono SQL nativo di questo dialetto: la definizione gli appartiene.\"\r\n >\r\n Dialetto \u00B7 <strong>{{ dialectLabels[dialect()] }}</strong>\r\n </span>\r\n\r\n <div class=\"dpe-shell__group\">\r\n <button\r\n type=\"button\"\r\n class=\"dpe-shell__btn\"\r\n [disabled]=\"!store.canUndo()\"\r\n title=\"Annulla (la cronologia e\u2019 una conseguenza dell\u2019immutabilita\u2019 dello stato)\"\r\n (click)=\"store.undo()\"\r\n >\r\n \u21B6 Annulla\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"dpe-shell__btn\"\r\n [disabled]=\"!store.canRedo()\"\r\n title=\"Ripeti\"\r\n (click)=\"store.redo()\"\r\n >\r\n \u21B7 Ripeti\r\n </button>\r\n <button type=\"button\" class=\"dpe-shell__btn\" title=\"Riordina il grafo\" (click)=\"runAutoLayout()\">\r\n \u2317 Riordina\r\n </button>\r\n </div>\r\n\r\n <!--\r\n Salvataggio attraverso il contratto (`DpeBuilderApi.save`, opzionale). E\u2019 il comando\r\n **primario**, separato dal gruppo annulla/ripeti/riordina: quelli disfano, questo scrive.\r\n\r\n **Sempre presente, mai nascosto.** Un bottone assente dice \u00ABquesto editor non salva\u00BB; un\r\n bottone disabilitato con il motivo dice \u00ABqui non si salva, ed ecco perche\u2019\u00BB \u2014 ed e\u2019 la\r\n verita\u2019 quando l\u2019host non espone `save()`. La disponibilita\u2019 si sa **prima del clic**\r\n (`core/dpe-capabilities.ts`), non dal primo fallimento.\r\n -->\r\n <div class=\"dpe-shell__group dpe-shell__group--primary\">\r\n <button\r\n type=\"button\"\r\n class=\"dpe-shell__btn dpe-shell__btn--primary\"\r\n [disabled]=\"!canSave()\"\r\n [title]=\"\r\n !validation.isSaveAvailable()\r\n ? 'Salvataggio non disponibile su questo ambiente: l\u2019host non espone save()'\r\n : validation.isSaving()\r\n ? 'Salvataggio in corso'\r\n : !hasKey()\r\n ? 'Il documento non ha un nome: e\u2019 la chiave del catalogo, e va creato da la\u2019 (l\u2019aggiornamento e\u2019 PUT /definitions/{nome})'\r\n : store.isDirty()\r\n ? 'Salva la definizione (Ctrl+S)'\r\n : 'Nessuna modifica da salvare'\r\n \"\r\n (click)=\"validation.save()\"\r\n >\r\n @if (validation.isSaving()) {\r\n \u2913 Salvataggio\u2026\r\n } @else {\r\n \u2913 Salva\r\n }\r\n </button>\r\n @if (validation.justSaved()) {\r\n <!-- Avviso transitorio: il fatto durevole e\u2019 \u00ABnessuna modifica\u00BB nell\u2019indicatore. -->\r\n <span class=\"dpe-shell__toast\" role=\"status\">\u2713 Salvata</span>\r\n }\r\n </div>\r\n\r\n <div class=\"dpe-shell__status\">\r\n @if (validation.isValidating()) {\r\n <span class=\"dpe-shell__pill dpe-shell__pill--busy\">Validazione in corso\u2026</span>\r\n } @else if (validation.error(); as error) {\r\n <!--\r\n Un guasto di trasporto non cancella le diagnostiche precedenti: dire \u00ABva tutto bene\u00BB\r\n quando non lo sappiamo piu\u2019 e\u2019 peggio che mostrare un risultato vecchio dichiarandolo.\r\n -->\r\n <span class=\"dpe-shell__pill dpe-shell__pill--error\" [title]=\"error.message\">\r\n Backend non raggiungibile ({{ error.category }})\r\n </span>\r\n } @else if (store.validateResult()) {\r\n <span\r\n class=\"dpe-shell__pill\"\r\n [class.dpe-shell__pill--error]=\"store.validateResult()!.hasErrors\"\r\n [class.dpe-shell__pill--ok]=\"!store.validateResult()!.hasErrors\"\r\n >\r\n @if (store.validateResult()!.hasErrors) {\r\n Con errori\r\n } @else {\r\n Nessun errore\r\n }\r\n </span>\r\n }\r\n </div>\r\n </header>\r\n\r\n @if (dialectMismatch(); as declared) {\r\n <!--\r\n Disallineamento fra la dichiarazione dell\u2019ambiente e la definizione (ADR 0017). Si nominano\r\n **entrambi** i dialetti e non si offre di correggere: correggere significherebbe migrare le\r\n espressioni SQL native, ed e\u2019 dell\u2019host. Non e\u2019 una regola di dominio duplicata \u2014 non giudica\r\n la definizione, riporta il disaccordo fra due fatti che l\u2019ospite ha fornito.\r\n -->\r\n <div class=\"dpe-shell__warn\" role=\"alert\">\r\n <strong>\r\n L\u2019ambiente dichiara {{ dialectLabels[declared] }}, la definizione e\u2019 scritta per\r\n {{ dialectLabels[dialect()] }}.\r\n </strong>\r\n <p>\r\n Il backend compilera\u2019 con il dialetto <strong>della definizione</strong>. Allinearli e\u2019 una\r\n migrazione delle espressioni SQL native e non si fa da qui.\r\n </p>\r\n </div>\r\n }\r\n\r\n <!--\r\n Il documento ricevuto non e' una definizione. Banner **persistente** e in cima: e' un difetto\r\n dell'integrazione, non dell'utente, e un canvas vuoto da solo non lo dice \u2014 sembra una\r\n definizione senza nodi. Non si nasconde l'editor: cio' che c'e' resta visibile, e il salvataggio\r\n e' disabilitato da se' perche' la chiave manca.\r\n -->\r\n @if (documentProblem(); as problem) {\r\n <div class=\"dpe-shell__warn dpe-shell__warn--error\" role=\"alert\">\r\n <strong>Il documento ricevuto non e\u2019 una definizione: {{ problem }}.</strong>\r\n <p>\r\n L\u2019editor riceve il documento dall\u2019applicazione ospite, e questo non ha la forma di una\r\n <code>DpeDefinition</code>. Le tre cause reali: il backend restituisce l\u2019involucro di lettura\r\n (<code>{{ '{' }} definition, diagnostics {{ '}' }}</code>) invece del documento; l\u2019ospite\r\n passa il <strong>sommario</strong> del catalogo al posto della definizione caricata con\r\n <code>load(nome)</code>; la risposta e\u2019 in PascalCase (<code>Name</code> non e\u2019\r\n <code>name</code>).\r\n </p>\r\n </div>\r\n }\r\n\r\n @if (validation.saveError(); as error) {\r\n <!--\r\n Banner **persistente**: un salvataggio fallito in silenzio e\u2019 peggio di nessun salvataggio,\r\n perche\u2019 l\u2019indicatore \u00ABmodificata\u00BB sparirebbe per un\u2019operazione che non e\u2019 avvenuta. Qui non\r\n sparisce: `markSaved()` viene chiamato solo al successo.\r\n -->\r\n <div class=\"dpe-shell__warn dpe-shell__warn--error\" role=\"alert\">\r\n <strong>Salvataggio non riuscito ({{ error.category }})</strong>\r\n <p>{{ error.message }}</p>\r\n <div class=\"dpe-shell__group\">\r\n <button type=\"button\" class=\"dpe-shell__btn\" (click)=\"validation.dismissSaveError()\">\r\n Ho capito\r\n </button>\r\n </div>\r\n </div>\r\n }\r\n\r\n @if (selectedNames().length > 1) {\r\n <!--\r\n Striscia contestuale della **selezione multipla**. Sta qui, fra i banner di documento e il\r\n corpo, perche' e' un fatto che riguarda l\u2019insieme e non un nodo \u2014 ed e' dove l\u2019occhio guarda\r\n per i fatti dell\u2019insieme. Non mostra i campi di un nodo: mostrarli mentre N sono evidenziati\r\n e' la stessa bugia della doppia evidenziazione, detta piu' piano.\r\n -->\r\n <div class=\"dpe-shell__multi\">\r\n <div class=\"dpe-shell__multi-head\">\r\n <strong>{{ selectedNames().length }} nodi selezionati</strong>\r\n <ul class=\"dpe-multi__names\">\r\n @for (name of selectedNames(); track $index) {\r\n <li class=\"dpe-multi__name\">{{ name }}</li>\r\n }\r\n </ul>\r\n </div>\r\n\r\n <div class=\"dpe-shell__group\">\r\n <button type=\"button\" class=\"dpe-shell__btn\" (click)=\"removeSelectedNodes()\">\r\n Elimina i {{ selectedNames().length }} nodi\r\n </button>\r\n <!--\r\n Il riordino e\u2019 dichiaratamente **dell\u2019intero grafo**: dagre su un sottografo sarebbe una\r\n funzione nuova, e chiamarla \u00ABriordina i selezionati\u00BB mentre riordina tutto sarebbe una\r\n bugia \u2014 la stessa classe di difetto che questa striscia esiste per evitare.\r\n -->\r\n <button type=\"button\" class=\"dpe-shell__btn\" (click)=\"runAutoLayout()\">\r\n Riordina il grafo\r\n </button>\r\n </div>\r\n\r\n <p class=\"dpe-note dpe-note--warning\">\r\n L\u2019eliminazione lascia i riferimenti a valle cos\u00EC come sono: diventano riferimenti rotti, e\r\n il backend li segnala. Si annulla con \u21B6.\r\n </p>\r\n </div>\r\n }\r\n\r\n <div class=\"dpe-shell__body\">\r\n <dpe-node-palette class=\"dpe-shell__palette\" (nodeRequested)=\"onNodeRequested($event)\" />\r\n\r\n <dpe-canvas\r\n class=\"dpe-shell__canvas\"\r\n [isEditable]=\"isEditable()\"\r\n (nodeDropped)=\"onNodeDropped($event)\"\r\n (nodeOpened)=\"onNodeOpened($event)\"\r\n />\r\n </div>\r\n\r\n <!--\r\n **Una sola istanza**, fratello di `.dpe-shell__body`: non dentro il canvas (finirebbe nella\r\n trasformazione CSS di `f-canvas`) e non dentro il corpo (partecipererebbe alla griglia). Non\r\n partecipa al layout: `showModal()` la promuove nel top layer, quindi aprirla non produce\r\n nessun reflow del canvas e non c'e' nessuna inquadratura da rifare.\r\n -->\r\n <dpe-node-dialog />\r\n\r\n <div class=\"dpe-shell__bottom\">\r\n <div class=\"dpe-shell__tabs\" role=\"tablist\" aria-label=\"Pannelli\">\r\n <button\r\n type=\"button\"\r\n role=\"tab\"\r\n class=\"dpe-shell__tab\"\r\n [class.dpe-shell__tab--active]=\"bottomTab() === 'diagnostics'\"\r\n [attr.aria-selected]=\"bottomTab() === 'diagnostics'\"\r\n (click)=\"bottomTab.set('diagnostics')\"\r\n >\r\n Diagnostiche\r\n </button>\r\n <button\r\n type=\"button\"\r\n role=\"tab\"\r\n class=\"dpe-shell__tab\"\r\n [class.dpe-shell__tab--active]=\"bottomTab() === 'schema'\"\r\n [attr.aria-selected]=\"bottomTab() === 'schema'\"\r\n (click)=\"bottomTab.set('schema')\"\r\n >\r\n Schema\r\n </button>\r\n <button\r\n type=\"button\"\r\n role=\"tab\"\r\n class=\"dpe-shell__tab\"\r\n [class.dpe-shell__tab--active]=\"bottomTab() === 'sql'\"\r\n [attr.aria-selected]=\"bottomTab() === 'sql'\"\r\n (click)=\"bottomTab.set('sql')\"\r\n >\r\n SQL generato\r\n </button>\r\n </div>\r\n\r\n @switch (bottomTab()) {\r\n @case ('diagnostics') {\r\n <dpe-diagnostics-panel class=\"dpe-shell__panel\" (navigate)=\"onNavigate($event)\" />\r\n }\r\n @case ('schema') {\r\n <dpe-schema-panel class=\"dpe-shell__panel\" />\r\n }\r\n @case ('sql') {\r\n <dpe-sql-preview class=\"dpe-shell__panel\" />\r\n }\r\n }\r\n </div>\r\n</div>\r\n" }]
8777
9121
  }], ctorParameters: () => [], propDecorators: { definition: [{ type: i0.Input, args: [{ isSignal: true, alias: "definition", required: true }] }], isEditable: [{ type: i0.Input, args: [{ isSignal: true, alias: "isEditable", required: false }] }], definitionChange: [{ type: i0.Output, args: ["definitionChange"] }], canvas: [{ type: i0.ViewChild, args: [i0.forwardRef(() => DpeCanvasComponent), { isSignal: true }] }] } });
8778
9122
 
8779
9123
  /**
@@ -8939,5 +9283,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.27", ngImpo
8939
9283
  * Generated bundle index. Do not edit.
8940
9284
  */
8941
9285
 
8942
- export { DPE_API_ERROR_FALLBACK_MESSAGE, DPE_BUILDER_HTTP_CONFIG, DPE_DIALECT_LABELS, DPE_LAYOUT_KEY, DPE_NODE_CATEGORIES, DPE_NODE_COLLECTIONS, DPE_NODE_HEIGHT, DPE_NODE_ICON_PATHS, DPE_NODE_NOT_COMPILABLE, DPE_SOURCE_SLOTS, DpeApiError, DpeBuilderApi, DpeBuilderComponent, DpeCanvasComponent, DpeCatalogComponent, DpeCatalogStore, DpeColumnTypeEditorComponent, DpeDiagnosticsPanelComponent, DpeDocumentStore, DpeExpressionEditorComponent, DpeLayoutService, DpeMetadataStore, DpeNodeIconComponent, DpeNodeInspectorBase, DpeNodeInspectorComponent, DpeNodePaletteComponent, DpeSchemaPanelComponent, DpeSelectValueDirective, DpeSqlPreviewComponent, DpeValidationService, HttpDpeBuilderApi, diagnosticPathToString, dpeAllNodes, dpeBuildGraph, dpeCountReferences, dpeCreateCommandAvailability, dpeCreateNode, dpeDefinitionLevelDiagnostics, dpeDiagnosticField, dpeDiagnosticLocationLabel, dpeDiagnosticNodeName, dpeEdgeId, dpeEmptyDefinition, dpeFindNode, dpeGroupDiagnosticsByNode, dpeHasCompleteLayout, dpeInputPortId, dpeInputPorts, dpeIsDialectDiagnostic, dpeIsNotCompilable, dpeIsPrimitiveAvailable, dpeIsRectFullyVisible, dpeKnownNodeNames, dpeNodeCategory, dpeNodeIconFamily, dpeNodeIconFamilyClass, dpeNodeIconPaths, dpeNodeId, dpeNodeSourceReferences, dpeNodeSubtitle, dpeNodeWidth, dpeNodeWidthClass, dpeOutputPortId, dpeParseInputPortId, dpeParseNodeId, dpeParseOutputPortId, dpeProposeNodeName, dpeProposeWritebackSequence, dpeReadCapabilities, dpeReadNodePosition, dpeRenameNode, dpeWorstSeverity, dpeWriteNodePosition, dpeWriteSourceReference, provideDpeBuilderHttpApi };
9286
+ export { DPE_API_ERROR_FALLBACK_MESSAGE, DPE_BUILDER_HTTP_CONFIG, DPE_DIALECT_LABELS, DPE_LAYOUT_KEY, DPE_NODE_CATEGORIES, DPE_NODE_COLLECTIONS, DPE_NODE_HEIGHT, DPE_NODE_ICON_PATHS, DPE_NODE_NOT_COMPILABLE, DPE_SOURCE_SLOTS, DpeApiError, DpeBuilderApi, DpeBuilderComponent, DpeCanvasComponent, DpeCatalogComponent, DpeCatalogStore, DpeColumnTypeEditorComponent, DpeDiagnosticsPanelComponent, DpeDocumentStore, DpeExpressionEditorComponent, DpeLayoutService, DpeMetadataStore, DpeNodeDialogComponent, DpeNodeFactsComponent, DpeNodeIconComponent, DpeNodeInspectorBase, DpeNodeInspectorComponent, DpeNodePaletteComponent, DpeSchemaPanelComponent, DpeSelectValueDirective, DpeSqlPreviewComponent, DpeValidationService, HttpDpeBuilderApi, diagnosticPathToString, dpeAllNodes, dpeBuildGraph, dpeCountReferences, dpeCreateCommandAvailability, dpeCreateNode, dpeDefinitionLevelDiagnostics, dpeDiagnosticField, dpeDiagnosticLocationLabel, dpeDiagnosticNodeName, dpeEdgeId, dpeEmptyDefinition, dpeFindNode, dpeGroupDiagnosticsByNode, dpeHasCompleteLayout, dpeInputPortId, dpeInputPorts, dpeIsDialectDiagnostic, dpeIsNotCompilable, dpeIsPrimitiveAvailable, dpeIsRectFullyVisible, dpeKnownNodeNames, dpeNodeCategory, dpeNodeIconFamily, dpeNodeIconFamilyClass, dpeNodeIconPaths, dpeNodeId, dpeNodeSourceReferences, dpeNodeSubtitle, dpeNodeWidth, dpeNodeWidthClass, dpeOutputPortId, dpeParseInputPortId, dpeParseNodeId, dpeParseOutputPortId, dpeProposeNodeName, dpeProposeWritebackSequence, dpeReadCapabilities, dpeReadNodePosition, dpeRenameNode, dpeWorstSeverity, dpeWriteNodePosition, dpeWriteSourceReference, provideDpeBuilderHttpApi };
8943
9287
  //# sourceMappingURL=esfaenza-dpe-builder.mjs.map