wuic-framework-lib 1.3.2 → 1.5.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-DAP-TPJR.mjs → wuic-framework-lib-chart-list.component-0J1ghIrS.mjs} +4 -4
- package/fesm2022/{wuic-framework-lib-code-editor.component-CLMDvOWs.mjs → wuic-framework-lib-code-editor.component-C21mvjyr.mjs} +277 -13
- package/fesm2022/{wuic-framework-lib-designer.component-DA7abiLW.mjs → wuic-framework-lib-designer.component-x10ZzUEy.mjs} +1583 -194
- package/fesm2022/{wuic-framework-lib-first-run-wizard.component-BLrUrfZ9.mjs → wuic-framework-lib-first-run-wizard.component-BuSxs_CM.mjs} +7 -7
- package/fesm2022/{wuic-framework-lib-framework-docs.component-DphKhTef.mjs → wuic-framework-lib-framework-docs.component-xTGLbEnp.mjs} +1289 -82
- package/fesm2022/{wuic-framework-lib-pivot-builder.component-BqUUwrJM.mjs → wuic-framework-lib-pivot-builder.component-BkFFjPm4.mjs} +6 -6
- package/fesm2022/{wuic-framework-lib-prompt-dialog-fallback-host.component-DZrhwMH5.mjs → wuic-framework-lib-prompt-dialog-fallback-host.component-Bi-TrJB6.mjs} +2 -2
- package/fesm2022/{wuic-framework-lib-report-designer.component-D_qbefud.mjs → wuic-framework-lib-report-designer.component-ZvB46-LV.mjs} +2 -2
- package/fesm2022/{wuic-framework-lib-report-viewer.component-CIN2fY0P.mjs → wuic-framework-lib-report-viewer.component-BR-BfI2r.mjs} +2 -2
- package/fesm2022/wuic-framework-lib-scene3d-designer.component-BQzFxSXP.mjs +4578 -0
- package/fesm2022/wuic-framework-lib-scene3d-light-baker-gpu-0LjqhSNn.mjs +303 -0
- package/fesm2022/wuic-framework-lib-scene3d-shader-effect-CQLWkuh1.mjs +2227 -0
- package/fesm2022/wuic-framework-lib-scene3d-viewer.component-imoV3HOs.mjs +999 -0
- package/fesm2022/{wuic-framework-lib-scheduler-list.component-CdcI1Uor.mjs → wuic-framework-lib-scheduler-list.component-xzlTEkoq.mjs} +2 -2
- package/fesm2022/{wuic-framework-lib-spreadsheet-list-sf.component-j1HTRI_P.mjs → wuic-framework-lib-spreadsheet-list-sf.component-CrS6fOGj.mjs} +4 -4
- package/fesm2022/wuic-framework-lib-workflow-designer.component-5xtQ7-6z.mjs +9474 -0
- package/fesm2022/{wuic-framework-lib-wuic-framework-lib-BRujEwpR.mjs → wuic-framework-lib-wuic-framework-lib-3TZ-j-yL.mjs} +2615 -719
- package/fesm2022/wuic-framework-lib.mjs +1 -1
- package/package.json +2 -1
- package/types/wuic-framework-lib.d.ts +740 -8
- package/fesm2022/wuic-framework-lib-workflow-designer.component-SE_7ZogW.mjs +0 -5114
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
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';
|
|
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, ViewChild } from '@angular/core';
|
|
3
3
|
import * as i10 from 'primeng/splitter';
|
|
4
4
|
import { SplitterModule } from 'primeng/splitter';
|
|
5
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, C as ChatbotHostRegistryService, 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-3TZ-j-yL.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';
|
|
@@ -17,12 +17,12 @@ import * as i2$1 from 'primeng/colorpicker';
|
|
|
17
17
|
import { ColorPickerModule } from 'primeng/colorpicker';
|
|
18
18
|
import * as i2$2 from 'primeng/autocomplete';
|
|
19
19
|
import { AutoCompleteModule } from 'primeng/autocomplete';
|
|
20
|
-
import * as
|
|
20
|
+
import * as i2$3 from '@ngx-translate/core';
|
|
21
21
|
import { TranslateModule } from '@ngx-translate/core';
|
|
22
22
|
import * as i1$1 from '@angular/router';
|
|
23
23
|
import { UrlSegmentGroup, UrlTree, ActivatedRouteSnapshot, RouterStateSnapshot, RouterState, ActivatedRoute } from '@angular/router';
|
|
24
24
|
import { SafeSubscriber } from 'rxjs/internal/Subscriber';
|
|
25
|
-
import * as
|
|
25
|
+
import * as i10$1 from 'primeng/contextmenu';
|
|
26
26
|
import { ContextMenuModule } from 'primeng/contextmenu';
|
|
27
27
|
import * as i3 from 'primeng/api';
|
|
28
28
|
import { TreeDragDropService } from 'primeng/api';
|
|
@@ -33,7 +33,7 @@ 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
|
|
36
|
+
import * as i7 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';
|
|
@@ -439,6 +439,23 @@ class NgxDraggableDomUtilities {
|
|
|
439
439
|
}
|
|
440
440
|
}
|
|
441
441
|
|
|
442
|
+
/**
|
|
443
|
+
* Stato condiviso dello snap-to-grid del designer.
|
|
444
|
+
*
|
|
445
|
+
* Sorgente di verita' NEUTRA (nessun import Angular) letta dalle direttive di
|
|
446
|
+
* drag (`NgxDraggableDomDirective`) e resize (`AngularResizableDirective`) e
|
|
447
|
+
* scritta da `DesignerComponent.toggleSnapToGrid`. Serve perche' quelle
|
|
448
|
+
* direttive sono applicate agli elementi via TEMPLATE DINAMICO generato
|
|
449
|
+
* (`DynamicDashboardTemplateComponent` riscrive `ngxDraggableDom="true"`), dove
|
|
450
|
+
* non e' pratico bindare un `[snapGridSize]` per-elemento. Un modulo neutro
|
|
451
|
+
* evita anche l'import circolare direttiva <-> componente.
|
|
452
|
+
*
|
|
453
|
+
* `gridSize` = passo in px quando lo snap e' attivo, 0 = snap off.
|
|
454
|
+
*/
|
|
455
|
+
const DesignerSnapState = {
|
|
456
|
+
gridSize: 0,
|
|
457
|
+
};
|
|
458
|
+
|
|
442
459
|
class NgxDraggableDomDirective {
|
|
443
460
|
el;
|
|
444
461
|
renderer;
|
|
@@ -840,6 +857,16 @@ class NgxDraggableDomDirective {
|
|
|
840
857
|
translation = boundsCheck.translation;
|
|
841
858
|
}
|
|
842
859
|
}
|
|
860
|
+
// Designer snap-to-grid: arrotonda la posizione ASSOLUTA dell'elemento al
|
|
861
|
+
// multiplo di griglia, poi riporta il delta nel translation. `offsetLeft`/
|
|
862
|
+
// `offsetTop` NON includono il transform, quindi (offset + translation) e' la
|
|
863
|
+
// posizione visiva reale sul canvas (offsetParent). No-op se snap off.
|
|
864
|
+
const _snapGrid = DesignerSnapState.gridSize;
|
|
865
|
+
if (_snapGrid && _snapGrid > 0) {
|
|
866
|
+
const _dragEl = this.el.nativeElement;
|
|
867
|
+
translation.x = Math.round((_dragEl.offsetLeft + translation.x) / _snapGrid) * _snapGrid - _dragEl.offsetLeft;
|
|
868
|
+
translation.y = Math.round((_dragEl.offsetTop + translation.y) / _snapGrid) * _snapGrid - _dragEl.offsetTop;
|
|
869
|
+
}
|
|
843
870
|
// if it is possible, get the transform from the computed style and modify the matrix to maintain transform properties
|
|
844
871
|
if (window) {
|
|
845
872
|
// update the x and y values as part of the matrix
|
|
@@ -2048,6 +2075,12 @@ class AngularResizableDirective {
|
|
|
2048
2075
|
this._gridSize = { x: this.rzGrid[0], y: this.rzGrid[1] };
|
|
2049
2076
|
}
|
|
2050
2077
|
}
|
|
2078
|
+
else if (DesignerSnapState.gridSize && DesignerSnapState.gridSize > 0) {
|
|
2079
|
+
// Designer snap-to-grid (stato condiviso): usa il passo griglia quando lo
|
|
2080
|
+
// snap e' attivo e nessun `rzGrid` esplicito e' bindato (il designer
|
|
2081
|
+
// applica la direttiva via template dinamico, senza binding per-elemento).
|
|
2082
|
+
this._gridSize = { x: DesignerSnapState.gridSize, y: DesignerSnapState.gridSize };
|
|
2083
|
+
}
|
|
2051
2084
|
}
|
|
2052
2085
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: AngularResizableDirective, deps: [{ token: i0.ElementRef }, { token: i0.Renderer2 }], target: i0.ɵɵFactoryTarget.Directive });
|
|
2053
2086
|
static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.9", type: AngularResizableDirective, isStandalone: true, selector: "[ngResizable]", inputs: { ngResizable: "ngResizable", rzHandles: "rzHandles", rzHandleDoms: "rzHandleDoms", rzAspectRatio: "rzAspectRatio", rzContainment: "rzContainment", rzGrid: "rzGrid", rzMinWidth: "rzMinWidth", rzMinHeight: "rzMinHeight", rzMaxWidth: "rzMaxWidth", rzMaxHeight: "rzMaxHeight", rzScale: "rzScale", preventDefaultEvent: "preventDefaultEvent" }, outputs: { rzStart: "rzStart", rzResizing: "rzResizing", rzStop: "rzStop" }, usesOnChanges: true, ngImport: i0 });
|
|
@@ -2202,11 +2235,11 @@ class TabViewWrapperComponent {
|
|
|
2202
2235
|
});
|
|
2203
2236
|
}
|
|
2204
2237
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: TabViewWrapperComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2205
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: TabViewWrapperComponent, isStandalone: true, selector: "p-tabView", inputs: { tabs: "tabs", activeIndex: "activeIndex" }, ngImport: i0, template: "<div data-pc-name=\"tabview\" class=\"p-tabview p-component\">\n <div class=\"p-tabview-nav-container\">\n <div class=\"p-tabview-nav-content\" data-pc-section=\"navcontent\">\n <ul role=\"tablist\" class=\"p-tabview-nav\" data-pc-section=\"nav\">\n @for (tab of tabs; track tab; let i = $index) {\n <li role=\"presentation\" data-p-disabled=\"false\"\n [ngClass]=\"{'p-highlight':
|
|
2238
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: TabViewWrapperComponent, isStandalone: true, selector: "p-tabView", inputs: { tabs: "tabs", activeIndex: "activeIndex" }, ngImport: i0, template: "<div data-pc-name=\"tabview\" class=\"p-tabview p-component\">\n <div class=\"p-tabview-nav-container\">\n <div class=\"p-tabview-nav-content\" data-pc-section=\"navcontent\">\n <ul role=\"tablist\" class=\"p-tabview-nav\" data-pc-section=\"nav\">\n @for (tab of tabs; track tab; let i = $index) {\n <li role=\"presentation\" data-p-disabled=\"false\"\n [ngClass]=\"{'p-highlight': tab.inputs?.selected}\">\n <button type=\"button\" role=\"tab\" pripple=\"\" class=\"p-element p-ripple p-tabview-nav-link\" (click)=\"setIndex(i)\"\n [attr.aria-selected]=\"tab.inputs?.selected\" [attr.tabindex]=\"i\" aria-disabled=\"false\"\n [attr.data-pc-index]=\"i\" data-pc-section=\"headeraction\">\n <span class=\"p-tabview-title\">\n {{tab.inputs.header}}\n </span>\n </button>\n </li>\n }\n </ul>\n </div>\n </div>\n <div class=\"p-tabview-panels\">\n <ng-content></ng-content>\n </div>\n</div>", styles: [".p-tabview.p-component{height:100%}.p-tabview .p-tabview-nav-content{scroll-padding-inline:3rem}.p-tabview-nav-content{overflow-x:auto;overflow-y:hidden;scroll-behavior:smooth;scrollbar-width:none;overscroll-behavior:contain auto}.p-tabview-nav{display:inline-flex;min-width:100%;margin:0;padding:0;list-style-type:none;flex:1 1 auto}.p-tabview .p-tabview-nav li .p-tabview-nav-link{border-width:0 0 2px 0;padding:1.25rem;font-weight:700;border-top-right-radius:6px;border-top-left-radius:6px;transition:box-shadow .2s;margin:0 0 -2px}.p-tabview-nav-link{appearance:none;background:transparent;border:none;font:inherit;color:inherit;cursor:pointer;-webkit-user-select:none;user-select:none;display:flex;align-items:center;position:relative;text-decoration:none;overflow:hidden;width:100%}.p-tabview .p-tabview-nav li.p-highlight .p-tabview-nav-link{border-color:var(--p-primary-color, #3B82F6);color:var(--p-primary-color, #3B82F6);background-color:transparent}.p-tabview-panels{height:calc(100% - 60px);overflow:auto}\n"], dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }] });
|
|
2206
2239
|
}
|
|
2207
2240
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: TabViewWrapperComponent, decorators: [{
|
|
2208
2241
|
type: Component,
|
|
2209
|
-
args: [{ selector: 'p-tabView', imports: [NgClass], template: "<div data-pc-name=\"tabview\" class=\"p-tabview p-component\">\n <div class=\"p-tabview-nav-container\">\n <div class=\"p-tabview-nav-content\" data-pc-section=\"navcontent\">\n <ul role=\"tablist\" class=\"p-tabview-nav\" data-pc-section=\"nav\">\n @for (tab of tabs; track tab; let i = $index) {\n <li role=\"presentation\" data-p-disabled=\"false\"\n [ngClass]=\"{'p-highlight':
|
|
2242
|
+
args: [{ selector: 'p-tabView', imports: [NgClass], template: "<div data-pc-name=\"tabview\" class=\"p-tabview p-component\">\n <div class=\"p-tabview-nav-container\">\n <div class=\"p-tabview-nav-content\" data-pc-section=\"navcontent\">\n <ul role=\"tablist\" class=\"p-tabview-nav\" data-pc-section=\"nav\">\n @for (tab of tabs; track tab; let i = $index) {\n <li role=\"presentation\" data-p-disabled=\"false\"\n [ngClass]=\"{'p-highlight': tab.inputs?.selected}\">\n <button type=\"button\" role=\"tab\" pripple=\"\" class=\"p-element p-ripple p-tabview-nav-link\" (click)=\"setIndex(i)\"\n [attr.aria-selected]=\"tab.inputs?.selected\" [attr.tabindex]=\"i\" aria-disabled=\"false\"\n [attr.data-pc-index]=\"i\" data-pc-section=\"headeraction\">\n <span class=\"p-tabview-title\">\n {{tab.inputs.header}}\n </span>\n </button>\n </li>\n }\n </ul>\n </div>\n </div>\n <div class=\"p-tabview-panels\">\n <ng-content></ng-content>\n </div>\n</div>", styles: [".p-tabview.p-component{height:100%}.p-tabview .p-tabview-nav-content{scroll-padding-inline:3rem}.p-tabview-nav-content{overflow-x:auto;overflow-y:hidden;scroll-behavior:smooth;scrollbar-width:none;overscroll-behavior:contain auto}.p-tabview-nav{display:inline-flex;min-width:100%;margin:0;padding:0;list-style-type:none;flex:1 1 auto}.p-tabview .p-tabview-nav li .p-tabview-nav-link{border-width:0 0 2px 0;padding:1.25rem;font-weight:700;border-top-right-radius:6px;border-top-left-radius:6px;transition:box-shadow .2s;margin:0 0 -2px}.p-tabview-nav-link{appearance:none;background:transparent;border:none;font:inherit;color:inherit;cursor:pointer;-webkit-user-select:none;user-select:none;display:flex;align-items:center;position:relative;text-decoration:none;overflow:hidden;width:100%}.p-tabview .p-tabview-nav li.p-highlight .p-tabview-nav-link{border-color:var(--p-primary-color, #3B82F6);color:var(--p-primary-color, #3B82F6);background-color:transparent}.p-tabview-panels{height:calc(100% - 60px);overflow:auto}\n"] }]
|
|
2210
2243
|
}], propDecorators: { tabs: [{
|
|
2211
2244
|
type: Input
|
|
2212
2245
|
}], activeIndex: [{
|
|
@@ -3352,6 +3385,23 @@ class DynamicDashboardTemplateComponent {
|
|
|
3352
3385
|
* does not trigger a spurious commit.
|
|
3353
3386
|
*/
|
|
3354
3387
|
static lastDesignerGestureTimestamp = 0;
|
|
3388
|
+
/**
|
|
3389
|
+
* Dimensione della griglia di snap (px) per drag/resize nel designer.
|
|
3390
|
+
* 0 = snap disattivato (comportamento libero, default). Impostato staticamente
|
|
3391
|
+
* da DesignerComponent quando l'utente attiva "snap alla griglia": statico per
|
|
3392
|
+
* lo stesso motivo di lastDesignerGestureTimestamp (ogni elemento ha la propria
|
|
3393
|
+
* istanza di template). onMoving/onResizing arrotondano posizione/dimensione al
|
|
3394
|
+
* multiplo piu' vicino quando > 0.
|
|
3395
|
+
*/
|
|
3396
|
+
static snapGridSize = 0;
|
|
3397
|
+
/** Arrotonda un valore al multiplo di `snapGridSize` (no-op se snap off). */
|
|
3398
|
+
static snap(value) {
|
|
3399
|
+
const g = DynamicDashboardTemplateComponent.snapGridSize;
|
|
3400
|
+
if (!g || g <= 0) {
|
|
3401
|
+
return value;
|
|
3402
|
+
}
|
|
3403
|
+
return Math.round(value / g) * g;
|
|
3404
|
+
}
|
|
3355
3405
|
/**
|
|
3356
3406
|
* Input dal componente padre per inputs; usata nella configurazione e nel rendering del componente.
|
|
3357
3407
|
*/
|
|
@@ -3460,8 +3510,8 @@ class DynamicDashboardTemplateComponent {
|
|
|
3460
3510
|
}
|
|
3461
3511
|
else {
|
|
3462
3512
|
}
|
|
3463
|
-
element.inputs.width = event.size.width + 'px';
|
|
3464
|
-
element.inputs.height = event.size.height + 'px';
|
|
3513
|
+
element.inputs.width = DynamicDashboardTemplateComponent.snap(event.size.width) + 'px';
|
|
3514
|
+
element.inputs.height = DynamicDashboardTemplateComponent.snap(event.size.height) + 'px';
|
|
3465
3515
|
// Mark "designer mutation in progress" so the next window:mouseup in
|
|
3466
3516
|
// DesignerComponent commits a history snapshot. See the field doc.
|
|
3467
3517
|
DynamicDashboardTemplateComponent.lastDesignerGestureTimestamp = Date.now();
|
|
@@ -3492,6 +3542,11 @@ class DynamicDashboardTemplateComponent {
|
|
|
3492
3542
|
scaleY = matrix[3];
|
|
3493
3543
|
}
|
|
3494
3544
|
}
|
|
3545
|
+
// La direttiva ngx-draggable-dom snappa gia' la traslazione alla griglia
|
|
3546
|
+
// ASSOLUTA (offsetLeft/Top + delta), quindi $event.position e' gia' allineato.
|
|
3547
|
+
// NON ri-snappare con DynamicDashboardTemplateComponent.snap(): e' un
|
|
3548
|
+
// arrotondamento grid-STEP che, applicato a una traslazione gia' assoluta,
|
|
3549
|
+
// la disallineava (l'offset visto sui box trascinati).
|
|
3495
3550
|
element.inputs.transform = `matrix(${scaleX}, ${skewY}, ${skewX}, ${scaleY}, ${$event.position.x}, ${$event.position.y})`;
|
|
3496
3551
|
// Mark "designer mutation in progress" so the next window:mouseup in
|
|
3497
3552
|
// DesignerComponent commits a history snapshot. See the field doc.
|
|
@@ -3724,6 +3779,24 @@ class DashboardComponent {
|
|
|
3724
3779
|
* Input dal componente padre per dashboard elements; usata nella configurazione e nel rendering del componente.
|
|
3725
3780
|
*/
|
|
3726
3781
|
dashboardElements;
|
|
3782
|
+
/**
|
|
3783
|
+
* Layout assoluto vs flusso normale: il "modo" con cui la dashboard e' stata
|
|
3784
|
+
* SALVATA e' codificato per-elemento nel boardcontent (`inputs.position`).
|
|
3785
|
+
* Se almeno un figlio diretto e' `absolute`/`fixed`, questo host deve diventare
|
|
3786
|
+
* il contesto di posizionamento dei figli (altrimenti risalgono al viewport e
|
|
3787
|
+
* finiscono sopra il menu fixed). In flusso normale la classe NON viene
|
|
3788
|
+
* applicata -> nessun cambiamento di layout, retrocompatibile.
|
|
3789
|
+
*
|
|
3790
|
+
* Vale sia top-level (host = area dashboard sotto il menu) sia annidato
|
|
3791
|
+
* (host = dentro il container `position:relative` gia' salvato dal designer):
|
|
3792
|
+
* in entrambi i casi il figlio absolute risolve left/top rispetto a questo box.
|
|
3793
|
+
*/
|
|
3794
|
+
get hasAbsoluteLayout() {
|
|
3795
|
+
return (this.dashboardElements || []).some((el) => {
|
|
3796
|
+
const pos = String(el?.inputs?.position || '').trim().toLowerCase();
|
|
3797
|
+
return pos === 'absolute' || pos === 'fixed';
|
|
3798
|
+
});
|
|
3799
|
+
}
|
|
3727
3800
|
/**
|
|
3728
3801
|
* function Object() { [native code] }
|
|
3729
3802
|
*/
|
|
@@ -3755,13 +3828,16 @@ class DashboardComponent {
|
|
|
3755
3828
|
}
|
|
3756
3829
|
;
|
|
3757
3830
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: DashboardComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
3758
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: DashboardComponent, isStandalone: true, selector: "wuic-dashboard", inputs: { dashboardElements: "dashboardElements" }, ngImport: i0, template: "@for (dashboardElement of dashboardElements; track dashboardElement.uniqueName) {\r\n<ng-container *ngComponentOutlet=\"getComponent(dashboardElement); inputs: getComponentInputs(dashboardElement)\"></ng-container>\r\n}\r\n", styles: [""], dependencies: [{ kind: "directive", type: NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletEnvironmentInjector", "ngComponentOutletContent", "ngComponentOutletNgModule"], exportAs: ["ngComponentOutlet"] }] });
|
|
3831
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: DashboardComponent, isStandalone: true, selector: "wuic-dashboard", inputs: { dashboardElements: "dashboardElements" }, host: { properties: { "class.wuic-dashboard-absolute-layout": "this.hasAbsoluteLayout" } }, ngImport: i0, template: "@for (dashboardElement of dashboardElements; track dashboardElement.uniqueName) {\r\n<ng-container *ngComponentOutlet=\"getComponent(dashboardElement); inputs: getComponentInputs(dashboardElement)\"></ng-container>\r\n}\r\n", styles: [":host(.wuic-dashboard-absolute-layout){position:relative;display:block;width:100%;height:100%}\n"], dependencies: [{ kind: "directive", type: NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletEnvironmentInjector", "ngComponentOutletContent", "ngComponentOutletNgModule"], exportAs: ["ngComponentOutlet"] }] });
|
|
3759
3832
|
}
|
|
3760
3833
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: DashboardComponent, decorators: [{
|
|
3761
3834
|
type: Component,
|
|
3762
|
-
args: [{ selector: 'wuic-dashboard', imports: [NgComponentOutlet], template: "@for (dashboardElement of dashboardElements; track dashboardElement.uniqueName) {\r\n<ng-container *ngComponentOutlet=\"getComponent(dashboardElement); inputs: getComponentInputs(dashboardElement)\"></ng-container>\r\n}\r\n" }]
|
|
3835
|
+
args: [{ selector: 'wuic-dashboard', imports: [NgComponentOutlet], template: "@for (dashboardElement of dashboardElements; track dashboardElement.uniqueName) {\r\n<ng-container *ngComponentOutlet=\"getComponent(dashboardElement); inputs: getComponentInputs(dashboardElement)\"></ng-container>\r\n}\r\n", styles: [":host(.wuic-dashboard-absolute-layout){position:relative;display:block;width:100%;height:100%}\n"] }]
|
|
3763
3836
|
}], ctorParameters: () => [], propDecorators: { dashboardElements: [{
|
|
3764
3837
|
type: Input
|
|
3838
|
+
}], hasAbsoluteLayout: [{
|
|
3839
|
+
type: HostBinding,
|
|
3840
|
+
args: ['class.wuic-dashboard-absolute-layout']
|
|
3765
3841
|
}] } });
|
|
3766
3842
|
|
|
3767
3843
|
class CssSheetEditorComponent {
|
|
@@ -4527,79 +4603,12 @@ class DesignerComponent {
|
|
|
4527
4603
|
this.metadataEditorSrv = metadataEditorSrv;
|
|
4528
4604
|
this.chatbotHostRegistry = chatbotHostRegistry;
|
|
4529
4605
|
this.workflowRuntimeMetadata = workflowRuntimeMetadata;
|
|
4530
|
-
|
|
4531
|
-
|
|
4532
|
-
|
|
4533
|
-
|
|
4534
|
-
|
|
4535
|
-
if (this.ctxElement) {
|
|
4536
|
-
this.selectDashboardItem(this.ctxElement);
|
|
4537
|
-
this.cd.detectChanges();
|
|
4538
|
-
}
|
|
4539
|
-
}
|
|
4540
|
-
},
|
|
4541
|
-
{
|
|
4542
|
-
label: this.t('designer.remove', 'Remove'),
|
|
4543
|
-
icon: 'pi pi-trash',
|
|
4544
|
-
command: (menuItem) => {
|
|
4545
|
-
if (this.ctxElement) {
|
|
4546
|
-
this.removeElementByName(this.ctxElement.uniqueName);
|
|
4547
|
-
}
|
|
4548
|
-
}
|
|
4549
|
-
}
|
|
4550
|
-
];
|
|
4606
|
+
// Context menu elemento: UNICA sorgente per tutti e tre i punti di accesso
|
|
4607
|
+
// (canvas / gerarchia / footer breadcrumb) -> `buildElementContextMenuItems`.
|
|
4608
|
+
// I tre handler `show*ContextMenu` ripopolano il rispettivo array con
|
|
4609
|
+
// l'elemento risolto al click, cosi' le voci sono sempre identiche.
|
|
4610
|
+
this.defaultCtxItems = this.buildElementContextMenuItems(null);
|
|
4551
4611
|
this.ctxItems.next(this.defaultCtxItems);
|
|
4552
|
-
this.footerCtxItems = [
|
|
4553
|
-
{
|
|
4554
|
-
label: this.t('designer.edit_properties', 'Edit properties'),
|
|
4555
|
-
icon: 'pi pi-cog',
|
|
4556
|
-
command: () => {
|
|
4557
|
-
if (this.footerCtxElement) {
|
|
4558
|
-
this.selectDashboardItem(this.footerCtxElement);
|
|
4559
|
-
this.cd.detectChanges();
|
|
4560
|
-
}
|
|
4561
|
-
}
|
|
4562
|
-
},
|
|
4563
|
-
{
|
|
4564
|
-
label: this.t('designer.rename', 'Rename'),
|
|
4565
|
-
icon: 'pi pi-pencil',
|
|
4566
|
-
command: () => {
|
|
4567
|
-
this.renameFooterContextItem();
|
|
4568
|
-
}
|
|
4569
|
-
},
|
|
4570
|
-
{
|
|
4571
|
-
label: this.t('designer.remove', 'Remove'),
|
|
4572
|
-
icon: 'pi pi-trash',
|
|
4573
|
-
command: () => {
|
|
4574
|
-
const uniqueName = String(this.footerCtxElement?.uniqueName || '').trim();
|
|
4575
|
-
if (uniqueName) {
|
|
4576
|
-
this.removeElementByName(uniqueName);
|
|
4577
|
-
}
|
|
4578
|
-
}
|
|
4579
|
-
}
|
|
4580
|
-
];
|
|
4581
|
-
this.hierarchyCtxItems = [
|
|
4582
|
-
{
|
|
4583
|
-
label: this.t('designer.edit_properties', 'Edit properties'),
|
|
4584
|
-
icon: 'pi pi-cog',
|
|
4585
|
-
command: () => {
|
|
4586
|
-
if (this.hierarchyCtxElement) {
|
|
4587
|
-
this.selectDashboardItem(this.hierarchyCtxElement);
|
|
4588
|
-
this.cd.detectChanges();
|
|
4589
|
-
}
|
|
4590
|
-
}
|
|
4591
|
-
},
|
|
4592
|
-
{
|
|
4593
|
-
label: this.t('designer.remove', 'Remove'),
|
|
4594
|
-
icon: 'pi pi-trash',
|
|
4595
|
-
command: () => {
|
|
4596
|
-
const uniqueName = String(this.hierarchyCtxElement?.uniqueName || '').trim();
|
|
4597
|
-
if (uniqueName) {
|
|
4598
|
-
this.removeElementByName(uniqueName);
|
|
4599
|
-
}
|
|
4600
|
-
}
|
|
4601
|
-
}
|
|
4602
|
-
];
|
|
4603
4612
|
this.htmlStyleString = '[ngClass]="0.cssClass" [ngStyle]="{width: 0.width, maxWidth: 0.maxWidth, height: 0.height, maxHeight: 0.maxHeight, minWidth: 0.minWidth, minHeight: 0.minHeight, position: 0.position, left: 0.left, top: 0.top, float: 0.float, display: 0.display, boxSizing: 0.boxSizing, backgroundColor: 0.backgroundColor, color: 0.color, borderRadius: 0.borderRadius, borderBottomStyle: 0.borderBottomStyle, borderBottomWidth: 0.borderBottomWidth, borderBottomColor: 0.borderBottomColor, borderLeftStyle: 0.borderLeftStyle, borderLeftWidth: 0.borderLeftWidth, borderLeftColor: 0.borderLeftColor, borderRightStyle: 0.borderRightStyle, borderRightWidth: 0.borderRightWidth, borderRightColor: 0.borderRightColor, borderTopStyle: 0.borderTopStyle, borderTopWidth: 0.borderTopWidth, borderTopColor: 0.borderTopColor, paddingTop: 0.paddingTop, paddingRight: 0.paddingRight, paddingBottom: 0.paddingBottom, paddingLeft: 0.paddingLeft, marginTop: 0.marginTop, marginRight: 0.marginRight, marginBottom: 0.marginBottom, marginLeft: 0.marginLeft, fontSize: 0.fontSize, fontFamily: 0.fontFamily, fontWeight: 0.fontWeight, fontStyle: 0.fontStyle, textDecoration: 0.textDecoration, textAlign: 0.textAlign, verticalAlign: 0.verticalAlign, lineHeight: 0.lineHeight, letterSpacing: 0.letterSpacing, wordSpacing: 0.wordSpacing, textTransform: 0.textTransform, textIndent: 0.textIndent, whiteSpace: 0.whiteSpace, wordWrap: 0.wordWrap, wordBreak: 0.wordBreak, overflow: 0.overflow, overflowX: 0.overflowX, overflowY: 0.overflowY, zIndex: 0.zIndex, cursor: 0.cursor, visibility: 0.visibility, opacity: 0.opacity, filter: 0.filter, transform: 0.transform, transformOrigin: 0.transformOrigin, transition: 0.transition, boxShadow: 0.boxShadow, textShadow: 0.textShadow, backgroundPosition: 0.backgroundPosition, backgroundRepeat: 0.backgroundRepeat, backgroundAttachment: 0.backgroundAttachment, backgroundSize: 0.backgroundSize, backgroundImage: 0.backgroundImage, backgroundClip: 0.backgroundClip, backgroundOrigin: 0.backgroundOrigin, backgroundBlendMode: 0.backgroundBlendMode, clip: 0.clip, clipPath: 0.clipPath, clipRule: 0.clipRule, mask: 0.mask, maskType: 0.maskType, maskImage: 0.maskImage, maskMode: 0.maskMode, maskSize: 0.maskSize, maskRepeat: 0.maskRepeat, maskPosition: 0.maskPosition, maskClip: 0.maskClip, maskOrigin: 0.maskOrigin, maskComposite: 0.maskComposite, maskBorder: 0.maskBorder, maskBorderSource: 0.maskBorderSource, maskBorderSlice: 0.maskBorderSlice, maskBorderWidth: 0.maskBorderWidth, maskBorderOutset: 0.maskBorderOutset, maskBorderRepeat: 0.maskBorderRepeat, maskBorderMode: 0.maskBorderMode, maskBorderClip: 0.maskBorderClip, maskBorderOrigin: 0.maskBorderOrigin, maskBorder, tableLayout: 0.tableLayout, borderCollapse: 0.borderCollapse, borderSpacing: 0.borderSpacing, captionSide: 0.captionSide, emptyCells: 0.emptyCells, tableCaption: 0.tableCaption, tableBorder: 0.tableBorder, tableBorderSpacing: 0.tableBorderSpacing, tableBorderCollapse: 0.tableBorderCollapse, listStyle: 0.listStyle}"';
|
|
4604
4613
|
this.htmlInputProperties = {
|
|
4605
4614
|
animation: { type: 'string' },
|
|
@@ -5108,6 +5117,27 @@ class DesignerComponent {
|
|
|
5108
5117
|
height: '280px'
|
|
5109
5118
|
})
|
|
5110
5119
|
},
|
|
5120
|
+
{
|
|
5121
|
+
group: 'HTML',
|
|
5122
|
+
toolId: 130,
|
|
5123
|
+
name: 'IMAGE',
|
|
5124
|
+
tag: `<wuic-image-wrapper-lazy [attr.id]="uniqueName" [src]="inputs.src" [alt]="inputs.alt" [preview]="inputs.preview" [previewImageSrc]="inputs.previewImageSrc" [databound]="inputs.datasource?.component" [bindingFunction]="inputs.bindingFunction" [inputs]="inputs" ngxDraggableDom="true" ngResizable [rzHandles] = "'se'" (rzResizing)="onResizing($event, this)" (moved)="onMoving($event, this)" ${this.htmlStyleString.replace(/0/g, 'inputs')}></wuic-image-wrapper-lazy>`,
|
|
5125
|
+
icon: 'pi pi-images',
|
|
5126
|
+
inputProps: Object.assign({}, this.htmlInputProperties, {
|
|
5127
|
+
src: { type: 'string', propertyCaption: 'Source URL' },
|
|
5128
|
+
previewImageSrc: { type: 'string', propertyCaption: 'Preview (full-size) URL' },
|
|
5129
|
+
alt: { type: 'string', propertyCaption: 'Alt text' },
|
|
5130
|
+
preview: { type: 'boolean', propertyCaption: 'Enable lightbox preview' }
|
|
5131
|
+
}),
|
|
5132
|
+
inputs: Object.assign({}, this.htmlInputs, {
|
|
5133
|
+
src: '',
|
|
5134
|
+
previewImageSrc: '',
|
|
5135
|
+
alt: '',
|
|
5136
|
+
preview: true,
|
|
5137
|
+
width: '240px',
|
|
5138
|
+
height: '160px'
|
|
5139
|
+
})
|
|
5140
|
+
},
|
|
5111
5141
|
{
|
|
5112
5142
|
group: 'HTML',
|
|
5113
5143
|
toolId: 7,
|
|
@@ -5476,7 +5506,10 @@ class DesignerComponent {
|
|
|
5476
5506
|
selected: { type: 'boolean', propertyCaption: 'Selected', hide: true }
|
|
5477
5507
|
},
|
|
5478
5508
|
inputs: { header: '<header>' },
|
|
5479
|
-
nestedComponents: []
|
|
5509
|
+
nestedComponents: [],
|
|
5510
|
+
// Appena droppato dentro un TABVIEW il tab deve diventare quello attivo,
|
|
5511
|
+
// altrimenti la sua area non e' visibile e non ci si possono droppare figli.
|
|
5512
|
+
onDrop: this.dropTabPanel.bind(this)
|
|
5480
5513
|
},
|
|
5481
5514
|
{
|
|
5482
5515
|
group: 'CONTAINER',
|
|
@@ -5722,7 +5755,18 @@ class DesignerComponent {
|
|
|
5722
5755
|
// Aggiorna il last top-level per i prossimi placeholder. NB: usiamo
|
|
5723
5756
|
// findLastInjectedNode per pescare l'istanza vera (con onDrop appena
|
|
5724
5757
|
// eseguito = TR/TD/AREA gia' popolati), non il template clonato.
|
|
5725
|
-
|
|
5758
|
+
// FIX 2026-06-21 (designer-canvas-verify [2], layout 3-colonne): aggiorna
|
|
5759
|
+
// SOLO se il nodo iniettato e' un CONTAINER che auto-crea celle/aree
|
|
5760
|
+
// referenziabili via placeholder (TABLE/SPLITTER/ACCORDION/TABVIEW). I
|
|
5761
|
+
// leaf (DATASOURCE/DATAREPEATER/LABEL/...) NON devono sostituirlo: altrimenti
|
|
5762
|
+
// i placeholder <TD-N>/<AREA-N> dei nodi SUCCESSIVI risolverebbero contro
|
|
5763
|
+
// il leaf invece del container -> errore "Placeholder '<TD-0>' non
|
|
5764
|
+
// risolvibile dentro DATASOURCE__6" e i DATAREPEATER restano 0/3. Cosi'
|
|
5765
|
+
// tutti i nodi di un layout multi-cella risolvono contro lo stesso container.
|
|
5766
|
+
const PLACEHOLDER_CONTAINERS = ['TABLE', 'SPLITTER', 'ACCORDION', 'TABVIEW'];
|
|
5767
|
+
if (PLACEHOLDER_CONTAINERS.indexOf(String(node.tool_name || '').toUpperCase()) >= 0) {
|
|
5768
|
+
lastTopLevelInjected = this.findLastInjectedNode(String(node.tool_name || ''), topParent) || lastTopLevelInjected;
|
|
5769
|
+
}
|
|
5726
5770
|
}
|
|
5727
5771
|
const rationale = (action?.['rationale'] || '').toString().slice(0, 100);
|
|
5728
5772
|
return { ok: true, message: `Iniettati ${totalInjected} elementi nel designer${rationale ? ': ' + rationale : ''}.` };
|
|
@@ -7454,11 +7498,53 @@ class DesignerComponent {
|
|
|
7454
7498
|
if (this.isTypingTarget(event.target)) {
|
|
7455
7499
|
return;
|
|
7456
7500
|
}
|
|
7501
|
+
const rawKey = String(event.key || '');
|
|
7457
7502
|
const ctrlOrMeta = !!event.ctrlKey || !!event.metaKey;
|
|
7503
|
+
// ---- Scorciatoie SENZA modificatore (feature B) ----
|
|
7458
7504
|
if (!ctrlOrMeta) {
|
|
7505
|
+
// Delete/Backspace: rimuove l'elemento selezionato (o TUTTI i multi-selezionati).
|
|
7506
|
+
if (rawKey === 'Delete' || rawKey === 'Backspace') {
|
|
7507
|
+
if (this.selectedTools.length > 1) {
|
|
7508
|
+
event.preventDefault();
|
|
7509
|
+
const names = this.selectedTools.map((t) => t.uniqueName);
|
|
7510
|
+
this.clearMultiSelection();
|
|
7511
|
+
names.forEach((n) => this.removeElementByName(n));
|
|
7512
|
+
this.commitHistoryIfChanged();
|
|
7513
|
+
return;
|
|
7514
|
+
}
|
|
7515
|
+
if (this.tool?.uniqueName) {
|
|
7516
|
+
event.preventDefault();
|
|
7517
|
+
this.removeElementByName(this.tool.uniqueName);
|
|
7518
|
+
this.commitHistoryIfChanged();
|
|
7519
|
+
return;
|
|
7520
|
+
}
|
|
7521
|
+
}
|
|
7522
|
+
// Frecce: nudge di posizione dell'elemento selezionato (passo = gridSize se
|
|
7523
|
+
// snap attivo, altrimenti 1px; con Shift un passo piu' grande).
|
|
7524
|
+
const nudges = {
|
|
7525
|
+
ArrowLeft: [-1, 0], ArrowRight: [1, 0], ArrowUp: [0, -1], ArrowDown: [0, 1]
|
|
7526
|
+
};
|
|
7527
|
+
if (nudges[rawKey] && (this.tool || this.selectedTools.length > 1)) {
|
|
7528
|
+
event.preventDefault();
|
|
7529
|
+
const step = (this.snapToGridEnabled ? this.gridSize : 1) * (event.shiftKey ? 5 : 1);
|
|
7530
|
+
const ux = nudges[rawKey][0] * step, uy = nudges[rawKey][1] * step;
|
|
7531
|
+
if (this.selectedTools.length > 1) {
|
|
7532
|
+
this.nudgeElements(this.selectedTools, ux, uy);
|
|
7533
|
+
}
|
|
7534
|
+
else {
|
|
7535
|
+
this.nudgeSelectedElement(ux, uy);
|
|
7536
|
+
}
|
|
7537
|
+
return;
|
|
7538
|
+
}
|
|
7539
|
+
return;
|
|
7540
|
+
}
|
|
7541
|
+
const key = rawKey.toLowerCase();
|
|
7542
|
+
// Ctrl/Cmd+S: salva (previene il dialog di salvataggio del browser).
|
|
7543
|
+
if (key === 's') {
|
|
7544
|
+
event.preventDefault();
|
|
7545
|
+
void this.saveDashboard();
|
|
7459
7546
|
return;
|
|
7460
7547
|
}
|
|
7461
|
-
const key = String(event.key || '').toLowerCase();
|
|
7462
7548
|
if (key === 'z' && event.shiftKey) {
|
|
7463
7549
|
event.preventDefault();
|
|
7464
7550
|
this.redo();
|
|
@@ -7472,6 +7558,28 @@ class DesignerComponent {
|
|
|
7472
7558
|
if (key === 'y') {
|
|
7473
7559
|
event.preventDefault();
|
|
7474
7560
|
this.redo();
|
|
7561
|
+
return;
|
|
7562
|
+
}
|
|
7563
|
+
if (key === 'd') {
|
|
7564
|
+
event.preventDefault();
|
|
7565
|
+
void this.duplicateDesignerElement(this.tool);
|
|
7566
|
+
return;
|
|
7567
|
+
}
|
|
7568
|
+
if (key === 'c') {
|
|
7569
|
+
// Non intercettare Ctrl+C se l'utente sta copiando una selezione di testo.
|
|
7570
|
+
const textSelection = (typeof window !== 'undefined' && window.getSelection)
|
|
7571
|
+
? String(window.getSelection() || '')
|
|
7572
|
+
: '';
|
|
7573
|
+
if (textSelection) {
|
|
7574
|
+
return;
|
|
7575
|
+
}
|
|
7576
|
+
event.preventDefault();
|
|
7577
|
+
this.copyDesignerElement(this.tool);
|
|
7578
|
+
return;
|
|
7579
|
+
}
|
|
7580
|
+
if (key === 'v') {
|
|
7581
|
+
event.preventDefault();
|
|
7582
|
+
void this.pasteDesignerElement();
|
|
7475
7583
|
}
|
|
7476
7584
|
}
|
|
7477
7585
|
/**
|
|
@@ -7697,12 +7805,49 @@ class DesignerComponent {
|
|
|
7697
7805
|
{
|
|
7698
7806
|
separator: true
|
|
7699
7807
|
},
|
|
7808
|
+
{
|
|
7809
|
+
label: this.t('designer.menu_export_json', 'Esporta JSON'),
|
|
7810
|
+
icon: 'pi pi-download',
|
|
7811
|
+
command: () => this.exportDashboardJson()
|
|
7812
|
+
},
|
|
7813
|
+
{
|
|
7814
|
+
label: this.t('designer.menu_import_json', 'Importa JSON'),
|
|
7815
|
+
icon: 'pi pi-upload',
|
|
7816
|
+
command: () => this.importDashboardJson()
|
|
7817
|
+
},
|
|
7818
|
+
{
|
|
7819
|
+
label: this.t('designer.menu_save_preset', 'Salva come preset'),
|
|
7820
|
+
icon: 'pi pi-bookmark',
|
|
7821
|
+
command: () => this.saveDashboardAsPreset()
|
|
7822
|
+
},
|
|
7823
|
+
{
|
|
7824
|
+
label: this.t('designer.menu_apply_preset', 'Applica preset'),
|
|
7825
|
+
icon: 'pi pi-bookmark-fill',
|
|
7826
|
+
command: () => this.applyDashboardPreset()
|
|
7827
|
+
},
|
|
7828
|
+
{
|
|
7829
|
+
separator: true
|
|
7830
|
+
},
|
|
7700
7831
|
{
|
|
7701
7832
|
label: this.hideDatasourcesInCanvas
|
|
7702
7833
|
? this.t('designer.menu_show_datasources', 'Mostra datasource nel canvas')
|
|
7703
7834
|
: this.t('designer.menu_hide_datasources', 'Nascondi datasource nel canvas'),
|
|
7704
7835
|
icon: this.hideDatasourcesInCanvas ? 'pi pi-eye' : 'pi pi-eye-slash',
|
|
7705
7836
|
command: () => this.toggleHideDatasourcesInCanvas()
|
|
7837
|
+
},
|
|
7838
|
+
{
|
|
7839
|
+
label: this.snapToGridEnabled
|
|
7840
|
+
? this.t('designer.menu_snap_off', 'Disattiva snap alla griglia')
|
|
7841
|
+
: this.t('designer.menu_snap_on', 'Attiva snap alla griglia'),
|
|
7842
|
+
icon: this.snapToGridEnabled ? 'pi pi-th-large' : 'pi pi-table',
|
|
7843
|
+
command: () => this.toggleSnapToGrid()
|
|
7844
|
+
},
|
|
7845
|
+
{
|
|
7846
|
+
label: this.absoluteFlowEnabled
|
|
7847
|
+
? this.t('designer.menu_absflow_off', 'Disattiva flusso assoluto')
|
|
7848
|
+
: this.t('designer.menu_absflow_on', 'Attiva flusso assoluto'),
|
|
7849
|
+
icon: 'pi pi-arrows-alt',
|
|
7850
|
+
command: () => this.toggleAbsoluteFlow()
|
|
7706
7851
|
}
|
|
7707
7852
|
];
|
|
7708
7853
|
}
|
|
@@ -7710,112 +7855,1164 @@ class DesignerComponent {
|
|
|
7710
7855
|
this.hideDatasourcesInCanvas = !this.hideDatasourcesInCanvas;
|
|
7711
7856
|
this.refreshGraphActionsMenuItems();
|
|
7712
7857
|
}
|
|
7713
|
-
|
|
7714
|
-
|
|
7715
|
-
|
|
7716
|
-
|
|
7717
|
-
|
|
7858
|
+
/**
|
|
7859
|
+
* Flusso "assoluto" (default OFF = flusso normale come sempre). Quando ON, ogni
|
|
7860
|
+
* elemento droppato nasce `position: absolute` con `left/top` dalla posizione
|
|
7861
|
+
* del drop -> non partecipa al flusso, quindi ridimensionarne uno NON sposta gli
|
|
7862
|
+
* altri. E' per-elemento (chi nasce con position:absolute segue il nuovo modello,
|
|
7863
|
+
* gli elementi salvati senza restano a flusso) -> retrocompatibile senza flag globale.
|
|
7864
|
+
*/
|
|
7865
|
+
absoluteFlowEnabled = false;
|
|
7866
|
+
toggleAbsoluteFlow() {
|
|
7867
|
+
const turningOn = !this.absoluteFlowEnabled;
|
|
7868
|
+
// Se attivo il flusso assoluto con elementi gia' nel canvas, quelli restano a
|
|
7869
|
+
// flusso: notifico che vanno riposizionati manualmente.
|
|
7870
|
+
if (turningOn && (this.dashboardElements?.length || 0) > 0) {
|
|
7871
|
+
WtoolboxService.messageNotificationService.add({
|
|
7872
|
+
severity: 'warn',
|
|
7873
|
+
summary: this.t('designer.absflow.title', 'Flusso assoluto'),
|
|
7874
|
+
detail: this.t('designer.absflow.existing_manual', 'Gli elementi gia\' presenti restano nel flusso attuale: vanno riposizionati a mano (position/left/top) per adottare il layout assoluto.')
|
|
7875
|
+
});
|
|
7876
|
+
}
|
|
7877
|
+
this.absoluteFlowEnabled = turningOn;
|
|
7878
|
+
this.refreshGraphActionsMenuItems();
|
|
7879
|
+
this.cd.detectChanges();
|
|
7718
7880
|
}
|
|
7719
|
-
|
|
7720
|
-
|
|
7881
|
+
/**
|
|
7882
|
+
* Posizione del drop relativa a un elemento di riferimento (il canvas per i drop
|
|
7883
|
+
* top-level, oppure il container quando droppo dentro un contenitore), snappata
|
|
7884
|
+
* alla griglia se lo snap e' attivo.
|
|
7885
|
+
*/
|
|
7886
|
+
getDropPositionRelativeTo(event, refEl) {
|
|
7887
|
+
const rect = refEl ? refEl.getBoundingClientRect() : null;
|
|
7888
|
+
const oe = (event && event.originalEvent) ? event.originalEvent : event;
|
|
7889
|
+
const cx = (typeof oe?.clientX === 'number') ? oe.clientX : (typeof event?.clientX === 'number' ? event.clientX : null);
|
|
7890
|
+
const cy = (typeof oe?.clientY === 'number') ? oe.clientY : (typeof event?.clientY === 'number' ? event.clientY : null);
|
|
7891
|
+
if (!rect || cx === null || cy === null) {
|
|
7892
|
+
return { left: 0, top: 0 };
|
|
7893
|
+
}
|
|
7894
|
+
// Coordinate del cursore relative al box del riferimento (client rect gia'
|
|
7895
|
+
// include l'eventuale bordo/scroll del riferimento: sottraggo il suo scroll).
|
|
7896
|
+
let left = cx - rect.left + (refEl ? refEl.scrollLeft : 0);
|
|
7897
|
+
let top = cy - rect.top + (refEl ? refEl.scrollTop : 0);
|
|
7898
|
+
const g = DesignerSnapState.gridSize;
|
|
7899
|
+
if (g && g > 0) {
|
|
7900
|
+
left = Math.round(left / g) * g;
|
|
7901
|
+
top = Math.round(top / g) * g;
|
|
7902
|
+
}
|
|
7903
|
+
return { left: Math.max(0, Math.round(left)), top: Math.max(0, Math.round(top)) };
|
|
7904
|
+
}
|
|
7905
|
+
/** Snap alla griglia per drag/resize (feature B). Off di default. */
|
|
7906
|
+
snapToGridEnabled = false;
|
|
7907
|
+
/** Passo della griglia di snap in px. */
|
|
7908
|
+
gridSize = 10;
|
|
7909
|
+
/**
|
|
7910
|
+
* Attiva/disattiva lo snap alla griglia: propaga la dimensione griglia alle
|
|
7911
|
+
* istanze DynamicDashboardTemplateComponent (che arrotondano posizione/dimensione
|
|
7912
|
+
* in onMoving/onResizing) e mostra un overlay griglia sul canvas.
|
|
7913
|
+
*/
|
|
7914
|
+
toggleSnapToGrid() {
|
|
7915
|
+
this.snapToGridEnabled = !this.snapToGridEnabled;
|
|
7916
|
+
DynamicDashboardTemplateComponent.snapGridSize = this.snapToGridEnabled ? this.gridSize : 0;
|
|
7917
|
+
// Alimenta lo stato condiviso letto dalle direttive drag/resize (lo snap
|
|
7918
|
+
// effettivo su move/resize avviene li', non solo nel bookkeeping onMoving).
|
|
7919
|
+
DesignerSnapState.gridSize = this.snapToGridEnabled ? this.gridSize : 0;
|
|
7920
|
+
// Attivando lo snap, allinea SUBITO alla griglia anche gli elementi gia'
|
|
7921
|
+
// presenti (droppati/spostati prima di attivarlo).
|
|
7922
|
+
if (this.snapToGridEnabled) {
|
|
7923
|
+
this.snapAllExistingElementsToGrid();
|
|
7924
|
+
}
|
|
7925
|
+
this.refreshGraphActionsMenuItems();
|
|
7926
|
+
this.cd.detectChanges();
|
|
7721
7927
|
}
|
|
7722
|
-
|
|
7723
|
-
|
|
7928
|
+
/** Snappa alla griglia la posizione assoluta di tutti gli elementi top-level presenti. */
|
|
7929
|
+
snapAllExistingElementsToGrid() {
|
|
7930
|
+
const g = DesignerSnapState.gridSize;
|
|
7931
|
+
if (!g || g <= 0) {
|
|
7932
|
+
return;
|
|
7933
|
+
}
|
|
7934
|
+
setTimeout(() => {
|
|
7935
|
+
let changed = false;
|
|
7936
|
+
for (const tool of (this.dashboardElements || [])) {
|
|
7937
|
+
if (this.snapElementAbsoluteToGrid(tool, g)) {
|
|
7938
|
+
changed = true;
|
|
7939
|
+
}
|
|
7940
|
+
}
|
|
7941
|
+
if (changed) {
|
|
7942
|
+
this.commitHistoryIfChanged();
|
|
7943
|
+
this.cd.detectChanges();
|
|
7944
|
+
}
|
|
7945
|
+
}, 0);
|
|
7724
7946
|
}
|
|
7725
7947
|
/**
|
|
7726
|
-
*
|
|
7948
|
+
* Allinea la posizione VISIVA assoluta di un elemento al multiplo di griglia
|
|
7949
|
+
* agendo sulla traslazione del suo `transform` (offsetLeft/Top escludono il
|
|
7950
|
+
* transform -> assoluto = offset + traslazione). Ritorna true se ha modificato.
|
|
7727
7951
|
*/
|
|
7728
|
-
|
|
7729
|
-
const
|
|
7730
|
-
if (
|
|
7731
|
-
return
|
|
7952
|
+
snapElementAbsoluteToGrid(tool, g) {
|
|
7953
|
+
const dom = tool?.uniqueName ? document.getElementById(tool.uniqueName) : null;
|
|
7954
|
+
if (!dom || !tool.inputs) {
|
|
7955
|
+
return false;
|
|
7732
7956
|
}
|
|
7733
|
-
|
|
7734
|
-
const
|
|
7735
|
-
if (
|
|
7736
|
-
|
|
7957
|
+
let sx = 1, sy = 0, kx = 0, ky = 1, tx = 0, ty = 0;
|
|
7958
|
+
const m = String(tool.inputs['transform'] || '').match(/matrix\(([^)]+)\)/);
|
|
7959
|
+
if (m) {
|
|
7960
|
+
const p = m[1].split(',').map((x) => parseFloat(x));
|
|
7961
|
+
if (p.length >= 6 && p.every((n) => !isNaN(n))) {
|
|
7962
|
+
[sx, sy, kx, ky, tx, ty] = p;
|
|
7963
|
+
}
|
|
7737
7964
|
}
|
|
7738
|
-
|
|
7965
|
+
const newTx = Math.round((dom.offsetLeft + tx) / g) * g - dom.offsetLeft;
|
|
7966
|
+
const newTy = Math.round((dom.offsetTop + ty) / g) * g - dom.offsetTop;
|
|
7967
|
+
if (newTx === tx && newTy === ty) {
|
|
7968
|
+
return false;
|
|
7969
|
+
}
|
|
7970
|
+
tool.inputs['transform'] = `matrix(${sx}, ${sy}, ${kx}, ${ky}, ${newTx}, ${newTy})`;
|
|
7971
|
+
return true;
|
|
7739
7972
|
}
|
|
7740
7973
|
/**
|
|
7741
|
-
*
|
|
7974
|
+
* Sposta l'elemento selezionato di (dx, dy) px agendo sulla translation della
|
|
7975
|
+
* matrice `transform` (stesso campo che usa onMoving). Usato dalle frecce.
|
|
7742
7976
|
*/
|
|
7743
|
-
|
|
7744
|
-
|
|
7977
|
+
nudgeSelectedElement(dx, dy) {
|
|
7978
|
+
const el = this.tool;
|
|
7979
|
+
if (!el?.inputs) {
|
|
7745
7980
|
return;
|
|
7746
7981
|
}
|
|
7747
|
-
|
|
7748
|
-
|
|
7749
|
-
|
|
7750
|
-
|
|
7751
|
-
|
|
7752
|
-
|
|
7753
|
-
|
|
7754
|
-
|
|
7755
|
-
|
|
7982
|
+
let scaleX = 1, skewY = 0, skewX = 0, scaleY = 1, tx = 0, ty = 0;
|
|
7983
|
+
const cur = String(el.inputs['transform'] || '');
|
|
7984
|
+
const m = cur.match(/matrix\(([^)]+)\)/);
|
|
7985
|
+
if (m) {
|
|
7986
|
+
const parts = m[1].split(',').map((x) => parseFloat(x));
|
|
7987
|
+
if (parts.length >= 6 && parts.every((n) => !isNaN(n))) {
|
|
7988
|
+
[scaleX, skewY, skewX, scaleY, tx, ty] = parts;
|
|
7989
|
+
}
|
|
7990
|
+
}
|
|
7991
|
+
el.inputs['transform'] = `matrix(${scaleX}, ${skewY}, ${skewX}, ${scaleY}, ${tx + dx}, ${ty + dy})`;
|
|
7992
|
+
this.commitHistoryIfChanged();
|
|
7993
|
+
this.cd.detectChanges();
|
|
7756
7994
|
}
|
|
7757
|
-
|
|
7758
|
-
|
|
7759
|
-
|
|
7760
|
-
|
|
7995
|
+
// ===================== Feature F: preset/template riusabili =====================
|
|
7996
|
+
// Libreria di preset lato client (localStorage): mappa nome -> boardcontent JSON.
|
|
7997
|
+
// Riusa buildSerializedDashboardElements (salva) e loadDashboard (applica), gli
|
|
7998
|
+
// stessi choke-point di save/import. Nessuno schema change.
|
|
7999
|
+
static PRESETS_STORAGE_KEY = 'wuic.designer.presets';
|
|
8000
|
+
readDesignerPresets() {
|
|
8001
|
+
try {
|
|
8002
|
+
const raw = localStorage.getItem(DesignerComponent.PRESETS_STORAGE_KEY);
|
|
8003
|
+
const parsed = raw ? JSON.parse(raw) : {};
|
|
8004
|
+
return (parsed && typeof parsed === 'object') ? parsed : {};
|
|
7761
8005
|
}
|
|
7762
|
-
|
|
7763
|
-
|
|
7764
|
-
return;
|
|
8006
|
+
catch {
|
|
8007
|
+
return {};
|
|
7765
8008
|
}
|
|
7766
|
-
|
|
7767
|
-
|
|
7768
|
-
|
|
8009
|
+
}
|
|
8010
|
+
writeDesignerPresets(presets) {
|
|
8011
|
+
try {
|
|
8012
|
+
localStorage.setItem(DesignerComponent.PRESETS_STORAGE_KEY, JSON.stringify(presets));
|
|
7769
8013
|
}
|
|
7770
|
-
|
|
7771
|
-
this.cd.detectChanges();
|
|
8014
|
+
catch { /* quota / storage disabilitato */ }
|
|
7772
8015
|
}
|
|
7773
|
-
|
|
7774
|
-
|
|
7775
|
-
|
|
8016
|
+
/** Salva il layout corrente come preset nominato nella libreria locale. */
|
|
8017
|
+
async saveDashboardAsPreset() {
|
|
8018
|
+
const resp = await WtoolboxService.promptDialog(this.t('designer.preset.save_title', 'Salva come preset'), [{ name: 'presetName', caption: this.t('designer.preset.name_caption', 'Nome preset'), type: 'string', value: '', required: true }], null, '400px', '230px');
|
|
8019
|
+
const name = String(resp?.presetName?.value || '').trim();
|
|
8020
|
+
if (!name) {
|
|
7776
8021
|
return;
|
|
7777
8022
|
}
|
|
7778
|
-
this.
|
|
7779
|
-
|
|
7780
|
-
|
|
7781
|
-
|
|
7782
|
-
|
|
7783
|
-
|
|
7784
|
-
|
|
7785
|
-
|
|
7786
|
-
return !!nodeKey && nodeKey === selectedKey;
|
|
8023
|
+
const presets = this.readDesignerPresets();
|
|
8024
|
+
presets[name] = this.buildSerializedDashboardElements();
|
|
8025
|
+
this.writeDesignerPresets(presets);
|
|
8026
|
+
WtoolboxService.messageNotificationService.add({
|
|
8027
|
+
severity: 'success',
|
|
8028
|
+
summary: this.t('success', 'Success'),
|
|
8029
|
+
detail: this.t('designer.preset.saved', 'Preset salvato')
|
|
8030
|
+
});
|
|
7787
8031
|
}
|
|
7788
|
-
|
|
7789
|
-
|
|
7790
|
-
this.
|
|
7791
|
-
|
|
8032
|
+
/** Applica un preset della libreria al canvas (sostituisce il layout corrente). */
|
|
8033
|
+
async applyDashboardPreset() {
|
|
8034
|
+
const presets = this.readDesignerPresets();
|
|
8035
|
+
const names = Object.keys(presets);
|
|
8036
|
+
if (names.length === 0) {
|
|
8037
|
+
WtoolboxService.messageNotificationService.add({
|
|
8038
|
+
severity: 'info',
|
|
8039
|
+
summary: this.t('info', 'Info'),
|
|
8040
|
+
detail: this.t('designer.preset.none', 'Nessun preset salvato')
|
|
8041
|
+
});
|
|
7792
8042
|
return;
|
|
7793
8043
|
}
|
|
7794
|
-
const
|
|
7795
|
-
|
|
7796
|
-
|
|
8044
|
+
const resp = await WtoolboxService.promptDialog(this.t('designer.preset.apply_title', 'Applica preset'), [{
|
|
8045
|
+
name: 'preset',
|
|
8046
|
+
caption: this.t('designer.preset.pick_caption', 'Preset'),
|
|
8047
|
+
type: 'dictionary',
|
|
8048
|
+
required: true,
|
|
8049
|
+
dictionaryData: names.map((n) => ({ label: n, value: n }))
|
|
8050
|
+
}], null, '400px', '230px');
|
|
8051
|
+
const chosen = String(resp?.preset?.value || '').trim();
|
|
8052
|
+
if (!chosen || !presets[chosen]) {
|
|
8053
|
+
return;
|
|
8054
|
+
}
|
|
8055
|
+
if ((this.flattenedDashboardElements || []).length > 0) {
|
|
8056
|
+
const ok = await WtoolboxService.confirm({
|
|
8057
|
+
header: this.t('warning', 'Warning'),
|
|
8058
|
+
message: this.t('designer.preset.apply_confirm', 'Applicare il preset sostituira\' il canvas corrente. Continuare?'),
|
|
8059
|
+
acceptLabel: this.t('confirm', 'Confirm'),
|
|
8060
|
+
rejectLabel: this.t('cancel', 'Cancel')
|
|
8061
|
+
});
|
|
8062
|
+
if (!ok) {
|
|
8063
|
+
return;
|
|
8064
|
+
}
|
|
7797
8065
|
}
|
|
7798
|
-
|
|
7799
|
-
|
|
7800
|
-
|
|
8066
|
+
try {
|
|
8067
|
+
await this.loadDashboard({
|
|
8068
|
+
boardcontent: presets[chosen],
|
|
8069
|
+
board_route: this.currentDashboardRoute || '',
|
|
8070
|
+
board_des: this.currentDashboardDescription || ''
|
|
8071
|
+
}, true);
|
|
8072
|
+
WtoolboxService.messageNotificationService.add({
|
|
8073
|
+
severity: 'success',
|
|
8074
|
+
summary: this.t('success', 'Success'),
|
|
8075
|
+
detail: this.t('designer.preset.applied', 'Preset applicato')
|
|
8076
|
+
});
|
|
7801
8077
|
}
|
|
7802
|
-
|
|
7803
|
-
|
|
7804
|
-
|
|
7805
|
-
this.hierarchyHoverFooterElement = footerElement;
|
|
8078
|
+
catch (error) {
|
|
8079
|
+
console.error('Failed to apply designer preset', error);
|
|
8080
|
+
WtoolboxService.errorHandler?.handleError?.(error);
|
|
7806
8081
|
}
|
|
7807
8082
|
}
|
|
7808
|
-
|
|
7809
|
-
|
|
7810
|
-
|
|
7811
|
-
|
|
8083
|
+
/** Clipboard interno per copy/paste di elementi del designer (inject-node). */
|
|
8084
|
+
designerClipboard = null;
|
|
8085
|
+
/**
|
|
8086
|
+
* Converte un elemento del canvas nella shape `node` accettata da
|
|
8087
|
+
* {@link injectLayoutNode} (`{ tool_name, inputs }`), estraendo i soli input
|
|
8088
|
+
* SCALARI (string/number/boolean/array) + i binding datasource (ridotti al
|
|
8089
|
+
* loro `uniqueName`, che il path canonico ri-idrata post-drop). Function e
|
|
8090
|
+
* oggetti complessi vengono scartati: verranno ricostruiti dal template della
|
|
8091
|
+
* palette al drop. Ritorna null se l'elemento non e' copiabile.
|
|
8092
|
+
*/
|
|
8093
|
+
buildInjectNodeFromElement(el) {
|
|
8094
|
+
const name = String(el?.name || '').trim();
|
|
8095
|
+
if (!name) {
|
|
8096
|
+
return null;
|
|
7812
8097
|
}
|
|
7813
|
-
|
|
7814
|
-
|
|
7815
|
-
|
|
7816
|
-
|
|
7817
|
-
|
|
7818
|
-
|
|
8098
|
+
const inputs = {};
|
|
8099
|
+
const rawInputs = el?.inputs || {};
|
|
8100
|
+
for (const key of Object.keys(rawInputs)) {
|
|
8101
|
+
let v = rawInputs[key];
|
|
8102
|
+
if (v instanceof BehaviorSubject) {
|
|
8103
|
+
v = v.value;
|
|
8104
|
+
}
|
|
8105
|
+
if (typeof v === 'function') {
|
|
8106
|
+
continue;
|
|
8107
|
+
}
|
|
8108
|
+
// Binding datasource (dropped-component): passa il solo uniqueName stringa
|
|
8109
|
+
if (v && typeof v === 'object' && !Array.isArray(v) && 'uniqueName' in v && 'component' in v) {
|
|
8110
|
+
const un = String(v.uniqueName || '').trim();
|
|
8111
|
+
if (un) {
|
|
8112
|
+
inputs[key] = un;
|
|
8113
|
+
}
|
|
8114
|
+
continue;
|
|
8115
|
+
}
|
|
8116
|
+
// Scarta gli altri oggetti complessi non serializzabili come scalari
|
|
8117
|
+
if (v !== null && typeof v === 'object' && !Array.isArray(v)) {
|
|
8118
|
+
continue;
|
|
8119
|
+
}
|
|
8120
|
+
inputs[key] = v;
|
|
8121
|
+
}
|
|
8122
|
+
return { tool_name: name, inputs };
|
|
8123
|
+
}
|
|
8124
|
+
/** Trova l'uniqueName del parent di un elemento nel tree, o null se e' a root. */
|
|
8125
|
+
findParentUniqueName(target) {
|
|
8126
|
+
const targetUn = String(target?.uniqueName || '').trim();
|
|
8127
|
+
if (!targetUn) {
|
|
8128
|
+
return null;
|
|
8129
|
+
}
|
|
8130
|
+
const walk = (nodes) => {
|
|
8131
|
+
for (const n of nodes || []) {
|
|
8132
|
+
const kids = n?.nestedComponents || [];
|
|
8133
|
+
if (kids.some((k) => String(k?.uniqueName || '') === targetUn)) {
|
|
8134
|
+
return String(n?.uniqueName || '') || null;
|
|
8135
|
+
}
|
|
8136
|
+
const deeper = walk(kids);
|
|
8137
|
+
if (deeper !== undefined) {
|
|
8138
|
+
return deeper;
|
|
8139
|
+
}
|
|
8140
|
+
}
|
|
8141
|
+
return undefined;
|
|
8142
|
+
};
|
|
8143
|
+
const res = walk(this.dashboardElements || []);
|
|
8144
|
+
return res === undefined ? null : res;
|
|
8145
|
+
}
|
|
8146
|
+
/**
|
|
8147
|
+
* Copia l'elemento selezionato (o `ctxElement`) nel clipboard interno.
|
|
8148
|
+
* I contenitori con figli non sono ancora supportati (richiedono il mapping
|
|
8149
|
+
* dei placeholder strutturali): mostra un avviso e non copia.
|
|
8150
|
+
*/
|
|
8151
|
+
copyDesignerElement(el) {
|
|
8152
|
+
const target = el || this.tool || this.ctxElement;
|
|
8153
|
+
if (!target) {
|
|
8154
|
+
return;
|
|
8155
|
+
}
|
|
8156
|
+
if (Array.isArray(target.nestedComponents) && target.nestedComponents.length > 0) {
|
|
8157
|
+
WtoolboxService.messageNotificationService.add({
|
|
8158
|
+
severity: 'info',
|
|
8159
|
+
summary: this.t('info', 'Info'),
|
|
8160
|
+
detail: this.t('designer.clipboard.container_unsupported', 'La copia di contenitori con figli non e\' ancora supportata')
|
|
8161
|
+
});
|
|
8162
|
+
return;
|
|
8163
|
+
}
|
|
8164
|
+
// Gli elementi con binding datasource "live" + rendering per-riga
|
|
8165
|
+
// (DATAREPEATER/SELECT/MULTISELECT/UL) non si duplicano ancora in modo
|
|
8166
|
+
// affidabile: il clone via injectLayoutNode ricrea un list-grid il cui
|
|
8167
|
+
// selection model non e' inizializzato ad array -> IsSelectedRowPipe fallisce
|
|
8168
|
+
// (`items.find is not a function`). Fino a quando la re-idratazione del
|
|
8169
|
+
// binding non e' gestita, escludiamo questi tipi con un avviso chiaro.
|
|
8170
|
+
const DATASOURCE_BOUND = new Set(['DATAREPEATER', 'SELECT', 'MULTISELECT', 'UL']);
|
|
8171
|
+
if (DATASOURCE_BOUND.has(String(target.name || ''))) {
|
|
8172
|
+
WtoolboxService.messageNotificationService.add({
|
|
8173
|
+
severity: 'info',
|
|
8174
|
+
summary: this.t('info', 'Info'),
|
|
8175
|
+
detail: this.t('designer.clipboard.datasource_bound_unsupported', 'La duplicazione di componenti legati a un datasource non e\' ancora supportata')
|
|
8176
|
+
});
|
|
8177
|
+
return;
|
|
8178
|
+
}
|
|
8179
|
+
const node = this.buildInjectNodeFromElement(target);
|
|
8180
|
+
if (!node) {
|
|
8181
|
+
return;
|
|
8182
|
+
}
|
|
8183
|
+
this.designerClipboard = node;
|
|
8184
|
+
WtoolboxService.messageNotificationService.add({
|
|
8185
|
+
severity: 'success',
|
|
8186
|
+
summary: this.t('success', 'Success'),
|
|
8187
|
+
detail: this.t('designer.clipboard.copied', 'Elemento copiato')
|
|
8188
|
+
});
|
|
8189
|
+
}
|
|
8190
|
+
/**
|
|
8191
|
+
* Incolla l'elemento dal clipboard. Se `parentUniqueName` e' passato (usato da
|
|
8192
|
+
* `duplicateDesignerElement` per creare un sibling) inietta li'; altrimenti
|
|
8193
|
+
* dentro l'elemento selezionato se e' un contenitore, altrimenti a root.
|
|
8194
|
+
* Riusa il path canonico {@link injectLayoutNode} (drop() reale) per garantire
|
|
8195
|
+
* uniqueName freschi, hydration e history coerenti.
|
|
8196
|
+
*/
|
|
8197
|
+
async pasteDesignerElement(parentUniqueName) {
|
|
8198
|
+
if (!this.designerClipboard) {
|
|
8199
|
+
return;
|
|
8200
|
+
}
|
|
8201
|
+
let parent = parentUniqueName ?? null;
|
|
8202
|
+
if (parent === null && this.tool && Array.isArray(this.tool.nestedComponents)) {
|
|
8203
|
+
parent = String(this.tool.uniqueName || '') || null;
|
|
8204
|
+
}
|
|
8205
|
+
try {
|
|
8206
|
+
await this.injectLayoutNode(JSON.parse(JSON.stringify(this.designerClipboard)), parent);
|
|
8207
|
+
this.cd.detectChanges();
|
|
8208
|
+
WtoolboxService.messageNotificationService.add({
|
|
8209
|
+
severity: 'success',
|
|
8210
|
+
summary: this.t('success', 'Success'),
|
|
8211
|
+
detail: this.t('designer.clipboard.pasted', 'Elemento incollato')
|
|
8212
|
+
});
|
|
8213
|
+
}
|
|
8214
|
+
catch (error) {
|
|
8215
|
+
console.error('Failed to paste designer element', error);
|
|
8216
|
+
WtoolboxService.errorHandler?.handleError?.(error);
|
|
8217
|
+
}
|
|
8218
|
+
}
|
|
8219
|
+
/**
|
|
8220
|
+
* Duplica un elemento: lo copia e lo incolla come sibling (stesso parent).
|
|
8221
|
+
*/
|
|
8222
|
+
async duplicateDesignerElement(el) {
|
|
8223
|
+
const target = el || this.tool || this.ctxElement;
|
|
8224
|
+
if (!target) {
|
|
8225
|
+
return;
|
|
8226
|
+
}
|
|
8227
|
+
this.copyDesignerElement(target);
|
|
8228
|
+
if (!this.designerClipboard) {
|
|
8229
|
+
// caso contenitore: copyDesignerElement ha gia' avvisato
|
|
8230
|
+
return;
|
|
8231
|
+
}
|
|
8232
|
+
const parentUn = this.findParentUniqueName(target);
|
|
8233
|
+
await this.pasteDesignerElement(parentUn);
|
|
8234
|
+
}
|
|
8235
|
+
/**
|
|
8236
|
+
* Esporta il boardcontent della dashboard corrente come file JSON scaricabile.
|
|
8237
|
+
* Riusa lo stesso choke-point di serializzazione del salvataggio
|
|
8238
|
+
* (`buildSerializedDashboardElements`), quindi il file prodotto e' identico al
|
|
8239
|
+
* `boardcontent` persistito su `dom_board` ed e' re-importabile qui o su un
|
|
8240
|
+
* altro ambiente (import/export tra ambienti).
|
|
8241
|
+
*/
|
|
8242
|
+
exportDashboardJson() {
|
|
8243
|
+
try {
|
|
8244
|
+
const raw = this.buildSerializedDashboardElements();
|
|
8245
|
+
let pretty = raw;
|
|
8246
|
+
try {
|
|
8247
|
+
pretty = JSON.stringify(JSON.parse(raw), null, 2);
|
|
8248
|
+
}
|
|
8249
|
+
catch { /* keep raw */ }
|
|
8250
|
+
const routeSlug = String(this.currentDashboardRoute || 'dashboard')
|
|
8251
|
+
.replace(/[^a-zA-Z0-9._-]+/g, '_')
|
|
8252
|
+
.replace(/^_+|_+$/g, '') || 'dashboard';
|
|
8253
|
+
const fileName = `${routeSlug}.dashboard.json`;
|
|
8254
|
+
const blob = new Blob([pretty], { type: 'application/json' });
|
|
8255
|
+
const url = URL.createObjectURL(blob);
|
|
8256
|
+
const anchor = document.createElement('a');
|
|
8257
|
+
anchor.href = url;
|
|
8258
|
+
anchor.download = fileName;
|
|
8259
|
+
document.body.appendChild(anchor);
|
|
8260
|
+
anchor.click();
|
|
8261
|
+
document.body.removeChild(anchor);
|
|
8262
|
+
setTimeout(() => URL.revokeObjectURL(url), 0);
|
|
8263
|
+
WtoolboxService.messageNotificationService.add({
|
|
8264
|
+
severity: 'success',
|
|
8265
|
+
summary: this.t('success', 'Success'),
|
|
8266
|
+
detail: this.t('designer.export_json_success', 'Dashboard JSON exported')
|
|
8267
|
+
});
|
|
8268
|
+
}
|
|
8269
|
+
catch (error) {
|
|
8270
|
+
console.error('Failed to export dashboard JSON', error);
|
|
8271
|
+
WtoolboxService.errorHandler?.handleError?.(error);
|
|
8272
|
+
}
|
|
8273
|
+
}
|
|
8274
|
+
/**
|
|
8275
|
+
* Importa un boardcontent da un file JSON precedentemente esportato.
|
|
8276
|
+
* Apre un file picker nativo (input[type=file] creato al volo, nessun prompt
|
|
8277
|
+
* nativo del browser), valida il JSON, chiede conferma di sovrascrittura del
|
|
8278
|
+
* canvas corrente e infine ricostruisce la dashboard riusando `loadDashboard`
|
|
8279
|
+
* (con `skipServerFetch=true`), lo stesso path usato per aprire una dashboard
|
|
8280
|
+
* salvata — cosi' l'import passa da idratazione/backfill/rebind canonici.
|
|
8281
|
+
*/
|
|
8282
|
+
importDashboardJson() {
|
|
8283
|
+
const input = document.createElement('input');
|
|
8284
|
+
input.type = 'file';
|
|
8285
|
+
input.accept = 'application/json,.json';
|
|
8286
|
+
input.style.display = 'none';
|
|
8287
|
+
input.addEventListener('change', () => {
|
|
8288
|
+
const file = input.files && input.files[0];
|
|
8289
|
+
document.body.removeChild(input);
|
|
8290
|
+
if (!file) {
|
|
8291
|
+
return;
|
|
8292
|
+
}
|
|
8293
|
+
const reader = new FileReader();
|
|
8294
|
+
reader.onload = () => {
|
|
8295
|
+
const text = String(reader.result || '');
|
|
8296
|
+
void this.applyImportedDashboardJson(text);
|
|
8297
|
+
};
|
|
8298
|
+
reader.onerror = () => {
|
|
8299
|
+
WtoolboxService.messageNotificationService.add({
|
|
8300
|
+
severity: 'error',
|
|
8301
|
+
summary: this.t('error', 'Error'),
|
|
8302
|
+
detail: this.t('designer.import_json_read_error', 'Could not read the selected file')
|
|
8303
|
+
});
|
|
8304
|
+
};
|
|
8305
|
+
reader.readAsText(file);
|
|
8306
|
+
});
|
|
8307
|
+
document.body.appendChild(input);
|
|
8308
|
+
input.click();
|
|
8309
|
+
}
|
|
8310
|
+
/**
|
|
8311
|
+
* Valida e applica un boardcontent importato. Estratta da `importDashboardJson`
|
|
8312
|
+
* per essere riusabile (es. incolla da clipboard / test) e testabile.
|
|
8313
|
+
*/
|
|
8314
|
+
async applyImportedDashboardJson(text) {
|
|
8315
|
+
let parsed;
|
|
8316
|
+
try {
|
|
8317
|
+
parsed = JSON.parse(text);
|
|
8318
|
+
}
|
|
8319
|
+
catch {
|
|
8320
|
+
WtoolboxService.messageNotificationService.add({
|
|
8321
|
+
severity: 'error',
|
|
8322
|
+
summary: this.t('error', 'Error'),
|
|
8323
|
+
detail: this.t('designer.import_json_invalid', 'The file does not contain valid JSON')
|
|
8324
|
+
});
|
|
8325
|
+
return;
|
|
8326
|
+
}
|
|
8327
|
+
// Accetta sia l'array di elementi top-level sia il wrapper {elements|dashboardElements}
|
|
8328
|
+
const elements = Array.isArray(parsed)
|
|
8329
|
+
? parsed
|
|
8330
|
+
: (Array.isArray(parsed?.elements)
|
|
8331
|
+
? parsed.elements
|
|
8332
|
+
: (Array.isArray(parsed?.dashboardElements) ? parsed.dashboardElements : null));
|
|
8333
|
+
if (!elements) {
|
|
8334
|
+
WtoolboxService.messageNotificationService.add({
|
|
8335
|
+
severity: 'error',
|
|
8336
|
+
summary: this.t('error', 'Error'),
|
|
8337
|
+
detail: this.t('designer.import_json_unrecognized', 'The JSON is not a recognizable dashboard layout')
|
|
8338
|
+
});
|
|
8339
|
+
return;
|
|
8340
|
+
}
|
|
8341
|
+
if ((this.flattenedDashboardElements || []).length > 0) {
|
|
8342
|
+
const ok = await WtoolboxService.confirm({
|
|
8343
|
+
header: this.t('warning', 'Warning'),
|
|
8344
|
+
message: this.t('designer.import_json_confirm', 'Importing will replace the current canvas. Continue?'),
|
|
8345
|
+
acceptLabel: this.t('confirm', 'Confirm'),
|
|
8346
|
+
rejectLabel: this.t('cancel', 'Cancel')
|
|
8347
|
+
});
|
|
8348
|
+
if (!ok) {
|
|
8349
|
+
return;
|
|
8350
|
+
}
|
|
8351
|
+
}
|
|
8352
|
+
try {
|
|
8353
|
+
await this.loadDashboard({
|
|
8354
|
+
boardcontent: JSON.stringify(elements),
|
|
8355
|
+
board_route: this.currentDashboardRoute || '',
|
|
8356
|
+
board_des: this.currentDashboardDescription || ''
|
|
8357
|
+
}, true);
|
|
8358
|
+
WtoolboxService.messageNotificationService.add({
|
|
8359
|
+
severity: 'success',
|
|
8360
|
+
summary: this.t('success', 'Success'),
|
|
8361
|
+
detail: this.t('designer.import_json_success', 'Dashboard JSON imported')
|
|
8362
|
+
});
|
|
8363
|
+
}
|
|
8364
|
+
catch (error) {
|
|
8365
|
+
console.error('Failed to import dashboard JSON', error);
|
|
8366
|
+
WtoolboxService.errorHandler?.handleError?.(error);
|
|
8367
|
+
}
|
|
8368
|
+
}
|
|
8369
|
+
get droppedDashboardItems() {
|
|
8370
|
+
return (this.flattenedDashboardElements || []).filter((item) => !!item?.uniqueName);
|
|
8371
|
+
}
|
|
8372
|
+
get hierarchyTreeNodes() {
|
|
8373
|
+
return this.hierarchyTreeNodesState;
|
|
8374
|
+
}
|
|
8375
|
+
get canUndo() {
|
|
8376
|
+
return this.undoHistory.length > 0;
|
|
8377
|
+
}
|
|
8378
|
+
get canRedo() {
|
|
8379
|
+
return this.redoHistory.length > 0;
|
|
8380
|
+
}
|
|
8381
|
+
/**
|
|
8382
|
+
* Calcola la label visualizzata per un elemento nel pannello "dropped items".
|
|
8383
|
+
*/
|
|
8384
|
+
getDroppedDashboardItemLabel(item) {
|
|
8385
|
+
const displayName = String(item?.displayName || '').trim();
|
|
8386
|
+
if (displayName) {
|
|
8387
|
+
return displayName;
|
|
8388
|
+
}
|
|
8389
|
+
const uniqueName = String(item?.uniqueName || '').trim();
|
|
8390
|
+
const name = String(item?.name || '').trim();
|
|
8391
|
+
if (!uniqueName) {
|
|
8392
|
+
return name;
|
|
8393
|
+
}
|
|
8394
|
+
return name ? `${uniqueName} (${name})` : uniqueName;
|
|
8395
|
+
}
|
|
8396
|
+
/**
|
|
8397
|
+
* Imposta come selezionato l'elemento cliccato nella lista elementi.
|
|
8398
|
+
*/
|
|
8399
|
+
selectDashboardItem(item) {
|
|
8400
|
+
if (!item) {
|
|
8401
|
+
return;
|
|
8402
|
+
}
|
|
8403
|
+
this.tool = item;
|
|
8404
|
+
this.syncHierarchySelectionToCurrentTool();
|
|
8405
|
+
}
|
|
8406
|
+
// ===================== Multi-selezione (rubber-band) =====================
|
|
8407
|
+
/** Canvas host (per le coordinate del rettangolo di selezione). */
|
|
8408
|
+
canvasRef;
|
|
8409
|
+
/** Elementi top-level multi-selezionati. `tool` (attivo nel property panel) e' null quando != 1. */
|
|
8410
|
+
selectedTools = [];
|
|
8411
|
+
/** Overlay del rettangolo di selezione (rubber-band). */
|
|
8412
|
+
rubberBandVisible = false;
|
|
8413
|
+
rubberBandStyle = { left: '0px', top: '0px', width: '0px', height: '0px' };
|
|
8414
|
+
rubber = null;
|
|
8415
|
+
canvasRectAtStart = null;
|
|
8416
|
+
boundRubberMove = (e) => this.onRubberMove(e);
|
|
8417
|
+
boundRubberUp = (e) => this.onRubberUp(e);
|
|
8418
|
+
/**
|
|
8419
|
+
* Mousedown sul canvas: avvia la selezione a rettangolo SOLO se parte da area
|
|
8420
|
+
* vuota (nessun elemento sotto il puntatore). Sugli elementi il mousedown e'
|
|
8421
|
+
* consumato dalla direttiva di drag (`stopImmediatePropagation`), quindi qui
|
|
8422
|
+
* arriva solo il click su spazio libero — coerente col vincolo richiesto.
|
|
8423
|
+
*/
|
|
8424
|
+
onCanvasMouseDown(event) {
|
|
8425
|
+
if (this.noEdit || !event || event.button !== 0) {
|
|
8426
|
+
return;
|
|
8427
|
+
}
|
|
8428
|
+
const canvasEl = this.canvasRef?.nativeElement;
|
|
8429
|
+
if (!canvasEl || !this.isEmptyCanvasTarget(event.target, canvasEl)) {
|
|
8430
|
+
return;
|
|
8431
|
+
}
|
|
8432
|
+
event.preventDefault();
|
|
8433
|
+
this.clearMultiSelection();
|
|
8434
|
+
this.tool = null;
|
|
8435
|
+
this.canvasRectAtStart = canvasEl.getBoundingClientRect();
|
|
8436
|
+
this.rubber = { startClientX: event.clientX, startClientY: event.clientY };
|
|
8437
|
+
this.rubberBandVisible = true;
|
|
8438
|
+
this.updateRubberBandStyle(event.clientX, event.clientY);
|
|
8439
|
+
window.addEventListener('mousemove', this.boundRubberMove);
|
|
8440
|
+
window.addEventListener('mouseup', this.boundRubberUp);
|
|
8441
|
+
this.cd.detectChanges();
|
|
8442
|
+
}
|
|
8443
|
+
/** true se il target risale fino al canvas senza attraversare un elemento designer (id con '__'). */
|
|
8444
|
+
isEmptyCanvasTarget(target, canvasEl) {
|
|
8445
|
+
let node = target;
|
|
8446
|
+
while (node && node !== canvasEl) {
|
|
8447
|
+
const id = node.getAttribute ? (node.getAttribute('id') || '') : '';
|
|
8448
|
+
if (id.indexOf('__') > -1) {
|
|
8449
|
+
return false;
|
|
8450
|
+
}
|
|
8451
|
+
node = node.parentElement;
|
|
8452
|
+
}
|
|
8453
|
+
return node === canvasEl;
|
|
8454
|
+
}
|
|
8455
|
+
onRubberMove(event) {
|
|
8456
|
+
if (!this.rubber) {
|
|
8457
|
+
return;
|
|
8458
|
+
}
|
|
8459
|
+
this.updateRubberBandStyle(event.clientX, event.clientY);
|
|
8460
|
+
this.cd.detectChanges();
|
|
8461
|
+
}
|
|
8462
|
+
updateRubberBandStyle(curX, curY) {
|
|
8463
|
+
if (!this.rubber || !this.canvasRectAtStart) {
|
|
8464
|
+
return;
|
|
8465
|
+
}
|
|
8466
|
+
const canvasEl = this.canvasRef?.nativeElement;
|
|
8467
|
+
const r = this.canvasRectAtStart;
|
|
8468
|
+
const sL = canvasEl ? canvasEl.scrollLeft : 0;
|
|
8469
|
+
const sT = canvasEl ? canvasEl.scrollTop : 0;
|
|
8470
|
+
const x1 = Math.min(this.rubber.startClientX, curX), x2 = Math.max(this.rubber.startClientX, curX);
|
|
8471
|
+
const y1 = Math.min(this.rubber.startClientY, curY), y2 = Math.max(this.rubber.startClientY, curY);
|
|
8472
|
+
this.rubberBandStyle = {
|
|
8473
|
+
left: (x1 - r.left + sL) + 'px',
|
|
8474
|
+
top: (y1 - r.top + sT) + 'px',
|
|
8475
|
+
width: (x2 - x1) + 'px',
|
|
8476
|
+
height: (y2 - y1) + 'px',
|
|
8477
|
+
};
|
|
8478
|
+
}
|
|
8479
|
+
onRubberUp(event) {
|
|
8480
|
+
window.removeEventListener('mousemove', this.boundRubberMove);
|
|
8481
|
+
window.removeEventListener('mouseup', this.boundRubberUp);
|
|
8482
|
+
const rubber = this.rubber;
|
|
8483
|
+
this.rubber = null;
|
|
8484
|
+
this.rubberBandVisible = false;
|
|
8485
|
+
if (!rubber) {
|
|
8486
|
+
this.cd.detectChanges();
|
|
8487
|
+
return;
|
|
8488
|
+
}
|
|
8489
|
+
const x1 = Math.min(rubber.startClientX, event.clientX), x2 = Math.max(rubber.startClientX, event.clientX);
|
|
8490
|
+
const y1 = Math.min(rubber.startClientY, event.clientY), y2 = Math.max(rubber.startClientY, event.clientY);
|
|
8491
|
+
const selected = [];
|
|
8492
|
+
// Ignora i "click" (rettangolo minuscolo): niente selezione, solo deselect.
|
|
8493
|
+
if (Math.abs(x2 - x1) >= 3 && Math.abs(y2 - y1) >= 3) {
|
|
8494
|
+
// Hit-test dei soli elementi TOP-LEVEL (spostare/eliminare un top-level porta i figli).
|
|
8495
|
+
for (const el of (this.dashboardElements || [])) {
|
|
8496
|
+
const dom = el?.uniqueName ? document.getElementById(el.uniqueName) : null;
|
|
8497
|
+
if (!dom) {
|
|
8498
|
+
continue;
|
|
8499
|
+
}
|
|
8500
|
+
const b = dom.getBoundingClientRect();
|
|
8501
|
+
if (b.left < x2 && b.right > x1 && b.top < y2 && b.bottom > y1) {
|
|
8502
|
+
selected.push(el);
|
|
8503
|
+
}
|
|
8504
|
+
}
|
|
8505
|
+
}
|
|
8506
|
+
this.setMultiSelection(selected);
|
|
8507
|
+
this.cd.detectChanges();
|
|
8508
|
+
}
|
|
8509
|
+
/** Imposta la multi-selezione: `tool` resta valorizzato solo con esattamente 1 elemento. */
|
|
8510
|
+
setMultiSelection(selected) {
|
|
8511
|
+
this.clearHighlight();
|
|
8512
|
+
this.selectedTools = selected || [];
|
|
8513
|
+
this.tool = this.selectedTools.length === 1 ? this.selectedTools[0] : null;
|
|
8514
|
+
this.applyHighlight();
|
|
8515
|
+
}
|
|
8516
|
+
clearMultiSelection() {
|
|
8517
|
+
this.clearHighlight();
|
|
8518
|
+
this.selectedTools = [];
|
|
8519
|
+
}
|
|
8520
|
+
applyHighlight() {
|
|
8521
|
+
for (const el of (this.selectedTools || [])) {
|
|
8522
|
+
const dom = el?.uniqueName ? document.getElementById(el.uniqueName) : null;
|
|
8523
|
+
if (dom) {
|
|
8524
|
+
dom.classList.add('designer-multi-selected');
|
|
8525
|
+
}
|
|
8526
|
+
}
|
|
8527
|
+
}
|
|
8528
|
+
clearHighlight() {
|
|
8529
|
+
for (const el of (this.selectedTools || [])) {
|
|
8530
|
+
const dom = el?.uniqueName ? document.getElementById(el.uniqueName) : null;
|
|
8531
|
+
if (dom) {
|
|
8532
|
+
dom.classList.remove('designer-multi-selected');
|
|
8533
|
+
}
|
|
8534
|
+
}
|
|
8535
|
+
}
|
|
8536
|
+
/** Applica un delta di traslazione (px) a un insieme di elementi (multi-move via frecce). */
|
|
8537
|
+
nudgeElements(tools, dx, dy) {
|
|
8538
|
+
for (const el of (tools || [])) {
|
|
8539
|
+
if (!el?.inputs) {
|
|
8540
|
+
continue;
|
|
8541
|
+
}
|
|
8542
|
+
let scaleX = 1, skewY = 0, skewX = 0, scaleY = 1, tx = 0, ty = 0;
|
|
8543
|
+
const m = String(el.inputs['transform'] || '').match(/matrix\(([^)]+)\)/);
|
|
8544
|
+
if (m) {
|
|
8545
|
+
const p = m[1].split(',').map((x) => parseFloat(x));
|
|
8546
|
+
if (p.length >= 6 && p.every((n) => !isNaN(n))) {
|
|
8547
|
+
[scaleX, skewY, skewX, scaleY, tx, ty] = p;
|
|
8548
|
+
}
|
|
8549
|
+
}
|
|
8550
|
+
el.inputs['transform'] = `matrix(${scaleX}, ${skewY}, ${skewX}, ${scaleY}, ${tx + dx}, ${ty + dy})`;
|
|
8551
|
+
}
|
|
8552
|
+
this.commitHistoryIfChanged();
|
|
8553
|
+
this.cd.detectChanges();
|
|
8554
|
+
}
|
|
8555
|
+
// ============ Import dashboard/preset DENTRO un elemento (context menu) ============
|
|
8556
|
+
/** true se il tool e' un container che accetta figli arbitrari (nestedComponents + nessun allowedChildren). */
|
|
8557
|
+
isFreeContainer(tool) {
|
|
8558
|
+
return !!tool && Array.isArray(tool.nestedComponents) && !(tool.allowedChildren);
|
|
8559
|
+
}
|
|
8560
|
+
/** Voci di context menu per importare dashboard/preset dentro un container. */
|
|
8561
|
+
buildImportContextItems(el) {
|
|
8562
|
+
return [
|
|
8563
|
+
{ separator: true },
|
|
8564
|
+
{ label: this.t('designer.ctx_import_dashboard', 'Importa dashboard qui'), icon: 'pi pi-download', command: () => this.importDashboardIntoElement(el) },
|
|
8565
|
+
{ label: this.t('designer.ctx_import_preset', 'Importa preset qui'), icon: 'pi pi-bookmark', command: () => this.importPresetIntoElement(el) },
|
|
8566
|
+
];
|
|
8567
|
+
}
|
|
8568
|
+
/**
|
|
8569
|
+
* UNICA sorgente del context menu di un elemento, condivisa dai tre punti di
|
|
8570
|
+
* accesso (canvas `showCtx`, gerarchia `showHierarchyNodeContextMenu`, footer
|
|
8571
|
+
* breadcrumb `showFooterContextMenu`). Le `command` operano sull'elemento
|
|
8572
|
+
* passato (risolto al click), non su una variabile globale, quindi il menu si
|
|
8573
|
+
* ricostruisce per-elemento con voci identiche ovunque. Il caller canvas
|
|
8574
|
+
* antepone eventuali voci field-editor (specifiche del click su un campo).
|
|
8575
|
+
*/
|
|
8576
|
+
buildElementContextMenuItems(el) {
|
|
8577
|
+
const items = [
|
|
8578
|
+
{
|
|
8579
|
+
label: this.t('designer.edit_properties', 'Edit properties'),
|
|
8580
|
+
icon: 'pi pi-cog',
|
|
8581
|
+
command: () => { if (el) {
|
|
8582
|
+
this.selectDashboardItem(el);
|
|
8583
|
+
this.cd.detectChanges();
|
|
8584
|
+
} }
|
|
8585
|
+
},
|
|
8586
|
+
{
|
|
8587
|
+
label: this.t('designer.duplicate', 'Duplica'),
|
|
8588
|
+
icon: 'pi pi-clone',
|
|
8589
|
+
command: () => { if (el) {
|
|
8590
|
+
void this.duplicateDesignerElement(el);
|
|
8591
|
+
} }
|
|
8592
|
+
},
|
|
8593
|
+
{
|
|
8594
|
+
label: this.t('designer.rename', 'Rename'),
|
|
8595
|
+
icon: 'pi pi-pencil',
|
|
8596
|
+
command: () => { if (el) {
|
|
8597
|
+
void this.renameDesignerElement(el);
|
|
8598
|
+
} }
|
|
8599
|
+
},
|
|
8600
|
+
{
|
|
8601
|
+
label: this.t('designer.remove', 'Remove'),
|
|
8602
|
+
icon: 'pi pi-trash',
|
|
8603
|
+
command: () => { const n = String(el?.uniqueName || '').trim(); if (n) {
|
|
8604
|
+
this.removeElementByName(n);
|
|
8605
|
+
} }
|
|
8606
|
+
}
|
|
8607
|
+
];
|
|
8608
|
+
// Sposta in nuovo Tab: SOLO se l'elemento e' dentro un TABPANEL di un TABVIEW
|
|
8609
|
+
// (migrazione elemento -> nuovo TABPANEL del TABVIEW padre).
|
|
8610
|
+
if (this.isInsideTabPanel(el)) {
|
|
8611
|
+
items.push({
|
|
8612
|
+
label: this.t('designer.ctx_move_to_new_tab', 'Sposta in nuovo Tab'),
|
|
8613
|
+
icon: 'pi pi-folder-open',
|
|
8614
|
+
command: () => { void this.moveElementToNewTab(el); }
|
|
8615
|
+
});
|
|
8616
|
+
// Sposta in altro tab: solo se il TABVIEW ha piu' di un tab (scelta esclusa il corrente).
|
|
8617
|
+
if (this.hasOtherTabs(el)) {
|
|
8618
|
+
items.push({
|
|
8619
|
+
label: this.t('designer.ctx_move_to_existing_tab_element', 'Sposta in altro tab'),
|
|
8620
|
+
icon: 'pi pi-arrow-right-arrow-left',
|
|
8621
|
+
command: () => { void this.moveElementToExistingTab(el); }
|
|
8622
|
+
});
|
|
8623
|
+
}
|
|
8624
|
+
}
|
|
8625
|
+
// Import dashboard/preset: solo per container liberi (nestedComponents senza allowedChildren).
|
|
8626
|
+
if (this.isFreeContainer(el)) {
|
|
8627
|
+
items.push(...this.buildImportContextItems(el));
|
|
8628
|
+
}
|
|
8629
|
+
return items;
|
|
8630
|
+
}
|
|
8631
|
+
/**
|
|
8632
|
+
* Risolve il contesto tab di un elemento: il TABPANEL che lo contiene e il
|
|
8633
|
+
* TABVIEW che contiene quel pannello. null se l'elemento non e' dentro un tab.
|
|
8634
|
+
*/
|
|
8635
|
+
resolveTabContext(el) {
|
|
8636
|
+
if (!el) {
|
|
8637
|
+
return null;
|
|
8638
|
+
}
|
|
8639
|
+
const panelUn = this.findParentUniqueName(el);
|
|
8640
|
+
const panel = panelUn ? this.getElementByName(panelUn) : null;
|
|
8641
|
+
if (!panel || String(panel.name || '').toUpperCase() !== 'TABPANEL') {
|
|
8642
|
+
return null;
|
|
8643
|
+
}
|
|
8644
|
+
const tabViewUn = this.findParentUniqueName(panel);
|
|
8645
|
+
const tabView = tabViewUn ? this.getElementByName(tabViewUn) : null;
|
|
8646
|
+
if (!tabView || String(tabView.name || '').toUpperCase() !== 'TABVIEW') {
|
|
8647
|
+
return null;
|
|
8648
|
+
}
|
|
8649
|
+
return { panel, tabView };
|
|
8650
|
+
}
|
|
8651
|
+
/** true se l'elemento e' dentro un TABPANEL di un TABVIEW (eleggibile a "Sposta in nuovo Tab"). */
|
|
8652
|
+
isInsideTabPanel(el) {
|
|
8653
|
+
return !!this.resolveTabContext(el);
|
|
8654
|
+
}
|
|
8655
|
+
/** true se il TABVIEW che contiene l'elemento ha piu' di un tab (eleggibile a "Sposta in altro tab"). */
|
|
8656
|
+
hasOtherTabs(el) {
|
|
8657
|
+
const ctx = this.resolveTabContext(el);
|
|
8658
|
+
return !!ctx && (ctx.tabView.nestedComponents?.length || 0) > 1;
|
|
8659
|
+
}
|
|
8660
|
+
/**
|
|
8661
|
+
* Sposta l'elemento in un NUOVO tab (TABPANEL) del TABVIEW che lo contiene:
|
|
8662
|
+
* crea un TABPANEL fresco (clonando il template di palette, uniqueName/componentId
|
|
8663
|
+
* nuovi), rimuove l'elemento dal tab corrente e ci mette lo STESSO oggetto (move,
|
|
8664
|
+
* non clone -> binding datasource/stato preservati), poi seleziona il nuovo tab.
|
|
8665
|
+
*/
|
|
8666
|
+
async moveElementToNewTab(el) {
|
|
8667
|
+
if (!el) {
|
|
8668
|
+
return;
|
|
8669
|
+
}
|
|
8670
|
+
const ctx = this.resolveTabContext(el);
|
|
8671
|
+
if (!ctx) {
|
|
8672
|
+
return;
|
|
8673
|
+
}
|
|
8674
|
+
const { panel, tabView } = ctx;
|
|
8675
|
+
// Nome del nuovo tab (default: displayName/name dell'elemento).
|
|
8676
|
+
const resp = await WtoolboxService.promptDialog(this.t('designer.ctx_move_to_new_tab', 'Sposta in nuovo Tab'), [{
|
|
8677
|
+
name: 'tabName',
|
|
8678
|
+
caption: this.t('designer.tab_name_caption', 'Nome Tab'),
|
|
8679
|
+
type: 'text',
|
|
8680
|
+
required: true,
|
|
8681
|
+
value: String(el.displayName || el.name || 'Tab').trim()
|
|
8682
|
+
}], '440px', '320px');
|
|
8683
|
+
if (!resp) {
|
|
8684
|
+
return;
|
|
8685
|
+
}
|
|
8686
|
+
const tabName = String(resp?.tabName?.value || '').trim();
|
|
8687
|
+
if (!tabName) {
|
|
8688
|
+
return;
|
|
8689
|
+
}
|
|
8690
|
+
// Nuovo TABPANEL dalla stessa shape del drop (template + id freschi).
|
|
8691
|
+
const template = (this.availableTools || []).find((t) => String(t?.name || '').toUpperCase() === 'TABPANEL');
|
|
8692
|
+
if (!template) {
|
|
8693
|
+
return;
|
|
8694
|
+
}
|
|
8695
|
+
const newPanel = JSON.parse(JSON.stringify(template));
|
|
8696
|
+
newPanel.componentId = this.getNewID();
|
|
8697
|
+
newPanel.uniqueName = newPanel.name + '__' + newPanel.componentId;
|
|
8698
|
+
newPanel.inputs = newPanel.inputs || {};
|
|
8699
|
+
newPanel.inputs['header'] = tabName;
|
|
8700
|
+
newPanel.nestedComponents = [];
|
|
8701
|
+
// Move: rimuovi dall'attuale tab e inserisci lo stesso oggetto nel nuovo.
|
|
8702
|
+
panel.nestedComponents = (panel.nestedComponents || []).filter((k) => String(k?.uniqueName || '') !== String(el.uniqueName || ''));
|
|
8703
|
+
newPanel.nestedComponents.push(el);
|
|
8704
|
+
// Aggancia il nuovo tab al TABVIEW e selezionalo (deseleziona gli altri).
|
|
8705
|
+
tabView.nestedComponents = tabView.nestedComponents || [];
|
|
8706
|
+
tabView.nestedComponents.push(newPanel);
|
|
8707
|
+
this.selectTabPanelExclusive(newPanel);
|
|
8708
|
+
this.flattenedDashboardElements = this.flattenComponentTree(this.dashboardElements);
|
|
8709
|
+
this.commitHistoryIfChanged();
|
|
8710
|
+
this.cd.detectChanges();
|
|
8711
|
+
WtoolboxService.messageNotificationService.add({
|
|
8712
|
+
severity: 'success',
|
|
8713
|
+
summary: this.t('success', 'Success'),
|
|
8714
|
+
detail: this.t('designer.moved_to_new_tab', 'Elemento spostato nel nuovo tab')
|
|
8715
|
+
});
|
|
8716
|
+
}
|
|
8717
|
+
/**
|
|
8718
|
+
* Sposta l'elemento in un ALTRO tab (TABPANEL) gia' esistente dello stesso
|
|
8719
|
+
* TABVIEW: picker dei tab escluso il corrente, poi move dello STESSO oggetto
|
|
8720
|
+
* (binding/stato preservati) nel tab scelto, che viene selezionato.
|
|
8721
|
+
*/
|
|
8722
|
+
async moveElementToExistingTab(el) {
|
|
8723
|
+
if (!el) {
|
|
8724
|
+
return;
|
|
8725
|
+
}
|
|
8726
|
+
const ctx = this.resolveTabContext(el);
|
|
8727
|
+
if (!ctx) {
|
|
8728
|
+
return;
|
|
8729
|
+
}
|
|
8730
|
+
const { panel, tabView } = ctx;
|
|
8731
|
+
// Tab candidati: tutti i TABPANEL del TABVIEW tranne quello corrente.
|
|
8732
|
+
const others = (tabView.nestedComponents || []).filter((p) => String(p?.uniqueName || '') !== String(panel.uniqueName || ''));
|
|
8733
|
+
if (!others.length) {
|
|
8734
|
+
return;
|
|
8735
|
+
}
|
|
8736
|
+
const choices = others.map((p, idx) => {
|
|
8737
|
+
const label = String(p?.inputs?.header || p?.displayName || p?.uniqueName || '').trim() || ('Tab ' + (idx + 1));
|
|
8738
|
+
return { label, value: String(p?.uniqueName || '') };
|
|
8739
|
+
});
|
|
8740
|
+
const resp = await WtoolboxService.promptDialog(this.t('designer.ctx_move_to_existing_tab_element', 'Sposta in altro tab'), [{
|
|
8741
|
+
name: 'targetTab',
|
|
8742
|
+
caption: this.t('designer.target_tab_caption', 'Tab di destinazione'),
|
|
8743
|
+
type: 'dictionary',
|
|
8744
|
+
required: true,
|
|
8745
|
+
value: choices[0].value,
|
|
8746
|
+
dictionaryData: choices
|
|
8747
|
+
}], '440px', '320px');
|
|
8748
|
+
if (!resp) {
|
|
8749
|
+
return;
|
|
8750
|
+
}
|
|
8751
|
+
const targetUn = String(resp?.targetTab?.value || '').trim();
|
|
8752
|
+
if (!targetUn) {
|
|
8753
|
+
return;
|
|
8754
|
+
}
|
|
8755
|
+
const targetPanel = (tabView.nestedComponents || []).find((p) => String(p?.uniqueName || '') === targetUn);
|
|
8756
|
+
if (!targetPanel) {
|
|
8757
|
+
return;
|
|
8758
|
+
}
|
|
8759
|
+
// Move: rimuovi dal tab corrente e inserisci lo stesso oggetto nel target.
|
|
8760
|
+
panel.nestedComponents = (panel.nestedComponents || []).filter((k) => String(k?.uniqueName || '') !== String(el.uniqueName || ''));
|
|
8761
|
+
targetPanel.nestedComponents = Array.isArray(targetPanel.nestedComponents) ? targetPanel.nestedComponents : [];
|
|
8762
|
+
targetPanel.nestedComponents.push(el);
|
|
8763
|
+
// Seleziona il tab di destinazione.
|
|
8764
|
+
this.selectTabPanelExclusive(targetPanel);
|
|
8765
|
+
this.flattenedDashboardElements = this.flattenComponentTree(this.dashboardElements);
|
|
8766
|
+
this.commitHistoryIfChanged();
|
|
8767
|
+
this.cd.detectChanges();
|
|
8768
|
+
WtoolboxService.messageNotificationService.add({
|
|
8769
|
+
severity: 'success',
|
|
8770
|
+
summary: this.t('success', 'Success'),
|
|
8771
|
+
detail: this.t('designer.moved_to_tab', 'Elemento spostato nel tab')
|
|
8772
|
+
});
|
|
8773
|
+
}
|
|
8774
|
+
/**
|
|
8775
|
+
* onDrop del TABPANEL: appena droppato dentro un TABVIEW lo rende il tab attivo,
|
|
8776
|
+
* cosi' la sua area diventa visibile e droppabile. Il pannello a questo punto e'
|
|
8777
|
+
* gia' stato inserito in `tabView.nestedComponents` dal drop() (push prima di onDrop).
|
|
8778
|
+
*/
|
|
8779
|
+
dropTabPanel(currentTool) {
|
|
8780
|
+
this.selectTabPanelExclusive(currentTool);
|
|
8781
|
+
this.cd.detectChanges();
|
|
8782
|
+
}
|
|
8783
|
+
/**
|
|
8784
|
+
* Seleziona in modo esclusivo un TABPANEL nel TABVIEW che lo contiene
|
|
8785
|
+
* (`inputs.selected = true` sul pannello, `false` sugli altri). No-op se il
|
|
8786
|
+
* parent non e' un TABVIEW. Stesso meccanismo usato da `activateTabPanelForHover`.
|
|
8787
|
+
*/
|
|
8788
|
+
selectTabPanelExclusive(panel) {
|
|
8789
|
+
const tabViewUn = this.findParentUniqueName(panel);
|
|
8790
|
+
const tabView = tabViewUn ? this.getElementByName(tabViewUn) : null;
|
|
8791
|
+
if (!tabView || String(tabView.name || '').toUpperCase() !== 'TABVIEW' || !Array.isArray(tabView.nestedComponents)) {
|
|
8792
|
+
return;
|
|
8793
|
+
}
|
|
8794
|
+
tabView.nestedComponents.forEach((p) => {
|
|
8795
|
+
if (!p?.inputs) {
|
|
8796
|
+
p.inputs = {};
|
|
8797
|
+
}
|
|
8798
|
+
p.inputs.selected = String(p?.uniqueName || '') === String(panel?.uniqueName || '');
|
|
8799
|
+
});
|
|
8800
|
+
}
|
|
8801
|
+
/** Importa una dashboard salvata DENTRO l'elemento (nei suoi nestedComponents). */
|
|
8802
|
+
async importDashboardIntoElement(target) {
|
|
8803
|
+
if (!this.isFreeContainer(target)) {
|
|
8804
|
+
return;
|
|
8805
|
+
}
|
|
8806
|
+
const options = (this.savedDashboardOptions || []).map((o) => ({ label: o.label, value: o.value }));
|
|
8807
|
+
if (!options.length) {
|
|
8808
|
+
WtoolboxService.messageNotificationService.add({ severity: 'info', summary: this.t('designer.import.title', 'Import'), detail: this.t('designer.import.no_dashboards', 'Nessuna dashboard salvata') });
|
|
8809
|
+
return;
|
|
8810
|
+
}
|
|
8811
|
+
const resp = await WtoolboxService.promptDialog(this.t('designer.import.pick_dashboard', 'Importa dashboard qui'), [{ name: 'dash', caption: this.t('designer.import.dashboard', 'Dashboard'), type: 'dictionary', dictionaryData: options, value: '', required: true }], null, '440px', '230px');
|
|
8812
|
+
const route = String(resp?.dash?.value ?? '').trim();
|
|
8813
|
+
if (!route) {
|
|
8814
|
+
return;
|
|
8815
|
+
}
|
|
8816
|
+
let boardcontent = null;
|
|
8817
|
+
try {
|
|
8818
|
+
const result = await this.dataSrv.loadDashboard({ user_id: this.userInfo.getuserInfo().user_id, dashRoute: route });
|
|
8819
|
+
const rows = Array.isArray(result) ? result : (Array.isArray(result?.results) ? result.results : (result ? [result] : []));
|
|
8820
|
+
boardcontent = rows?.[0]?.boardcontent ?? null;
|
|
8821
|
+
}
|
|
8822
|
+
catch {
|
|
8823
|
+
boardcontent = null;
|
|
8824
|
+
}
|
|
8825
|
+
await this.importBoardcontentIntoElement(target, boardcontent);
|
|
8826
|
+
}
|
|
8827
|
+
/** Importa un PRESET locale DENTRO l'elemento (nei suoi nestedComponents). */
|
|
8828
|
+
async importPresetIntoElement(target) {
|
|
8829
|
+
if (!this.isFreeContainer(target)) {
|
|
8830
|
+
return;
|
|
8831
|
+
}
|
|
8832
|
+
const presets = this.readDesignerPresets();
|
|
8833
|
+
const names = Object.keys(presets || {});
|
|
8834
|
+
if (!names.length) {
|
|
8835
|
+
WtoolboxService.messageNotificationService.add({ severity: 'info', summary: this.t('designer.import.title', 'Import'), detail: this.t('designer.import.no_presets', 'Nessun preset salvato') });
|
|
8836
|
+
return;
|
|
8837
|
+
}
|
|
8838
|
+
const resp = await WtoolboxService.promptDialog(this.t('designer.import.pick_preset', 'Importa preset qui'), [{ name: 'preset', caption: this.t('designer.import.preset', 'Preset'), type: 'dictionary', dictionaryData: names.map((n) => ({ label: n, value: n })), value: '', required: true }], null, '440px', '230px');
|
|
8839
|
+
const name = String(resp?.preset?.value ?? '').trim();
|
|
8840
|
+
if (!name || !presets[name]) {
|
|
8841
|
+
return;
|
|
8842
|
+
}
|
|
8843
|
+
await this.importBoardcontentIntoElement(target, presets[name]);
|
|
8844
|
+
}
|
|
8845
|
+
/**
|
|
8846
|
+
* Importa un boardcontent (dashboard salvata o preset) DENTRO un container:
|
|
8847
|
+
* rimappa uniqueName/componentId con id freschi (niente collisioni) e i riferimenti
|
|
8848
|
+
* che li puntano (datasource, parentDatasource, dropped-component...), poi riusa la
|
|
8849
|
+
* rehydration completa di loadDashboard (datasource inclusi) in modalita' insert-target.
|
|
8850
|
+
*/
|
|
8851
|
+
async importBoardcontentIntoElement(target, boardContentRaw) {
|
|
8852
|
+
if (!target || !boardContentRaw) {
|
|
8853
|
+
WtoolboxService.messageNotificationService.add({ severity: 'warn', summary: this.t('designer.import.title', 'Import'), detail: this.t('designer.import.empty', 'Contenuto vuoto o non valido') });
|
|
8854
|
+
return;
|
|
8855
|
+
}
|
|
8856
|
+
let imported;
|
|
8857
|
+
try {
|
|
8858
|
+
imported = JSON.parse(boardContentRaw);
|
|
8859
|
+
}
|
|
8860
|
+
catch {
|
|
8861
|
+
imported = null;
|
|
8862
|
+
}
|
|
8863
|
+
if (!Array.isArray(imported)) {
|
|
8864
|
+
imported = Array.isArray(imported?.dashboardElements) ? imported.dashboardElements
|
|
8865
|
+
: Array.isArray(imported?.elements) ? imported.elements : [];
|
|
8866
|
+
}
|
|
8867
|
+
if (!imported.length) {
|
|
8868
|
+
WtoolboxService.messageNotificationService.add({ severity: 'warn', summary: this.t('designer.import.title', 'Import'), detail: this.t('designer.import.empty', 'Contenuto vuoto o non valido') });
|
|
8869
|
+
return;
|
|
8870
|
+
}
|
|
8871
|
+
// 1. Rimappa gli ID con id freschi (getNewID -> uniqueName = name__id) e traccia old->new.
|
|
8872
|
+
const nameMap = new Map();
|
|
8873
|
+
const idMap = new Map();
|
|
8874
|
+
this.remapImportedIds(imported, nameMap, idMap);
|
|
8875
|
+
// 2. Aggiorna i riferimenti agli elementi (datasource ecc.) coi nuovi uniqueName/id.
|
|
8876
|
+
this.remapImportedReferences(imported, nameMap, idMap);
|
|
8877
|
+
// 3. Riusa la rehydration completa di loadDashboard in modalita' insert-target.
|
|
8878
|
+
await this.loadDashboard({ boardcontent: JSON.stringify(imported), board_route: '' }, true, target);
|
|
8879
|
+
WtoolboxService.messageNotificationService.add({ severity: 'success', summary: this.t('designer.import.title', 'Import'), detail: this.t('designer.import.done', 'Contenuto importato nell\'elemento') });
|
|
8880
|
+
}
|
|
8881
|
+
/** Rimappa ricorsivamente componentId/uniqueName degli elementi importati con id freschi. */
|
|
8882
|
+
remapImportedIds(nodes, nameMap, idMap) {
|
|
8883
|
+
for (const el of (nodes || [])) {
|
|
8884
|
+
if (!el || typeof el !== 'object') {
|
|
8885
|
+
continue;
|
|
8886
|
+
}
|
|
8887
|
+
const oldName = String(el.uniqueName || '');
|
|
8888
|
+
const oldId = Number(el.componentId);
|
|
8889
|
+
const newId = this.getNewID();
|
|
8890
|
+
const newName = String(el.name || 'ITEM') + '__' + newId;
|
|
8891
|
+
if (oldName) {
|
|
8892
|
+
nameMap.set(oldName, newName);
|
|
8893
|
+
}
|
|
8894
|
+
if (!isNaN(oldId)) {
|
|
8895
|
+
idMap.set(oldId, newId);
|
|
8896
|
+
}
|
|
8897
|
+
el.componentId = newId;
|
|
8898
|
+
el.uniqueName = newName;
|
|
8899
|
+
if (Array.isArray(el.nestedComponents)) {
|
|
8900
|
+
this.remapImportedIds(el.nestedComponents, nameMap, idMap);
|
|
8901
|
+
}
|
|
8902
|
+
}
|
|
8903
|
+
}
|
|
8904
|
+
/** Aggiorna i riferimenti (oggetti con uniqueName/id) dentro gli inputs, coi nuovi valori. */
|
|
8905
|
+
remapImportedReferences(nodes, nameMap, idMap) {
|
|
8906
|
+
const SKIP = new Set(['nestedComponents', 'inputProps', 'toolProps', 'metaInfo', 'componentRef', 'propertyTree']);
|
|
8907
|
+
const remap = (v, depth) => {
|
|
8908
|
+
if (!v || typeof v !== 'object' || depth > 6) {
|
|
8909
|
+
return;
|
|
8910
|
+
}
|
|
8911
|
+
if (Array.isArray(v)) {
|
|
8912
|
+
for (const it of v) {
|
|
8913
|
+
remap(it, depth + 1);
|
|
8914
|
+
}
|
|
8915
|
+
return;
|
|
8916
|
+
}
|
|
8917
|
+
if (typeof v.uniqueName === 'string' && nameMap.has(v.uniqueName)) {
|
|
8918
|
+
v.uniqueName = nameMap.get(v.uniqueName);
|
|
8919
|
+
if (typeof v.id === 'number' && idMap.has(v.id)) {
|
|
8920
|
+
v.id = idMap.get(v.id);
|
|
8921
|
+
}
|
|
8922
|
+
if (typeof v.componentId === 'number' && idMap.has(v.componentId)) {
|
|
8923
|
+
v.componentId = idMap.get(v.componentId);
|
|
8924
|
+
}
|
|
8925
|
+
}
|
|
8926
|
+
for (const k of Object.keys(v)) {
|
|
8927
|
+
if (SKIP.has(k)) {
|
|
8928
|
+
continue;
|
|
8929
|
+
}
|
|
8930
|
+
remap(v[k], depth + 1);
|
|
8931
|
+
}
|
|
8932
|
+
};
|
|
8933
|
+
const walk = (list) => {
|
|
8934
|
+
for (const el of (list || [])) {
|
|
8935
|
+
if (!el) {
|
|
8936
|
+
continue;
|
|
8937
|
+
}
|
|
8938
|
+
remap(el.inputs, 0);
|
|
8939
|
+
if (Array.isArray(el.nestedComponents)) {
|
|
8940
|
+
walk(el.nestedComponents);
|
|
8941
|
+
}
|
|
8942
|
+
}
|
|
8943
|
+
};
|
|
8944
|
+
walk(nodes);
|
|
8945
|
+
}
|
|
8946
|
+
showHierarchyNodeContextMenu(event, node, menu) {
|
|
8947
|
+
event.preventDefault();
|
|
8948
|
+
event.stopPropagation();
|
|
8949
|
+
const uniqueName = String(node?.key || '').trim();
|
|
8950
|
+
this.hierarchyCtxElement = uniqueName ? this.getElementByName(uniqueName) : null;
|
|
8951
|
+
this.hierarchyCtxItems = this.buildElementContextMenuItems(this.hierarchyCtxElement);
|
|
8952
|
+
menu.show(event);
|
|
8953
|
+
}
|
|
8954
|
+
onHierarchyNodeSelect(event) {
|
|
8955
|
+
const node = event?.node;
|
|
8956
|
+
if (!node) {
|
|
8957
|
+
return;
|
|
8958
|
+
}
|
|
8959
|
+
const uniqueName = String(node?.key || '').trim();
|
|
8960
|
+
if (!uniqueName) {
|
|
8961
|
+
return;
|
|
8962
|
+
}
|
|
8963
|
+
const item = this.getElementByName(uniqueName);
|
|
8964
|
+
if (!item) {
|
|
8965
|
+
return;
|
|
8966
|
+
}
|
|
8967
|
+
this.selectDashboardItem(item);
|
|
8968
|
+
this.cd.detectChanges();
|
|
8969
|
+
}
|
|
8970
|
+
onHierarchyNodeMouseEnter(node) {
|
|
8971
|
+
const uniqueName = String(node?.key || '').trim();
|
|
8972
|
+
if (!uniqueName) {
|
|
8973
|
+
return;
|
|
8974
|
+
}
|
|
8975
|
+
this.setHierarchyHoverHighlightByKey(uniqueName);
|
|
8976
|
+
}
|
|
8977
|
+
onHierarchyNodeMouseLeave() {
|
|
8978
|
+
this.clearHierarchyHoverHighlight();
|
|
8979
|
+
}
|
|
8980
|
+
isHierarchyNodeSelected(node) {
|
|
8981
|
+
const nodeKey = String(node?.key || '').trim();
|
|
8982
|
+
const selectedKey = String(this.hierarchySelection?.key || '').trim();
|
|
8983
|
+
return !!nodeKey && nodeKey === selectedKey;
|
|
8984
|
+
}
|
|
8985
|
+
setHierarchyHoverHighlightByKey(uniqueName) {
|
|
8986
|
+
const key = String(uniqueName || '').trim();
|
|
8987
|
+
this.clearHierarchyHoverHighlight();
|
|
8988
|
+
if (!key) {
|
|
8989
|
+
return;
|
|
8990
|
+
}
|
|
8991
|
+
const item = this.getElementByName(key);
|
|
8992
|
+
if (!item) {
|
|
8993
|
+
return;
|
|
8994
|
+
}
|
|
8995
|
+
this.setFooterHoverHighlight(item);
|
|
8996
|
+
if (typeof document === 'undefined') {
|
|
8997
|
+
return;
|
|
8998
|
+
}
|
|
8999
|
+
const footerElement = document.querySelector(`.designer-footer-item[data-designer-unique-name="${key}"]`);
|
|
9000
|
+
if (footerElement) {
|
|
9001
|
+
footerElement.classList.add('designer-tree-hover-target');
|
|
9002
|
+
this.hierarchyHoverFooterElement = footerElement;
|
|
9003
|
+
}
|
|
9004
|
+
}
|
|
9005
|
+
clearHierarchyHoverHighlight() {
|
|
9006
|
+
if (this.hierarchyHoverFooterElement) {
|
|
9007
|
+
this.hierarchyHoverFooterElement.classList.remove('designer-tree-hover-target');
|
|
9008
|
+
this.hierarchyHoverFooterElement = null;
|
|
9009
|
+
}
|
|
9010
|
+
this.clearFooterHoverHighlight();
|
|
9011
|
+
}
|
|
9012
|
+
onHierarchyNodeDrop(event) {
|
|
9013
|
+
const draggedUniqueName = String(event?.dragNode?.key || '').trim();
|
|
9014
|
+
const dropUniqueName = String(event?.dropNode?.key || '').trim();
|
|
9015
|
+
if (!draggedUniqueName) {
|
|
7819
9016
|
return;
|
|
7820
9017
|
}
|
|
7821
9018
|
this.moveComponentInHierarchy(draggedUniqueName, dropUniqueName || null, event?.index);
|
|
@@ -8007,13 +9204,14 @@ class DesignerComponent {
|
|
|
8007
9204
|
event.preventDefault();
|
|
8008
9205
|
event.stopPropagation();
|
|
8009
9206
|
this.footerCtxElement = item;
|
|
9207
|
+
this.footerCtxItems = this.buildElementContextMenuItems(item);
|
|
8010
9208
|
menu.show(event);
|
|
8011
9209
|
}
|
|
8012
9210
|
/**
|
|
8013
|
-
* Rinomina
|
|
9211
|
+
* Rinomina un elemento aggiornando `displayName`. Usato dal context menu
|
|
9212
|
+
* unificato (canvas/gerarchia/footer) via `buildElementContextMenuItems`.
|
|
8014
9213
|
*/
|
|
8015
|
-
async
|
|
8016
|
-
const item = this.footerCtxElement;
|
|
9214
|
+
async renameDesignerElement(item) {
|
|
8017
9215
|
if (!item) {
|
|
8018
9216
|
return;
|
|
8019
9217
|
}
|
|
@@ -8489,12 +9687,9 @@ class DesignerComponent {
|
|
|
8489
9687
|
const fieldCtxItems = (contextField && contextDs)
|
|
8490
9688
|
? this.buildFieldEditorContextMenuItems(contextField, contextDs)
|
|
8491
9689
|
: [];
|
|
8492
|
-
|
|
8493
|
-
|
|
8494
|
-
|
|
8495
|
-
else {
|
|
8496
|
-
this.ctxItems.next([...fieldCtxItems, ...this.defaultCtxItems]);
|
|
8497
|
-
}
|
|
9690
|
+
// Menu elemento dalla sorgente unificata (edit/duplica/rinomina/rimuovi +
|
|
9691
|
+
// import per i container); il canvas antepone le voci field-editor.
|
|
9692
|
+
this.ctxItems.next([...fieldCtxItems, ...this.buildElementContextMenuItems(el)]);
|
|
8498
9693
|
if (el) {
|
|
8499
9694
|
this.ctxElement = el;
|
|
8500
9695
|
}
|
|
@@ -9307,6 +10502,20 @@ class DesignerComponent {
|
|
|
9307
10502
|
applyArchetypeConfigToTool(tool, action, nextConfig) {
|
|
9308
10503
|
if (!tool || !action || !nextConfig)
|
|
9309
10504
|
return;
|
|
10505
|
+
// Chart AGGREGATO: con groupInfo/aggregates il backend ritorna il valore per-gruppo nella
|
|
10506
|
+
// colonna `<field>_<AGG>` (es. LatestRecordedPopulation_AVG), NON nel campo grezzo (null nelle
|
|
10507
|
+
// righe gruppo). Il modello/configuratore emette il campo grezzo come dataField + l'aggregato in
|
|
10508
|
+
// `aggregates`: qui colleghiamo i due riscrivendo il dataField dei datasets sul nome aggregato,
|
|
10509
|
+
// cosi' il chart plotta i valori per gruppo (suffix uppercase, coerente con _Metadati_methods).
|
|
10510
|
+
if (Array.isArray(nextConfig.aggregates) && nextConfig.aggregates.length && nextConfig.dataOptions?.datasets) {
|
|
10511
|
+
for (const dset of nextConfig.dataOptions.datasets) {
|
|
10512
|
+
const agg = nextConfig.aggregates.find((a) => a?.field === dset?.dataField);
|
|
10513
|
+
const sfx = agg?.aggregate ? '_' + String(agg.aggregate).toUpperCase() : '';
|
|
10514
|
+
if (sfx && dset?.dataField && !String(dset.dataField).toUpperCase().endsWith(sfx)) {
|
|
10515
|
+
dset.dataField = dset.dataField + sfx;
|
|
10516
|
+
}
|
|
10517
|
+
}
|
|
10518
|
+
}
|
|
9310
10519
|
const customKey = 'customProps_' + action;
|
|
9311
10520
|
tool.inputs[customKey] = this.toBehaviorSubjectsObject(nextConfig);
|
|
9312
10521
|
const metaInfo = this.getDesignerDatasourceMetaInfo(tool);
|
|
@@ -9315,10 +10524,29 @@ class DesignerComponent {
|
|
|
9315
10524
|
extraProps.archetypes = extraProps.archetypes || {};
|
|
9316
10525
|
const currentArchetype = extraProps.archetypes[action] || {};
|
|
9317
10526
|
extraProps.archetypes[action] = Object.assign({}, currentArchetype, nextConfig);
|
|
10527
|
+
// AGGREGAZIONE chart: groupInfo/aggregates sono config DATASOURCE-level (extraProps
|
|
10528
|
+
// top-level, lette in data-source.component initMetadata:1914), non archetype-level. Se
|
|
10529
|
+
// l'archetype_config li porta (es. chart 'popolazione media per provincia'), sollevali a
|
|
10530
|
+
// extraProps top-level cosi' il datasource raggruppa/aggrega lato server nel fetch.
|
|
10531
|
+
if (Array.isArray(nextConfig.groupInfo))
|
|
10532
|
+
extraProps.groupInfo = nextConfig.groupInfo;
|
|
10533
|
+
if (Array.isArray(nextConfig.aggregates))
|
|
10534
|
+
extraProps.aggregates = nextConfig.aggregates;
|
|
9318
10535
|
metaInfo.tableMetadata.extraProps = extraProps;
|
|
9319
10536
|
metaInfo.tableMetadata.md_props_bag = JSON.stringify(extraProps);
|
|
9320
10537
|
}
|
|
9321
10538
|
const ds = tool.inputs?.['datasource']?.component?.value;
|
|
10539
|
+
// Il datasource parsa extraProps.groupInfo/aggregates SOLO all'init metadata: qui (config
|
|
10540
|
+
// applicato a runtime dall'injection/configuratore) li settiamo a mano sull'istanza PRIMA
|
|
10541
|
+
// del fetch, cosi' la fetchData successiva raggruppa/aggrega lato server (un punto per gruppo).
|
|
10542
|
+
if (ds) {
|
|
10543
|
+
if (Array.isArray(nextConfig.groupInfo)) {
|
|
10544
|
+
ds.groupInfo = nextConfig.groupInfo.map((x) => ({ field: x?.field, groupFormula: x?.groupFormula, dir: x?.dir, groupCount: x?.groupCount }));
|
|
10545
|
+
}
|
|
10546
|
+
if (Array.isArray(nextConfig.aggregates)) {
|
|
10547
|
+
ds.aggregationInfo = nextConfig.aggregates.map((x) => ({ field: x?.field, aggregate: x?.aggregate }));
|
|
10548
|
+
}
|
|
10549
|
+
}
|
|
9322
10550
|
if (ds?.fetchData) {
|
|
9323
10551
|
ds.fetchData();
|
|
9324
10552
|
}
|
|
@@ -10512,6 +11740,7 @@ Display formula context:
|
|
|
10512
11740
|
});
|
|
10513
11741
|
}
|
|
10514
11742
|
let destination = this.dashboardElements;
|
|
11743
|
+
let parentContainerTool = null;
|
|
10515
11744
|
if (event.toElement) {
|
|
10516
11745
|
let id = event.toElement.getAttribute("id");
|
|
10517
11746
|
let parentEl = event.toElement.parentElement;
|
|
@@ -10527,6 +11756,7 @@ Display formula context:
|
|
|
10527
11756
|
if (match) {
|
|
10528
11757
|
if (!match.allowedChildren || match.allowedChildren.includes(currentTool.name)) {
|
|
10529
11758
|
destination = match?.nestedComponents;
|
|
11759
|
+
parentContainerTool = match;
|
|
10530
11760
|
}
|
|
10531
11761
|
else {
|
|
10532
11762
|
WtoolboxService.messageNotificationService.add({
|
|
@@ -10552,11 +11782,68 @@ Display formula context:
|
|
|
10552
11782
|
currentTool.onDrop(currentTool);
|
|
10553
11783
|
}
|
|
10554
11784
|
this.tool = currentTool;
|
|
11785
|
+
// Flusso assoluto (feature, default OFF): l'elemento droppato nasce
|
|
11786
|
+
// position:absolute con left/top dalla posizione del drop -> fuori dal flusso,
|
|
11787
|
+
// quindi ridimensionarne uno non sposta gli altri.
|
|
11788
|
+
if (this.absoluteFlowEnabled) {
|
|
11789
|
+
// Riferimento di posizionamento: il container se droppo dentro uno, altrimenti il canvas.
|
|
11790
|
+
let refEl = this.canvasRef?.nativeElement || null;
|
|
11791
|
+
if (parentContainerTool) {
|
|
11792
|
+
const containerDom = document.getElementById(parentContainerTool.uniqueName);
|
|
11793
|
+
if (containerDom) {
|
|
11794
|
+
refEl = containerDom;
|
|
11795
|
+
// Il container diventa il contesto di posizionamento del figlio absolute
|
|
11796
|
+
// (se non e' gia' posizionato): cosi' left/top del figlio sono relativi a lui.
|
|
11797
|
+
const curPos = String(parentContainerTool.inputs?.['position'] || '').trim();
|
|
11798
|
+
if (!curPos || curPos === 'static') {
|
|
11799
|
+
parentContainerTool.inputs['position'] = 'relative';
|
|
11800
|
+
}
|
|
11801
|
+
}
|
|
11802
|
+
}
|
|
11803
|
+
const pos = this.getDropPositionRelativeTo(event, refEl);
|
|
11804
|
+
currentTool.inputs['position'] = 'absolute';
|
|
11805
|
+
currentTool.inputs['left'] = pos.left + 'px';
|
|
11806
|
+
currentTool.inputs['top'] = pos.top + 'px';
|
|
11807
|
+
}
|
|
10555
11808
|
this.flattenedDashboardElements = this.flattenComponentTree(this.dashboardElements);
|
|
11809
|
+
// Snap-on-drop SOLO in flusso normale: in modalita' assoluta la posizione e'
|
|
11810
|
+
// gia' settata (left/top) e snappata sopra.
|
|
11811
|
+
if (!this.absoluteFlowEnabled) {
|
|
11812
|
+
this.snapDroppedElementToGrid(currentTool);
|
|
11813
|
+
}
|
|
10556
11814
|
this.commitHistoryIfChanged();
|
|
10557
11815
|
this.draggedPayload = null;
|
|
10558
11816
|
}
|
|
10559
11817
|
}
|
|
11818
|
+
/**
|
|
11819
|
+
* Snap-on-drop: se lo snap alla griglia e' attivo, allinea la posizione
|
|
11820
|
+
* dell'elemento appena droppato al multiplo di griglia scrivendo un
|
|
11821
|
+
* `inputs.transform` di traslazione (stessa proprieta' usata da `onMoving`,
|
|
11822
|
+
* quindi persiste su save/reload). Deferred perche' l'elemento deve essere
|
|
11823
|
+
* nel DOM per leggere `offsetLeft/offsetTop` (posizione di flusso, senza
|
|
11824
|
+
* transform). No-op se snap off o gia' allineato.
|
|
11825
|
+
*/
|
|
11826
|
+
snapDroppedElementToGrid(tool) {
|
|
11827
|
+
const g = DesignerSnapState.gridSize;
|
|
11828
|
+
if (!g || g <= 0 || !tool?.uniqueName) {
|
|
11829
|
+
return;
|
|
11830
|
+
}
|
|
11831
|
+
setTimeout(() => {
|
|
11832
|
+
const el = document.getElementById(tool.uniqueName);
|
|
11833
|
+
if (!el) {
|
|
11834
|
+
return;
|
|
11835
|
+
}
|
|
11836
|
+
const dx = Math.round(el.offsetLeft / g) * g - el.offsetLeft;
|
|
11837
|
+
const dy = Math.round(el.offsetTop / g) * g - el.offsetTop;
|
|
11838
|
+
if (dx === 0 && dy === 0) {
|
|
11839
|
+
return;
|
|
11840
|
+
}
|
|
11841
|
+
tool.inputs = tool.inputs || {};
|
|
11842
|
+
tool.inputs['transform'] = `matrix(1, 0, 0, 1, ${dx}, ${dy})`;
|
|
11843
|
+
this.cd.detectChanges();
|
|
11844
|
+
this.commitHistoryIfChanged();
|
|
11845
|
+
}, 0);
|
|
11846
|
+
}
|
|
10560
11847
|
/**
|
|
10561
11848
|
* Genera un nuovo identificativo incrementale per `componentId`.
|
|
10562
11849
|
*/
|
|
@@ -10845,7 +12132,80 @@ Display formula context:
|
|
|
10845
12132
|
* - `elements` (JSON) -> `boardcontent` (TEXT)
|
|
10846
12133
|
* e aggiorna anche i fogli collegati in `dom_board_sheet`.
|
|
10847
12134
|
*/
|
|
12135
|
+
/**
|
|
12136
|
+
* Lint pre-save del boardcontent: individua problemi di configurazione che
|
|
12137
|
+
* altrimenti emergerebbero solo a runtime. Metodo puro (nessun side-effect),
|
|
12138
|
+
* riusabile e testabile. Rileva:
|
|
12139
|
+
* - uniqueName duplicati (rompono selezione/rimozione/hierarchy tree, key=uniqueName);
|
|
12140
|
+
* - binding datasource orfani (DATAREPEATER/SELECT/MULTISELECT/UL che puntano a
|
|
12141
|
+
* un DATASOURCE.uniqueName inesistente nel layout);
|
|
12142
|
+
* - DATASOURCE senza route impostata.
|
|
12143
|
+
*/
|
|
12144
|
+
computeDashboardLintIssues() {
|
|
12145
|
+
const issues = [];
|
|
12146
|
+
const flat = this.flattenComponentTree(this.dashboardElements) || [];
|
|
12147
|
+
// 1. uniqueName duplicati
|
|
12148
|
+
const seen = new Set();
|
|
12149
|
+
const dup = new Set();
|
|
12150
|
+
for (const el of flat) {
|
|
12151
|
+
const un = String(el?.uniqueName || '').trim();
|
|
12152
|
+
if (!un) {
|
|
12153
|
+
continue;
|
|
12154
|
+
}
|
|
12155
|
+
if (seen.has(un)) {
|
|
12156
|
+
dup.add(un);
|
|
12157
|
+
}
|
|
12158
|
+
seen.add(un);
|
|
12159
|
+
}
|
|
12160
|
+
for (const un of dup) {
|
|
12161
|
+
issues.push(this.trslSrv.format(this.t('designer.lint.duplicate_unique_name', "Nome elemento duplicato: '{0}'"), un));
|
|
12162
|
+
}
|
|
12163
|
+
// 2. uniqueName dei DATASOURCE disponibili
|
|
12164
|
+
const dsNames = new Set();
|
|
12165
|
+
for (const el of flat) {
|
|
12166
|
+
if (el?.name === 'DATASOURCE') {
|
|
12167
|
+
const un = String(el?.uniqueName || '').trim();
|
|
12168
|
+
if (un) {
|
|
12169
|
+
dsNames.add(un);
|
|
12170
|
+
}
|
|
12171
|
+
}
|
|
12172
|
+
}
|
|
12173
|
+
// 3. binding orfani + DATASOURCE senza route
|
|
12174
|
+
for (const el of flat) {
|
|
12175
|
+
const name = String(el?.name || '');
|
|
12176
|
+
if (name === 'DATAREPEATER' || name === 'SELECT' || name === 'MULTISELECT' || name === 'UL') {
|
|
12177
|
+
const ds = el?.inputs?.['datasource'];
|
|
12178
|
+
const dsUn = ds && typeof ds === 'object' ? String(ds.uniqueName || '').trim() : '';
|
|
12179
|
+
if (dsUn && !dsNames.has(dsUn)) {
|
|
12180
|
+
issues.push(this.trslSrv.format(this.t('designer.lint.orphan_datasource_binding', "Il componente '{0}' punta a un datasource inesistente ('{1}')"), String(el?.uniqueName || el?.name || ''), dsUn));
|
|
12181
|
+
}
|
|
12182
|
+
}
|
|
12183
|
+
if (name === 'DATASOURCE') {
|
|
12184
|
+
const routeVal = el?.inputs?.['route'];
|
|
12185
|
+
const routeStr = routeVal instanceof BehaviorSubject ? routeVal.value : routeVal;
|
|
12186
|
+
if (!String(routeStr || '').trim()) {
|
|
12187
|
+
issues.push(this.trslSrv.format(this.t('designer.lint.datasource_without_route', "Il datasource '{0}' non ha una route impostata"), String(el?.uniqueName || 'DATASOURCE')));
|
|
12188
|
+
}
|
|
12189
|
+
}
|
|
12190
|
+
}
|
|
12191
|
+
return issues;
|
|
12192
|
+
}
|
|
10848
12193
|
async saveDashboard() {
|
|
12194
|
+
// Lint pre-save non bloccante: se ci sono problemi, elencali e chiedi conferma.
|
|
12195
|
+
const lintIssues = this.computeDashboardLintIssues();
|
|
12196
|
+
if (lintIssues.length > 0) {
|
|
12197
|
+
const proceed = await WtoolboxService.confirm({
|
|
12198
|
+
header: this.t('designer.lint.title', 'Problemi nel layout'),
|
|
12199
|
+
message: this.t('designer.lint.intro', 'Il layout presenta alcuni problemi:')
|
|
12200
|
+
+ ' ' + lintIssues.map((i) => '• ' + i).join(' ')
|
|
12201
|
+
+ ' — ' + this.t('designer.lint.confirm_save_anyway', 'Salvare comunque?'),
|
|
12202
|
+
acceptLabel: this.t('confirm', 'Confirm'),
|
|
12203
|
+
rejectLabel: this.t('cancel', 'Cancel')
|
|
12204
|
+
});
|
|
12205
|
+
if (!proceed) {
|
|
12206
|
+
return;
|
|
12207
|
+
}
|
|
12208
|
+
}
|
|
10849
12209
|
const route = String(this.currentDashboardRoute || '').trim();
|
|
10850
12210
|
if (route) {
|
|
10851
12211
|
const payload = {
|
|
@@ -10931,14 +12291,18 @@ Display formula context:
|
|
|
10931
12291
|
* e ricostruisce lo stato runtime del designer partendo da `dom_board.boardcontent`.
|
|
10932
12292
|
* Supporta route in forme alias (`board_route`, `boardroute`, `dashRoute`).
|
|
10933
12293
|
*/
|
|
10934
|
-
async loadDashboard(dashboard, skipServerFetch = false) {
|
|
12294
|
+
async loadDashboard(dashboard, skipServerFetch = false, insertTarget = null) {
|
|
10935
12295
|
const dashRoute = String(dashboard?.board_route ??
|
|
10936
12296
|
dashboard?.boardroute ??
|
|
10937
12297
|
dashboard?.boardRoute ??
|
|
10938
12298
|
dashboard?.dashRoute ??
|
|
10939
12299
|
dashboard ??
|
|
10940
12300
|
'').trim();
|
|
10941
|
-
|
|
12301
|
+
// Import/preset (feature C/F) passano boardcontent con route eventualmente
|
|
12302
|
+
// vuota (board non ancora salvata). NON fare reset in quel caso: il reset va
|
|
12303
|
+
// fatto solo quando NON c'e' ne' route ne' contenuto (nuovo grafo / clear).
|
|
12304
|
+
const providedBoardcontent = dashboard?.boardcontent ?? dashboard?.boardContent ?? null;
|
|
12305
|
+
if (!dashRoute && !providedBoardcontent) {
|
|
10942
12306
|
this.dashboardElements = [];
|
|
10943
12307
|
this.flattenedDashboardElements = [];
|
|
10944
12308
|
this.currentDashboardRoute = '';
|
|
@@ -10952,8 +12316,10 @@ Display formula context:
|
|
|
10952
12316
|
this.cd.detectChanges();
|
|
10953
12317
|
return;
|
|
10954
12318
|
}
|
|
10955
|
-
|
|
10956
|
-
|
|
12319
|
+
if (!insertTarget) {
|
|
12320
|
+
this.currentDashboardRoute = dashRoute;
|
|
12321
|
+
this.refreshGraphActionsMenuItems();
|
|
12322
|
+
}
|
|
10957
12323
|
let resultRows = [];
|
|
10958
12324
|
let result = null;
|
|
10959
12325
|
if (!skipServerFetch) {
|
|
@@ -10966,14 +12332,16 @@ Display formula context:
|
|
|
10966
12332
|
? result
|
|
10967
12333
|
: (Array.isArray(result?.results) ? result.results : (result ? [result] : []));
|
|
10968
12334
|
}
|
|
10969
|
-
|
|
10970
|
-
dashboard?.
|
|
10971
|
-
|
|
10972
|
-
|
|
10973
|
-
|
|
10974
|
-
|
|
10975
|
-
|
|
10976
|
-
|
|
12335
|
+
if (!insertTarget) {
|
|
12336
|
+
this.currentDashboardDescription = String(dashboard?.board_des ??
|
|
12337
|
+
dashboard?.boarddes ??
|
|
12338
|
+
resultRows?.[0]?.board_des ??
|
|
12339
|
+
resultRows?.[0]?.boarddes ??
|
|
12340
|
+
this.currentDashboardDescription ??
|
|
12341
|
+
dashRoute).trim();
|
|
12342
|
+
const boardSheetPaths = this.extractDashboardSheetPaths(resultRows, dashboard);
|
|
12343
|
+
await this.loadDashboardCssSheets(boardSheetPaths);
|
|
12344
|
+
}
|
|
10977
12345
|
const boardContentRaw = resultRows?.[0]?.boardcontent ?? dashboard?.boardcontent ?? dashboard?.boardContent ?? null;
|
|
10978
12346
|
if (boardContentRaw) {
|
|
10979
12347
|
let loadedDashboard = [];
|
|
@@ -10991,7 +12359,9 @@ Display formula context:
|
|
|
10991
12359
|
: [];
|
|
10992
12360
|
}
|
|
10993
12361
|
this.mergeMissingToolPropsRecursive(loadedDashboard);
|
|
10994
|
-
|
|
12362
|
+
if (!insertTarget) {
|
|
12363
|
+
this.dashboardComponents = [];
|
|
12364
|
+
}
|
|
10995
12365
|
let dsSubs = [];
|
|
10996
12366
|
let dsElements = [];
|
|
10997
12367
|
let dsElementCount = (loadedDashboard || []).filter(x => x?.name == 'DATASOURCE').length;
|
|
@@ -11133,18 +12503,34 @@ Display formula context:
|
|
|
11133
12503
|
});
|
|
11134
12504
|
// Ensure a new reference for template control-flow and force refresh when
|
|
11135
12505
|
// async loading happens outside Angular zone.
|
|
11136
|
-
|
|
11137
|
-
|
|
12506
|
+
if (insertTarget) {
|
|
12507
|
+
// Import IN-elemento: inserisci gli elementi importati nei nestedComponents
|
|
12508
|
+
// del target invece di sostituire l'intero canvas.
|
|
12509
|
+
insertTarget.nestedComponents = Array.isArray(insertTarget.nestedComponents) ? insertTarget.nestedComponents : [];
|
|
12510
|
+
insertTarget.nestedComponents.push(...(Array.isArray(loadedDashboard) ? loadedDashboard : []));
|
|
12511
|
+
this.dashboardElements = Array.isArray(this.dashboardElements) ? [...this.dashboardElements] : [];
|
|
12512
|
+
this.flattenedDashboardElements = this.flattenComponentTree(this.dashboardElements);
|
|
12513
|
+
}
|
|
12514
|
+
else {
|
|
12515
|
+
this.dashboardElements = Array.isArray(loadedDashboard) ? [...loadedDashboard] : [];
|
|
12516
|
+
this.flattenedDashboardElements = flattened;
|
|
12517
|
+
}
|
|
11138
12518
|
this.cd.detectChanges();
|
|
11139
12519
|
}
|
|
11140
|
-
else {
|
|
12520
|
+
else if (!insertTarget) {
|
|
11141
12521
|
this.dashboardElements = [];
|
|
11142
12522
|
this.flattenedDashboardElements = [];
|
|
11143
12523
|
this.currentDashboardRoute = '';
|
|
11144
12524
|
this.cd.detectChanges();
|
|
11145
12525
|
console.warn('[Designer] No dashboard content found for route:', dashRoute, result);
|
|
11146
12526
|
}
|
|
11147
|
-
|
|
12527
|
+
if (insertTarget) {
|
|
12528
|
+
// Import in-elemento: NON resettare la history (stiamo aggiungendo), committa.
|
|
12529
|
+
this.commitHistoryIfChanged();
|
|
12530
|
+
}
|
|
12531
|
+
else {
|
|
12532
|
+
this.resetHistory();
|
|
12533
|
+
}
|
|
11148
12534
|
this.refreshGraphActionsMenuItems();
|
|
11149
12535
|
this.maxId = (this.flattenedDashboardElements || []).reduce((max, item) => Math.max(max, Number(item?.componentId || 0)), 0);
|
|
11150
12536
|
}
|
|
@@ -11904,11 +13290,11 @@ Display formula context:
|
|
|
11904
13290
|
// return customProps;
|
|
11905
13291
|
}
|
|
11906
13292
|
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 });
|
|
11907
|
-
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" }] });
|
|
13293
|
+
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], viewQueries: [{ propertyName: "canvasRef", first: true, predicate: ["canvas"], descendants: true }], 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\" data-testid=\"designer-actions-menu-btn\" (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\"\n [class.designer-snap-grid]=\"snapToGridEnabled\" [style.--wuic-grid-size.px]=\"gridSize\" pDroppable\n (onDrop)=\"drop($event)\" #canvas (mousedown)=\"onCanvasMouseDown($event)\"\n (contextmenu)=\"showCtx($event, this)\">\n <wuic-dashboard [dashboardElements]=\"dashboardElements\"></wuic-dashboard>\n @if (rubberBandVisible) {\n <div class=\"designer-selection-rect\" [style.left]=\"rubberBandStyle.left\" [style.top]=\"rubberBandStyle.top\"\n [style.width]=\"rubberBandStyle.width\" [style.height]=\"rubberBandStyle.height\"></div>\n }\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;position:relative}.designer-selection-rect{position:absolute;border:1px dashed #3b82f6;background:#3b82f61f;pointer-events:none;z-index:10000}:host ::ng-deep .designer-multi-selected{outline:2px solid #3b82f6!important;outline-offset:1px}: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}.dashboard-design.designer-snap-grid{background-image:linear-gradient(to right,rgba(128,128,128,.16) 1px,transparent 1px),linear-gradient(to bottom,rgba(128,128,128,.16) 1px,transparent 1px);background-size:var(--wuic-grid-size, 10px) var(--wuic-grid-size, 10px)}\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: i10$1.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: i7.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: i2$3.TranslatePipe, name: "translate" }, { kind: "pipe", type: AsyncPipe, name: "async" }] });
|
|
11908
13294
|
}
|
|
11909
13295
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: DesignerComponent, decorators: [{
|
|
11910
13296
|
type: Component,
|
|
11911
|
-
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"] }]
|
|
13297
|
+
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\" data-testid=\"designer-actions-menu-btn\" (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\"\n [class.designer-snap-grid]=\"snapToGridEnabled\" [style.--wuic-grid-size.px]=\"gridSize\" pDroppable\n (onDrop)=\"drop($event)\" #canvas (mousedown)=\"onCanvasMouseDown($event)\"\n (contextmenu)=\"showCtx($event, this)\">\n <wuic-dashboard [dashboardElements]=\"dashboardElements\"></wuic-dashboard>\n @if (rubberBandVisible) {\n <div class=\"designer-selection-rect\" [style.left]=\"rubberBandStyle.left\" [style.top]=\"rubberBandStyle.top\"\n [style.width]=\"rubberBandStyle.width\" [style.height]=\"rubberBandStyle.height\"></div>\n }\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;position:relative}.designer-selection-rect{position:absolute;border:1px dashed #3b82f6;background:#3b82f61f;pointer-events:none;z-index:10000}:host ::ng-deep .designer-multi-selected{outline:2px solid #3b82f6!important;outline-offset:1px}: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}.dashboard-design.designer-snap-grid{background-image:linear-gradient(to right,rgba(128,128,128,.16) 1px,transparent 1px),linear-gradient(to bottom,rgba(128,128,128,.16) 1px,transparent 1px);background-size:var(--wuic-grid-size, 10px) var(--wuic-grid-size, 10px)}\n"] }]
|
|
11912
13298
|
}], ctorParameters: () => [{ type: i1$1.ActivatedRoute }, { type: DataProviderService }, { type: MetadataProviderService }, { type: UserInfoService }, { type: i0.ChangeDetectorRef }, { type: TranslationManagerService }, { type: MetadataEditorService }, { type: ChatbotHostRegistryService }, { type: WorkflowRuntimeMetadataService }], propDecorators: { onWindowHashChange: [{
|
|
11913
13299
|
type: HostListener,
|
|
11914
13300
|
args: ['window:hashchange']
|
|
@@ -11930,7 +13316,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
11930
13316
|
}], onWindowChange: [{
|
|
11931
13317
|
type: HostListener,
|
|
11932
13318
|
args: ['window:change']
|
|
13319
|
+
}], canvasRef: [{
|
|
13320
|
+
type: ViewChild,
|
|
13321
|
+
args: ['canvas']
|
|
11933
13322
|
}] } });
|
|
11934
13323
|
|
|
11935
13324
|
export { DesignerComponent };
|
|
11936
|
-
//# sourceMappingURL=wuic-framework-lib-designer.component-
|
|
13325
|
+
//# sourceMappingURL=wuic-framework-lib-designer.component-x10ZzUEy.mjs.map
|