wuic-framework-lib 1.2.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/fesm2022/{wuic-framework-lib-chart-list.component-B1V8Q-Qq.mjs → wuic-framework-lib-chart-list.component-C02jeFGy.mjs} +2 -2
- package/fesm2022/{wuic-framework-lib-code-editor.component-4N95qt3D.mjs → wuic-framework-lib-code-editor.component-BZNLr0Jj.mjs} +59 -2
- package/fesm2022/{wuic-framework-lib-designer.component-pW1YK-oY.mjs → wuic-framework-lib-designer.component-N_CfbGnr.mjs} +986 -17
- package/fesm2022/{wuic-framework-lib-first-run-wizard.component-CaGePYLi.mjs → wuic-framework-lib-first-run-wizard.component-lW7-xMm3.mjs} +7 -61
- package/fesm2022/{wuic-framework-lib-framework-docs.component-m0IlS0Sl.mjs → wuic-framework-lib-framework-docs.component-CGQbmSkv.mjs} +3716 -347
- package/fesm2022/{wuic-framework-lib-pivot-builder.component-VIs0CIH_.mjs → wuic-framework-lib-pivot-builder.component-0gJz9RWr.mjs} +5 -5
- package/fesm2022/{wuic-framework-lib-prompt-dialog-fallback-host.component-Dm9GTZ07.mjs → wuic-framework-lib-prompt-dialog-fallback-host.component-C9LvmFsL.mjs} +2 -2
- package/fesm2022/{wuic-framework-lib-report-designer.component-FiAwNkPM.mjs → wuic-framework-lib-report-designer.component-DcpdzFjh.mjs} +2 -2
- package/fesm2022/{wuic-framework-lib-report-viewer.component-DY0dGMrt.mjs → wuic-framework-lib-report-viewer.component-BHpbGpp9.mjs} +2 -2
- package/fesm2022/{wuic-framework-lib-scheduler-list.component-3E5m-9Qd.mjs → wuic-framework-lib-scheduler-list.component-DHoukjf-.mjs} +2 -2
- package/fesm2022/{wuic-framework-lib-spreadsheet-list-sf.component-D9P9WbDl.mjs → wuic-framework-lib-spreadsheet-list-sf.component-VsaXepUC.mjs} +2 -2
- package/fesm2022/{wuic-framework-lib-workflow-designer.component-4RC2EkGA.mjs → wuic-framework-lib-workflow-designer.component-epj3oKq3.mjs} +2 -2
- package/fesm2022/{wuic-framework-lib-wuic-framework-lib-BSyGhpM1.mjs → wuic-framework-lib-wuic-framework-lib-Deudd4MY.mjs} +2186 -144
- package/fesm2022/wuic-framework-lib.mjs +1 -1
- package/package.json +1 -1
- package/types/wuic-framework-lib.d.ts +829 -11
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
2
|
import { Pipe, EventEmitter, ElementRef, Renderer2, ChangeDetectorRef, HostListener, Input, Output, Inject, Directive, Component, computed, signal, untracked, inject, NgZone, numberAttribute, DOCUMENT, input, output, isDevMode, InjectionToken, Injector, contentChildren, contentChild, booleanAttribute, effect, HostBinding, ChangeDetectionStrategy } from '@angular/core';
|
|
3
|
-
import * as
|
|
3
|
+
import * as i10 from 'primeng/splitter';
|
|
4
4
|
import { SplitterModule } from 'primeng/splitter';
|
|
5
|
-
import * as
|
|
5
|
+
import * as i12 from 'primeng/dragdrop';
|
|
6
6
|
import { DragDropModule } from 'primeng/dragdrop';
|
|
7
7
|
import { NgClass, NgStyle, NgTemplateOutlet, AsyncPipe, JsonPipe, NgFor, NgIf, NgComponentOutlet } from '@angular/common';
|
|
8
|
-
import { W as WtoolboxService, D as DynamicGenericTemplateComponent, a as DynamicCompilerService, b as DataSourceComponent, c as DataRepeaterComponent, F as FilterBarComponent, L as LazyParametricDialogComponent, P as PagerComponent, M as MetadataProviderService, d as ParametricDialogComponent, e as MetadatiColonna, f as DataProviderService, U as UserInfoService, T as TranslationManagerService, g as MetadataEditorService, h as WorkflowRuntimeMetadataService, i as FieldEditorComponent, j as MetadataEditorComponent, A as ArchetypeConfiguratorComponent } from './wuic-framework-lib-wuic-framework-lib-
|
|
8
|
+
import { W as WtoolboxService, D as DynamicGenericTemplateComponent, a as DynamicCompilerService, b as DataSourceComponent, c as DataRepeaterComponent, F as FilterBarComponent, L as LazyParametricDialogComponent, P as PagerComponent, M as MetadataProviderService, d as ParametricDialogComponent, e as MetadatiColonna, f as DataProviderService, U as UserInfoService, T as TranslationManagerService, g as MetadataEditorService, C as ChatbotHostRegistryService, h as WorkflowRuntimeMetadataService, i as FieldEditorComponent, j as MetadataEditorComponent, A as ArchetypeConfiguratorComponent } from './wuic-framework-lib-wuic-framework-lib-Deudd4MY.mjs';
|
|
9
9
|
import { fromEvent, BehaviorSubject, merge, filter, Observable, switchMap, take, map, takeUntil, tap, timeInterval, scan, mergeMap, of, delay, repeat, Subject, startWith, pairwise, skipWhile, combineLatest } from 'rxjs';
|
|
10
10
|
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
11
11
|
import { CdkAccordionModule, CdkAccordion, CdkAccordionItem } from '@angular/cdk/accordion';
|
|
@@ -29,11 +29,11 @@ import { TreeDragDropService } from 'primeng/api';
|
|
|
29
29
|
import { MenubarModule } from 'primeng/menubar';
|
|
30
30
|
import * as i4 from 'primeng/button';
|
|
31
31
|
import { ButtonModule } from 'primeng/button';
|
|
32
|
-
import * as
|
|
32
|
+
import * as i18 from 'primeng/multiselect';
|
|
33
33
|
import { MultiSelectModule } from 'primeng/multiselect';
|
|
34
34
|
import * as i2 from 'primeng/dialog';
|
|
35
35
|
import { DialogModule } from 'primeng/dialog';
|
|
36
|
-
import * as i9
|
|
36
|
+
import * as i9 from 'primeng/menu';
|
|
37
37
|
import { MenuModule } from 'primeng/menu';
|
|
38
38
|
import * as i4$1 from 'primeng/tree';
|
|
39
39
|
import { TreeModule } from 'primeng/tree';
|
|
@@ -4415,6 +4415,7 @@ class DesignerComponent {
|
|
|
4415
4415
|
cd;
|
|
4416
4416
|
trslSrv;
|
|
4417
4417
|
metadataEditorSrv;
|
|
4418
|
+
chatbotHostRegistry;
|
|
4418
4419
|
workflowRuntimeMetadata;
|
|
4419
4420
|
draggedPayload;
|
|
4420
4421
|
selected;
|
|
@@ -4516,7 +4517,7 @@ class DesignerComponent {
|
|
|
4516
4517
|
* e binding ai converter/callback usati dall'editor proprieta.
|
|
4517
4518
|
* Non carica dati remoti: il bootstrap runtime avviene in `ngOnInit`.
|
|
4518
4519
|
*/
|
|
4519
|
-
constructor(route, dataSrv, metaSrv, userInfo, cd, trslSrv, metadataEditorSrv, workflowRuntimeMetadata = {}) {
|
|
4520
|
+
constructor(route, dataSrv, metaSrv, userInfo, cd, trslSrv, metadataEditorSrv, chatbotHostRegistry, workflowRuntimeMetadata = {}) {
|
|
4520
4521
|
this.route = route;
|
|
4521
4522
|
this.dataSrv = dataSrv;
|
|
4522
4523
|
this.metaSrv = metaSrv;
|
|
@@ -4524,6 +4525,7 @@ class DesignerComponent {
|
|
|
4524
4525
|
this.cd = cd;
|
|
4525
4526
|
this.trslSrv = trslSrv;
|
|
4526
4527
|
this.metadataEditorSrv = metadataEditorSrv;
|
|
4528
|
+
this.chatbotHostRegistry = chatbotHostRegistry;
|
|
4527
4529
|
this.workflowRuntimeMetadata = workflowRuntimeMetadata;
|
|
4528
4530
|
this.defaultCtxItems = [
|
|
4529
4531
|
{
|
|
@@ -5551,6 +5553,21 @@ class DesignerComponent {
|
|
|
5551
5553
|
async ngOnInit() {
|
|
5552
5554
|
this.lastKnownHashRoute = String(window?.location?.hash || '');
|
|
5553
5555
|
this.refreshAvailableProjectCssClasses();
|
|
5556
|
+
// Registra handler chatbot per kind 'designer_inject': il chatbot
|
|
5557
|
+
// popover (globale, vedi rag-chatbot.component.ts onApplyAction)
|
|
5558
|
+
// intercetta il kind e delega a questo handler invece di chiamare
|
|
5559
|
+
// /api/Rag/ApplyAction. unregister in ngOnDestroy per evitare retain.
|
|
5560
|
+
this.chatbotHostRegistry.register('designer_inject', (action) => this.onChatbotProposedDesignerAction(action));
|
|
5561
|
+
// Registra state provider: il chatbot (in buildRouteContext) chiama
|
|
5562
|
+
// questo provider per ottenere uno snapshot compatto del canvas e
|
|
5563
|
+
// iniettarlo nel system prompt del LLM. Cosi' il LLM puo' proporre
|
|
5564
|
+
// set_property / remove referenziando uniqueName reali (TD__126 ecc.).
|
|
5565
|
+
this.chatbotHostRegistry.registerStateProvider('designer_inject', () => this.buildCompactDashboardStateDump());
|
|
5566
|
+
// NB: il fetch della lista route disponibili NON va fatto qui (al mount
|
|
5567
|
+
// del designer). Sarebbe una chiamata HTTP per ogni apertura del designer
|
|
5568
|
+
// anche quando l'utente non usa mai il chatbot. Lo facciamo LAZY dentro
|
|
5569
|
+
// `buildCompactDashboardStateDump` al primo prompt del chatbot
|
|
5570
|
+
// (state provider invocato solo quando il chatbot prepara il route_context).
|
|
5554
5571
|
let menus = [];
|
|
5555
5572
|
menus = [
|
|
5556
5573
|
{
|
|
@@ -5622,6 +5639,922 @@ class DesignerComponent {
|
|
|
5622
5639
|
this.clearHierarchyHoverHighlight();
|
|
5623
5640
|
this.clearFooterHoverHighlight();
|
|
5624
5641
|
this.setDesignerEditModeClass(false);
|
|
5642
|
+
// Deregistra handler chatbot: il prossimo mount (es. navigation
|
|
5643
|
+
// back-and-forth a /designer) registrera' una nuova istanza.
|
|
5644
|
+
try {
|
|
5645
|
+
this.chatbotHostRegistry.unregister('designer_inject');
|
|
5646
|
+
}
|
|
5647
|
+
catch { /* ignore */ }
|
|
5648
|
+
try {
|
|
5649
|
+
this.chatbotHostRegistry.unregisterStateProvider('designer_inject');
|
|
5650
|
+
}
|
|
5651
|
+
catch { /* ignore */ }
|
|
5652
|
+
}
|
|
5653
|
+
// ===========================================================================
|
|
5654
|
+
// CHATBOT HOST HANDLER: kind 'designer_inject' (client-only)
|
|
5655
|
+
// ===========================================================================
|
|
5656
|
+
//
|
|
5657
|
+
// Riceve un payload `propose_designer_inject` dal LLM, lo deserializza e
|
|
5658
|
+
// simula drag&drop iterativo sui `dashboardElements` usando le API
|
|
5659
|
+
// canoniche `dropToolAtRoot()` / `drop()` esistenti.
|
|
5660
|
+
//
|
|
5661
|
+
// Per i nodi nested (`children[]`): dopo aver iniettato il parent, leggiamo
|
|
5662
|
+
// l'`uniqueName` generato dal sistema (TOOL__<componentId>) e lo passiamo
|
|
5663
|
+
// come parent_unique_name ai figli. Il `drop()` risolve il parent via
|
|
5664
|
+
// `flattenedDashboardElements.find(componentId)`.
|
|
5665
|
+
//
|
|
5666
|
+
// Per TABLE/SPLITTER/ACCORDION (custom onDrop): il framework crea
|
|
5667
|
+
// automaticamente i nodi figli TR/TD / SPLITTER-AREA / ACCORDION-AREA
|
|
5668
|
+
// basandosi su inputs.rows/cols/areas/items. Il LLM NON deve includerli
|
|
5669
|
+
// nel children[] (vedi docs/pages/_internal/designer-tool-catalog.md
|
|
5670
|
+
// constraint #2).
|
|
5671
|
+
//
|
|
5672
|
+
// Per TABVIEW: il LLM deve invece includere espliciti TABPANEL nel
|
|
5673
|
+
// children[] (allowedChildren) - non sono auto-create.
|
|
5674
|
+
//
|
|
5675
|
+
// commitHistoryIfChanged() viene gia' invocato da drop() -> undo/redo
|
|
5676
|
+
// gratis. Nessun side-effect su backend metadata (kind client-only).
|
|
5677
|
+
async onChatbotProposedDesignerAction(action) {
|
|
5678
|
+
if (this.noEdit) {
|
|
5679
|
+
return { ok: false, message: 'Designer in modalita read-only' };
|
|
5680
|
+
}
|
|
5681
|
+
// Discrimina su action_type. Backward-compat: se assente, assume 'inject'
|
|
5682
|
+
// (lo schema precedente non aveva action_type).
|
|
5683
|
+
const actionType = String(action?.['action_type'] || 'inject').toLowerCase();
|
|
5684
|
+
try {
|
|
5685
|
+
if (actionType === 'inject') {
|
|
5686
|
+
const layout = Array.isArray(action?.['layout']) ? action['layout'] : [];
|
|
5687
|
+
if (!layout.length)
|
|
5688
|
+
return { ok: false, message: 'Layout vuoto: nessun tool proposto dal LLM' };
|
|
5689
|
+
let totalInjected = 0;
|
|
5690
|
+
// Top-level: il LLM puo' specificare `parent_unique_name` per innestare
|
|
5691
|
+
// dentro un componente gia' presente nel canvas. Tre casi:
|
|
5692
|
+
// 1) uniqueName reale gia' presente (es. "TD__126") => pass-through;
|
|
5693
|
+
// 2) placeholder semantico (es. "<TD-0>") riferito al nodo top-level
|
|
5694
|
+
// iniettato POCO PRIMA in questo stesso layout: resolve dentro
|
|
5695
|
+
// l'ultimo top-level injected (necessario quando il LLM
|
|
5696
|
+
// preferisce mettere TABLE + LABEL come 2 top-level sibling,
|
|
5697
|
+
// con LABEL.parent_unique_name="<TD-0>" puntando alla TABLE
|
|
5698
|
+
// appena create);
|
|
5699
|
+
// 3) null/missing: va al ROOT (dashboardElements).
|
|
5700
|
+
let lastTopLevelInjected = null;
|
|
5701
|
+
for (const node of layout) {
|
|
5702
|
+
let topParent = null;
|
|
5703
|
+
const declared = (node && typeof node.parent_unique_name === 'string') ? node.parent_unique_name.trim() : '';
|
|
5704
|
+
if (declared) {
|
|
5705
|
+
if (declared.startsWith('<') && declared.endsWith('>')) {
|
|
5706
|
+
if (lastTopLevelInjected) {
|
|
5707
|
+
const resolved = this.resolveLayoutPlaceholder(declared, lastTopLevelInjected);
|
|
5708
|
+
if (resolved)
|
|
5709
|
+
topParent = resolved;
|
|
5710
|
+
else
|
|
5711
|
+
throw new Error(`Placeholder '${declared}' non risolvibile dentro ${lastTopLevelInjected?.uniqueName} (verifica struttura: rows/cols/areas/items match)`);
|
|
5712
|
+
}
|
|
5713
|
+
else {
|
|
5714
|
+
throw new Error(`Placeholder '${declared}' per nodo top-level senza nodo precedente da cui risolvere (deve esserci un TABLE/SPLITTER/ACCORDION/TABVIEW iniettato prima)`);
|
|
5715
|
+
}
|
|
5716
|
+
}
|
|
5717
|
+
else {
|
|
5718
|
+
topParent = declared; // uniqueName reale, pass-through al drop()
|
|
5719
|
+
}
|
|
5720
|
+
}
|
|
5721
|
+
totalInjected += await this.injectLayoutNode(node, topParent);
|
|
5722
|
+
// Aggiorna il last top-level per i prossimi placeholder. NB: usiamo
|
|
5723
|
+
// findLastInjectedNode per pescare l'istanza vera (con onDrop appena
|
|
5724
|
+
// eseguito = TR/TD/AREA gia' popolati), non il template clonato.
|
|
5725
|
+
lastTopLevelInjected = this.findLastInjectedNode(String(node.tool_name || ''), topParent) || lastTopLevelInjected;
|
|
5726
|
+
}
|
|
5727
|
+
const rationale = (action?.['rationale'] || '').toString().slice(0, 100);
|
|
5728
|
+
return { ok: true, message: `Iniettati ${totalInjected} elementi nel designer${rationale ? ': ' + rationale : ''}.` };
|
|
5729
|
+
}
|
|
5730
|
+
if (actionType === 'set_property') {
|
|
5731
|
+
return this.applySetPropertyAction(action);
|
|
5732
|
+
}
|
|
5733
|
+
if (actionType === 'remove') {
|
|
5734
|
+
return this.applyRemoveAction(action);
|
|
5735
|
+
}
|
|
5736
|
+
return { ok: false, message: `action_type sconosciuto: '${actionType}' (atteso 'inject', 'set_property' o 'remove')` };
|
|
5737
|
+
}
|
|
5738
|
+
catch (err) {
|
|
5739
|
+
return { ok: false, message: err?.message || String(err) };
|
|
5740
|
+
}
|
|
5741
|
+
}
|
|
5742
|
+
/**
|
|
5743
|
+
* Applica `set_property`: modifica UN valore di proprieta' su un componente
|
|
5744
|
+
* gia' presente nel canvas. Lookup via uniqueName in flattenedDashboardElements,
|
|
5745
|
+
* assign del valore (BehaviorSubject .next() per async, plain assign per sync),
|
|
5746
|
+
* commitHistoryIfChanged() per undo/redo.
|
|
5747
|
+
*
|
|
5748
|
+
* NB: per coerenza con il property panel runtime, simuliamo il flow di
|
|
5749
|
+
* setValue() canonico (riga 4306) quando possibile: usiamo la toolProp
|
|
5750
|
+
* dell'inputProps target per propagare converter/conditional, in modo che
|
|
5751
|
+
* effetti collaterali (es. borderColor che setta border-{top,right,bottom,left}-color)
|
|
5752
|
+
* siano applicati correttamente.
|
|
5753
|
+
*/
|
|
5754
|
+
applySetPropertyAction(action) {
|
|
5755
|
+
const targetName = String(action?.['target_unique_name'] || '').trim();
|
|
5756
|
+
const propName = String(action?.['prop_name'] || '').trim();
|
|
5757
|
+
const newValue = action?.['value'];
|
|
5758
|
+
if (!targetName)
|
|
5759
|
+
return { ok: false, message: "set_property: 'target_unique_name' mancante" };
|
|
5760
|
+
if (!propName)
|
|
5761
|
+
return { ok: false, message: "set_property: 'prop_name' mancante" };
|
|
5762
|
+
const target = (this.flattenedDashboardElements || []).find((n) => n?.uniqueName === targetName);
|
|
5763
|
+
if (!target)
|
|
5764
|
+
return { ok: false, message: `set_property: componente '${targetName}' non trovato nel canvas. Verifica DESIGNER STATE.` };
|
|
5765
|
+
// La prop deve essere esposta in `inputProps` (schema delle proprieta'
|
|
5766
|
+
// editabili del tool, dichiarate al boot). NON controllare `inputs`:
|
|
5767
|
+
// gli inputs partono spesso vuoti/parziali e si popolano solo on-edit
|
|
5768
|
+
// (es. backgroundColor non e' in inputs di default ma e' in
|
|
5769
|
+
// htmlInputProperties -> inputProps). Coerente col property panel
|
|
5770
|
+
// che legge inputProps per costruire l'UI.
|
|
5771
|
+
if (!target.inputProps || !Object.prototype.hasOwnProperty.call(target.inputProps, propName)) {
|
|
5772
|
+
return { ok: false, message: `set_property: proprieta' '${propName}' non esposta su ${target.name} (${targetName})` };
|
|
5773
|
+
}
|
|
5774
|
+
// Inizializza inputs[propName] se non esiste, cosi' setValue() canonico
|
|
5775
|
+
// (o l'assign fallback) lo trova.
|
|
5776
|
+
if (target.inputs && !Object.prototype.hasOwnProperty.call(target.inputs, propName)) {
|
|
5777
|
+
target.inputs[propName] = undefined;
|
|
5778
|
+
}
|
|
5779
|
+
// Trova la toolProp matching (chiave 'key' === propName) per delegare a setValue
|
|
5780
|
+
// canonico - applica converter, conditional, hooks specifici (datasource/select).
|
|
5781
|
+
const toolProp = Array.isArray(target.toolProps)
|
|
5782
|
+
? target.toolProps.find((tp) => tp?.key === propName)
|
|
5783
|
+
: null;
|
|
5784
|
+
try {
|
|
5785
|
+
if (toolProp) {
|
|
5786
|
+
// Path canonico: setValue() applica converter (es. borderColor -> 4
|
|
5787
|
+
// border-*-color), conditional, e gestisce BehaviorSubject.
|
|
5788
|
+
this.setValue(newValue, target, toolProp);
|
|
5789
|
+
}
|
|
5790
|
+
else {
|
|
5791
|
+
// Fallback: la prop non e' nelle toolProps (raro - inputProps inconsistenti).
|
|
5792
|
+
// Assign diretto su inputs[prop], distinguendo BehaviorSubject vs primitivo.
|
|
5793
|
+
const cur = target.inputs[propName];
|
|
5794
|
+
if (cur && typeof cur.next === 'function' && typeof cur.subscribe === 'function') {
|
|
5795
|
+
cur.next(newValue);
|
|
5796
|
+
}
|
|
5797
|
+
else {
|
|
5798
|
+
target.inputs[propName] = newValue;
|
|
5799
|
+
}
|
|
5800
|
+
this.commitHistoryIfChanged();
|
|
5801
|
+
}
|
|
5802
|
+
const rationale = (action?.['rationale'] || '').toString().slice(0, 100);
|
|
5803
|
+
return { ok: true, message: `Aggiornato ${target.name}.${propName} = ${this.formatPropValueForToast(newValue)}${rationale ? ': ' + rationale : ''}` };
|
|
5804
|
+
}
|
|
5805
|
+
catch (err) {
|
|
5806
|
+
return { ok: false, message: `set_property fallito: ${err?.message || String(err)}` };
|
|
5807
|
+
}
|
|
5808
|
+
}
|
|
5809
|
+
/**
|
|
5810
|
+
* Applica `remove`: rimuove un nodo dal tree dashboardElements (e tutti i
|
|
5811
|
+
* suoi nestedComponents per via della rimozione dal parent).
|
|
5812
|
+
*
|
|
5813
|
+
* Strategia: walk ricorsivo dei dashboardElements + ogni nestedComponents[]
|
|
5814
|
+
* cercando l'array che contiene un elemento con uniqueName==target. splice.
|
|
5815
|
+
* Poi rebuilda flatten + commitHistoryIfChanged.
|
|
5816
|
+
*/
|
|
5817
|
+
applyRemoveAction(action) {
|
|
5818
|
+
const targetName = String(action?.['target_unique_name'] || '').trim();
|
|
5819
|
+
if (!targetName)
|
|
5820
|
+
return { ok: false, message: "remove: 'target_unique_name' mancante" };
|
|
5821
|
+
const removed = this.removeNodeByUniqueName(this.dashboardElements, targetName);
|
|
5822
|
+
if (!removed) {
|
|
5823
|
+
return { ok: false, message: `remove: componente '${targetName}' non trovato nel canvas` };
|
|
5824
|
+
}
|
|
5825
|
+
try {
|
|
5826
|
+
this.flattenedDashboardElements = this.flattenComponentTree(this.dashboardElements);
|
|
5827
|
+
}
|
|
5828
|
+
catch { /* best-effort */ }
|
|
5829
|
+
try {
|
|
5830
|
+
this.commitHistoryIfChanged();
|
|
5831
|
+
}
|
|
5832
|
+
catch { /* best-effort */ }
|
|
5833
|
+
const rationale = (action?.['rationale'] || '').toString().slice(0, 100);
|
|
5834
|
+
return { ok: true, message: `Rimosso ${targetName}${rationale ? ': ' + rationale : ''}` };
|
|
5835
|
+
}
|
|
5836
|
+
/** Walk ricorsivo: rimuove la prima occorrenza del nodo con uniqueName==target. */
|
|
5837
|
+
removeNodeByUniqueName(scope, targetName) {
|
|
5838
|
+
if (!Array.isArray(scope))
|
|
5839
|
+
return false;
|
|
5840
|
+
for (let i = 0; i < scope.length; i++) {
|
|
5841
|
+
const node = scope[i];
|
|
5842
|
+
if (node?.uniqueName === targetName) {
|
|
5843
|
+
scope.splice(i, 1);
|
|
5844
|
+
return true;
|
|
5845
|
+
}
|
|
5846
|
+
if (Array.isArray(node?.nestedComponents)) {
|
|
5847
|
+
if (this.removeNodeByUniqueName(node.nestedComponents, targetName))
|
|
5848
|
+
return true;
|
|
5849
|
+
}
|
|
5850
|
+
}
|
|
5851
|
+
return false;
|
|
5852
|
+
}
|
|
5853
|
+
/**
|
|
5854
|
+
* Best-effort format del value per il toast (max 40 char).
|
|
5855
|
+
* Solo per UX feedback - non per persistenza.
|
|
5856
|
+
*/
|
|
5857
|
+
formatPropValueForToast(v) {
|
|
5858
|
+
try {
|
|
5859
|
+
if (v === null || v === undefined)
|
|
5860
|
+
return String(v);
|
|
5861
|
+
if (typeof v === 'string')
|
|
5862
|
+
return v.length > 40 ? v.slice(0, 37) + '...' : v;
|
|
5863
|
+
if (typeof v === 'number' || typeof v === 'boolean')
|
|
5864
|
+
return String(v);
|
|
5865
|
+
const json = JSON.stringify(v);
|
|
5866
|
+
return json.length > 40 ? json.slice(0, 37) + '...' : json;
|
|
5867
|
+
}
|
|
5868
|
+
catch {
|
|
5869
|
+
return '?';
|
|
5870
|
+
}
|
|
5871
|
+
}
|
|
5872
|
+
/**
|
|
5873
|
+
* Dump compatto dello state del designer per iniezione nel system prompt
|
|
5874
|
+
* del LLM (via ChatbotHostRegistryService state provider, vedi
|
|
5875
|
+
* rag-chatbot.component.ts buildHostStateContext()).
|
|
5876
|
+
*
|
|
5877
|
+
* Formato tree indentato con uniqueName + nome + inputs significativi
|
|
5878
|
+
* (skippa CSS standard ereditate da htmlInputProperties per non gonfiare
|
|
5879
|
+
* il context). Max ~50 nodi (canvas tipico) per stare entro un budget di
|
|
5880
|
+
* ~2-3K token.
|
|
5881
|
+
*
|
|
5882
|
+
* Esempio output:
|
|
5883
|
+
* TABLE__123 (rows=2, cols=2)
|
|
5884
|
+
* TR__124
|
|
5885
|
+
* TD__125
|
|
5886
|
+
* TD__126
|
|
5887
|
+
* TR__127
|
|
5888
|
+
* TD__128
|
|
5889
|
+
* TD__129
|
|
5890
|
+
* DATASOURCE__130 (route="cities", autoload=true)
|
|
5891
|
+
* DATAREPEATER__131 (action="list", datasource=DATASOURCE__130)
|
|
5892
|
+
*
|
|
5893
|
+
* Ritorna null se il canvas e' vuoto.
|
|
5894
|
+
*/
|
|
5895
|
+
buildCompactDashboardStateDump() {
|
|
5896
|
+
// Sezione 1: stato canvas designer (tree compatto dei dashboardElements).
|
|
5897
|
+
const root = this.dashboardElements || [];
|
|
5898
|
+
const canvasLines = [];
|
|
5899
|
+
const MAX_NODES = 60;
|
|
5900
|
+
let count = 0;
|
|
5901
|
+
const walk = (nodes, depth) => {
|
|
5902
|
+
if (!Array.isArray(nodes))
|
|
5903
|
+
return;
|
|
5904
|
+
for (const n of nodes) {
|
|
5905
|
+
if (count >= MAX_NODES)
|
|
5906
|
+
return;
|
|
5907
|
+
count++;
|
|
5908
|
+
const indent = ' '.repeat(depth);
|
|
5909
|
+
const uname = String(n?.uniqueName || '?');
|
|
5910
|
+
const inputsSummary = this.summarizeInputsForDump(n);
|
|
5911
|
+
canvasLines.push(`${indent}${uname}${inputsSummary ? ' (' + inputsSummary + ')' : ''}`);
|
|
5912
|
+
if (Array.isArray(n?.nestedComponents) && n.nestedComponents.length > 0) {
|
|
5913
|
+
walk(n.nestedComponents, depth + 1);
|
|
5914
|
+
}
|
|
5915
|
+
}
|
|
5916
|
+
};
|
|
5917
|
+
walk(root, 0);
|
|
5918
|
+
if (count >= MAX_NODES) {
|
|
5919
|
+
canvasLines.push(`... (canvas con ${this.flattenedDashboardElements?.length || '?'} nodi totali, mostrati i primi ${MAX_NODES})`);
|
|
5920
|
+
}
|
|
5921
|
+
// Sezione 2: whitelist route metadata disponibili.
|
|
5922
|
+
//
|
|
5923
|
+
// Lazy-fetch: la prima volta che il chatbot invoca lo state provider
|
|
5924
|
+
// (= primo prompt utente al chatbot dopo apertura designer), triggera
|
|
5925
|
+
// il fetch fire-and-forget. La cache viene popolata in background; al
|
|
5926
|
+
// SECONDO prompt sara' disponibile. Al PRIMO prompt il LLM vede
|
|
5927
|
+
// 'caricamento in corso' e risponde "riprova tra un secondo".
|
|
5928
|
+
//
|
|
5929
|
+
// Questo approccio evita la chiamata HTTP al mount del designer
|
|
5930
|
+
// (sprecata se l'utente non usa mai il chatbot) e isola eventuali
|
|
5931
|
+
// errori (es. 404, utente non admin) dall'apertura del designer.
|
|
5932
|
+
const metaSrvCtor = this.metaSrv?.constructor;
|
|
5933
|
+
const routesCache = metaSrvCtor?._availableRoutesCache;
|
|
5934
|
+
const fetchInflight = metaSrvCtor?._availableRoutesInflight;
|
|
5935
|
+
if (routesCache === null && !fetchInflight) {
|
|
5936
|
+
// Trigger fire-and-forget: non aspettiamo, la promise popolera' la
|
|
5937
|
+
// cache; questo dump corrente restituira' 'caricamento' al LLM.
|
|
5938
|
+
try {
|
|
5939
|
+
void this.metaSrv.getAvailableRoutesAsync();
|
|
5940
|
+
}
|
|
5941
|
+
catch { /* ignore */ }
|
|
5942
|
+
}
|
|
5943
|
+
let routesSection;
|
|
5944
|
+
if (Array.isArray(routesCache) && routesCache.length > 0) {
|
|
5945
|
+
// Compatta: <route_name> (<display_string>) per ogni route. Limit
|
|
5946
|
+
// 300 entries per evitare context overflow su install con molte route.
|
|
5947
|
+
const MAX_ROUTES = 300;
|
|
5948
|
+
const slice = routesCache.slice(0, MAX_ROUTES);
|
|
5949
|
+
const routeLines = slice
|
|
5950
|
+
.map(r => ` - ${r.md_route_name}${r.md_display_string ? ' ("' + r.md_display_string + '")' : ''}`)
|
|
5951
|
+
.join('\n');
|
|
5952
|
+
const trunc = routesCache.length > MAX_ROUTES
|
|
5953
|
+
? `\n ... (${routesCache.length - MAX_ROUTES} altre route non mostrate)` : '';
|
|
5954
|
+
routesSection = `\n\nROUTE METADATA DISPONIBILI (per DATASOURCE.inputs.route):\n${routeLines}${trunc}\n`
|
|
5955
|
+
+ `REGOLA: in DATASOURCE.inputs.route usa ESCLUSIVAMENTE uno dei valori `
|
|
5956
|
+
+ `md_route_name sopra (la prima colonna, non il display string). Se `
|
|
5957
|
+
+ `l'utente menziona un termine non esatto (es. "provincie" invece di `
|
|
5958
|
+
+ `"provinces", "fornitori" invece di "suppliers"), scegli il FUZZY `
|
|
5959
|
+
+ `MATCH semantico piu' probabile dalla whitelist. Se NESSUN match e' `
|
|
5960
|
+
+ `ragionevole (la route non esiste proprio), NON inventare: rispondi `
|
|
5961
|
+
+ `con un messaggio testuale (senza tool_use) che chiede all'utente di `
|
|
5962
|
+
+ `scegliere tra 2-3 candidati piu' vicini dalla whitelist.`;
|
|
5963
|
+
}
|
|
5964
|
+
else if (Array.isArray(routesCache) && routesCache.length === 0) {
|
|
5965
|
+
// Cache popolata ma vuota (utente non admin, o nessuna route non-system).
|
|
5966
|
+
routesSection = `\n\nROUTE METADATA DISPONIBILI: nessuna (utente senza `
|
|
5967
|
+
+ `accesso o nessuna route business). NON proporre DATASOURCE con `
|
|
5968
|
+
+ `inputs.route valorizzato finche' l'utente non specifica esplicitamente.`;
|
|
5969
|
+
}
|
|
5970
|
+
else {
|
|
5971
|
+
// Cache ancora in caricamento (race con primo prompt subito dopo mount).
|
|
5972
|
+
routesSection = `\n\nROUTE METADATA DISPONIBILI: <caricamento in corso>. `
|
|
5973
|
+
+ `Se l'utente menziona una route specifica, NON proporre il DATASOURCE `
|
|
5974
|
+
+ `con quella route alla cieca; rispondi che la whitelist sta caricando `
|
|
5975
|
+
+ `e chiedi di riprovare tra un secondo.`;
|
|
5976
|
+
}
|
|
5977
|
+
if (canvasLines.length === 0 && !routesCache)
|
|
5978
|
+
return null;
|
|
5979
|
+
const canvasSection = canvasLines.length > 0
|
|
5980
|
+
? canvasLines.join('\n')
|
|
5981
|
+
: '(canvas vuoto)';
|
|
5982
|
+
return canvasSection + routesSection;
|
|
5983
|
+
}
|
|
5984
|
+
/**
|
|
5985
|
+
* Compatta i `inputs` di un nodo per il dump. Skip:
|
|
5986
|
+
* - proprieta' CSS standard (htmlInputProperties: backgroundColor,
|
|
5987
|
+
* fontSize, ecc.) per evitare di gonfiare il context con
|
|
5988
|
+
* proprieta' che il LLM puo' inferire dal tool_name + catalog.
|
|
5989
|
+
* - BehaviorSubject/oggetti complessi: solo serializzazione minima
|
|
5990
|
+
* (uniqueName se e' un dropped-component).
|
|
5991
|
+
* - undefined/null/stringhe vuote.
|
|
5992
|
+
*
|
|
5993
|
+
* Mostra solo le proprieta' "semantiche": rows/cols/areas/items per
|
|
5994
|
+
* container, route/action per datasource, caption/value/innerText per
|
|
5995
|
+
* leaf, header per tab/accordion area.
|
|
5996
|
+
*/
|
|
5997
|
+
summarizeInputsForDump(node) {
|
|
5998
|
+
if (!node?.inputs)
|
|
5999
|
+
return '';
|
|
6000
|
+
// Strategia: confronta ogni inputs[key] del nodo con il DEFAULT del template
|
|
6001
|
+
// (availableTools.find(t.name === node.name).inputs[key]). Se uguale, skip.
|
|
6002
|
+
// Cosi' il dump espone SOLO le proprieta' esplicitamente modificate
|
|
6003
|
+
// dall'utente (manualmente nel property panel o via set_property RAG),
|
|
6004
|
+
// dando al LLM un focus chirurgico sui "tratti distintivi" del componente
|
|
6005
|
+
// per riferimenti deittici ("riquadro blu", "kpi grande", "testo rosso").
|
|
6006
|
+
//
|
|
6007
|
+
// Skip aggiuntivi:
|
|
6008
|
+
// - chiavi tecniche runtime (componentId/uniqueName/toolProps/onDrop/ctxItems...)
|
|
6009
|
+
// - BehaviorSubject del datasource: estrai solo uniqueName interno
|
|
6010
|
+
// - oggetti complessi non-datasource (callback bound, props bag): json
|
|
6011
|
+
// troncato a 40 char per non gonfiare
|
|
6012
|
+
// - inputs[propName] = undefined a default (es. width/height resettati
|
|
6013
|
+
// da dropTable per TR/TD)
|
|
6014
|
+
const tplDefaults = this.getTemplateDefaultsFor(node?.name);
|
|
6015
|
+
const technicalKeys = new Set([
|
|
6016
|
+
'componentId', 'uniqueName', 'toolProps', 'onDrop', 'ctxItems',
|
|
6017
|
+
'componentRef', 'parentRecord', 'parentMetaInfo', 'metaEditor',
|
|
6018
|
+
]);
|
|
6019
|
+
const parts = [];
|
|
6020
|
+
for (const key of Object.keys(node.inputs)) {
|
|
6021
|
+
if (technicalKeys.has(key))
|
|
6022
|
+
continue;
|
|
6023
|
+
const v = node.inputs[key];
|
|
6024
|
+
const def = tplDefaults ? tplDefaults[key] : undefined;
|
|
6025
|
+
if (v === null || v === undefined || v === '') {
|
|
6026
|
+
if (def === null || def === undefined || def === '')
|
|
6027
|
+
continue;
|
|
6028
|
+
// L'utente ha esplicitamente cancellato un valore non-default
|
|
6029
|
+
parts.push(`${key}=null`);
|
|
6030
|
+
continue;
|
|
6031
|
+
}
|
|
6032
|
+
// BehaviorSubject: per datasource estrai uniqueName dell'oggetto wrappato
|
|
6033
|
+
if (v && typeof v.subscribe === 'function') {
|
|
6034
|
+
const inner = v.value;
|
|
6035
|
+
const wrappedName = inner?.uniqueName || inner?.component?.uniqueName;
|
|
6036
|
+
if (wrappedName)
|
|
6037
|
+
parts.push(`${key}=${wrappedName}`);
|
|
6038
|
+
continue;
|
|
6039
|
+
}
|
|
6040
|
+
// Oggetto datasource (dropped-component-list, sync)
|
|
6041
|
+
if (v && typeof v === 'object' && v.uniqueName) {
|
|
6042
|
+
parts.push(`${key}=${v.uniqueName}`);
|
|
6043
|
+
continue;
|
|
6044
|
+
}
|
|
6045
|
+
// Primitivi: includi solo se DIVERSI dal default del template
|
|
6046
|
+
if (typeof v === 'boolean' || typeof v === 'number') {
|
|
6047
|
+
if (v === def)
|
|
6048
|
+
continue;
|
|
6049
|
+
parts.push(`${key}=${v}`);
|
|
6050
|
+
continue;
|
|
6051
|
+
}
|
|
6052
|
+
if (typeof v === 'string') {
|
|
6053
|
+
if (v === def)
|
|
6054
|
+
continue;
|
|
6055
|
+
const trimmed = v.length > 40 ? v.slice(0, 37) + '...' : v;
|
|
6056
|
+
parts.push(`${key}="${trimmed}"`);
|
|
6057
|
+
continue;
|
|
6058
|
+
}
|
|
6059
|
+
// Funzioni: skip silenzioso (sono callback bound, non utili al LLM)
|
|
6060
|
+
if (typeof v === 'function')
|
|
6061
|
+
continue;
|
|
6062
|
+
// Altri oggetti: serializza compatto (es. {a:1,b:2}) e diff vs default
|
|
6063
|
+
try {
|
|
6064
|
+
const json = JSON.stringify(v);
|
|
6065
|
+
const defJson = def !== undefined ? JSON.stringify(def) : undefined;
|
|
6066
|
+
if (json === defJson)
|
|
6067
|
+
continue;
|
|
6068
|
+
const trimmed = json.length > 40 ? json.slice(0, 37) + '...' : json;
|
|
6069
|
+
parts.push(`${key}=${trimmed}`);
|
|
6070
|
+
}
|
|
6071
|
+
catch { /* ignore */ }
|
|
6072
|
+
}
|
|
6073
|
+
return parts.join(', ');
|
|
6074
|
+
}
|
|
6075
|
+
/**
|
|
6076
|
+
* Best-effort lookup dei defaults dell'inputs di un tool dal `availableTools`
|
|
6077
|
+
* catalog. Usato dal dump per fare diff vs defaults. Ritorna `undefined` se
|
|
6078
|
+
* tool name non trovato (compaiono solo i tool custom registrati via
|
|
6079
|
+
* MetadataProviderService.customDesignerTools).
|
|
6080
|
+
*/
|
|
6081
|
+
getTemplateDefaultsFor(toolName) {
|
|
6082
|
+
if (!toolName)
|
|
6083
|
+
return undefined;
|
|
6084
|
+
const tpl = (this.availableTools || []).find(t => t?.name === toolName);
|
|
6085
|
+
return tpl?.inputs;
|
|
6086
|
+
}
|
|
6087
|
+
/**
|
|
6088
|
+
* Inietta UN nodo del layout proposto. Ricorsivo per children[].
|
|
6089
|
+
*
|
|
6090
|
+
* Pattern: clona template da availableTools, override inputs, simula drop
|
|
6091
|
+
* (programmatico via this.drop({toElement: fakeEl con id=parentUniqueName})).
|
|
6092
|
+
*
|
|
6093
|
+
* Ritorna il numero di elementi creati (1 + somma dei children).
|
|
6094
|
+
*/
|
|
6095
|
+
async injectLayoutNode(node, parentUniqueName) {
|
|
6096
|
+
const toolName = String(node?.tool_name || '').trim();
|
|
6097
|
+
if (!toolName)
|
|
6098
|
+
throw new Error('Nodo senza tool_name');
|
|
6099
|
+
const template = this.availableTools.find(t => t.name === toolName);
|
|
6100
|
+
if (!template) {
|
|
6101
|
+
throw new Error(`Tool '${toolName}' non trovato in availableTools (controlla docs/pages/_internal/designer-tool-catalog.md)`);
|
|
6102
|
+
}
|
|
6103
|
+
if (template.hide) {
|
|
6104
|
+
throw new Error(`Tool '${toolName}' ha hide=true: non puo' essere proposto come top-level. Sara' creato automaticamente dal parent (es. TABLE -> TR/TD).`);
|
|
6105
|
+
}
|
|
6106
|
+
// CRITICAL: il drop() canonico (riga 5847+) recovera le function references
|
|
6107
|
+
// (onDrop, ctxItems, inputProps[*].conditional/converter/callback) leggendole
|
|
6108
|
+
// da `this.draggedPayload`. Se passiamo un clone via JSON.parse(JSON.stringify)
|
|
6109
|
+
// tutte le function vengono perse -> dropTable/dropSplitter/dropAccordion NON
|
|
6110
|
+
// vengono mai chiamati -> niente TR/TD auto-generati per TABLE/SPLITTER/ACCORDION.
|
|
6111
|
+
//
|
|
6112
|
+
// Pattern corretto: usare il template ORIGINALE (con function vive) come
|
|
6113
|
+
// draggedPayload. Override temporaneo degli inputs + restore in finally,
|
|
6114
|
+
// cosi' inject ripetuti non inquinano i defaults della palette.
|
|
6115
|
+
const inputsBackup = template.inputs ? { ...template.inputs } : {};
|
|
6116
|
+
// Override degli inputs SCALARI (string/number/boolean/array) PRIMA
|
|
6117
|
+
// del drop(). I binding di tipo `dropped-component-list` /
|
|
6118
|
+
// `dropped-component` vanno invece applicati POST-drop sull'istanza
|
|
6119
|
+
// appena iniettata, perche':
|
|
6120
|
+
// 1. il drop() canonico fa `JSON.parse(JSON.stringify(template))`
|
|
6121
|
+
// sul currentTool -> qualsiasi BehaviorSubject infilato in
|
|
6122
|
+
// template.inputs verrebbe serializzato come `{}` (perde valore
|
|
6123
|
+
// e prototype) -> hydration distrutta;
|
|
6124
|
+
// 2. pre-drop l'oggetto target potrebbe non esistere ancora
|
|
6125
|
+
// (es. caso edge in cui il LLM riferisce un componente che
|
|
6126
|
+
// verra' creato in un nodo successivo del layout - non
|
|
6127
|
+
// supportato, ma diagnosticabile post-drop con messaggio chiaro).
|
|
6128
|
+
//
|
|
6129
|
+
// Estraiamo qui le keys "drop-binding" (per applicarle dopo) e teniamo
|
|
6130
|
+
// SOLO gli scalari per il pre-drop Object.assign.
|
|
6131
|
+
const droppedBindingKeys = [];
|
|
6132
|
+
if (node.inputs && typeof node.inputs === 'object' && template.inputProps) {
|
|
6133
|
+
for (const key of Object.keys(node.inputs)) {
|
|
6134
|
+
const propDef = template.inputProps[key];
|
|
6135
|
+
if (propDef && (propDef.type === 'dropped-component-list' || propDef.type === 'dropped-component')) {
|
|
6136
|
+
droppedBindingKeys.push(key);
|
|
6137
|
+
}
|
|
6138
|
+
}
|
|
6139
|
+
}
|
|
6140
|
+
if (node.inputs && typeof node.inputs === 'object' && template.inputs) {
|
|
6141
|
+
for (const key of Object.keys(node.inputs)) {
|
|
6142
|
+
if (droppedBindingKeys.includes(key))
|
|
6143
|
+
continue;
|
|
6144
|
+
template.inputs[key] = node.inputs[key];
|
|
6145
|
+
}
|
|
6146
|
+
}
|
|
6147
|
+
// DEFAULTS auto: se il tool e' un CONTAINER block-level (TABLE/DIV/
|
|
6148
|
+
// SPLITTER/ACCORDION/TABVIEW/TABPANEL) e l'utente non ha specificato
|
|
6149
|
+
// width/height in node.inputs, settali a '100%' cosi' riempie lo slot
|
|
6150
|
+
// ospitante (TD/SPLITTER-AREA/canvas). Coerente con l'aspettativa
|
|
6151
|
+
// utente: "crea TABLE 2x2" -> occupa il canvas, non un quadratino.
|
|
6152
|
+
//
|
|
6153
|
+
// ESCLUSI (inline-level / fit-content tools): LABEL/SPAN/Hx/ANCHOR/
|
|
6154
|
+
// INPUT/BUTTON/CHECKBOX/DATE/SELECT/MULTISELECT/IMG/IFRAME/UL/KPI/
|
|
6155
|
+
// SEPARATOR/HR/TEXTAREA. Forzando width:100% su questi tool si
|
|
6156
|
+
// rompe il layout del parent (es. LABEL width:100% dentro un TD
|
|
6157
|
+
// costringe il browser a espandere il TD perche' table-layout:auto
|
|
6158
|
+
// non sa la dimensione finale -> squilibrio colonne).
|
|
6159
|
+
//
|
|
6160
|
+
// Sono OUT anche per def:
|
|
6161
|
+
// - tool senza width/height in inputProps (DATASOURCE, DATAREPEATER,
|
|
6162
|
+
// FILTERBAR, PAGER, TR, TD, SPLITTER-AREA, ACCORDION-AREA).
|
|
6163
|
+
// - tool dove l'utente ha specificato esplicitamente width/height.
|
|
6164
|
+
const BLOCK_CONTAINER_TOOLS = new Set([
|
|
6165
|
+
'TABLE', 'DIV', 'SPLITTER', 'ACCORDION', 'TABVIEW', 'TABPANEL'
|
|
6166
|
+
]);
|
|
6167
|
+
const explicitInputs = node.inputs || {};
|
|
6168
|
+
if (template.inputs && template.inputProps && BLOCK_CONTAINER_TOOLS.has(toolName)) {
|
|
6169
|
+
if ('width' in template.inputProps && !('width' in explicitInputs)) {
|
|
6170
|
+
template.inputs['width'] = '100%';
|
|
6171
|
+
}
|
|
6172
|
+
if ('height' in template.inputProps && !('height' in explicitInputs)) {
|
|
6173
|
+
template.inputs['height'] = '100%';
|
|
6174
|
+
}
|
|
6175
|
+
}
|
|
6176
|
+
// DEFAULT B: per TABLE forziamo `table-layout: fixed` se non specificato
|
|
6177
|
+
// dall'utente. In modalita' fixed le colonne sono equipartite a priori
|
|
6178
|
+
// (cols * (width / cols)) e NON dipendono dal contenuto. Cosi' iniettare
|
|
6179
|
+
// contenuti di dimensioni diverse nelle celle (LABEL "ciao" + altro)
|
|
6180
|
+
// non causa squilibrio. Default storico del framework era 'auto' ma per
|
|
6181
|
+
// le table create dal RAG-chatbot 'fixed' e' molto piu' robusto.
|
|
6182
|
+
if (toolName === 'TABLE' && template.inputs
|
|
6183
|
+
&& template.inputProps && 'tableLayout' in template.inputProps
|
|
6184
|
+
&& !('tableLayout' in explicitInputs)) {
|
|
6185
|
+
template.inputs['tableLayout'] = 'fixed';
|
|
6186
|
+
}
|
|
6187
|
+
try {
|
|
6188
|
+
// Stash il template ORIGINALE (preserva function references).
|
|
6189
|
+
// Il drop() interno fara' un JSON.parse(JSON.stringify(draggedPayload))
|
|
6190
|
+
// per il currentTool, e poi ripristinera' currentTool.onDrop =
|
|
6191
|
+
// draggedPayload.onDrop dalla nostra istanza viva.
|
|
6192
|
+
this.draggedPayload = template;
|
|
6193
|
+
const fakeEvent = { toElement: null };
|
|
6194
|
+
if (parentUniqueName) {
|
|
6195
|
+
const fakeEl = document.createElement('div');
|
|
6196
|
+
fakeEl.setAttribute('id', parentUniqueName);
|
|
6197
|
+
fakeEvent.toElement = fakeEl;
|
|
6198
|
+
}
|
|
6199
|
+
this.drop(fakeEvent);
|
|
6200
|
+
}
|
|
6201
|
+
finally {
|
|
6202
|
+
// Restore SEMPRE, anche su exception in drop(), cosi' il template della
|
|
6203
|
+
// palette torna ai defaults originali (rows=2, cols=2, route=null, ecc.)
|
|
6204
|
+
if (template.inputs) {
|
|
6205
|
+
// Rimuovi le chiavi che node.inputs aveva aggiunto e non c'erano nel backup
|
|
6206
|
+
for (const k of Object.keys(template.inputs)) {
|
|
6207
|
+
if (!Object.prototype.hasOwnProperty.call(inputsBackup, k)) {
|
|
6208
|
+
delete template.inputs[k];
|
|
6209
|
+
}
|
|
6210
|
+
}
|
|
6211
|
+
// Ripristina i valori delle chiavi originali
|
|
6212
|
+
Object.assign(template.inputs, inputsBackup);
|
|
6213
|
+
}
|
|
6214
|
+
}
|
|
6215
|
+
// L'ultimo nodo pushed e' quello appena iniettato. Recuperalo dal flatten
|
|
6216
|
+
// tree (aggiornato da drop()) per ottenere l'uniqueName generato.
|
|
6217
|
+
const justInjected = this.findLastInjectedNode(toolName, parentUniqueName);
|
|
6218
|
+
const injectedUniqueName = justInjected?.uniqueName;
|
|
6219
|
+
// EQUIPARTITION VERTICALE per TABLE/SPLITTER/ACCORDION iniettati via RAG.
|
|
6220
|
+
// `table-layout: fixed` equipartisce solo le colonne (asse orizzontale);
|
|
6221
|
+
// le righe TR (senza height esplicita) restano content-driven -> una
|
|
6222
|
+
// riga con contenuto alto si espande, schiacciando le altre.
|
|
6223
|
+
//
|
|
6224
|
+
// Fix: dopo dropTable/dropSplitter/dropAccordion settiamo height/size
|
|
6225
|
+
// percentuale equa sui figli auto-create.
|
|
6226
|
+
// - TABLE: ogni TR riceve height = 100/rows %
|
|
6227
|
+
// - SPLITTER: ogni SPLITTER-AREA riceve size = 100/areas
|
|
6228
|
+
// (gia' fatto da dropSplitter con size=50 hardcoded, qui rigiamo
|
|
6229
|
+
// per essere coerenti con N != 2)
|
|
6230
|
+
// - ACCORDION: items non hanno height (sono collassabili), skip
|
|
6231
|
+
if (justInjected && Array.isArray(justInjected.nestedComponents) && justInjected.nestedComponents.length > 0) {
|
|
6232
|
+
const childCount = justInjected.nestedComponents.length;
|
|
6233
|
+
if (toolName === 'TABLE') {
|
|
6234
|
+
const rowPct = `${Math.floor(10000 / childCount) / 100}%`;
|
|
6235
|
+
for (const tr of justInjected.nestedComponents) {
|
|
6236
|
+
if (tr?.inputs)
|
|
6237
|
+
tr.inputs['height'] = rowPct;
|
|
6238
|
+
}
|
|
6239
|
+
}
|
|
6240
|
+
else if (toolName === 'SPLITTER') {
|
|
6241
|
+
// SPLITTER-AREA.size e' un number (% o px in base a inputs.unit)
|
|
6242
|
+
const sizePct = Math.floor(10000 / childCount) / 100;
|
|
6243
|
+
for (const area of justInjected.nestedComponents) {
|
|
6244
|
+
if (area?.inputs)
|
|
6245
|
+
area.inputs['size'] = sizePct;
|
|
6246
|
+
}
|
|
6247
|
+
}
|
|
6248
|
+
}
|
|
6249
|
+
// HYDRATION POST-DROP dei binding `dropped-component-list` /
|
|
6250
|
+
// `dropped-component`. Il LLM passa una STRINGA (uniqueName diretto
|
|
6251
|
+
// o placeholder `<DATASOURCE-N>`); il template runtime aspetta un
|
|
6252
|
+
// OGGETTO `{uniqueName, [asyncPath]: BehaviorSubject(liveComp)}`
|
|
6253
|
+
// (stessa shape costruita da setValue() canonico riga ~4972).
|
|
6254
|
+
//
|
|
6255
|
+
// Senza hydration: `inputs.datasource = "DATASOURCE__15"` (stringa),
|
|
6256
|
+
// il tag `[datasource]="inputs.datasource?.component"` legge undefined,
|
|
6257
|
+
// DATAREPEATER renderizza vuoto = grid senza dati e senza colonne.
|
|
6258
|
+
//
|
|
6259
|
+
// POST-drop perche':
|
|
6260
|
+
// 1. `drop()` clona via JSON un eventuale BehaviorSubject pre-drop;
|
|
6261
|
+
// 2. il target referenziato (es. DATASOURCE_15) deve esistere gia'
|
|
6262
|
+
// in flattenedDashboardElements (= iniettato in nodo precedente
|
|
6263
|
+
// del layout o gia' nel canvas).
|
|
6264
|
+
if (justInjected && justInjected.inputs && droppedBindingKeys.length > 0 && node.inputs) {
|
|
6265
|
+
for (const key of droppedBindingKeys) {
|
|
6266
|
+
const propDef = template.inputProps?.[key];
|
|
6267
|
+
if (!propDef)
|
|
6268
|
+
continue;
|
|
6269
|
+
const raw = node.inputs[key];
|
|
6270
|
+
if (raw === null || raw === undefined)
|
|
6271
|
+
continue;
|
|
6272
|
+
// Se gia' oggetto (LLM mandasse direttamente {uniqueName,component})
|
|
6273
|
+
// o BehaviorSubject -> pass-through. Solo stringhe vengono risolte.
|
|
6274
|
+
if (typeof raw !== 'string') {
|
|
6275
|
+
justInjected.inputs[key] = raw;
|
|
6276
|
+
continue;
|
|
6277
|
+
}
|
|
6278
|
+
const resolved = (raw.startsWith('<') && raw.endsWith('>'))
|
|
6279
|
+
? this.resolveCrossTreePlaceholder(raw)
|
|
6280
|
+
: raw.trim();
|
|
6281
|
+
if (!resolved) {
|
|
6282
|
+
throw new Error(`Binding '${toolName}.${key}': placeholder '${raw}' non risolvibile. `
|
|
6283
|
+
+ `Pattern atteso: '<DATASOURCE-N>' (N=indice 0-based del DATASOURCE iniettato). `
|
|
6284
|
+
+ `Assicurati che il componente target sia stato iniettato PRIMA nel layout[].`);
|
|
6285
|
+
}
|
|
6286
|
+
const refElement = (this.flattenedDashboardElements || []).find((n) => n?.uniqueName === resolved);
|
|
6287
|
+
if (!refElement) {
|
|
6288
|
+
throw new Error(`Binding '${toolName}.${key}': componente '${resolved}' non presente nel canvas`);
|
|
6289
|
+
}
|
|
6290
|
+
const filter = propDef.filter;
|
|
6291
|
+
if (filter && refElement.name && String(refElement.name).toUpperCase() !== String(filter).toUpperCase()) {
|
|
6292
|
+
throw new Error(`Binding '${toolName}.${key}': atteso ${filter}, trovato ${refElement.name} (${resolved})`);
|
|
6293
|
+
}
|
|
6294
|
+
// Estrai snapshot del liveComp dal componentRef del DATASOURCE
|
|
6295
|
+
// referenziato. Stessa logica di setValue() canonico riga 4977-4982.
|
|
6296
|
+
//
|
|
6297
|
+
// CRITICAL ASYNC: in scenario "inject programmatico" (chatbot RAG),
|
|
6298
|
+
// il DATASOURCE e il DATAREPEATER vengono droppati nello stesso
|
|
6299
|
+
// tick. Il componente Angular <wuic-data-source> NON e' ancora
|
|
6300
|
+
// materializzato (ngAfterViewInit non e' partito) -> il suo
|
|
6301
|
+
// `componentRef.next({component: this, ...})` (riga 838 di
|
|
6302
|
+
// data-source.component.ts) succede SOLO al prossimo CD tick.
|
|
6303
|
+
//
|
|
6304
|
+
// Quindi al momento dell'hydration `compRef.value?.component` e' null.
|
|
6305
|
+
// Se ci fermassimo a snapshot, creeremmo `BehaviorSubject(null)` e
|
|
6306
|
+
// il DATAREPEATER resterebbe vuoto FOREVER (il BS interno del repeater
|
|
6307
|
+
// non sa che deve ri-leggere quando il DATASOURCE finisce l'init).
|
|
6308
|
+
//
|
|
6309
|
+
// Fix: subscribe al compRef BS originale del DATASOURCE e propagare
|
|
6310
|
+
// al compBs locale del repeater quando emette. Cosi' appena il
|
|
6311
|
+
// DataSourceComponent si materializza, il binding si chiude e la
|
|
6312
|
+
// grid renderizza i dati.
|
|
6313
|
+
const compRef = refElement.inputs?.componentRef;
|
|
6314
|
+
const liveComp = (compRef instanceof BehaviorSubject)
|
|
6315
|
+
? (compRef.value?.component ?? compRef.value)
|
|
6316
|
+
: (compRef?.value?.component ?? compRef?.component ?? compRef ?? null);
|
|
6317
|
+
const asyncPath = propDef.asyncPath || 'component';
|
|
6318
|
+
const compBs = new BehaviorSubject(liveComp);
|
|
6319
|
+
if (compRef instanceof BehaviorSubject) {
|
|
6320
|
+
// Subscribe sul compRef del DATASOURCE: ogni volta che il payload
|
|
6321
|
+
// viene emesso (es. quando il componente Angular fa next con
|
|
6322
|
+
// {component: this} in ngAfterViewInit), propaghiamo al BS del
|
|
6323
|
+
// repeater. La sottoscrizione vive finche' il designer e' aperto;
|
|
6324
|
+
// viene cleaned-up via `this.subs.add(...)` se la subs collection
|
|
6325
|
+
// esiste, altrimenti scope al componente lifecycle.
|
|
6326
|
+
const sub = compRef.subscribe((emitted) => {
|
|
6327
|
+
const newComp = (emitted && typeof emitted === 'object')
|
|
6328
|
+
? (emitted.component ?? emitted)
|
|
6329
|
+
: null;
|
|
6330
|
+
if (newComp && newComp !== compBs.value) {
|
|
6331
|
+
compBs.next(newComp);
|
|
6332
|
+
}
|
|
6333
|
+
});
|
|
6334
|
+
try {
|
|
6335
|
+
const selfAny = this;
|
|
6336
|
+
if (selfAny.subs && typeof selfAny.subs.add === 'function') {
|
|
6337
|
+
selfAny.subs.add(sub);
|
|
6338
|
+
}
|
|
6339
|
+
}
|
|
6340
|
+
catch { /* subs collection non disponibile, sub continuera' a vivere col designer */ }
|
|
6341
|
+
}
|
|
6342
|
+
justInjected.inputs[key] = {
|
|
6343
|
+
uniqueName: refElement.uniqueName,
|
|
6344
|
+
[asyncPath]: compBs
|
|
6345
|
+
};
|
|
6346
|
+
}
|
|
6347
|
+
}
|
|
6348
|
+
// SIDE-EFFECT DATAREPEATER: dopo l'inject e l'hydration di
|
|
6349
|
+
// `inputs.datasource`, il property panel canonico (setValue riga ~4997)
|
|
6350
|
+
// chiama `propertyTreeBuilder(action, inputs)` per popolare la lista
|
|
6351
|
+
// colonne del repeater (action='list' -> grid). Replichiamo qui per
|
|
6352
|
+
// chiudere il binding end-to-end: senza questo, l'archetipo 'list'
|
|
6353
|
+
// non sa quali colonne mostrare e la grid resta vuota anche con
|
|
6354
|
+
// datasource bindato correttamente.
|
|
6355
|
+
if (justInjected && toolName === 'DATAREPEATER' && justInjected.inputs) {
|
|
6356
|
+
const ds = justInjected.inputs['datasource'];
|
|
6357
|
+
const action = this.getRepeaterAction(justInjected) || justInjected.inputs['action'] || 'list';
|
|
6358
|
+
if (ds) {
|
|
6359
|
+
try {
|
|
6360
|
+
this.propertyTreeBuilder(action, justInjected.inputs);
|
|
6361
|
+
}
|
|
6362
|
+
catch (err) {
|
|
6363
|
+
try {
|
|
6364
|
+
console.warn('[injectLayoutNode] propertyTreeBuilder failed for DATAREPEATER', err);
|
|
6365
|
+
}
|
|
6366
|
+
catch { /* ignore */ }
|
|
6367
|
+
}
|
|
6368
|
+
}
|
|
6369
|
+
}
|
|
6370
|
+
let count = 1;
|
|
6371
|
+
// Recursion sui children. Tre casi per il parent del child:
|
|
6372
|
+
// 1. `ch.parent_unique_name` e' un uniqueName REALE (es. "TD__126")
|
|
6373
|
+
// => usalo direttamente, il drop() lo risolvera' via componentId.
|
|
6374
|
+
// 2. `ch.parent_unique_name` e' un PLACEHOLDER semantico (es. "<TD-0>",
|
|
6375
|
+
// "<SPLITTER-AREA-1>") => risolvilo dentro `justInjected.nestedComponents`
|
|
6376
|
+
// (i nodi auto-create da dropTable/dropSplitter/dropAccordion).
|
|
6377
|
+
// 3. Manca o null => default = il parent appena iniettato (LABEL dentro
|
|
6378
|
+
// DIV, TABPANEL dentro TABVIEW, ecc.).
|
|
6379
|
+
const children = Array.isArray(node.children) ? node.children : [];
|
|
6380
|
+
if (children.length && injectedUniqueName) {
|
|
6381
|
+
for (const ch of children) {
|
|
6382
|
+
let childParent = injectedUniqueName;
|
|
6383
|
+
const declared = (ch && typeof ch.parent_unique_name === 'string') ? ch.parent_unique_name.trim() : '';
|
|
6384
|
+
if (declared) {
|
|
6385
|
+
if (declared.startsWith('<') && declared.endsWith('>')) {
|
|
6386
|
+
// Placeholder semantico: risolvi dentro justInjected
|
|
6387
|
+
const resolved = this.resolveLayoutPlaceholder(declared, justInjected);
|
|
6388
|
+
if (resolved)
|
|
6389
|
+
childParent = resolved;
|
|
6390
|
+
else
|
|
6391
|
+
throw new Error(`Placeholder '${declared}' non risolvibile dentro ${justInjected?.uniqueName} (verifica struttura: rows/cols/areas/items match)`);
|
|
6392
|
+
}
|
|
6393
|
+
else {
|
|
6394
|
+
// uniqueName reale (es. TD__126): pass-through al drop()
|
|
6395
|
+
childParent = declared;
|
|
6396
|
+
}
|
|
6397
|
+
}
|
|
6398
|
+
count += await this.injectLayoutNode(ch, childParent);
|
|
6399
|
+
}
|
|
6400
|
+
}
|
|
6401
|
+
return count;
|
|
6402
|
+
}
|
|
6403
|
+
/**
|
|
6404
|
+
* Risolve i placeholder semantici che il LLM usa per riferirsi a nodi
|
|
6405
|
+
* auto-generati dai custom onDrop (TABLE crea TR/TD, SPLITTER crea
|
|
6406
|
+
* SPLITTER-AREA, ACCORDION crea ACCORDION-AREA). Lookup dentro il parent
|
|
6407
|
+
* appena iniettato.
|
|
6408
|
+
*
|
|
6409
|
+
* Pattern supportati (case-insensitive sui tag):
|
|
6410
|
+
* - `<TD-N>` - N-esima cella in row-major order (TR[0].TD[0],
|
|
6411
|
+
* TR[0].TD[1], ..., TR[1].TD[0], ...). N zero-based.
|
|
6412
|
+
* - `<TD-R-C>` - cella a riga R colonna C (zero-based).
|
|
6413
|
+
* - `<TR-N>` - N-esima riga.
|
|
6414
|
+
* - `<SPLITTER-AREA-N>` - N-esima area dello splitter.
|
|
6415
|
+
* - `<ACCORDION-AREA-N>` - N-esimo item dell'accordion.
|
|
6416
|
+
* - `<TABPANEL-N>` - N-esimo tab del tabview.
|
|
6417
|
+
*
|
|
6418
|
+
* Ritorna l'uniqueName del nodo risolto, o `null` se il pattern non matcha
|
|
6419
|
+
* o l'index e' out-of-bounds (es. <TD-3> in tabella 2x2).
|
|
6420
|
+
*/
|
|
6421
|
+
resolveLayoutPlaceholder(placeholder, parentNode) {
|
|
6422
|
+
if (!placeholder || !parentNode)
|
|
6423
|
+
return null;
|
|
6424
|
+
const inner = placeholder.replace(/^<|>$/g, '').trim();
|
|
6425
|
+
const parts = inner.split('-');
|
|
6426
|
+
if (parts.length < 2)
|
|
6427
|
+
return null;
|
|
6428
|
+
const tag = parts[0].toUpperCase();
|
|
6429
|
+
const nested = parentNode.nestedComponents || [];
|
|
6430
|
+
if (tag === 'TD') {
|
|
6431
|
+
// <TD-N> singolo index OR <TD-R-C> riga/colonna
|
|
6432
|
+
if (parts.length === 2) {
|
|
6433
|
+
const n = parseInt(parts[1], 10);
|
|
6434
|
+
if (!Number.isFinite(n))
|
|
6435
|
+
return null;
|
|
6436
|
+
// Row-major lookup: TR[i].TD[j]
|
|
6437
|
+
let idx = 0;
|
|
6438
|
+
for (const tr of nested) {
|
|
6439
|
+
for (const td of (tr.nestedComponents || [])) {
|
|
6440
|
+
if (idx === n)
|
|
6441
|
+
return td.uniqueName || null;
|
|
6442
|
+
idx++;
|
|
6443
|
+
}
|
|
6444
|
+
}
|
|
6445
|
+
return null;
|
|
6446
|
+
}
|
|
6447
|
+
if (parts.length === 3) {
|
|
6448
|
+
const r = parseInt(parts[1], 10);
|
|
6449
|
+
const c = parseInt(parts[2], 10);
|
|
6450
|
+
if (!Number.isFinite(r) || !Number.isFinite(c))
|
|
6451
|
+
return null;
|
|
6452
|
+
const tr = nested[r];
|
|
6453
|
+
if (!tr)
|
|
6454
|
+
return null;
|
|
6455
|
+
const td = (tr.nestedComponents || [])[c];
|
|
6456
|
+
return td?.uniqueName || null;
|
|
6457
|
+
}
|
|
6458
|
+
return null;
|
|
6459
|
+
}
|
|
6460
|
+
if (tag === 'TR') {
|
|
6461
|
+
const n = parseInt(parts[1], 10);
|
|
6462
|
+
if (!Number.isFinite(n))
|
|
6463
|
+
return null;
|
|
6464
|
+
return nested[n]?.uniqueName || null;
|
|
6465
|
+
}
|
|
6466
|
+
if (tag === 'SPLITTER' && parts[1].toUpperCase() === 'AREA') {
|
|
6467
|
+
const n = parseInt(parts[2], 10);
|
|
6468
|
+
if (!Number.isFinite(n))
|
|
6469
|
+
return null;
|
|
6470
|
+
return nested[n]?.uniqueName || null;
|
|
6471
|
+
}
|
|
6472
|
+
if (tag === 'ACCORDION' && parts[1].toUpperCase() === 'AREA') {
|
|
6473
|
+
const n = parseInt(parts[2], 10);
|
|
6474
|
+
if (!Number.isFinite(n))
|
|
6475
|
+
return null;
|
|
6476
|
+
return nested[n]?.uniqueName || null;
|
|
6477
|
+
}
|
|
6478
|
+
if (tag === 'TABPANEL') {
|
|
6479
|
+
const n = parseInt(parts[1], 10);
|
|
6480
|
+
if (!Number.isFinite(n))
|
|
6481
|
+
return null;
|
|
6482
|
+
return nested[n]?.uniqueName || null;
|
|
6483
|
+
}
|
|
6484
|
+
return null;
|
|
6485
|
+
}
|
|
6486
|
+
/**
|
|
6487
|
+
* Risolve placeholder CROSS-TREE per binding di tipo `dropped-component-list`
|
|
6488
|
+
* (es. DATAREPEATER.inputs.datasource = "<DATASOURCE-0>" -> "DATASOURCE__15").
|
|
6489
|
+
*
|
|
6490
|
+
* Diversamente da `resolveLayoutPlaceholder` (lookup dentro
|
|
6491
|
+
* nestedComponents del parent), qui cerchiamo nel `flattenedDashboardElements`
|
|
6492
|
+
* globale, perche' il LLM puo' riferirsi a componenti che vivono in tutto
|
|
6493
|
+
* il canvas (es. DATASOURCE in un TD, DATAREPEATER in un TD diverso).
|
|
6494
|
+
*
|
|
6495
|
+
* Pattern supportato: `<NAME-N>` dove `NAME` matcha `tool.name`
|
|
6496
|
+
* (DATASOURCE, DATAREPEATER, FILTERBAR, PAGER, ...) e `N` e' l'index
|
|
6497
|
+
* 0-based dei tool con quel nome nell'ordine in cui appaiono in
|
|
6498
|
+
* `flattenedDashboardElements` (= ordine di inject).
|
|
6499
|
+
*
|
|
6500
|
+
* Ritorna l'uniqueName risolto, o `null` se non trova match.
|
|
6501
|
+
*
|
|
6502
|
+
* Esempi:
|
|
6503
|
+
* - "<DATASOURCE-0>" -> primo DATASOURCE iniettato (es. "DATASOURCE__3")
|
|
6504
|
+
* - "<DATASOURCE-1>" -> secondo DATASOURCE (master-detail)
|
|
6505
|
+
* - "<DATAREPEATER-0>" -> primo DATAREPEATER
|
|
6506
|
+
*
|
|
6507
|
+
* Casi degeneri:
|
|
6508
|
+
* - placeholder non riconosciuto -> null
|
|
6509
|
+
* - N out-of-bounds (es. <DATASOURCE-3> con solo 2 DS) -> null
|
|
6510
|
+
*
|
|
6511
|
+
* Usato in `injectLayoutNode` per risolvere binding del tipo
|
|
6512
|
+
* `inputs.datasource = "<DATASOURCE-0>"` PRIMA di costruire l'oggetto
|
|
6513
|
+
* wrapper `{uniqueName, component: BS}`.
|
|
6514
|
+
*/
|
|
6515
|
+
resolveCrossTreePlaceholder(placeholder) {
|
|
6516
|
+
if (!placeholder || typeof placeholder !== 'string')
|
|
6517
|
+
return null;
|
|
6518
|
+
const trim = placeholder.trim();
|
|
6519
|
+
if (!trim.startsWith('<') || !trim.endsWith('>'))
|
|
6520
|
+
return null;
|
|
6521
|
+
const inner = trim.replace(/^<|>$/g, '').trim();
|
|
6522
|
+
const m = inner.match(/^([A-Za-z][A-Za-z0-9_-]*)-(\d+)$/);
|
|
6523
|
+
if (!m)
|
|
6524
|
+
return null;
|
|
6525
|
+
const targetName = m[1].toUpperCase();
|
|
6526
|
+
const targetIdx = parseInt(m[2], 10);
|
|
6527
|
+
if (!Number.isFinite(targetIdx) || targetIdx < 0)
|
|
6528
|
+
return null;
|
|
6529
|
+
const matching = (this.flattenedDashboardElements || [])
|
|
6530
|
+
.filter((n) => n?.name && String(n.name).toUpperCase() === targetName);
|
|
6531
|
+
if (targetIdx >= matching.length)
|
|
6532
|
+
return null;
|
|
6533
|
+
return matching[targetIdx]?.uniqueName || null;
|
|
6534
|
+
}
|
|
6535
|
+
/**
|
|
6536
|
+
* Best-effort lookup dell'ultimo nodo iniettato. Strategia:
|
|
6537
|
+
* - se parentUniqueName fornito: cerca dentro il nestedComponents del
|
|
6538
|
+
* parent il piu' recente con quel toolName;
|
|
6539
|
+
* - altrimenti (root): cerca in dashboardElements il piu' recente con
|
|
6540
|
+
* quel toolName.
|
|
6541
|
+
*
|
|
6542
|
+
* "piu' recente" = ultimo elemento dell'array (drop() fa push() in append).
|
|
6543
|
+
*/
|
|
6544
|
+
findLastInjectedNode(toolName, parentUniqueName) {
|
|
6545
|
+
let scope;
|
|
6546
|
+
if (parentUniqueName) {
|
|
6547
|
+
const parent = (this.flattenedDashboardElements || []).find((n) => n?.uniqueName === parentUniqueName);
|
|
6548
|
+
scope = parent?.nestedComponents || [];
|
|
6549
|
+
}
|
|
6550
|
+
else {
|
|
6551
|
+
scope = this.dashboardElements || [];
|
|
6552
|
+
}
|
|
6553
|
+
for (let i = scope.length - 1; i >= 0; i--) {
|
|
6554
|
+
if (scope[i]?.name === toolName)
|
|
6555
|
+
return scope[i];
|
|
6556
|
+
}
|
|
6557
|
+
return undefined;
|
|
5625
6558
|
}
|
|
5626
6559
|
/**
|
|
5627
6560
|
* Handler capture-phase per mouseup/touchend su document. Riferimento
|
|
@@ -8795,8 +9728,24 @@ class DesignerComponent {
|
|
|
8795
9728
|
const detailColumnsHint = detailColumns.length
|
|
8796
9729
|
? detailColumns.map((col) => `// - ${col}`).join('\n')
|
|
8797
9730
|
: '// - (no detail columns available yet)';
|
|
8798
|
-
|
|
8799
|
-
|
|
9731
|
+
// Risoluzione FK del detail verso il master (allineata a
|
|
9732
|
+
// `injectMasterDetailLookupFilter` runtime — cosi' la formula proposta
|
|
9733
|
+
// produce lo stesso risultato del fallback automatico).
|
|
9734
|
+
// Cascata:
|
|
9735
|
+
// 1) FK lookup canonica via metadata (mc_ui_lookup_entity_name == masterRoute);
|
|
9736
|
+
// 2) colonna del detail che ha lo stesso nome della PK del master
|
|
9737
|
+
// (convention WUIC: `cities.StateProvinceID` punta a `stateprovinces.StateProvinceID`);
|
|
9738
|
+
// 3) backward-compat: prima colonna del detail (vecchio comportamento,
|
|
9739
|
+
// produce filtro sbagliato ma evita undefined).
|
|
9740
|
+
const fkFromLookup = detailDatasource
|
|
9741
|
+
? this.findDetailFkColumnToMasterRoute(detailDatasource, masterRoute)
|
|
9742
|
+
: null;
|
|
9743
|
+
const fkFromNameMatch = !fkFromLookup && masterPrimaryKey && detailColumns.includes(masterPrimaryKey)
|
|
9744
|
+
? masterPrimaryKey
|
|
9745
|
+
: null;
|
|
9746
|
+
const detailFkField = fkFromLookup || fkFromNameMatch || (detailColumns.length ? detailColumns[0] : '');
|
|
9747
|
+
const defaultFormula = detailFkField
|
|
9748
|
+
? `// Detail columns (use one as first argument of setFilter):\n${detailColumnsHint}\nsetFilter('${detailFkField.replace(/'/g, "\\'")}', ${masterPkValueExpression}, 'eq');`
|
|
8800
9749
|
: `// Detail columns (use one as first argument of setFilter):\n${detailColumnsHint}\n// Example:\n// setFilter('detail_fk', ${masterPkValueExpression}, 'eq');`;
|
|
8801
9750
|
const result = await WtoolboxService.promptDialog('Master-Detail Filter Formula', [
|
|
8802
9751
|
{
|
|
@@ -10791,12 +11740,25 @@ Display formula context:
|
|
|
10791
11740
|
return true;
|
|
10792
11741
|
}
|
|
10793
11742
|
/**
|
|
10794
|
-
*
|
|
10795
|
-
|
|
10796
|
-
|
|
11743
|
+
* Helper condiviso: trova nella metadata del DETAIL la colonna lookup che
|
|
11744
|
+
* punta alla route MASTER. Usato sia da `injectMasterDetailLookupFilter`
|
|
11745
|
+
* (runtime fallback) sia da `openMasterDetailFilterFormulaEditor` (default
|
|
11746
|
+
* formula in UI editor) — cosi' i 2 path producono lo stesso risultato e
|
|
11747
|
+
* non c'e' drift tra "filtro automatico" e "formula proposta nel dialog".
|
|
11748
|
+
*
|
|
11749
|
+
* Logica:
|
|
11750
|
+
* 1) cerca una colonna `mc_ui_column_type=='lookupbyid'` con
|
|
11751
|
+
* `mc_ui_lookup_entity_name==<masterRoute>` OPPURE `mc_ui_grid_route==<masterRoute>`.
|
|
11752
|
+
* Questo e' il match canonico WUIC (FK lookup definita nei metadata).
|
|
11753
|
+
* 2) ritorna `null` se non c'e' match (il chiamante decide il fallback —
|
|
11754
|
+
* `injectMasterDetailLookupFilter` ritorna `false`,
|
|
11755
|
+
* `openMasterDetailFilterFormulaEditor` cade su `detailColumns[0]` per
|
|
11756
|
+
* backward-compat).
|
|
11757
|
+
*/
|
|
11758
|
+
findDetailFkColumnToMasterRoute(detailDatasource, masterRoute) {
|
|
10797
11759
|
const normalizedMasterRoute = String(masterRoute || '').trim().toLowerCase();
|
|
10798
11760
|
if (!normalizedMasterRoute || !detailDatasource?.metaInfo?.columnMetadata?.length) {
|
|
10799
|
-
return
|
|
11761
|
+
return null;
|
|
10800
11762
|
}
|
|
10801
11763
|
const lookupColumn = detailDatasource.metaInfo.columnMetadata.find((column) => {
|
|
10802
11764
|
if (!column || String(column.mc_ui_column_type || '').toLowerCase() !== 'lookupbyid') {
|
|
@@ -10806,7 +11768,14 @@ Display formula context:
|
|
|
10806
11768
|
const gridRoute = String(column.mc_ui_grid_route || '').trim().toLowerCase();
|
|
10807
11769
|
return lookupRoute === normalizedMasterRoute || gridRoute === normalizedMasterRoute;
|
|
10808
11770
|
});
|
|
10809
|
-
const lookupField = lookupColumn?.mc_nome_colonna;
|
|
11771
|
+
const lookupField = String(lookupColumn?.mc_nome_colonna || '').trim();
|
|
11772
|
+
return lookupField || null;
|
|
11773
|
+
}
|
|
11774
|
+
/**
|
|
11775
|
+
* Fallback master-detail basato su colonne lookup: applica filtro `eq` sulla colonna che punta alla route master.
|
|
11776
|
+
*/
|
|
11777
|
+
injectMasterDetailLookupFilter(detailDatasource, masterRoute, masterRowId) {
|
|
11778
|
+
const lookupField = this.findDetailFkColumnToMasterRoute(detailDatasource, masterRoute);
|
|
10810
11779
|
if (!lookupField) {
|
|
10811
11780
|
return false;
|
|
10812
11781
|
}
|
|
@@ -10858,13 +11827,13 @@ Display formula context:
|
|
|
10858
11827
|
});
|
|
10859
11828
|
// return customProps;
|
|
10860
11829
|
}
|
|
10861
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: DesignerComponent, deps: [{ token: i1$1.ActivatedRoute }, { token: DataProviderService }, { token: MetadataProviderService }, { token: UserInfoService }, { token: i0.ChangeDetectorRef }, { token: TranslationManagerService }, { token: MetadataEditorService }, { token: WorkflowRuntimeMetadataService }], target: i0.ɵɵFactoryTarget.Component });
|
|
10862
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: DesignerComponent, isStandalone: true, selector: "wuic-designer", host: { listeners: { "window:hashchange": "onWindowHashChange()", "window:beforeunload": "onWindowBeforeUnload($event)", "window:keydown": "onWindowKeydown($event)", "window:mouseup": "onWindowMouseup()", "window:touchend": "onWindowTouchend()", "window:keyup": "onWindowKeyup()", "window:change": "onWindowChange()" } }, providers: [TreeDragDropService], ngImport: i0, template: "<!-- <button pButton type=\"button\" (click)=\"loadDashboard()\" class=\"mr-2\">{{'load' | translate}}</button> -->\n@if (!noEdit) {\n<div class=\"designer-layout\">\n <div class=\"designer-toolbar\">\n <div class=\"designer-current-dashboard\">{{ currentDashboardTitle }}</div>\n\n <div class=\"designer-toolbar-spacer\"></div>\n\n <div class=\"designer-toolbar-menu-group\">\n <button type=\"button\" (click)=\"undo()\" [disabled]=\"!canUndo\">{{ 'designer.undo' | translate }}</button>\n </div>\n\n <div class=\"designer-toolbar-menu-group\">\n <button type=\"button\" (click)=\"redo()\" [disabled]=\"!canRedo\">{{ 'designer.redo' | translate }}</button>\n </div>\n\n <div class=\"designer-toolbar-menu-group\">\n <button type=\"button\" (click)=\"refreshGraphActionsMenuItems(); graphActionsMenu.toggle($event)\">{{ 'designer.actions_menu' | translate }}</button>\n <p-menu #graphActionsMenu [popup]=\"true\" [model]=\"graphActionsMenuItems\" appendTo=\"body\"></p-menu>\n </div>\n\n <div class=\"designer-toolbar-menu-group\">\n <button type=\"button\" (click)=\"openCurrentDashboardInNewTab()\" [disabled]=\"!currentDashboardRoute\">{{\n 'designer.open_current_dashboard' | translate }}</button>\n </div>\n </div>\n <div class=\"designer-main\">\n <p-splitter [style]=\"{ height: '100%' }\" [panelSizes]=\"[15, 85]\" styleClass=\"designer-main-splitter\">\n <ng-template pTemplate>\n <p-splitter layout=\"vertical\" [style]=\"{ height: '100%', width: '100%' }\" [panelSizes]=\"[35, 65]\"\n [minSizes]=\"[20, 30]\" styleClass=\"designer-left-splitter\">\n <ng-template pTemplate>\n <div class=\"col flex designer-toolbox palette-panel\">\n <ul class=\"tool-groups\">\n @for (toolGroup of availableToolGroups; track toolGroup.group) {\n <li class=\"tool-group-item\">\n <div class=\"tool-group-title\">{{ toolGroup.group }}</div>\n <ul class=\"tool-group-list\">\n @for (tool of toolGroup.tools; track tool) {\n <li pDraggable (onDragStart)=\"dragStart(tool)\" (onDragEnd)=\"dragEnd()\" (onDrag)=\"onDrag($event)\"\n (dblclick)=\"dropToolAtRoot(tool)\">\n <i [ngClass]=\"tool.icon\" [title]=\"tool.name\"></i>\n </li>\n }\n </ul>\n </li>\n }\n </ul>\n </div>\n </ng-template>\n <ng-template pTemplate>\n <p-splitter layout=\"vertical\" [style]=\"{ height: '100%', width: '100%' }\" [panelSizes]=\"[68, 32]\"\n [minSizes]=\"[35, 20]\" styleClass=\"designer-left-bottom-splitter\">\n <ng-template pTemplate>\n <div class=\"properties-panel\">\n <p-select [style]=\"{width: '100%'}\" [options]=\"flattenedDashboardElements\" [(ngModel)]=\"tool\"\n optionLabel=\"uniqueName\" dataKey=\"componentId\" appendTo=\"body\" />\n <div class=\"designer-toolbox\">\n\n <span style=\"width: 100%;\">\n <input type=\"text\" style=\"width: calc(100% - 30px);\" [(ngModel)]=\"propFilter\"\n [placeholder]=\"'prop.filter' | translate\" />\n <button style=\"width: 25px\" (click)=\"clearPropFilter()\">X</button>\n </span>\n\n @for (section of getToolPropertySections(tool, propFilter); track section.key) {\n <details class=\"prop-section\" [open]=\"section.open\"\n (toggle)=\"onPropertySectionToggle(tool, section.key, $event)\">\n <summary class=\"prop-section-title\">{{ section.label }}</summary>\n <ul class=\"props\">\n @for (toolProp of section.props; track toolProp.key) {\n <li>\n @if (!toolProp.value.hideCaption) {\n <span>{{ toolProp.value?.propertyCaption ||\n toolProp.key[0].toUpperCase() + toolProp.key.slice(1)\n }}</span>\n }\n <br />\n @if (toolProp.value.type == 'button') {\n <p-button (click)=\"toolProp.value.callback(tool.inputs, toolProp.key, null, tool)\"\n class=\"k-button\" [style]=\"{width: '100%'}\">\n {{toolProp.value.propertyCaption}}\n </p-button>\n }\n @if (toolProp.value.type == 'metaEditor' &&\n tool.inputs['componentRef']?.value?.component) {\n <wuic-metadata-editor [hardcodedDatasource]=\"tool.inputs['componentRef']?.value?.component\"\n [saveCallback]=\"metaEditorSave\"></wuic-metadata-editor>\n }\n @if (toolProp.value.type == 'boolean') {\n <input type=\"checkbox\" [(ngModel)]=\"tool.inputs[toolProp.key]\" />\n }\n @if (toolProp.value.type == 'numberToArray') {\n <input style=\"width: 100%;\" type=\"number\" pInputText [ngModel]=\"tool.inputs[toolProp.key]\"\n (ngModelChange)=\"setValue($event, tool, toolProp)\" />\n }\n @if (toolProp.value.type == 'txt_area') {\n <textarea style=\"width: 100%;\" [(ngModel)]=\"tool.inputs[toolProp.key]\"\n [ngModelOptions]=\"{ updateOn: 'blur' }\"\n (ngModelChange)=\"setValue($event, tool, toolProp)\"></textarea>\n }\n @if (toolProp.value.type == 'string') {\n <input style=\"width: 100%;\" type=\"text\" pInputText [(ngModel)]=\"tool.inputs[toolProp.key]\"\n (ngModelChange)=\"setValue($event, tool, toolProp)\" />\n }\n @if (toolProp.value.type == 'color') {\n <p-colorPicker [ngModel]=\"getDesignerColorPickerHex(tool, toolProp)\"\n (ngModelChange)=\"onDesignerColorPickerHexChange($event, tool, toolProp)\" appendTo=\"body\" />\n <div class=\"designer-color-alpha-row\">\n <span class=\"designer-color-alpha-label\">{{ 'designer.alpha' | translate }}</span>\n <input type=\"range\" min=\"0\" max=\"1\" step=\"0.01\"\n [ngModel]=\"getDesignerColorAlpha(tool, toolProp)\"\n (ngModelChange)=\"onDesignerColorAlphaChange($event, tool, toolProp)\" />\n <input type=\"number\" min=\"0\" max=\"1\" step=\"0.01\"\n [ngModel]=\"getDesignerColorAlpha(tool, toolProp)\"\n (ngModelChange)=\"onDesignerColorAlphaChange($event, tool, toolProp)\" />\n </div>\n <input type=\"text\" pInputText [(ngModel)]=\"tool.inputs[toolProp.key]\"\n (ngModelChange)=\"setValue($event, tool, toolProp)\" />\n }\n @if (toolProp.value.type == 'dictionary' && !toolProp.value.async) {\n @if (toolProp.key == 'cssFile') {\n <p-select [style]=\"{width: '100%'}\" appendTo=\"body\" [options]=\"availableProjectCssFiles\"\n [ngModel]=\"tool.inputs[toolProp.key]\" (ngModelChange)=\"onCssFileChanged($event, tool)\" />\n } @else if (toolProp.key == 'cssClass') {\n <p-multiSelect [style]=\"{width: '100%'}\" appendTo=\"body\"\n [options]=\"getCssClassOptionsForTool(tool)\" [ngModel]=\"tool.inputs[toolProp.key]\"\n (ngModelChange)=\"onCssClassChanged($event, tool)\" [filter]=\"true\" [virtualScroll]=\"true\"\n [virtualScrollItemSize]=\"38\" scrollHeight=\"260px\" [showToggleAll]=\"false\" display=\"chip\"\n [maxSelectedLabels]=\"3\" [selectedItemsLabel]=\"'{0} selected'\">\n </p-multiSelect>\n } @else if ((tool.name == 'SELECT' || tool.name == 'MULTISELECT' || tool.name == 'UL') &&\n (toolProp.key == 'valueField' || toolProp.key == 'textField')) {\n <p-select [style]=\"{width: '100%'}\" appendTo=\"body\"\n [options]=\"getDatasourceColumnOptions(tool)\" [ngModel]=\"tool.inputs[toolProp.key]\"\n (ngModelChange)=\"setValue($event, tool, toolProp)\" />\n } @else if (tool.name == 'LABEL' && toolProp.key == 'displayField') {\n <p-select [style]=\"{width: '100%'}\" appendTo=\"body\"\n [options]=\"getDatasourceColumnOptions(tool)\" [ngModel]=\"tool.inputs[toolProp.key]\"\n (ngModelChange)=\"setValue($event, tool, toolProp)\" />\n } @else {\n <p-select [style]=\"{width: '100%'}\" appendTo=\"body\" [options]=\"toolProp.value.values\"\n [(ngModel)]=\"tool.inputs[toolProp.key]\"\n (ngModelChange)=\"setValue($event, tool, toolProp)\" />\n }\n }\n @if (toolProp.value.type == 'dictionary' && toolProp.value.async) {\n <p-select [style]=\"{width: '100%'}\" appendTo=\"body\" [options]=\"toolProp.value.values\"\n [ngModel]=\"tool.inputs[toolProp.key] | async\"\n (ngModelChange)=\"setValue($event, tool, toolProp)\" />\n }\n @if (tool.name == 'DATAREPEATER' && toolProp.key == 'action' &&\n canOpenArchetypeConfigurator(tool)) {\n <button style=\"margin-top: 8px;\" (click)=\"openArchetypeConfigurator(tool)\">\n {{ 'designer.configure' | translate }} {{ (tool.inputs['action'] | async) || '' }}\n </button>\n }\n @if (toolProp.value.type == 'dropped-component-list') {\n <p-select [style]=\"{width: '100%'}\" appendTo=\"body\"\n [placeholder]=\"'select.component' | translate\" [options]=\"elementFilter(toolProp)\"\n [ngModel]=\"getDroppedComponentListModel(tool, toolProp)\"\n (ngModelChange)=\"setValue($event, tool, toolProp)\" optionLabel=\"uniqueName\"\n dataKey=\"uniqueName\" />\n }\n @if (toolProp.value.type == 'autocomplete') {\n <p-autoComplete [style]=\"{width: '100%'}\" appendTo=\"body\"\n [ngModel]=\"tool.inputs[toolProp.key] | async\" [forceSelection]=\"true\"\n (ngModelChange)=\"setValue($event, tool, toolProp)\" [suggestions]=\"tool.suggestions\"\n [optionLabel]=\"toolProp.value.displayField\" [optionValue]=\"toolProp.value.valueField\"\n (completeMethod)=\"search($event, tool, toolProp)\" [dropdown]=\"true\" dropdownMode=\"current\"\n [multiple]=\"toolProp.value.multiple\" />\n }\n @if (toolProp.value.type == 'propertyTree' && shouldShowPropertyTree(tool)) {\n <ul>\n @for (item of $any(tool.inputs[toolProp.key] | async); track item) {\n <li style=\"padding: 0; margin: 0;\">\n @if (item.col && tool.inputs['customProps_' + (tool.inputs['action'] | async)])\n {\n <wuic-field-editor [field]=\"item.col\"\n [record]=\"tool.inputs['customProps_' + (tool.inputs['action'] | async)]\"\n [metaInfo]=\"tool.inputs['datasource']?.component?.value?.metaInfo\"\n [triggerProp]=\"tool.inputs['action']\"></wuic-field-editor>\n }\n </li>\n }\n </ul>\n }\n </li>\n }\n </ul>\n </details>\n }\n </div>\n </div>\n </ng-template>\n <ng-template pTemplate>\n <div class=\"hierarchy-panel\">\n <div class=\"hierarchy-panel-title\">{{ 'designer.canvas_hierarchy' | translate }}</div>\n <div class=\"hierarchy-tree-wrap\">\n <p-tree [value]=\"hierarchyTreeNodes\" [draggableNodes]=\"true\" [droppableNodes]=\"true\"\n selectionMode=\"single\" [(selection)]=\"hierarchySelection\"\n (onNodeSelect)=\"onHierarchyNodeSelect($event)\" draggableScope=\"designer-hierarchy\"\n droppableScope=\"designer-hierarchy\" (onNodeDrop)=\"onHierarchyNodeDrop($event)\">\n <ng-template let-node pTemplate=\"default\">\n <span class=\"hierarchy-node-label\" [class.active]=\"isHierarchyNodeSelected(node)\"\n (mouseenter)=\"onHierarchyNodeMouseEnter(node)\" (mouseleave)=\"onHierarchyNodeMouseLeave()\"\n (contextmenu)=\"showHierarchyNodeContextMenu($event, node, hierarchyCtxMenu)\">\n {{ node.label }}\n </span>\n </ng-template>\n </p-tree>\n </div>\n <p-contextMenu #hierarchyCtxMenu [model]=\"hierarchyCtxItems\" appendTo=\"body\" />\n </div>\n </ng-template>\n </p-splitter>\n </ng-template>\n </p-splitter>\n </ng-template>\n <ng-template pTemplate>\n <div class=\"col dashboard-design\" [class.designer-hide-datasources]=\"hideDatasourcesInCanvas\" pDroppable\n (onDrop)=\"drop($event)\" #canvas (contextmenu)=\"showCtx($event, this)\">\n <wuic-dashboard [dashboardElements]=\"dashboardElements\"></wuic-dashboard>\n <p-contextMenu [target]=\"canvas\" [model]=\"ctxItems | async\" appendTo=\"body\" />\n </div>\n </ng-template>\n </p-splitter>\n </div>\n <div class=\"designer-footer\">\n <div class=\"designer-footer-label\">{{ 'designer.items' | translate }}:</div>\n <div class=\"designer-footer-list\">\n @for (item of droppedDashboardItems; track item.uniqueName) {\n <button type=\"button\" class=\"designer-footer-item\" [class.active]=\"tool?.uniqueName === item?.uniqueName\"\n [attr.data-designer-unique-name]=\"item?.uniqueName\" (mouseenter)=\"setFooterHoverHighlight(item)\"\n (mouseleave)=\"clearFooterHoverHighlight()\" (click)=\"selectDashboardItem(item)\"\n (contextmenu)=\"showFooterContextMenu($event, item, footerCtxMenu)\">\n {{ getDroppedDashboardItemLabel(item) }}\n </button>\n }\n </div>\n <p-contextMenu #footerCtxMenu [model]=\"footerCtxItems\" appendTo=\"body\" />\n </div>\n</div>\n}\n\n@if (noEdit) {\n<wuic-dashboard [dashboardElements]=\"dashboardElements\"></wuic-dashboard>\n}\n\n<wuic-archetype-configurator [visible]=\"showArchetypeConfigurator\"\n (visibleChange)=\"onArchetypeConfigVisibleChange($event)\" [archetype]=\"getActiveArchetypeConfig()\"\n [metaInfo]=\"getArchetypeConfigMetaInfoForTool(archetypeConfigTool)\"\n [value]=\"getArchetypeConfigValueForTool(archetypeConfigTool)\" (applyConfig)=\"onArchetypeConfigApply($event)\">\n</wuic-archetype-configurator>\n\n<wuic-css-sheet-editor [visible]=\"showCssSheetEditor\" (visibleChange)=\"onCssSheetEditorVisibleChange($event)\"\n [sheets]=\"dashboardCssSheets\" [selectedSheetPath]=\"selectedCssEditorSheetPath\" [assetsFolder]=\"currentDashboardRoute\"\n [styleProps]=\"getCssEditorStyleProps()\" (applyEditor)=\"onCssSheetEditorApply($event)\">\n</wuic-css-sheet-editor>\n\n<p-dialog [header]=\"'designer.reopen_dashboard' | translate\" [(visible)]=\"reopenDashboardDialogVisible\" [modal]=\"true\"\n [closable]=\"true\" [draggable]=\"false\" [resizable]=\"false\" [appendTo]=\"'body'\" [style]=\"{ width: '34rem' }\"\n (onHide)=\"cancelReopenDashboardDialog()\">\n <div class=\"route-popup-form\">\n <label for=\"designer-reopen-select\">{{ 'designer.existing_dashboards' | translate }}</label>\n <p-select inputId=\"designer-reopen-select\" [options]=\"savedDashboardOptions\" optionLabel=\"label\" optionValue=\"value\"\n [(ngModel)]=\"reopenDialogSelectedDashboardRoute\" [filter]=\"true\" [showClear]=\"true\" [style]=\"{ width: '100%' }\"\n appendTo=\"body\" />\n\n <div class=\"route-popup-actions\">\n <button type=\"button\" class=\"route-popup-btn\" (click)=\"cancelReopenDashboardDialog()\">{{ 'cancel' | translate\n }}</button>\n <button type=\"button\" class=\"route-popup-btn route-popup-btn-primary\" (click)=\"confirmReopenDashboardDialog()\"\n [disabled]=\"!reopenDialogSelectedDashboardRoute\">{{ 'designer.reopen' | translate }}</button>\n </div>\n </div>\n</p-dialog>", styles: [":host{display:block;width:100%;height:100%;min-width:0}.designer-layout{display:grid;grid-template-rows:auto minmax(0,1fr) auto;height:100%;min-height:0;width:100%;min-width:0}.designer-toolbar{display:flex;align-items:center;flex-wrap:wrap;gap:8px;border:1px solid inherit;border-radius:10px;background:transparent;padding:10px 12px;margin-bottom:8px}.designer-current-dashboard{min-width:220px;font-size:.86rem;font-weight:700;color:inherit}.designer-toolbar-spacer{flex:1 1 auto;min-width:16px}.designer-toolbar-menu-group{display:inline-flex}.designer-toolbar-menu-group button{height:32px;border:1px solid #2f67d8;border-radius:6px;background:#2f67d8;color:#fff;padding:0 10px;font-size:.82rem;cursor:pointer}.designer-toolbar-menu-group button[disabled]{opacity:.6;cursor:not-allowed}.designer-main{min-height:0;min-width:0;width:100%;overflow:hidden;direction:ltr}.designer-main-splitter{height:100%;width:100%;min-width:0;direction:ltr}.designer-toolbox{font-size:3rem}.designer-toolbox ul{list-style-type:none;padding:0;margin:0;width:100%}.designer-toolbox li{float:left;margin-left:5px;margin-top:5px}.designer-toolbox .tool-groups{display:block}.designer-toolbox .tool-group-item{float:none;margin-left:0;margin-top:10px;clear:both}.designer-toolbox .tool-group-title{font-size:.8rem;font-weight:600;text-transform:uppercase;letter-spacing:.04em;opacity:.7;margin:0 0 4px 6px}.designer-toolbox .tool-group-list{display:flex;flex-wrap:wrap;align-items:flex-start;gap:4px}.designer-toolbox .tool-group-list li{float:none;margin-left:0;margin-top:0}.designer-toolbox .props{font-size:1rem}.designer-toolbox .props li{float:none;padding-bottom:15px}.designer-toolbox .prop-section{width:100%;border:1px solid inherit;border-radius:6px;margin-top:8px;background:transparent;padding:6px 8px 2px}.designer-toolbox .prop-section-title{font-size:.82rem;font-weight:600;text-transform:uppercase;letter-spacing:.04em;cursor:pointer;color:inherit;margin-bottom:6px}.palette-panel{height:100%;overflow:auto;width:100%}.designer-color-alpha-row{display:flex;align-items:center;gap:8px;margin-top:6px;margin-bottom:6px}.designer-color-alpha-label{font-size:.8rem;min-width:40px}.designer-color-alpha-row input[type=range]{flex:1 1 auto;min-width:90px}.designer-color-alpha-row input[type=number]{width:72px}.properties-panel{height:100%;overflow:hidden;width:100%;display:flex;flex-direction:column;min-height:0;padding-right:10px;padding-left:10px;padding-top:10px}.properties-panel>.designer-toolbox{flex:1 1 auto;min-height:0;overflow-y:auto;overflow-x:hidden;scrollbar-gutter:stable}:host ::ng-deep .designer-left-bottom-splitter .p-splitterpanel{min-height:0!important;overflow:hidden!important}.hierarchy-panel{height:100%;width:100%;min-height:0;display:flex;flex-direction:column;overflow:hidden;padding:6px}.hierarchy-panel-title{font-size:.82rem;font-weight:600;text-transform:uppercase;letter-spacing:.04em;color:#4b5563;margin:0 0 6px}.hierarchy-tree-wrap{flex:1 1 auto;min-height:0;border:1px solid inherit;border-radius:6px;background:transparent;overflow:auto;padding:4px}:host ::ng-deep .hierarchy-tree-wrap .p-tree{border:none;padding:0;height:100%;background:transparent}:host ::ng-deep .hierarchy-tree-wrap .p-tree-node-content{border:1px solid transparent;border-radius:4px;background:transparent!important;color:inherit!important;padding:0!important}:host ::ng-deep .hierarchy-tree-wrap .p-tree-node-content.p-highlight{border-color:transparent!important;background:transparent!important;color:inherit!important}:host ::ng-deep .hierarchy-tree-wrap .p-tree-node-content:hover{background:transparent!important;color:inherit!important}:host ::ng-deep .hierarchy-tree-wrap .hierarchy-node-label{display:inline-block;border:1px solid inherit;background:transparent;color:inherit;border-radius:4px;padding:2px 8px;font-size:.8rem;line-height:1.25rem;cursor:pointer}:host ::ng-deep .hierarchy-tree-wrap .hierarchy-node-label:hover{border-color:#94a3b8}:host ::ng-deep .hierarchy-tree-wrap .hierarchy-node-label.active{border-color:#3b82f6!important;background:#eff6ff!important;color:#1d4ed8!important}:host ::ng-deep .hierarchy-tree-wrap .p-tree-node-content.p-highlight .hierarchy-node-label{border-color:#3b82f6!important;background:#eff6ff!important;color:#1d4ed8!important}.designer-hierarchy-node{cursor:pointer;border:1px solid transparent;border-radius:4px;padding:2px 6px;font-size:.82rem;line-height:1.25rem}.designer-hierarchy-node-label{cursor:pointer;display:inline-block;width:100%}.designer-hierarchy-node.active{border-color:#3b82f6;background:#eff6ff;color:#1d4ed8}.designer-footer-item.designer-tree-hover-target{border-color:#3b82f6!important;outline:1px solid #3b82f6!important;outline-offset:-1px}.dashboard-design{height:100%;min-height:0;min-width:0;width:100%;overflow:auto;direction:ltr}:host ::ng-deep .dashboard-design.designer-hide-datasources .datasource-descriptor{display:none!important}.current-dashboard-title{font-weight:600;margin-right:12px}.designer-footer{display:flex;align-items:center;gap:8px;padding:6px 8px;border-top:1px solid inherit;background:transparent;min-height:36px;flex:0 0 auto;width:100%;overflow:auto}.designer-footer-label{font-size:.85rem;font-weight:600;color:inherit;white-space:nowrap}.designer-footer-list{display:flex;align-items:center;gap:6px;overflow-x:auto;overflow-y:hidden;white-space:nowrap;width:100%}.designer-footer-item{border:1px solid inherit;background:transparent;color:inherit;border-radius:4px;padding:2px 8px;font-size:.8rem;line-height:1.25rem;cursor:pointer;white-space:nowrap}.designer-footer-item.active{border-color:#3b82f6;background:transparent;color:#1d4ed8}:host ::ng-deep .designer-footer-hover-target{border-color:#3b82f6!important;outline:1px solid #3b82f6!important;outline-offset:-1px;box-shadow:inset 0 0 0 1px #3b82f6!important}.route-popup-form{display:flex;flex-direction:column;gap:8px}.route-popup-form label{font-size:.85rem;font-weight:600;color:#2f3f58}.route-popup-actions{display:flex;justify-content:flex-end;gap:8px;margin-top:10px}.route-popup-btn{border:1px solid inherit;background:transparent;color:inherit;border-radius:6px;padding:7px 12px;font-size:.85rem;cursor:pointer}.route-popup-btn-primary{border-color:#2f67d8;background:#2f67d8;color:#fff}.p-colorpicker{border:1px solid black;border-radius:4px}\n"], dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type: TranslateModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i1.RangeValueAccessor, selector: "input[type=range][formControlName],input[type=range][formControl],input[type=range][ngModel]" }, { kind: "directive", type: i1.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.MinValidator, selector: "input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]", inputs: ["min"] }, { kind: "directive", type: i1.MaxValidator, selector: "input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]", inputs: ["max"] }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: SplitterModule }, { kind: "component", type: i9.Splitter, selector: "p-splitter", inputs: ["styleClass", "panelStyleClass", "panelStyle", "stateStorage", "stateKey", "layout", "gutterSize", "step", "minSizes", "panelSizes"], outputs: ["onResizeEnd", "onResizeStart"] }, { kind: "directive", type: i3.PrimeTemplate, selector: "[pTemplate]", inputs: ["type", "pTemplate"] }, { kind: "ngmodule", type: DragDropModule }, { kind: "directive", type: i11.Draggable, selector: "[pDraggable]", inputs: ["pDraggable", "dragEffect", "dragHandle", "pDraggableDisabled"], outputs: ["onDragStart", "onDragEnd", "onDrag"] }, { kind: "directive", type: i11.Droppable, selector: "[pDroppable]", inputs: ["pDroppable", "pDroppableDisabled", "dropEffect"], outputs: ["onDragEnter", "onDragLeave", "onDrop"] }, { kind: "ngmodule", type: SelectModule }, { kind: "component", type: i6.Select, selector: "p-select", inputs: ["id", "scrollHeight", "filter", "panelStyle", "styleClass", "panelStyleClass", "readonly", "editable", "tabindex", "placeholder", "loadingIcon", "filterPlaceholder", "filterLocale", "inputId", "dataKey", "filterBy", "filterFields", "autofocus", "resetFilterOnHide", "checkmark", "dropdownIcon", "loading", "optionLabel", "optionValue", "optionDisabled", "optionGroupLabel", "optionGroupChildren", "group", "showClear", "emptyFilterMessage", "emptyMessage", "lazy", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "overlayOptions", "ariaFilterLabel", "ariaLabel", "ariaLabelledBy", "filterMatchMode", "tooltip", "tooltipPosition", "tooltipPositionStyle", "tooltipStyleClass", "focusOnHover", "selectOnFocus", "autoOptionFocus", "autofocusFilter", "filterValue", "options", "appendTo", "motionOptions"], outputs: ["onChange", "onFilter", "onFocus", "onBlur", "onClick", "onShow", "onHide", "onClear", "onLazyLoad"] }, { kind: "ngmodule", type: ColorPickerModule }, { kind: "component", type: i2$1.ColorPicker, selector: "p-colorPicker, p-colorpicker, p-color-picker", inputs: ["styleClass", "showTransitionOptions", "hideTransitionOptions", "inline", "format", "tabindex", "inputId", "autoZIndex", "autofocus", "defaultColor", "appendTo", "overlayOptions", "motionOptions"], outputs: ["onChange", "onShow", "onHide"] }, { kind: "ngmodule", type: AutoCompleteModule }, { kind: "component", type: i2$2.AutoComplete, selector: "p-autoComplete, p-autocomplete, p-auto-complete", inputs: ["minLength", "minQueryLength", "delay", "panelStyle", "styleClass", "panelStyleClass", "inputStyle", "inputId", "inputStyleClass", "placeholder", "readonly", "scrollHeight", "lazy", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "autoHighlight", "forceSelection", "type", "autoZIndex", "baseZIndex", "ariaLabel", "dropdownAriaLabel", "ariaLabelledBy", "dropdownIcon", "unique", "group", "completeOnFocus", "showClear", "dropdown", "showEmptyMessage", "dropdownMode", "multiple", "addOnTab", "tabindex", "dataKey", "emptyMessage", "showTransitionOptions", "hideTransitionOptions", "autofocus", "autocomplete", "optionGroupChildren", "optionGroupLabel", "overlayOptions", "suggestions", "optionLabel", "optionValue", "id", "searchMessage", "emptySelectionMessage", "selectionMessage", "autoOptionFocus", "selectOnFocus", "searchLocale", "optionDisabled", "focusOnHover", "typeahead", "addOnBlur", "separator", "appendTo", "motionOptions"], outputs: ["completeMethod", "onSelect", "onUnselect", "onAdd", "onFocus", "onBlur", "onDropdownClick", "onClear", "onInputKeydown", "onKeyUp", "onShow", "onHide", "onLazyLoad"] }, { kind: "component", type: DashboardComponent, selector: "wuic-dashboard", inputs: ["dashboardElements"] }, { kind: "component", type: FieldEditorComponent, selector: "wuic-field-editor", inputs: ["datasource", "record", "field", "metaInfo", "readOnly", "isFilter", "hideLabel", "forceShowLabel", "operator", "nestedIndex", "triggerProp", "tabIndex", "onInlineCellValueChange"] }, { kind: "ngmodule", type: ContextMenuModule }, { kind: "component", type: i14.ContextMenu, selector: "p-contextMenu, p-contextmenu, p-context-menu", inputs: ["model", "triggerEvent", "target", "global", "style", "styleClass", "autoZIndex", "baseZIndex", "id", "breakpoint", "ariaLabel", "ariaLabelledBy", "pressDelay", "appendTo", "motionOptions"], outputs: ["onShow", "onHide"] }, { kind: "component", type: MetadataEditorComponent, selector: "wuic-metadata-editor", inputs: ["datasource", "hardcodedDatasource", "saveCallback", "hideReports", "hideRelatedTableActions", "hideRelatedColumnActions"] }, { kind: "ngmodule", type: MenubarModule }, { kind: "component", type: ArchetypeConfiguratorComponent, selector: "wuic-archetype-configurator", inputs: ["visible", "archetype", "metaInfo", "value"], outputs: ["visibleChange", "applyConfig"] }, { kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i4.Button, selector: "p-button", inputs: ["hostName", "type", "badge", "disabled", "raised", "rounded", "text", "plain", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "iconPos", "icon", "label", "loading", "loadingIcon", "severity", "buttonProps", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "ngmodule", type: MultiSelectModule }, { kind: "component", type: i17.MultiSelect, selector: "p-multiSelect, p-multiselect, p-multi-select", inputs: ["id", "ariaLabel", "styleClass", "panelStyle", "panelStyleClass", "inputId", "readonly", "group", "filter", "filterPlaceHolder", "filterLocale", "overlayVisible", "tabindex", "dataKey", "ariaLabelledBy", "displaySelectedLabel", "maxSelectedLabels", "selectionLimit", "selectedItemsLabel", "showToggleAll", "emptyFilterMessage", "emptyMessage", "resetFilterOnHide", "dropdownIcon", "chipIcon", "optionLabel", "optionValue", "optionDisabled", "optionGroupLabel", "optionGroupChildren", "showHeader", "filterBy", "scrollHeight", "lazy", "virtualScroll", "loading", "virtualScrollItemSize", "loadingIcon", "virtualScrollOptions", "overlayOptions", "ariaFilterLabel", "filterMatchMode", "tooltip", "tooltipPosition", "tooltipPositionStyle", "tooltipStyleClass", "autofocusFilter", "display", "autocomplete", "showClear", "autofocus", "placeholder", "options", "filterValue", "selectAll", "focusOnHover", "filterFields", "selectOnFocus", "autoOptionFocus", "highlightOnSelect", "size", "variant", "fluid", "appendTo", "motionOptions"], outputs: ["onChange", "onFilter", "onFocus", "onBlur", "onClick", "onClear", "onPanelShow", "onPanelHide", "onLazyLoad", "onRemove", "onSelectAllChange"] }, { kind: "component", type: CssSheetEditorComponent, selector: "wuic-css-sheet-editor", inputs: ["visible", "sheets", "selectedSheetPath", "styleProps", "assetsFolder"], outputs: ["visibleChange", "applyEditor"] }, { kind: "ngmodule", type: MenuModule }, { kind: "component", type: i9$1.Menu, selector: "p-menu", inputs: ["model", "popup", "style", "styleClass", "autoZIndex", "baseZIndex", "showTransitionOptions", "hideTransitionOptions", "ariaLabel", "ariaLabelledBy", "id", "tabindex", "appendTo", "motionOptions"], outputs: ["onShow", "onHide", "onBlur", "onFocus"] }, { kind: "ngmodule", type: DialogModule }, { kind: "component", type: i2.Dialog, selector: "p-dialog", inputs: ["hostName", "header", "draggable", "resizable", "contentStyle", "contentStyleClass", "modal", "closeOnEscape", "dismissableMask", "rtl", "closable", "breakpoints", "styleClass", "maskStyleClass", "maskStyle", "showHeader", "blockScroll", "autoZIndex", "baseZIndex", "minX", "minY", "focusOnShow", "maximizable", "keepInViewport", "focusTrap", "transitionOptions", "maskMotionOptions", "motionOptions", "closeIcon", "closeAriaLabel", "closeTabindex", "minimizeIcon", "maximizeIcon", "closeButtonProps", "maximizeButtonProps", "visible", "style", "position", "role", "appendTo", "content", "contentTemplate", "footerTemplate", "closeIconTemplate", "maximizeIconTemplate", "minimizeIconTemplate", "headlessTemplate"], outputs: ["onShow", "onHide", "visibleChange", "onResizeInit", "onResizeEnd", "onDragEnd", "onMaximize"] }, { kind: "ngmodule", type: TreeModule }, { kind: "component", type: i4$1.Tree, selector: "p-tree", inputs: ["value", "selectionMode", "loadingMode", "selection", "styleClass", "contextMenu", "contextMenuSelectionMode", "contextMenuSelection", "draggableScope", "droppableScope", "draggableNodes", "droppableNodes", "metaKeySelection", "propagateSelectionUp", "propagateSelectionDown", "loading", "loadingIcon", "emptyMessage", "ariaLabel", "togglerAriaLabel", "ariaLabelledBy", "validateDrop", "filter", "filterInputAutoFocus", "filterBy", "filterMode", "filterOptions", "filterPlaceholder", "filteredNodes", "filterLocale", "scrollHeight", "lazy", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "indentation", "_templateMap", "trackBy", "highlightOnSelect"], outputs: ["selectionChange", "contextMenuSelectionChange", "onNodeSelect", "onNodeUnselect", "onNodeExpand", "onNodeCollapse", "onNodeContextMenuSelect", "onNodeDoubleClick", "onNodeDrop", "onLazyLoad", "onScroll", "onScrollIndexChange", "onFilter"] }, { kind: "pipe", type: i8.TranslatePipe, name: "translate" }, { kind: "pipe", type: AsyncPipe, name: "async" }] });
|
|
11830
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: DesignerComponent, deps: [{ token: i1$1.ActivatedRoute }, { token: DataProviderService }, { token: MetadataProviderService }, { token: UserInfoService }, { token: i0.ChangeDetectorRef }, { token: TranslationManagerService }, { token: MetadataEditorService }, { token: ChatbotHostRegistryService }, { token: WorkflowRuntimeMetadataService }], target: i0.ɵɵFactoryTarget.Component });
|
|
11831
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: DesignerComponent, isStandalone: true, selector: "wuic-designer", host: { listeners: { "window:hashchange": "onWindowHashChange()", "window:beforeunload": "onWindowBeforeUnload($event)", "window:keydown": "onWindowKeydown($event)", "window:mouseup": "onWindowMouseup()", "window:touchend": "onWindowTouchend()", "window:keyup": "onWindowKeyup()", "window:change": "onWindowChange()" } }, providers: [TreeDragDropService], ngImport: i0, template: "<!-- <button pButton type=\"button\" (click)=\"loadDashboard()\" class=\"mr-2\">{{'load' | translate}}</button> -->\n@if (!noEdit) {\n<div class=\"designer-layout\">\n <div class=\"designer-toolbar\">\n <div class=\"designer-current-dashboard\">{{ currentDashboardTitle }}</div>\n\n <div class=\"designer-toolbar-spacer\"></div>\n\n <div class=\"designer-toolbar-menu-group\">\n <button type=\"button\" (click)=\"undo()\" [disabled]=\"!canUndo\">{{ 'designer.undo' | translate }}</button>\n </div>\n\n <div class=\"designer-toolbar-menu-group\">\n <button type=\"button\" (click)=\"redo()\" [disabled]=\"!canRedo\">{{ 'designer.redo' | translate }}</button>\n </div>\n\n <div class=\"designer-toolbar-menu-group\">\n <button type=\"button\" (click)=\"refreshGraphActionsMenuItems(); graphActionsMenu.toggle($event)\">{{ 'designer.actions_menu' | translate }}</button>\n <p-menu #graphActionsMenu [popup]=\"true\" [model]=\"graphActionsMenuItems\" appendTo=\"body\"></p-menu>\n </div>\n\n <div class=\"designer-toolbar-menu-group\">\n <button type=\"button\" (click)=\"openCurrentDashboardInNewTab()\" [disabled]=\"!currentDashboardRoute\">{{\n 'designer.open_current_dashboard' | translate }}</button>\n </div>\n </div>\n <div class=\"designer-main\">\n <p-splitter [style]=\"{ height: '100%' }\" [panelSizes]=\"[15, 85]\" styleClass=\"designer-main-splitter\">\n <ng-template pTemplate>\n <p-splitter layout=\"vertical\" [style]=\"{ height: '100%', width: '100%' }\" [panelSizes]=\"[35, 65]\"\n [minSizes]=\"[20, 30]\" styleClass=\"designer-left-splitter\">\n <ng-template pTemplate>\n <div class=\"col flex designer-toolbox palette-panel\">\n <ul class=\"tool-groups\">\n @for (toolGroup of availableToolGroups; track toolGroup.group) {\n <li class=\"tool-group-item\">\n <div class=\"tool-group-title\">{{ toolGroup.group }}</div>\n <ul class=\"tool-group-list\">\n @for (tool of toolGroup.tools; track tool) {\n <li pDraggable (onDragStart)=\"dragStart(tool)\" (onDragEnd)=\"dragEnd()\" (onDrag)=\"onDrag($event)\"\n (dblclick)=\"dropToolAtRoot(tool)\">\n <i [ngClass]=\"tool.icon\" [title]=\"tool.name\"></i>\n </li>\n }\n </ul>\n </li>\n }\n </ul>\n </div>\n </ng-template>\n <ng-template pTemplate>\n <p-splitter layout=\"vertical\" [style]=\"{ height: '100%', width: '100%' }\" [panelSizes]=\"[68, 32]\"\n [minSizes]=\"[35, 20]\" styleClass=\"designer-left-bottom-splitter\">\n <ng-template pTemplate>\n <div class=\"properties-panel\">\n <p-select [style]=\"{width: '100%'}\" [options]=\"flattenedDashboardElements\" [(ngModel)]=\"tool\"\n optionLabel=\"uniqueName\" dataKey=\"componentId\" appendTo=\"body\" />\n <div class=\"designer-toolbox\">\n\n <span style=\"width: 100%;\">\n <input type=\"text\" style=\"width: calc(100% - 30px);\" [(ngModel)]=\"propFilter\"\n [placeholder]=\"'prop.filter' | translate\" />\n <button style=\"width: 25px\" (click)=\"clearPropFilter()\">X</button>\n </span>\n\n @for (section of getToolPropertySections(tool, propFilter); track section.key) {\n <details class=\"prop-section\" [open]=\"section.open\"\n (toggle)=\"onPropertySectionToggle(tool, section.key, $event)\">\n <summary class=\"prop-section-title\">{{ section.label }}</summary>\n <ul class=\"props\">\n @for (toolProp of section.props; track toolProp.key) {\n <li>\n @if (!toolProp.value.hideCaption) {\n <span>{{ toolProp.value?.propertyCaption ||\n toolProp.key[0].toUpperCase() + toolProp.key.slice(1)\n }}</span>\n }\n <br />\n @if (toolProp.value.type == 'button') {\n <p-button (click)=\"toolProp.value.callback(tool.inputs, toolProp.key, null, tool)\"\n class=\"k-button\" [style]=\"{width: '100%'}\">\n {{toolProp.value.propertyCaption}}\n </p-button>\n }\n @if (toolProp.value.type == 'metaEditor' &&\n tool.inputs['componentRef']?.value?.component) {\n <wuic-metadata-editor [hardcodedDatasource]=\"tool.inputs['componentRef']?.value?.component\"\n [saveCallback]=\"metaEditorSave\"></wuic-metadata-editor>\n }\n @if (toolProp.value.type == 'boolean') {\n <input type=\"checkbox\" [(ngModel)]=\"tool.inputs[toolProp.key]\" />\n }\n @if (toolProp.value.type == 'numberToArray') {\n <input style=\"width: 100%;\" type=\"number\" pInputText [ngModel]=\"tool.inputs[toolProp.key]\"\n (ngModelChange)=\"setValue($event, tool, toolProp)\" />\n }\n @if (toolProp.value.type == 'txt_area') {\n <textarea style=\"width: 100%;\" [(ngModel)]=\"tool.inputs[toolProp.key]\"\n [ngModelOptions]=\"{ updateOn: 'blur' }\"\n (ngModelChange)=\"setValue($event, tool, toolProp)\"></textarea>\n }\n @if (toolProp.value.type == 'string') {\n <input style=\"width: 100%;\" type=\"text\" pInputText [(ngModel)]=\"tool.inputs[toolProp.key]\"\n (ngModelChange)=\"setValue($event, tool, toolProp)\" />\n }\n @if (toolProp.value.type == 'color') {\n <p-colorPicker [ngModel]=\"getDesignerColorPickerHex(tool, toolProp)\"\n (ngModelChange)=\"onDesignerColorPickerHexChange($event, tool, toolProp)\" appendTo=\"body\" />\n <div class=\"designer-color-alpha-row\">\n <span class=\"designer-color-alpha-label\">{{ 'designer.alpha' | translate }}</span>\n <input type=\"range\" min=\"0\" max=\"1\" step=\"0.01\"\n [ngModel]=\"getDesignerColorAlpha(tool, toolProp)\"\n (ngModelChange)=\"onDesignerColorAlphaChange($event, tool, toolProp)\" />\n <input type=\"number\" min=\"0\" max=\"1\" step=\"0.01\"\n [ngModel]=\"getDesignerColorAlpha(tool, toolProp)\"\n (ngModelChange)=\"onDesignerColorAlphaChange($event, tool, toolProp)\" />\n </div>\n <input type=\"text\" pInputText [(ngModel)]=\"tool.inputs[toolProp.key]\"\n (ngModelChange)=\"setValue($event, tool, toolProp)\" />\n }\n @if (toolProp.value.type == 'dictionary' && !toolProp.value.async) {\n @if (toolProp.key == 'cssFile') {\n <p-select [style]=\"{width: '100%'}\" appendTo=\"body\" [options]=\"availableProjectCssFiles\"\n [ngModel]=\"tool.inputs[toolProp.key]\" (ngModelChange)=\"onCssFileChanged($event, tool)\" />\n } @else if (toolProp.key == 'cssClass') {\n <p-multiSelect [style]=\"{width: '100%'}\" appendTo=\"body\"\n [options]=\"getCssClassOptionsForTool(tool)\" [ngModel]=\"tool.inputs[toolProp.key]\"\n (ngModelChange)=\"onCssClassChanged($event, tool)\" [filter]=\"true\" [virtualScroll]=\"true\"\n [virtualScrollItemSize]=\"38\" scrollHeight=\"260px\" [showToggleAll]=\"false\" display=\"chip\"\n [maxSelectedLabels]=\"3\" [selectedItemsLabel]=\"'{0} selected'\">\n </p-multiSelect>\n } @else if ((tool.name == 'SELECT' || tool.name == 'MULTISELECT' || tool.name == 'UL') &&\n (toolProp.key == 'valueField' || toolProp.key == 'textField')) {\n <p-select [style]=\"{width: '100%'}\" appendTo=\"body\"\n [options]=\"getDatasourceColumnOptions(tool)\" [ngModel]=\"tool.inputs[toolProp.key]\"\n (ngModelChange)=\"setValue($event, tool, toolProp)\" />\n } @else if (tool.name == 'LABEL' && toolProp.key == 'displayField') {\n <p-select [style]=\"{width: '100%'}\" appendTo=\"body\"\n [options]=\"getDatasourceColumnOptions(tool)\" [ngModel]=\"tool.inputs[toolProp.key]\"\n (ngModelChange)=\"setValue($event, tool, toolProp)\" />\n } @else {\n <p-select [style]=\"{width: '100%'}\" appendTo=\"body\" [options]=\"toolProp.value.values\"\n [(ngModel)]=\"tool.inputs[toolProp.key]\"\n (ngModelChange)=\"setValue($event, tool, toolProp)\" />\n }\n }\n @if (toolProp.value.type == 'dictionary' && toolProp.value.async) {\n <p-select [style]=\"{width: '100%'}\" appendTo=\"body\" [options]=\"toolProp.value.values\"\n [ngModel]=\"tool.inputs[toolProp.key] | async\"\n (ngModelChange)=\"setValue($event, tool, toolProp)\" />\n }\n @if (tool.name == 'DATAREPEATER' && toolProp.key == 'action' &&\n canOpenArchetypeConfigurator(tool)) {\n <button style=\"margin-top: 8px;\" (click)=\"openArchetypeConfigurator(tool)\">\n {{ 'designer.configure' | translate }} {{ (tool.inputs['action'] | async) || '' }}\n </button>\n }\n @if (toolProp.value.type == 'dropped-component-list') {\n <p-select [style]=\"{width: '100%'}\" appendTo=\"body\"\n [placeholder]=\"'select.component' | translate\" [options]=\"elementFilter(toolProp)\"\n [ngModel]=\"getDroppedComponentListModel(tool, toolProp)\"\n (ngModelChange)=\"setValue($event, tool, toolProp)\" optionLabel=\"uniqueName\"\n dataKey=\"uniqueName\" />\n }\n @if (toolProp.value.type == 'autocomplete') {\n <p-autoComplete [style]=\"{width: '100%'}\" appendTo=\"body\"\n [ngModel]=\"tool.inputs[toolProp.key] | async\" [forceSelection]=\"true\"\n (ngModelChange)=\"setValue($event, tool, toolProp)\" [suggestions]=\"tool.suggestions\"\n [optionLabel]=\"toolProp.value.displayField\" [optionValue]=\"toolProp.value.valueField\"\n (completeMethod)=\"search($event, tool, toolProp)\" [dropdown]=\"true\" dropdownMode=\"current\"\n [multiple]=\"toolProp.value.multiple\" />\n }\n @if (toolProp.value.type == 'propertyTree' && shouldShowPropertyTree(tool)) {\n <ul>\n @for (item of $any(tool.inputs[toolProp.key] | async); track item) {\n <li style=\"padding: 0; margin: 0;\">\n @if (item.col && tool.inputs['customProps_' + (tool.inputs['action'] | async)])\n {\n <wuic-field-editor [field]=\"item.col\"\n [record]=\"tool.inputs['customProps_' + (tool.inputs['action'] | async)]\"\n [metaInfo]=\"tool.inputs['datasource']?.component?.value?.metaInfo\"\n [triggerProp]=\"tool.inputs['action']\"></wuic-field-editor>\n }\n </li>\n }\n </ul>\n }\n </li>\n }\n </ul>\n </details>\n }\n </div>\n </div>\n </ng-template>\n <ng-template pTemplate>\n <div class=\"hierarchy-panel\">\n <div class=\"hierarchy-panel-title\">{{ 'designer.canvas_hierarchy' | translate }}</div>\n <div class=\"hierarchy-tree-wrap\">\n <p-tree [value]=\"hierarchyTreeNodes\" [draggableNodes]=\"true\" [droppableNodes]=\"true\"\n selectionMode=\"single\" [(selection)]=\"hierarchySelection\"\n (onNodeSelect)=\"onHierarchyNodeSelect($event)\" draggableScope=\"designer-hierarchy\"\n droppableScope=\"designer-hierarchy\" (onNodeDrop)=\"onHierarchyNodeDrop($event)\">\n <ng-template let-node pTemplate=\"default\">\n <span class=\"hierarchy-node-label\" [class.active]=\"isHierarchyNodeSelected(node)\"\n (mouseenter)=\"onHierarchyNodeMouseEnter(node)\" (mouseleave)=\"onHierarchyNodeMouseLeave()\"\n (contextmenu)=\"showHierarchyNodeContextMenu($event, node, hierarchyCtxMenu)\">\n {{ node.label }}\n </span>\n </ng-template>\n </p-tree>\n </div>\n <p-contextMenu #hierarchyCtxMenu [model]=\"hierarchyCtxItems\" appendTo=\"body\" />\n </div>\n </ng-template>\n </p-splitter>\n </ng-template>\n </p-splitter>\n </ng-template>\n <ng-template pTemplate>\n <div class=\"col dashboard-design\" [class.designer-hide-datasources]=\"hideDatasourcesInCanvas\" pDroppable\n (onDrop)=\"drop($event)\" #canvas (contextmenu)=\"showCtx($event, this)\">\n <wuic-dashboard [dashboardElements]=\"dashboardElements\"></wuic-dashboard>\n <p-contextMenu [target]=\"canvas\" [model]=\"ctxItems | async\" appendTo=\"body\" />\n </div>\n </ng-template>\n </p-splitter>\n </div>\n <div class=\"designer-footer\">\n <div class=\"designer-footer-label\">{{ 'designer.items' | translate }}:</div>\n <div class=\"designer-footer-list\">\n @for (item of droppedDashboardItems; track item.uniqueName) {\n <button type=\"button\" class=\"designer-footer-item\" [class.active]=\"tool?.uniqueName === item?.uniqueName\"\n [attr.data-designer-unique-name]=\"item?.uniqueName\" (mouseenter)=\"setFooterHoverHighlight(item)\"\n (mouseleave)=\"clearFooterHoverHighlight()\" (click)=\"selectDashboardItem(item)\"\n (contextmenu)=\"showFooterContextMenu($event, item, footerCtxMenu)\">\n {{ getDroppedDashboardItemLabel(item) }}\n </button>\n }\n </div>\n <p-contextMenu #footerCtxMenu [model]=\"footerCtxItems\" appendTo=\"body\" />\n </div>\n</div>\n}\n\n@if (noEdit) {\n<wuic-dashboard [dashboardElements]=\"dashboardElements\"></wuic-dashboard>\n}\n\n<wuic-archetype-configurator [visible]=\"showArchetypeConfigurator\"\n (visibleChange)=\"onArchetypeConfigVisibleChange($event)\" [archetype]=\"getActiveArchetypeConfig()\"\n [metaInfo]=\"getArchetypeConfigMetaInfoForTool(archetypeConfigTool)\"\n [value]=\"getArchetypeConfigValueForTool(archetypeConfigTool)\" (applyConfig)=\"onArchetypeConfigApply($event)\">\n</wuic-archetype-configurator>\n\n<wuic-css-sheet-editor [visible]=\"showCssSheetEditor\" (visibleChange)=\"onCssSheetEditorVisibleChange($event)\"\n [sheets]=\"dashboardCssSheets\" [selectedSheetPath]=\"selectedCssEditorSheetPath\" [assetsFolder]=\"currentDashboardRoute\"\n [styleProps]=\"getCssEditorStyleProps()\" (applyEditor)=\"onCssSheetEditorApply($event)\">\n</wuic-css-sheet-editor>\n\n<p-dialog [header]=\"'designer.reopen_dashboard' | translate\" [(visible)]=\"reopenDashboardDialogVisible\" [modal]=\"true\"\n [closable]=\"true\" [draggable]=\"false\" [resizable]=\"false\" [appendTo]=\"'body'\" [style]=\"{ width: '34rem' }\"\n (onHide)=\"cancelReopenDashboardDialog()\">\n <div class=\"route-popup-form\">\n <label for=\"designer-reopen-select\">{{ 'designer.existing_dashboards' | translate }}</label>\n <p-select inputId=\"designer-reopen-select\" [options]=\"savedDashboardOptions\" optionLabel=\"label\" optionValue=\"value\"\n [(ngModel)]=\"reopenDialogSelectedDashboardRoute\" [filter]=\"true\" [showClear]=\"true\" [style]=\"{ width: '100%' }\"\n appendTo=\"body\" />\n\n <div class=\"route-popup-actions\">\n <button type=\"button\" class=\"route-popup-btn\" (click)=\"cancelReopenDashboardDialog()\">{{ 'cancel' | translate\n }}</button>\n <button type=\"button\" class=\"route-popup-btn route-popup-btn-primary\" (click)=\"confirmReopenDashboardDialog()\"\n [disabled]=\"!reopenDialogSelectedDashboardRoute\">{{ 'designer.reopen' | translate }}</button>\n </div>\n </div>\n</p-dialog>", styles: [":host{display:block;width:100%;height:100%;min-width:0}.designer-layout{display:grid;grid-template-rows:auto minmax(0,1fr) auto;height:100%;min-height:0;width:100%;min-width:0}.designer-toolbar{display:flex;align-items:center;flex-wrap:wrap;gap:8px;border:1px solid inherit;border-radius:10px;background:transparent;padding:10px 12px;margin-bottom:8px}.designer-current-dashboard{min-width:220px;font-size:.86rem;font-weight:700;color:inherit}.designer-toolbar-spacer{flex:1 1 auto;min-width:16px}.designer-toolbar-menu-group{display:inline-flex}.designer-toolbar-menu-group button{height:32px;border:1px solid #2f67d8;border-radius:6px;background:#2f67d8;color:#fff;padding:0 10px;font-size:.82rem;cursor:pointer}.designer-toolbar-menu-group button[disabled]{opacity:.6;cursor:not-allowed}.designer-main{min-height:0;min-width:0;width:100%;overflow:hidden;direction:ltr}.designer-main-splitter{height:100%;width:100%;min-width:0;direction:ltr}.designer-toolbox{font-size:3rem}.designer-toolbox ul{list-style-type:none;padding:0;margin:0;width:100%}.designer-toolbox li{float:left;margin-left:5px;margin-top:5px}.designer-toolbox .tool-groups{display:block}.designer-toolbox .tool-group-item{float:none;margin-left:0;margin-top:10px;clear:both}.designer-toolbox .tool-group-title{font-size:.8rem;font-weight:600;text-transform:uppercase;letter-spacing:.04em;opacity:.7;margin:0 0 4px 6px}.designer-toolbox .tool-group-list{display:flex;flex-wrap:wrap;align-items:flex-start;gap:4px}.designer-toolbox .tool-group-list li{float:none;margin-left:0;margin-top:0}.designer-toolbox .props{font-size:1rem}.designer-toolbox .props li{float:none;padding-bottom:15px}.designer-toolbox .prop-section{width:100%;border:1px solid inherit;border-radius:6px;margin-top:8px;background:transparent;padding:6px 8px 2px}.designer-toolbox .prop-section-title{font-size:.82rem;font-weight:600;text-transform:uppercase;letter-spacing:.04em;cursor:pointer;color:inherit;margin-bottom:6px}.palette-panel{height:100%;overflow:auto;width:100%}.designer-color-alpha-row{display:flex;align-items:center;gap:8px;margin-top:6px;margin-bottom:6px}.designer-color-alpha-label{font-size:.8rem;min-width:40px}.designer-color-alpha-row input[type=range]{flex:1 1 auto;min-width:90px}.designer-color-alpha-row input[type=number]{width:72px}.properties-panel{height:100%;overflow:hidden;width:100%;display:flex;flex-direction:column;min-height:0;padding-right:10px;padding-left:10px;padding-top:10px}.properties-panel>.designer-toolbox{flex:1 1 auto;min-height:0;overflow-y:auto;overflow-x:hidden;scrollbar-gutter:stable}:host ::ng-deep .designer-left-bottom-splitter .p-splitterpanel{min-height:0!important;overflow:hidden!important}.hierarchy-panel{height:100%;width:100%;min-height:0;display:flex;flex-direction:column;overflow:hidden;padding:6px}.hierarchy-panel-title{font-size:.82rem;font-weight:600;text-transform:uppercase;letter-spacing:.04em;color:#4b5563;margin:0 0 6px}.hierarchy-tree-wrap{flex:1 1 auto;min-height:0;border:1px solid inherit;border-radius:6px;background:transparent;overflow:auto;padding:4px}:host ::ng-deep .hierarchy-tree-wrap .p-tree{border:none;padding:0;height:100%;background:transparent}:host ::ng-deep .hierarchy-tree-wrap .p-tree-node-content{border:1px solid transparent;border-radius:4px;background:transparent!important;color:inherit!important;padding:0!important}:host ::ng-deep .hierarchy-tree-wrap .p-tree-node-content.p-highlight{border-color:transparent!important;background:transparent!important;color:inherit!important}:host ::ng-deep .hierarchy-tree-wrap .p-tree-node-content:hover{background:transparent!important;color:inherit!important}:host ::ng-deep .hierarchy-tree-wrap .hierarchy-node-label{display:inline-block;border:1px solid inherit;background:transparent;color:inherit;border-radius:4px;padding:2px 8px;font-size:.8rem;line-height:1.25rem;cursor:pointer}:host ::ng-deep .hierarchy-tree-wrap .hierarchy-node-label:hover{border-color:#94a3b8}:host ::ng-deep .hierarchy-tree-wrap .hierarchy-node-label.active{border-color:#3b82f6!important;background:#eff6ff!important;color:#1d4ed8!important}:host ::ng-deep .hierarchy-tree-wrap .p-tree-node-content.p-highlight .hierarchy-node-label{border-color:#3b82f6!important;background:#eff6ff!important;color:#1d4ed8!important}.designer-hierarchy-node{cursor:pointer;border:1px solid transparent;border-radius:4px;padding:2px 6px;font-size:.82rem;line-height:1.25rem}.designer-hierarchy-node-label{cursor:pointer;display:inline-block;width:100%}.designer-hierarchy-node.active{border-color:#3b82f6;background:#eff6ff;color:#1d4ed8}.designer-footer-item.designer-tree-hover-target{border-color:#3b82f6!important;outline:1px solid #3b82f6!important;outline-offset:-1px}.dashboard-design{height:100%;min-height:0;min-width:0;width:100%;overflow:auto;direction:ltr}:host ::ng-deep .dashboard-design.designer-hide-datasources .datasource-descriptor{display:none!important}.current-dashboard-title{font-weight:600;margin-right:12px}.designer-footer{display:flex;align-items:center;gap:8px;padding:6px 8px;border-top:1px solid inherit;background:transparent;min-height:36px;flex:0 0 auto;width:100%;overflow:auto}.designer-footer-label{font-size:.85rem;font-weight:600;color:inherit;white-space:nowrap}.designer-footer-list{display:flex;align-items:center;gap:6px;overflow-x:auto;overflow-y:hidden;white-space:nowrap;width:100%}.designer-footer-item{border:1px solid inherit;background:transparent;color:inherit;border-radius:4px;padding:2px 8px;font-size:.8rem;line-height:1.25rem;cursor:pointer;white-space:nowrap}.designer-footer-item.active{border-color:#3b82f6;background:transparent;color:#1d4ed8}:host ::ng-deep .designer-footer-hover-target{border-color:#3b82f6!important;outline:1px solid #3b82f6!important;outline-offset:-1px;box-shadow:inset 0 0 0 1px #3b82f6!important}.route-popup-form{display:flex;flex-direction:column;gap:8px}.route-popup-form label{font-size:.85rem;font-weight:600;color:#2f3f58}.route-popup-actions{display:flex;justify-content:flex-end;gap:8px;margin-top:10px}.route-popup-btn{border:1px solid inherit;background:transparent;color:inherit;border-radius:6px;padding:7px 12px;font-size:.85rem;cursor:pointer}.route-popup-btn-primary{border-color:#2f67d8;background:#2f67d8;color:#fff}.p-colorpicker{border:1px solid black;border-radius:4px}\n"], dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type: TranslateModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i1.RangeValueAccessor, selector: "input[type=range][formControlName],input[type=range][formControl],input[type=range][ngModel]" }, { kind: "directive", type: i1.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.MinValidator, selector: "input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]", inputs: ["min"] }, { kind: "directive", type: i1.MaxValidator, selector: "input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]", inputs: ["max"] }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: SplitterModule }, { kind: "component", type: i10.Splitter, selector: "p-splitter", inputs: ["styleClass", "panelStyleClass", "panelStyle", "stateStorage", "stateKey", "layout", "gutterSize", "step", "minSizes", "panelSizes"], outputs: ["onResizeEnd", "onResizeStart"] }, { kind: "directive", type: i3.PrimeTemplate, selector: "[pTemplate]", inputs: ["type", "pTemplate"] }, { kind: "ngmodule", type: DragDropModule }, { kind: "directive", type: i12.Draggable, selector: "[pDraggable]", inputs: ["pDraggable", "dragEffect", "dragHandle", "pDraggableDisabled"], outputs: ["onDragStart", "onDragEnd", "onDrag"] }, { kind: "directive", type: i12.Droppable, selector: "[pDroppable]", inputs: ["pDroppable", "pDroppableDisabled", "dropEffect"], outputs: ["onDragEnter", "onDragLeave", "onDrop"] }, { kind: "ngmodule", type: SelectModule }, { kind: "component", type: i6.Select, selector: "p-select", inputs: ["id", "scrollHeight", "filter", "panelStyle", "styleClass", "panelStyleClass", "readonly", "editable", "tabindex", "placeholder", "loadingIcon", "filterPlaceholder", "filterLocale", "inputId", "dataKey", "filterBy", "filterFields", "autofocus", "resetFilterOnHide", "checkmark", "dropdownIcon", "loading", "optionLabel", "optionValue", "optionDisabled", "optionGroupLabel", "optionGroupChildren", "group", "showClear", "emptyFilterMessage", "emptyMessage", "lazy", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "overlayOptions", "ariaFilterLabel", "ariaLabel", "ariaLabelledBy", "filterMatchMode", "tooltip", "tooltipPosition", "tooltipPositionStyle", "tooltipStyleClass", "focusOnHover", "selectOnFocus", "autoOptionFocus", "autofocusFilter", "filterValue", "options", "appendTo", "motionOptions"], outputs: ["onChange", "onFilter", "onFocus", "onBlur", "onClick", "onShow", "onHide", "onClear", "onLazyLoad"] }, { kind: "ngmodule", type: ColorPickerModule }, { kind: "component", type: i2$1.ColorPicker, selector: "p-colorPicker, p-colorpicker, p-color-picker", inputs: ["styleClass", "showTransitionOptions", "hideTransitionOptions", "inline", "format", "tabindex", "inputId", "autoZIndex", "autofocus", "defaultColor", "appendTo", "overlayOptions", "motionOptions"], outputs: ["onChange", "onShow", "onHide"] }, { kind: "ngmodule", type: AutoCompleteModule }, { kind: "component", type: i2$2.AutoComplete, selector: "p-autoComplete, p-autocomplete, p-auto-complete", inputs: ["minLength", "minQueryLength", "delay", "panelStyle", "styleClass", "panelStyleClass", "inputStyle", "inputId", "inputStyleClass", "placeholder", "readonly", "scrollHeight", "lazy", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "autoHighlight", "forceSelection", "type", "autoZIndex", "baseZIndex", "ariaLabel", "dropdownAriaLabel", "ariaLabelledBy", "dropdownIcon", "unique", "group", "completeOnFocus", "showClear", "dropdown", "showEmptyMessage", "dropdownMode", "multiple", "addOnTab", "tabindex", "dataKey", "emptyMessage", "showTransitionOptions", "hideTransitionOptions", "autofocus", "autocomplete", "optionGroupChildren", "optionGroupLabel", "overlayOptions", "suggestions", "optionLabel", "optionValue", "id", "searchMessage", "emptySelectionMessage", "selectionMessage", "autoOptionFocus", "selectOnFocus", "searchLocale", "optionDisabled", "focusOnHover", "typeahead", "addOnBlur", "separator", "appendTo", "motionOptions"], outputs: ["completeMethod", "onSelect", "onUnselect", "onAdd", "onFocus", "onBlur", "onDropdownClick", "onClear", "onInputKeydown", "onKeyUp", "onShow", "onHide", "onLazyLoad"] }, { kind: "component", type: DashboardComponent, selector: "wuic-dashboard", inputs: ["dashboardElements"] }, { kind: "component", type: FieldEditorComponent, selector: "wuic-field-editor", inputs: ["datasource", "record", "field", "metaInfo", "readOnly", "isFilter", "hideLabel", "forceShowLabel", "operator", "nestedIndex", "triggerProp", "tabIndex", "onInlineCellValueChange"] }, { kind: "ngmodule", type: ContextMenuModule }, { kind: "component", type: i14.ContextMenu, selector: "p-contextMenu, p-contextmenu, p-context-menu", inputs: ["model", "triggerEvent", "target", "global", "style", "styleClass", "autoZIndex", "baseZIndex", "id", "breakpoint", "ariaLabel", "ariaLabelledBy", "pressDelay", "appendTo", "motionOptions"], outputs: ["onShow", "onHide"] }, { kind: "component", type: MetadataEditorComponent, selector: "wuic-metadata-editor", inputs: ["datasource", "hardcodedDatasource", "saveCallback", "hideReports", "hideRelatedTableActions", "hideRelatedColumnActions"] }, { kind: "ngmodule", type: MenubarModule }, { kind: "component", type: ArchetypeConfiguratorComponent, selector: "wuic-archetype-configurator", inputs: ["visible", "archetype", "metaInfo", "value"], outputs: ["visibleChange", "applyConfig"] }, { kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i4.Button, selector: "p-button", inputs: ["hostName", "type", "badge", "disabled", "raised", "rounded", "text", "plain", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "iconPos", "icon", "label", "loading", "loadingIcon", "severity", "buttonProps", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "ngmodule", type: MultiSelectModule }, { kind: "component", type: i18.MultiSelect, selector: "p-multiSelect, p-multiselect, p-multi-select", inputs: ["id", "ariaLabel", "styleClass", "panelStyle", "panelStyleClass", "inputId", "readonly", "group", "filter", "filterPlaceHolder", "filterLocale", "overlayVisible", "tabindex", "dataKey", "ariaLabelledBy", "displaySelectedLabel", "maxSelectedLabels", "selectionLimit", "selectedItemsLabel", "showToggleAll", "emptyFilterMessage", "emptyMessage", "resetFilterOnHide", "dropdownIcon", "chipIcon", "optionLabel", "optionValue", "optionDisabled", "optionGroupLabel", "optionGroupChildren", "showHeader", "filterBy", "scrollHeight", "lazy", "virtualScroll", "loading", "virtualScrollItemSize", "loadingIcon", "virtualScrollOptions", "overlayOptions", "ariaFilterLabel", "filterMatchMode", "tooltip", "tooltipPosition", "tooltipPositionStyle", "tooltipStyleClass", "autofocusFilter", "display", "autocomplete", "showClear", "autofocus", "placeholder", "options", "filterValue", "selectAll", "focusOnHover", "filterFields", "selectOnFocus", "autoOptionFocus", "highlightOnSelect", "size", "variant", "fluid", "appendTo", "motionOptions"], outputs: ["onChange", "onFilter", "onFocus", "onBlur", "onClick", "onClear", "onPanelShow", "onPanelHide", "onLazyLoad", "onRemove", "onSelectAllChange"] }, { kind: "component", type: CssSheetEditorComponent, selector: "wuic-css-sheet-editor", inputs: ["visible", "sheets", "selectedSheetPath", "styleProps", "assetsFolder"], outputs: ["visibleChange", "applyEditor"] }, { kind: "ngmodule", type: MenuModule }, { kind: "component", type: i9.Menu, selector: "p-menu", inputs: ["model", "popup", "style", "styleClass", "autoZIndex", "baseZIndex", "showTransitionOptions", "hideTransitionOptions", "ariaLabel", "ariaLabelledBy", "id", "tabindex", "appendTo", "motionOptions"], outputs: ["onShow", "onHide", "onBlur", "onFocus"] }, { kind: "ngmodule", type: DialogModule }, { kind: "component", type: i2.Dialog, selector: "p-dialog", inputs: ["hostName", "header", "draggable", "resizable", "contentStyle", "contentStyleClass", "modal", "closeOnEscape", "dismissableMask", "rtl", "closable", "breakpoints", "styleClass", "maskStyleClass", "maskStyle", "showHeader", "blockScroll", "autoZIndex", "baseZIndex", "minX", "minY", "focusOnShow", "maximizable", "keepInViewport", "focusTrap", "transitionOptions", "maskMotionOptions", "motionOptions", "closeIcon", "closeAriaLabel", "closeTabindex", "minimizeIcon", "maximizeIcon", "closeButtonProps", "maximizeButtonProps", "visible", "style", "position", "role", "appendTo", "content", "contentTemplate", "footerTemplate", "closeIconTemplate", "maximizeIconTemplate", "minimizeIconTemplate", "headlessTemplate"], outputs: ["onShow", "onHide", "visibleChange", "onResizeInit", "onResizeEnd", "onDragEnd", "onMaximize"] }, { kind: "ngmodule", type: TreeModule }, { kind: "component", type: i4$1.Tree, selector: "p-tree", inputs: ["value", "selectionMode", "loadingMode", "selection", "styleClass", "contextMenu", "contextMenuSelectionMode", "contextMenuSelection", "draggableScope", "droppableScope", "draggableNodes", "droppableNodes", "metaKeySelection", "propagateSelectionUp", "propagateSelectionDown", "loading", "loadingIcon", "emptyMessage", "ariaLabel", "togglerAriaLabel", "ariaLabelledBy", "validateDrop", "filter", "filterInputAutoFocus", "filterBy", "filterMode", "filterOptions", "filterPlaceholder", "filteredNodes", "filterLocale", "scrollHeight", "lazy", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "indentation", "_templateMap", "trackBy", "highlightOnSelect"], outputs: ["selectionChange", "contextMenuSelectionChange", "onNodeSelect", "onNodeUnselect", "onNodeExpand", "onNodeCollapse", "onNodeContextMenuSelect", "onNodeDoubleClick", "onNodeDrop", "onLazyLoad", "onScroll", "onScrollIndexChange", "onFilter"] }, { kind: "pipe", type: i8.TranslatePipe, name: "translate" }, { kind: "pipe", type: AsyncPipe, name: "async" }] });
|
|
10863
11832
|
}
|
|
10864
11833
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: DesignerComponent, decorators: [{
|
|
10865
11834
|
type: Component,
|
|
10866
11835
|
args: [{ selector: 'wuic-designer', imports: [NgClass, TranslateModule, AsyncPipe, FormsModule, SplitterModule, DragDropModule, SelectModule, ColorPickerModule, AutoCompleteModule, DashboardComponent, FieldEditorComponent, ContextMenuModule, MetadataEditorComponent, MenubarModule, ArchetypeConfiguratorComponent, ButtonModule, MultiSelectModule, CssSheetEditorComponent, MenuModule, DialogModule, TreeModule], providers: [TreeDragDropService], template: "<!-- <button pButton type=\"button\" (click)=\"loadDashboard()\" class=\"mr-2\">{{'load' | translate}}</button> -->\n@if (!noEdit) {\n<div class=\"designer-layout\">\n <div class=\"designer-toolbar\">\n <div class=\"designer-current-dashboard\">{{ currentDashboardTitle }}</div>\n\n <div class=\"designer-toolbar-spacer\"></div>\n\n <div class=\"designer-toolbar-menu-group\">\n <button type=\"button\" (click)=\"undo()\" [disabled]=\"!canUndo\">{{ 'designer.undo' | translate }}</button>\n </div>\n\n <div class=\"designer-toolbar-menu-group\">\n <button type=\"button\" (click)=\"redo()\" [disabled]=\"!canRedo\">{{ 'designer.redo' | translate }}</button>\n </div>\n\n <div class=\"designer-toolbar-menu-group\">\n <button type=\"button\" (click)=\"refreshGraphActionsMenuItems(); graphActionsMenu.toggle($event)\">{{ 'designer.actions_menu' | translate }}</button>\n <p-menu #graphActionsMenu [popup]=\"true\" [model]=\"graphActionsMenuItems\" appendTo=\"body\"></p-menu>\n </div>\n\n <div class=\"designer-toolbar-menu-group\">\n <button type=\"button\" (click)=\"openCurrentDashboardInNewTab()\" [disabled]=\"!currentDashboardRoute\">{{\n 'designer.open_current_dashboard' | translate }}</button>\n </div>\n </div>\n <div class=\"designer-main\">\n <p-splitter [style]=\"{ height: '100%' }\" [panelSizes]=\"[15, 85]\" styleClass=\"designer-main-splitter\">\n <ng-template pTemplate>\n <p-splitter layout=\"vertical\" [style]=\"{ height: '100%', width: '100%' }\" [panelSizes]=\"[35, 65]\"\n [minSizes]=\"[20, 30]\" styleClass=\"designer-left-splitter\">\n <ng-template pTemplate>\n <div class=\"col flex designer-toolbox palette-panel\">\n <ul class=\"tool-groups\">\n @for (toolGroup of availableToolGroups; track toolGroup.group) {\n <li class=\"tool-group-item\">\n <div class=\"tool-group-title\">{{ toolGroup.group }}</div>\n <ul class=\"tool-group-list\">\n @for (tool of toolGroup.tools; track tool) {\n <li pDraggable (onDragStart)=\"dragStart(tool)\" (onDragEnd)=\"dragEnd()\" (onDrag)=\"onDrag($event)\"\n (dblclick)=\"dropToolAtRoot(tool)\">\n <i [ngClass]=\"tool.icon\" [title]=\"tool.name\"></i>\n </li>\n }\n </ul>\n </li>\n }\n </ul>\n </div>\n </ng-template>\n <ng-template pTemplate>\n <p-splitter layout=\"vertical\" [style]=\"{ height: '100%', width: '100%' }\" [panelSizes]=\"[68, 32]\"\n [minSizes]=\"[35, 20]\" styleClass=\"designer-left-bottom-splitter\">\n <ng-template pTemplate>\n <div class=\"properties-panel\">\n <p-select [style]=\"{width: '100%'}\" [options]=\"flattenedDashboardElements\" [(ngModel)]=\"tool\"\n optionLabel=\"uniqueName\" dataKey=\"componentId\" appendTo=\"body\" />\n <div class=\"designer-toolbox\">\n\n <span style=\"width: 100%;\">\n <input type=\"text\" style=\"width: calc(100% - 30px);\" [(ngModel)]=\"propFilter\"\n [placeholder]=\"'prop.filter' | translate\" />\n <button style=\"width: 25px\" (click)=\"clearPropFilter()\">X</button>\n </span>\n\n @for (section of getToolPropertySections(tool, propFilter); track section.key) {\n <details class=\"prop-section\" [open]=\"section.open\"\n (toggle)=\"onPropertySectionToggle(tool, section.key, $event)\">\n <summary class=\"prop-section-title\">{{ section.label }}</summary>\n <ul class=\"props\">\n @for (toolProp of section.props; track toolProp.key) {\n <li>\n @if (!toolProp.value.hideCaption) {\n <span>{{ toolProp.value?.propertyCaption ||\n toolProp.key[0].toUpperCase() + toolProp.key.slice(1)\n }}</span>\n }\n <br />\n @if (toolProp.value.type == 'button') {\n <p-button (click)=\"toolProp.value.callback(tool.inputs, toolProp.key, null, tool)\"\n class=\"k-button\" [style]=\"{width: '100%'}\">\n {{toolProp.value.propertyCaption}}\n </p-button>\n }\n @if (toolProp.value.type == 'metaEditor' &&\n tool.inputs['componentRef']?.value?.component) {\n <wuic-metadata-editor [hardcodedDatasource]=\"tool.inputs['componentRef']?.value?.component\"\n [saveCallback]=\"metaEditorSave\"></wuic-metadata-editor>\n }\n @if (toolProp.value.type == 'boolean') {\n <input type=\"checkbox\" [(ngModel)]=\"tool.inputs[toolProp.key]\" />\n }\n @if (toolProp.value.type == 'numberToArray') {\n <input style=\"width: 100%;\" type=\"number\" pInputText [ngModel]=\"tool.inputs[toolProp.key]\"\n (ngModelChange)=\"setValue($event, tool, toolProp)\" />\n }\n @if (toolProp.value.type == 'txt_area') {\n <textarea style=\"width: 100%;\" [(ngModel)]=\"tool.inputs[toolProp.key]\"\n [ngModelOptions]=\"{ updateOn: 'blur' }\"\n (ngModelChange)=\"setValue($event, tool, toolProp)\"></textarea>\n }\n @if (toolProp.value.type == 'string') {\n <input style=\"width: 100%;\" type=\"text\" pInputText [(ngModel)]=\"tool.inputs[toolProp.key]\"\n (ngModelChange)=\"setValue($event, tool, toolProp)\" />\n }\n @if (toolProp.value.type == 'color') {\n <p-colorPicker [ngModel]=\"getDesignerColorPickerHex(tool, toolProp)\"\n (ngModelChange)=\"onDesignerColorPickerHexChange($event, tool, toolProp)\" appendTo=\"body\" />\n <div class=\"designer-color-alpha-row\">\n <span class=\"designer-color-alpha-label\">{{ 'designer.alpha' | translate }}</span>\n <input type=\"range\" min=\"0\" max=\"1\" step=\"0.01\"\n [ngModel]=\"getDesignerColorAlpha(tool, toolProp)\"\n (ngModelChange)=\"onDesignerColorAlphaChange($event, tool, toolProp)\" />\n <input type=\"number\" min=\"0\" max=\"1\" step=\"0.01\"\n [ngModel]=\"getDesignerColorAlpha(tool, toolProp)\"\n (ngModelChange)=\"onDesignerColorAlphaChange($event, tool, toolProp)\" />\n </div>\n <input type=\"text\" pInputText [(ngModel)]=\"tool.inputs[toolProp.key]\"\n (ngModelChange)=\"setValue($event, tool, toolProp)\" />\n }\n @if (toolProp.value.type == 'dictionary' && !toolProp.value.async) {\n @if (toolProp.key == 'cssFile') {\n <p-select [style]=\"{width: '100%'}\" appendTo=\"body\" [options]=\"availableProjectCssFiles\"\n [ngModel]=\"tool.inputs[toolProp.key]\" (ngModelChange)=\"onCssFileChanged($event, tool)\" />\n } @else if (toolProp.key == 'cssClass') {\n <p-multiSelect [style]=\"{width: '100%'}\" appendTo=\"body\"\n [options]=\"getCssClassOptionsForTool(tool)\" [ngModel]=\"tool.inputs[toolProp.key]\"\n (ngModelChange)=\"onCssClassChanged($event, tool)\" [filter]=\"true\" [virtualScroll]=\"true\"\n [virtualScrollItemSize]=\"38\" scrollHeight=\"260px\" [showToggleAll]=\"false\" display=\"chip\"\n [maxSelectedLabels]=\"3\" [selectedItemsLabel]=\"'{0} selected'\">\n </p-multiSelect>\n } @else if ((tool.name == 'SELECT' || tool.name == 'MULTISELECT' || tool.name == 'UL') &&\n (toolProp.key == 'valueField' || toolProp.key == 'textField')) {\n <p-select [style]=\"{width: '100%'}\" appendTo=\"body\"\n [options]=\"getDatasourceColumnOptions(tool)\" [ngModel]=\"tool.inputs[toolProp.key]\"\n (ngModelChange)=\"setValue($event, tool, toolProp)\" />\n } @else if (tool.name == 'LABEL' && toolProp.key == 'displayField') {\n <p-select [style]=\"{width: '100%'}\" appendTo=\"body\"\n [options]=\"getDatasourceColumnOptions(tool)\" [ngModel]=\"tool.inputs[toolProp.key]\"\n (ngModelChange)=\"setValue($event, tool, toolProp)\" />\n } @else {\n <p-select [style]=\"{width: '100%'}\" appendTo=\"body\" [options]=\"toolProp.value.values\"\n [(ngModel)]=\"tool.inputs[toolProp.key]\"\n (ngModelChange)=\"setValue($event, tool, toolProp)\" />\n }\n }\n @if (toolProp.value.type == 'dictionary' && toolProp.value.async) {\n <p-select [style]=\"{width: '100%'}\" appendTo=\"body\" [options]=\"toolProp.value.values\"\n [ngModel]=\"tool.inputs[toolProp.key] | async\"\n (ngModelChange)=\"setValue($event, tool, toolProp)\" />\n }\n @if (tool.name == 'DATAREPEATER' && toolProp.key == 'action' &&\n canOpenArchetypeConfigurator(tool)) {\n <button style=\"margin-top: 8px;\" (click)=\"openArchetypeConfigurator(tool)\">\n {{ 'designer.configure' | translate }} {{ (tool.inputs['action'] | async) || '' }}\n </button>\n }\n @if (toolProp.value.type == 'dropped-component-list') {\n <p-select [style]=\"{width: '100%'}\" appendTo=\"body\"\n [placeholder]=\"'select.component' | translate\" [options]=\"elementFilter(toolProp)\"\n [ngModel]=\"getDroppedComponentListModel(tool, toolProp)\"\n (ngModelChange)=\"setValue($event, tool, toolProp)\" optionLabel=\"uniqueName\"\n dataKey=\"uniqueName\" />\n }\n @if (toolProp.value.type == 'autocomplete') {\n <p-autoComplete [style]=\"{width: '100%'}\" appendTo=\"body\"\n [ngModel]=\"tool.inputs[toolProp.key] | async\" [forceSelection]=\"true\"\n (ngModelChange)=\"setValue($event, tool, toolProp)\" [suggestions]=\"tool.suggestions\"\n [optionLabel]=\"toolProp.value.displayField\" [optionValue]=\"toolProp.value.valueField\"\n (completeMethod)=\"search($event, tool, toolProp)\" [dropdown]=\"true\" dropdownMode=\"current\"\n [multiple]=\"toolProp.value.multiple\" />\n }\n @if (toolProp.value.type == 'propertyTree' && shouldShowPropertyTree(tool)) {\n <ul>\n @for (item of $any(tool.inputs[toolProp.key] | async); track item) {\n <li style=\"padding: 0; margin: 0;\">\n @if (item.col && tool.inputs['customProps_' + (tool.inputs['action'] | async)])\n {\n <wuic-field-editor [field]=\"item.col\"\n [record]=\"tool.inputs['customProps_' + (tool.inputs['action'] | async)]\"\n [metaInfo]=\"tool.inputs['datasource']?.component?.value?.metaInfo\"\n [triggerProp]=\"tool.inputs['action']\"></wuic-field-editor>\n }\n </li>\n }\n </ul>\n }\n </li>\n }\n </ul>\n </details>\n }\n </div>\n </div>\n </ng-template>\n <ng-template pTemplate>\n <div class=\"hierarchy-panel\">\n <div class=\"hierarchy-panel-title\">{{ 'designer.canvas_hierarchy' | translate }}</div>\n <div class=\"hierarchy-tree-wrap\">\n <p-tree [value]=\"hierarchyTreeNodes\" [draggableNodes]=\"true\" [droppableNodes]=\"true\"\n selectionMode=\"single\" [(selection)]=\"hierarchySelection\"\n (onNodeSelect)=\"onHierarchyNodeSelect($event)\" draggableScope=\"designer-hierarchy\"\n droppableScope=\"designer-hierarchy\" (onNodeDrop)=\"onHierarchyNodeDrop($event)\">\n <ng-template let-node pTemplate=\"default\">\n <span class=\"hierarchy-node-label\" [class.active]=\"isHierarchyNodeSelected(node)\"\n (mouseenter)=\"onHierarchyNodeMouseEnter(node)\" (mouseleave)=\"onHierarchyNodeMouseLeave()\"\n (contextmenu)=\"showHierarchyNodeContextMenu($event, node, hierarchyCtxMenu)\">\n {{ node.label }}\n </span>\n </ng-template>\n </p-tree>\n </div>\n <p-contextMenu #hierarchyCtxMenu [model]=\"hierarchyCtxItems\" appendTo=\"body\" />\n </div>\n </ng-template>\n </p-splitter>\n </ng-template>\n </p-splitter>\n </ng-template>\n <ng-template pTemplate>\n <div class=\"col dashboard-design\" [class.designer-hide-datasources]=\"hideDatasourcesInCanvas\" pDroppable\n (onDrop)=\"drop($event)\" #canvas (contextmenu)=\"showCtx($event, this)\">\n <wuic-dashboard [dashboardElements]=\"dashboardElements\"></wuic-dashboard>\n <p-contextMenu [target]=\"canvas\" [model]=\"ctxItems | async\" appendTo=\"body\" />\n </div>\n </ng-template>\n </p-splitter>\n </div>\n <div class=\"designer-footer\">\n <div class=\"designer-footer-label\">{{ 'designer.items' | translate }}:</div>\n <div class=\"designer-footer-list\">\n @for (item of droppedDashboardItems; track item.uniqueName) {\n <button type=\"button\" class=\"designer-footer-item\" [class.active]=\"tool?.uniqueName === item?.uniqueName\"\n [attr.data-designer-unique-name]=\"item?.uniqueName\" (mouseenter)=\"setFooterHoverHighlight(item)\"\n (mouseleave)=\"clearFooterHoverHighlight()\" (click)=\"selectDashboardItem(item)\"\n (contextmenu)=\"showFooterContextMenu($event, item, footerCtxMenu)\">\n {{ getDroppedDashboardItemLabel(item) }}\n </button>\n }\n </div>\n <p-contextMenu #footerCtxMenu [model]=\"footerCtxItems\" appendTo=\"body\" />\n </div>\n</div>\n}\n\n@if (noEdit) {\n<wuic-dashboard [dashboardElements]=\"dashboardElements\"></wuic-dashboard>\n}\n\n<wuic-archetype-configurator [visible]=\"showArchetypeConfigurator\"\n (visibleChange)=\"onArchetypeConfigVisibleChange($event)\" [archetype]=\"getActiveArchetypeConfig()\"\n [metaInfo]=\"getArchetypeConfigMetaInfoForTool(archetypeConfigTool)\"\n [value]=\"getArchetypeConfigValueForTool(archetypeConfigTool)\" (applyConfig)=\"onArchetypeConfigApply($event)\">\n</wuic-archetype-configurator>\n\n<wuic-css-sheet-editor [visible]=\"showCssSheetEditor\" (visibleChange)=\"onCssSheetEditorVisibleChange($event)\"\n [sheets]=\"dashboardCssSheets\" [selectedSheetPath]=\"selectedCssEditorSheetPath\" [assetsFolder]=\"currentDashboardRoute\"\n [styleProps]=\"getCssEditorStyleProps()\" (applyEditor)=\"onCssSheetEditorApply($event)\">\n</wuic-css-sheet-editor>\n\n<p-dialog [header]=\"'designer.reopen_dashboard' | translate\" [(visible)]=\"reopenDashboardDialogVisible\" [modal]=\"true\"\n [closable]=\"true\" [draggable]=\"false\" [resizable]=\"false\" [appendTo]=\"'body'\" [style]=\"{ width: '34rem' }\"\n (onHide)=\"cancelReopenDashboardDialog()\">\n <div class=\"route-popup-form\">\n <label for=\"designer-reopen-select\">{{ 'designer.existing_dashboards' | translate }}</label>\n <p-select inputId=\"designer-reopen-select\" [options]=\"savedDashboardOptions\" optionLabel=\"label\" optionValue=\"value\"\n [(ngModel)]=\"reopenDialogSelectedDashboardRoute\" [filter]=\"true\" [showClear]=\"true\" [style]=\"{ width: '100%' }\"\n appendTo=\"body\" />\n\n <div class=\"route-popup-actions\">\n <button type=\"button\" class=\"route-popup-btn\" (click)=\"cancelReopenDashboardDialog()\">{{ 'cancel' | translate\n }}</button>\n <button type=\"button\" class=\"route-popup-btn route-popup-btn-primary\" (click)=\"confirmReopenDashboardDialog()\"\n [disabled]=\"!reopenDialogSelectedDashboardRoute\">{{ 'designer.reopen' | translate }}</button>\n </div>\n </div>\n</p-dialog>", styles: [":host{display:block;width:100%;height:100%;min-width:0}.designer-layout{display:grid;grid-template-rows:auto minmax(0,1fr) auto;height:100%;min-height:0;width:100%;min-width:0}.designer-toolbar{display:flex;align-items:center;flex-wrap:wrap;gap:8px;border:1px solid inherit;border-radius:10px;background:transparent;padding:10px 12px;margin-bottom:8px}.designer-current-dashboard{min-width:220px;font-size:.86rem;font-weight:700;color:inherit}.designer-toolbar-spacer{flex:1 1 auto;min-width:16px}.designer-toolbar-menu-group{display:inline-flex}.designer-toolbar-menu-group button{height:32px;border:1px solid #2f67d8;border-radius:6px;background:#2f67d8;color:#fff;padding:0 10px;font-size:.82rem;cursor:pointer}.designer-toolbar-menu-group button[disabled]{opacity:.6;cursor:not-allowed}.designer-main{min-height:0;min-width:0;width:100%;overflow:hidden;direction:ltr}.designer-main-splitter{height:100%;width:100%;min-width:0;direction:ltr}.designer-toolbox{font-size:3rem}.designer-toolbox ul{list-style-type:none;padding:0;margin:0;width:100%}.designer-toolbox li{float:left;margin-left:5px;margin-top:5px}.designer-toolbox .tool-groups{display:block}.designer-toolbox .tool-group-item{float:none;margin-left:0;margin-top:10px;clear:both}.designer-toolbox .tool-group-title{font-size:.8rem;font-weight:600;text-transform:uppercase;letter-spacing:.04em;opacity:.7;margin:0 0 4px 6px}.designer-toolbox .tool-group-list{display:flex;flex-wrap:wrap;align-items:flex-start;gap:4px}.designer-toolbox .tool-group-list li{float:none;margin-left:0;margin-top:0}.designer-toolbox .props{font-size:1rem}.designer-toolbox .props li{float:none;padding-bottom:15px}.designer-toolbox .prop-section{width:100%;border:1px solid inherit;border-radius:6px;margin-top:8px;background:transparent;padding:6px 8px 2px}.designer-toolbox .prop-section-title{font-size:.82rem;font-weight:600;text-transform:uppercase;letter-spacing:.04em;cursor:pointer;color:inherit;margin-bottom:6px}.palette-panel{height:100%;overflow:auto;width:100%}.designer-color-alpha-row{display:flex;align-items:center;gap:8px;margin-top:6px;margin-bottom:6px}.designer-color-alpha-label{font-size:.8rem;min-width:40px}.designer-color-alpha-row input[type=range]{flex:1 1 auto;min-width:90px}.designer-color-alpha-row input[type=number]{width:72px}.properties-panel{height:100%;overflow:hidden;width:100%;display:flex;flex-direction:column;min-height:0;padding-right:10px;padding-left:10px;padding-top:10px}.properties-panel>.designer-toolbox{flex:1 1 auto;min-height:0;overflow-y:auto;overflow-x:hidden;scrollbar-gutter:stable}:host ::ng-deep .designer-left-bottom-splitter .p-splitterpanel{min-height:0!important;overflow:hidden!important}.hierarchy-panel{height:100%;width:100%;min-height:0;display:flex;flex-direction:column;overflow:hidden;padding:6px}.hierarchy-panel-title{font-size:.82rem;font-weight:600;text-transform:uppercase;letter-spacing:.04em;color:#4b5563;margin:0 0 6px}.hierarchy-tree-wrap{flex:1 1 auto;min-height:0;border:1px solid inherit;border-radius:6px;background:transparent;overflow:auto;padding:4px}:host ::ng-deep .hierarchy-tree-wrap .p-tree{border:none;padding:0;height:100%;background:transparent}:host ::ng-deep .hierarchy-tree-wrap .p-tree-node-content{border:1px solid transparent;border-radius:4px;background:transparent!important;color:inherit!important;padding:0!important}:host ::ng-deep .hierarchy-tree-wrap .p-tree-node-content.p-highlight{border-color:transparent!important;background:transparent!important;color:inherit!important}:host ::ng-deep .hierarchy-tree-wrap .p-tree-node-content:hover{background:transparent!important;color:inherit!important}:host ::ng-deep .hierarchy-tree-wrap .hierarchy-node-label{display:inline-block;border:1px solid inherit;background:transparent;color:inherit;border-radius:4px;padding:2px 8px;font-size:.8rem;line-height:1.25rem;cursor:pointer}:host ::ng-deep .hierarchy-tree-wrap .hierarchy-node-label:hover{border-color:#94a3b8}:host ::ng-deep .hierarchy-tree-wrap .hierarchy-node-label.active{border-color:#3b82f6!important;background:#eff6ff!important;color:#1d4ed8!important}:host ::ng-deep .hierarchy-tree-wrap .p-tree-node-content.p-highlight .hierarchy-node-label{border-color:#3b82f6!important;background:#eff6ff!important;color:#1d4ed8!important}.designer-hierarchy-node{cursor:pointer;border:1px solid transparent;border-radius:4px;padding:2px 6px;font-size:.82rem;line-height:1.25rem}.designer-hierarchy-node-label{cursor:pointer;display:inline-block;width:100%}.designer-hierarchy-node.active{border-color:#3b82f6;background:#eff6ff;color:#1d4ed8}.designer-footer-item.designer-tree-hover-target{border-color:#3b82f6!important;outline:1px solid #3b82f6!important;outline-offset:-1px}.dashboard-design{height:100%;min-height:0;min-width:0;width:100%;overflow:auto;direction:ltr}:host ::ng-deep .dashboard-design.designer-hide-datasources .datasource-descriptor{display:none!important}.current-dashboard-title{font-weight:600;margin-right:12px}.designer-footer{display:flex;align-items:center;gap:8px;padding:6px 8px;border-top:1px solid inherit;background:transparent;min-height:36px;flex:0 0 auto;width:100%;overflow:auto}.designer-footer-label{font-size:.85rem;font-weight:600;color:inherit;white-space:nowrap}.designer-footer-list{display:flex;align-items:center;gap:6px;overflow-x:auto;overflow-y:hidden;white-space:nowrap;width:100%}.designer-footer-item{border:1px solid inherit;background:transparent;color:inherit;border-radius:4px;padding:2px 8px;font-size:.8rem;line-height:1.25rem;cursor:pointer;white-space:nowrap}.designer-footer-item.active{border-color:#3b82f6;background:transparent;color:#1d4ed8}:host ::ng-deep .designer-footer-hover-target{border-color:#3b82f6!important;outline:1px solid #3b82f6!important;outline-offset:-1px;box-shadow:inset 0 0 0 1px #3b82f6!important}.route-popup-form{display:flex;flex-direction:column;gap:8px}.route-popup-form label{font-size:.85rem;font-weight:600;color:#2f3f58}.route-popup-actions{display:flex;justify-content:flex-end;gap:8px;margin-top:10px}.route-popup-btn{border:1px solid inherit;background:transparent;color:inherit;border-radius:6px;padding:7px 12px;font-size:.85rem;cursor:pointer}.route-popup-btn-primary{border-color:#2f67d8;background:#2f67d8;color:#fff}.p-colorpicker{border:1px solid black;border-radius:4px}\n"] }]
|
|
10867
|
-
}], ctorParameters: () => [{ type: i1$1.ActivatedRoute }, { type: DataProviderService }, { type: MetadataProviderService }, { type: UserInfoService }, { type: i0.ChangeDetectorRef }, { type: TranslationManagerService }, { type: MetadataEditorService }, { type: WorkflowRuntimeMetadataService }], propDecorators: { onWindowHashChange: [{
|
|
11836
|
+
}], ctorParameters: () => [{ type: i1$1.ActivatedRoute }, { type: DataProviderService }, { type: MetadataProviderService }, { type: UserInfoService }, { type: i0.ChangeDetectorRef }, { type: TranslationManagerService }, { type: MetadataEditorService }, { type: ChatbotHostRegistryService }, { type: WorkflowRuntimeMetadataService }], propDecorators: { onWindowHashChange: [{
|
|
10868
11837
|
type: HostListener,
|
|
10869
11838
|
args: ['window:hashchange']
|
|
10870
11839
|
}], onWindowBeforeUnload: [{
|
|
@@ -10888,4 +11857,4 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
10888
11857
|
}] } });
|
|
10889
11858
|
|
|
10890
11859
|
export { DesignerComponent };
|
|
10891
|
-
//# sourceMappingURL=wuic-framework-lib-designer.component-
|
|
11860
|
+
//# sourceMappingURL=wuic-framework-lib-designer.component-N_CfbGnr.mjs.map
|