wuic-framework-lib 1.3.0 → 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-C02jeFGy.mjs → wuic-framework-lib-chart-list.component-0J1ghIrS.mjs} +4 -4
- package/fesm2022/{wuic-framework-lib-code-editor.component-BZNLr0Jj.mjs → wuic-framework-lib-code-editor.component-C21mvjyr.mjs} +277 -13
- package/fesm2022/{wuic-framework-lib-designer.component-N_CfbGnr.mjs → wuic-framework-lib-designer.component-x10ZzUEy.mjs} +1649 -184
- package/fesm2022/{wuic-framework-lib-first-run-wizard.component-lW7-xMm3.mjs → wuic-framework-lib-first-run-wizard.component-BuSxs_CM.mjs} +83 -43
- package/fesm2022/{wuic-framework-lib-framework-docs.component-CGQbmSkv.mjs → wuic-framework-lib-framework-docs.component-xTGLbEnp.mjs} +1326 -99
- package/fesm2022/{wuic-framework-lib-pivot-builder.component-0gJz9RWr.mjs → wuic-framework-lib-pivot-builder.component-BkFFjPm4.mjs} +6 -6
- package/fesm2022/{wuic-framework-lib-prompt-dialog-fallback-host.component-C9LvmFsL.mjs → wuic-framework-lib-prompt-dialog-fallback-host.component-Bi-TrJB6.mjs} +2 -2
- package/fesm2022/{wuic-framework-lib-report-designer.component-DcpdzFjh.mjs → wuic-framework-lib-report-designer.component-ZvB46-LV.mjs} +2 -2
- package/fesm2022/{wuic-framework-lib-report-viewer.component-BHpbGpp9.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-DHoukjf-.mjs → wuic-framework-lib-scheduler-list.component-xzlTEkoq.mjs} +2 -2
- package/fesm2022/{wuic-framework-lib-spreadsheet-list-sf.component-VsaXepUC.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-Deudd4MY.mjs → wuic-framework-lib-wuic-framework-lib-3TZ-j-yL.mjs} +2682 -732
- package/fesm2022/wuic-framework-lib.mjs +1 -1
- package/package.json +2 -1
- package/types/wuic-framework-lib.d.ts +762 -9
- package/fesm2022/wuic-framework-lib-workflow-designer.component-epj3oKq3.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 : ''}.` };
|
|
@@ -6092,6 +6136,48 @@ class DesignerComponent {
|
|
|
6092
6136
|
*
|
|
6093
6137
|
* Ritorna il numero di elementi creati (1 + somma dei children).
|
|
6094
6138
|
*/
|
|
6139
|
+
/**
|
|
6140
|
+
* Inietta la config di archetipo (`md_props_bag.archetypes.<action>`) nel
|
|
6141
|
+
* `metaInfo.tableMetadata` del DATASOURCE bindato a un DATAREPEATER appena
|
|
6142
|
+
* droppato. NESSUNA chiamata server: muta solo lo stato in-memory del
|
|
6143
|
+
* datasource (md_props_bag + extraProps.archetypes) e ri-emette `fetchInfo$`
|
|
6144
|
+
* cosi' il DATAREPEATER reagisce e renderizza l'archetipo configurato. La
|
|
6145
|
+
* config viene poi serializzata insieme al datasource quando l'utente salva
|
|
6146
|
+
* la dashboard (boardcontent), coerente con AGENTS regola 7 (md_props_bag e'
|
|
6147
|
+
* la fonte di verita', extraProps e' la proiezione runtime).
|
|
6148
|
+
* @param dsBinding il binding gia' idratato (`{uniqueName, component: BehaviorSubject<DataSourceComponent>}`)
|
|
6149
|
+
* @param action l'archetipo (chart/map/scheduler/kanban/tree/carousel/spreadsheet/...)
|
|
6150
|
+
* @param config oggetto config = contenuto di archetypes.<action>
|
|
6151
|
+
*/
|
|
6152
|
+
/**
|
|
6153
|
+
* Applica `archetype_config` (dal nodo designer_inject) a un DATAREPEATER tool
|
|
6154
|
+
* APPENA droppato, posticipando finche' il DataSourceComponent bindato e'
|
|
6155
|
+
* materializzato (`fetchData` disponibile). Poi delega ad
|
|
6156
|
+
* {@link applyArchetypeConfigToTool} — il path proven del configuratore.
|
|
6157
|
+
*/
|
|
6158
|
+
deferApplyArchetypeConfig(tool, action, config) {
|
|
6159
|
+
if (!tool || !action || !config || typeof config !== 'object')
|
|
6160
|
+
return;
|
|
6161
|
+
let tries = 0;
|
|
6162
|
+
const attempt = () => {
|
|
6163
|
+
const ds = tool.inputs?.['datasource']?.component?.value;
|
|
6164
|
+
if (ds && typeof ds.fetchData === 'function') {
|
|
6165
|
+
try {
|
|
6166
|
+
this.applyArchetypeConfigToTool(tool, action, config);
|
|
6167
|
+
}
|
|
6168
|
+
catch (e) {
|
|
6169
|
+
try {
|
|
6170
|
+
console.warn('[deferApplyArchetypeConfig] apply failed', e);
|
|
6171
|
+
}
|
|
6172
|
+
catch { /* */ }
|
|
6173
|
+
}
|
|
6174
|
+
return;
|
|
6175
|
+
}
|
|
6176
|
+
if (tries++ < 60)
|
|
6177
|
+
setTimeout(attempt, 200); // ~12s max per la materializzazione del ds
|
|
6178
|
+
};
|
|
6179
|
+
attempt();
|
|
6180
|
+
}
|
|
6095
6181
|
async injectLayoutNode(node, parentUniqueName) {
|
|
6096
6182
|
const toolName = String(node?.tool_name || '').trim();
|
|
6097
6183
|
if (!toolName)
|
|
@@ -6365,6 +6451,25 @@ class DesignerComponent {
|
|
|
6365
6451
|
}
|
|
6366
6452
|
catch { /* ignore */ }
|
|
6367
6453
|
}
|
|
6454
|
+
// CONFIG ARCHETIPO INLINE: il nodo puo' portare `archetype_config`
|
|
6455
|
+
// (= md_props_bag.archetypes.<action> JSON, es. chart {type,options,
|
|
6456
|
+
// dataOptions}, map {zoom,center,...}, scheduler {fromField,...},
|
|
6457
|
+
// kanban {statusField,statusColumns,...}). NESSUNA chiamata server: la
|
|
6458
|
+
// config viene scritta nel metaInfo.tableMetadata del DATASOURCE bindato
|
|
6459
|
+
// (md_props_bag + extraProps.archetypes); il DATAREPEATER reagisce via
|
|
6460
|
+
// fetchInfo$ e renderizza l'archetipo configurato. Persiste alla
|
|
6461
|
+
// serializzazione del datasource quando si salva la dashboard.
|
|
6462
|
+
const archCfg = (node && (node.archetype_config || node.archetypeConfig))
|
|
6463
|
+
|| (node?.inputs && (node.inputs.archetype_config || node.inputs.archetypeConfig))
|
|
6464
|
+
|| null;
|
|
6465
|
+
if (archCfg && typeof archCfg === 'object') {
|
|
6466
|
+
// Defer: il DataSourceComponent bindato (e l'archetipo renderizzato) si
|
|
6467
|
+
// materializzano async dopo il drop. Aspetta che il ds sia pronto
|
|
6468
|
+
// (fetchData disponibile), poi applica via il path PROVEN del configuratore
|
|
6469
|
+
// (`applyArchetypeConfigToTool`: customProps_<action> + metaInfo + ds.fetchData
|
|
6470
|
+
// + propertyTreeBuilder) — lo STESSO usato da "Configura chart".
|
|
6471
|
+
this.deferApplyArchetypeConfig(justInjected, String(action), archCfg);
|
|
6472
|
+
}
|
|
6368
6473
|
}
|
|
6369
6474
|
}
|
|
6370
6475
|
let count = 1;
|
|
@@ -7393,11 +7498,53 @@ class DesignerComponent {
|
|
|
7393
7498
|
if (this.isTypingTarget(event.target)) {
|
|
7394
7499
|
return;
|
|
7395
7500
|
}
|
|
7501
|
+
const rawKey = String(event.key || '');
|
|
7396
7502
|
const ctrlOrMeta = !!event.ctrlKey || !!event.metaKey;
|
|
7503
|
+
// ---- Scorciatoie SENZA modificatore (feature B) ----
|
|
7397
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();
|
|
7398
7546
|
return;
|
|
7399
7547
|
}
|
|
7400
|
-
const key = String(event.key || '').toLowerCase();
|
|
7401
7548
|
if (key === 'z' && event.shiftKey) {
|
|
7402
7549
|
event.preventDefault();
|
|
7403
7550
|
this.redo();
|
|
@@ -7411,6 +7558,28 @@ class DesignerComponent {
|
|
|
7411
7558
|
if (key === 'y') {
|
|
7412
7559
|
event.preventDefault();
|
|
7413
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();
|
|
7414
7583
|
}
|
|
7415
7584
|
}
|
|
7416
7585
|
/**
|
|
@@ -7636,12 +7805,49 @@ class DesignerComponent {
|
|
|
7636
7805
|
{
|
|
7637
7806
|
separator: true
|
|
7638
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
|
+
},
|
|
7639
7831
|
{
|
|
7640
7832
|
label: this.hideDatasourcesInCanvas
|
|
7641
7833
|
? this.t('designer.menu_show_datasources', 'Mostra datasource nel canvas')
|
|
7642
7834
|
: this.t('designer.menu_hide_datasources', 'Nascondi datasource nel canvas'),
|
|
7643
7835
|
icon: this.hideDatasourcesInCanvas ? 'pi pi-eye' : 'pi pi-eye-slash',
|
|
7644
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()
|
|
7645
7851
|
}
|
|
7646
7852
|
];
|
|
7647
7853
|
}
|
|
@@ -7649,99 +7855,1151 @@ class DesignerComponent {
|
|
|
7649
7855
|
this.hideDatasourcesInCanvas = !this.hideDatasourcesInCanvas;
|
|
7650
7856
|
this.refreshGraphActionsMenuItems();
|
|
7651
7857
|
}
|
|
7652
|
-
|
|
7653
|
-
|
|
7654
|
-
|
|
7655
|
-
|
|
7656
|
-
|
|
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();
|
|
7657
7880
|
}
|
|
7658
|
-
|
|
7659
|
-
|
|
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();
|
|
7660
7927
|
}
|
|
7661
|
-
|
|
7662
|
-
|
|
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);
|
|
7663
7946
|
}
|
|
7664
7947
|
/**
|
|
7665
|
-
*
|
|
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.
|
|
7666
7951
|
*/
|
|
7667
|
-
|
|
7668
|
-
const
|
|
7669
|
-
if (
|
|
7670
|
-
return
|
|
7952
|
+
snapElementAbsoluteToGrid(tool, g) {
|
|
7953
|
+
const dom = tool?.uniqueName ? document.getElementById(tool.uniqueName) : null;
|
|
7954
|
+
if (!dom || !tool.inputs) {
|
|
7955
|
+
return false;
|
|
7671
7956
|
}
|
|
7672
|
-
|
|
7673
|
-
const
|
|
7674
|
-
if (
|
|
7675
|
-
|
|
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
|
+
}
|
|
7676
7964
|
}
|
|
7677
|
-
|
|
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;
|
|
7678
7972
|
}
|
|
7679
7973
|
/**
|
|
7680
|
-
*
|
|
7974
|
+
* Sposta l'elemento selezionato di (dx, dy) px agendo sulla translation della
|
|
7975
|
+
* matrice `transform` (stesso campo che usa onMoving). Usato dalle frecce.
|
|
7681
7976
|
*/
|
|
7682
|
-
|
|
7683
|
-
|
|
7977
|
+
nudgeSelectedElement(dx, dy) {
|
|
7978
|
+
const el = this.tool;
|
|
7979
|
+
if (!el?.inputs) {
|
|
7684
7980
|
return;
|
|
7685
7981
|
}
|
|
7686
|
-
|
|
7687
|
-
|
|
7688
|
-
|
|
7689
|
-
|
|
7690
|
-
|
|
7691
|
-
|
|
7692
|
-
|
|
7693
|
-
|
|
7694
|
-
|
|
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();
|
|
7695
7994
|
}
|
|
7696
|
-
|
|
7697
|
-
|
|
7698
|
-
|
|
7699
|
-
|
|
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 : {};
|
|
7700
8005
|
}
|
|
7701
|
-
|
|
7702
|
-
|
|
7703
|
-
return;
|
|
8006
|
+
catch {
|
|
8007
|
+
return {};
|
|
7704
8008
|
}
|
|
7705
|
-
|
|
7706
|
-
|
|
7707
|
-
|
|
8009
|
+
}
|
|
8010
|
+
writeDesignerPresets(presets) {
|
|
8011
|
+
try {
|
|
8012
|
+
localStorage.setItem(DesignerComponent.PRESETS_STORAGE_KEY, JSON.stringify(presets));
|
|
7708
8013
|
}
|
|
7709
|
-
|
|
7710
|
-
this.cd.detectChanges();
|
|
8014
|
+
catch { /* quota / storage disabilitato */ }
|
|
7711
8015
|
}
|
|
7712
|
-
|
|
7713
|
-
|
|
7714
|
-
|
|
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) {
|
|
7715
8021
|
return;
|
|
7716
8022
|
}
|
|
7717
|
-
this.
|
|
7718
|
-
|
|
7719
|
-
|
|
7720
|
-
|
|
7721
|
-
|
|
7722
|
-
|
|
7723
|
-
|
|
7724
|
-
|
|
7725
|
-
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
|
+
});
|
|
7726
8031
|
}
|
|
7727
|
-
|
|
7728
|
-
|
|
7729
|
-
this.
|
|
7730
|
-
|
|
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
|
+
});
|
|
7731
8042
|
return;
|
|
7732
8043
|
}
|
|
7733
|
-
const
|
|
7734
|
-
|
|
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]) {
|
|
7735
8053
|
return;
|
|
7736
8054
|
}
|
|
7737
|
-
this.
|
|
7738
|
-
|
|
7739
|
-
|
|
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
|
+
}
|
|
7740
8065
|
}
|
|
7741
|
-
|
|
7742
|
-
|
|
7743
|
-
|
|
7744
|
-
|
|
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
|
+
});
|
|
8077
|
+
}
|
|
8078
|
+
catch (error) {
|
|
8079
|
+
console.error('Failed to apply designer preset', error);
|
|
8080
|
+
WtoolboxService.errorHandler?.handleError?.(error);
|
|
8081
|
+
}
|
|
8082
|
+
}
|
|
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;
|
|
8097
|
+
}
|
|
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;
|
|
7745
9003
|
}
|
|
7746
9004
|
}
|
|
7747
9005
|
clearHierarchyHoverHighlight() {
|
|
@@ -7946,13 +9204,14 @@ class DesignerComponent {
|
|
|
7946
9204
|
event.preventDefault();
|
|
7947
9205
|
event.stopPropagation();
|
|
7948
9206
|
this.footerCtxElement = item;
|
|
9207
|
+
this.footerCtxItems = this.buildElementContextMenuItems(item);
|
|
7949
9208
|
menu.show(event);
|
|
7950
9209
|
}
|
|
7951
9210
|
/**
|
|
7952
|
-
* Rinomina
|
|
9211
|
+
* Rinomina un elemento aggiornando `displayName`. Usato dal context menu
|
|
9212
|
+
* unificato (canvas/gerarchia/footer) via `buildElementContextMenuItems`.
|
|
7953
9213
|
*/
|
|
7954
|
-
async
|
|
7955
|
-
const item = this.footerCtxElement;
|
|
9214
|
+
async renameDesignerElement(item) {
|
|
7956
9215
|
if (!item) {
|
|
7957
9216
|
return;
|
|
7958
9217
|
}
|
|
@@ -8428,12 +9687,9 @@ class DesignerComponent {
|
|
|
8428
9687
|
const fieldCtxItems = (contextField && contextDs)
|
|
8429
9688
|
? this.buildFieldEditorContextMenuItems(contextField, contextDs)
|
|
8430
9689
|
: [];
|
|
8431
|
-
|
|
8432
|
-
|
|
8433
|
-
|
|
8434
|
-
else {
|
|
8435
|
-
this.ctxItems.next([...fieldCtxItems, ...this.defaultCtxItems]);
|
|
8436
|
-
}
|
|
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)]);
|
|
8437
9693
|
if (el) {
|
|
8438
9694
|
this.ctxElement = el;
|
|
8439
9695
|
}
|
|
@@ -9230,6 +10486,36 @@ class DesignerComponent {
|
|
|
9230
10486
|
this.closeArchetypeConfigurator();
|
|
9231
10487
|
return;
|
|
9232
10488
|
}
|
|
10489
|
+
this.applyArchetypeConfigToTool(tool, action, nextConfig);
|
|
10490
|
+
this.closeArchetypeConfigurator();
|
|
10491
|
+
}
|
|
10492
|
+
/**
|
|
10493
|
+
* Core riusabile di {@link onArchetypeConfigApply}: applica la config di
|
|
10494
|
+
* archetipo a un DATAREPEATER tool. Scrive `customProps_<action>` sulle
|
|
10495
|
+
* proprietà del tool (= ciò che il chatbot designer_inject persiste e l'archetipo
|
|
10496
|
+
* legge), aggiorna `metaInfo.tableMetadata.extraProps`+`md_props_bag`, e RIGENERA
|
|
10497
|
+
* il render chiamando `ds.fetchData()` (re-emit `fetchInfo$` → l'archetipo
|
|
10498
|
+
* ri-legge type/opzioni) + `propertyTreeBuilder`. NESSUNA chiamata server.
|
|
10499
|
+
* Usato sia dal configuratore UI ("Configura chart") sia dall'injection del
|
|
10500
|
+
* chatbot (drop di un DATAREPEATER con `archetype_config`).
|
|
10501
|
+
*/
|
|
10502
|
+
applyArchetypeConfigToTool(tool, action, nextConfig) {
|
|
10503
|
+
if (!tool || !action || !nextConfig)
|
|
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
|
+
}
|
|
9233
10519
|
const customKey = 'customProps_' + action;
|
|
9234
10520
|
tool.inputs[customKey] = this.toBehaviorSubjectsObject(nextConfig);
|
|
9235
10521
|
const metaInfo = this.getDesignerDatasourceMetaInfo(tool);
|
|
@@ -9238,15 +10524,33 @@ class DesignerComponent {
|
|
|
9238
10524
|
extraProps.archetypes = extraProps.archetypes || {};
|
|
9239
10525
|
const currentArchetype = extraProps.archetypes[action] || {};
|
|
9240
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;
|
|
9241
10535
|
metaInfo.tableMetadata.extraProps = extraProps;
|
|
9242
10536
|
metaInfo.tableMetadata.md_props_bag = JSON.stringify(extraProps);
|
|
9243
10537
|
}
|
|
9244
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
|
+
}
|
|
9245
10550
|
if (ds?.fetchData) {
|
|
9246
10551
|
ds.fetchData();
|
|
9247
10552
|
}
|
|
9248
10553
|
this.propertyTreeBuilder(action, tool.inputs);
|
|
9249
|
-
this.closeArchetypeConfigurator();
|
|
9250
10554
|
}
|
|
9251
10555
|
/**
|
|
9252
10556
|
* Espone i metadati datasource al popup configuratore archetype.
|
|
@@ -10436,6 +11740,7 @@ Display formula context:
|
|
|
10436
11740
|
});
|
|
10437
11741
|
}
|
|
10438
11742
|
let destination = this.dashboardElements;
|
|
11743
|
+
let parentContainerTool = null;
|
|
10439
11744
|
if (event.toElement) {
|
|
10440
11745
|
let id = event.toElement.getAttribute("id");
|
|
10441
11746
|
let parentEl = event.toElement.parentElement;
|
|
@@ -10451,6 +11756,7 @@ Display formula context:
|
|
|
10451
11756
|
if (match) {
|
|
10452
11757
|
if (!match.allowedChildren || match.allowedChildren.includes(currentTool.name)) {
|
|
10453
11758
|
destination = match?.nestedComponents;
|
|
11759
|
+
parentContainerTool = match;
|
|
10454
11760
|
}
|
|
10455
11761
|
else {
|
|
10456
11762
|
WtoolboxService.messageNotificationService.add({
|
|
@@ -10476,11 +11782,68 @@ Display formula context:
|
|
|
10476
11782
|
currentTool.onDrop(currentTool);
|
|
10477
11783
|
}
|
|
10478
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
|
+
}
|
|
10479
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
|
+
}
|
|
10480
11814
|
this.commitHistoryIfChanged();
|
|
10481
11815
|
this.draggedPayload = null;
|
|
10482
11816
|
}
|
|
10483
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
|
+
}
|
|
10484
11847
|
/**
|
|
10485
11848
|
* Genera un nuovo identificativo incrementale per `componentId`.
|
|
10486
11849
|
*/
|
|
@@ -10769,7 +12132,80 @@ Display formula context:
|
|
|
10769
12132
|
* - `elements` (JSON) -> `boardcontent` (TEXT)
|
|
10770
12133
|
* e aggiorna anche i fogli collegati in `dom_board_sheet`.
|
|
10771
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
|
+
}
|
|
10772
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
|
+
}
|
|
10773
12209
|
const route = String(this.currentDashboardRoute || '').trim();
|
|
10774
12210
|
if (route) {
|
|
10775
12211
|
const payload = {
|
|
@@ -10855,14 +12291,18 @@ Display formula context:
|
|
|
10855
12291
|
* e ricostruisce lo stato runtime del designer partendo da `dom_board.boardcontent`.
|
|
10856
12292
|
* Supporta route in forme alias (`board_route`, `boardroute`, `dashRoute`).
|
|
10857
12293
|
*/
|
|
10858
|
-
async loadDashboard(dashboard, skipServerFetch = false) {
|
|
12294
|
+
async loadDashboard(dashboard, skipServerFetch = false, insertTarget = null) {
|
|
10859
12295
|
const dashRoute = String(dashboard?.board_route ??
|
|
10860
12296
|
dashboard?.boardroute ??
|
|
10861
12297
|
dashboard?.boardRoute ??
|
|
10862
12298
|
dashboard?.dashRoute ??
|
|
10863
12299
|
dashboard ??
|
|
10864
12300
|
'').trim();
|
|
10865
|
-
|
|
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) {
|
|
10866
12306
|
this.dashboardElements = [];
|
|
10867
12307
|
this.flattenedDashboardElements = [];
|
|
10868
12308
|
this.currentDashboardRoute = '';
|
|
@@ -10876,8 +12316,10 @@ Display formula context:
|
|
|
10876
12316
|
this.cd.detectChanges();
|
|
10877
12317
|
return;
|
|
10878
12318
|
}
|
|
10879
|
-
|
|
10880
|
-
|
|
12319
|
+
if (!insertTarget) {
|
|
12320
|
+
this.currentDashboardRoute = dashRoute;
|
|
12321
|
+
this.refreshGraphActionsMenuItems();
|
|
12322
|
+
}
|
|
10881
12323
|
let resultRows = [];
|
|
10882
12324
|
let result = null;
|
|
10883
12325
|
if (!skipServerFetch) {
|
|
@@ -10890,14 +12332,16 @@ Display formula context:
|
|
|
10890
12332
|
? result
|
|
10891
12333
|
: (Array.isArray(result?.results) ? result.results : (result ? [result] : []));
|
|
10892
12334
|
}
|
|
10893
|
-
|
|
10894
|
-
dashboard?.
|
|
10895
|
-
|
|
10896
|
-
|
|
10897
|
-
|
|
10898
|
-
|
|
10899
|
-
|
|
10900
|
-
|
|
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
|
+
}
|
|
10901
12345
|
const boardContentRaw = resultRows?.[0]?.boardcontent ?? dashboard?.boardcontent ?? dashboard?.boardContent ?? null;
|
|
10902
12346
|
if (boardContentRaw) {
|
|
10903
12347
|
let loadedDashboard = [];
|
|
@@ -10915,7 +12359,9 @@ Display formula context:
|
|
|
10915
12359
|
: [];
|
|
10916
12360
|
}
|
|
10917
12361
|
this.mergeMissingToolPropsRecursive(loadedDashboard);
|
|
10918
|
-
|
|
12362
|
+
if (!insertTarget) {
|
|
12363
|
+
this.dashboardComponents = [];
|
|
12364
|
+
}
|
|
10919
12365
|
let dsSubs = [];
|
|
10920
12366
|
let dsElements = [];
|
|
10921
12367
|
let dsElementCount = (loadedDashboard || []).filter(x => x?.name == 'DATASOURCE').length;
|
|
@@ -11057,18 +12503,34 @@ Display formula context:
|
|
|
11057
12503
|
});
|
|
11058
12504
|
// Ensure a new reference for template control-flow and force refresh when
|
|
11059
12505
|
// async loading happens outside Angular zone.
|
|
11060
|
-
|
|
11061
|
-
|
|
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
|
+
}
|
|
11062
12518
|
this.cd.detectChanges();
|
|
11063
12519
|
}
|
|
11064
|
-
else {
|
|
12520
|
+
else if (!insertTarget) {
|
|
11065
12521
|
this.dashboardElements = [];
|
|
11066
12522
|
this.flattenedDashboardElements = [];
|
|
11067
12523
|
this.currentDashboardRoute = '';
|
|
11068
12524
|
this.cd.detectChanges();
|
|
11069
12525
|
console.warn('[Designer] No dashboard content found for route:', dashRoute, result);
|
|
11070
12526
|
}
|
|
11071
|
-
|
|
12527
|
+
if (insertTarget) {
|
|
12528
|
+
// Import in-elemento: NON resettare la history (stiamo aggiungendo), committa.
|
|
12529
|
+
this.commitHistoryIfChanged();
|
|
12530
|
+
}
|
|
12531
|
+
else {
|
|
12532
|
+
this.resetHistory();
|
|
12533
|
+
}
|
|
11072
12534
|
this.refreshGraphActionsMenuItems();
|
|
11073
12535
|
this.maxId = (this.flattenedDashboardElements || []).reduce((max, item) => Math.max(max, Number(item?.componentId || 0)), 0);
|
|
11074
12536
|
}
|
|
@@ -11828,11 +13290,11 @@ Display formula context:
|
|
|
11828
13290
|
// return customProps;
|
|
11829
13291
|
}
|
|
11830
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 });
|
|
11831
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.9", type: DesignerComponent, isStandalone: true, selector: "wuic-designer", host: { listeners: { "window:hashchange": "onWindowHashChange()", "window:beforeunload": "onWindowBeforeUnload($event)", "window:keydown": "onWindowKeydown($event)", "window:mouseup": "onWindowMouseup()", "window:touchend": "onWindowTouchend()", "window:keyup": "onWindowKeyup()", "window:change": "onWindowChange()" } }, providers: [TreeDragDropService], ngImport: i0, template: "<!-- <button pButton type=\"button\" (click)=\"loadDashboard()\" class=\"mr-2\">{{'load' | translate}}</button> -->\n@if (!noEdit) {\n<div class=\"designer-layout\">\n <div class=\"designer-toolbar\">\n <div class=\"designer-current-dashboard\">{{ currentDashboardTitle }}</div>\n\n <div class=\"designer-toolbar-spacer\"></div>\n\n <div class=\"designer-toolbar-menu-group\">\n <button type=\"button\" (click)=\"undo()\" [disabled]=\"!canUndo\">{{ 'designer.undo' | translate }}</button>\n </div>\n\n <div class=\"designer-toolbar-menu-group\">\n <button type=\"button\" (click)=\"redo()\" [disabled]=\"!canRedo\">{{ 'designer.redo' | translate }}</button>\n </div>\n\n <div class=\"designer-toolbar-menu-group\">\n <button type=\"button\" (click)=\"refreshGraphActionsMenuItems(); graphActionsMenu.toggle($event)\">{{ 'designer.actions_menu' | translate }}</button>\n <p-menu #graphActionsMenu [popup]=\"true\" [model]=\"graphActionsMenuItems\" appendTo=\"body\"></p-menu>\n </div>\n\n <div class=\"designer-toolbar-menu-group\">\n <button type=\"button\" (click)=\"openCurrentDashboardInNewTab()\" [disabled]=\"!currentDashboardRoute\">{{\n 'designer.open_current_dashboard' | translate }}</button>\n </div>\n </div>\n <div class=\"designer-main\">\n <p-splitter [style]=\"{ height: '100%' }\" [panelSizes]=\"[15, 85]\" styleClass=\"designer-main-splitter\">\n <ng-template pTemplate>\n <p-splitter layout=\"vertical\" [style]=\"{ height: '100%', width: '100%' }\" [panelSizes]=\"[35, 65]\"\n [minSizes]=\"[20, 30]\" styleClass=\"designer-left-splitter\">\n <ng-template pTemplate>\n <div class=\"col flex designer-toolbox palette-panel\">\n <ul class=\"tool-groups\">\n @for (toolGroup of availableToolGroups; track toolGroup.group) {\n <li class=\"tool-group-item\">\n <div class=\"tool-group-title\">{{ toolGroup.group }}</div>\n <ul class=\"tool-group-list\">\n @for (tool of toolGroup.tools; track tool) {\n <li pDraggable (onDragStart)=\"dragStart(tool)\" (onDragEnd)=\"dragEnd()\" (onDrag)=\"onDrag($event)\"\n (dblclick)=\"dropToolAtRoot(tool)\">\n <i [ngClass]=\"tool.icon\" [title]=\"tool.name\"></i>\n </li>\n }\n </ul>\n </li>\n }\n </ul>\n </div>\n </ng-template>\n <ng-template pTemplate>\n <p-splitter layout=\"vertical\" [style]=\"{ height: '100%', width: '100%' }\" [panelSizes]=\"[68, 32]\"\n [minSizes]=\"[35, 20]\" styleClass=\"designer-left-bottom-splitter\">\n <ng-template pTemplate>\n <div class=\"properties-panel\">\n <p-select [style]=\"{width: '100%'}\" [options]=\"flattenedDashboardElements\" [(ngModel)]=\"tool\"\n optionLabel=\"uniqueName\" dataKey=\"componentId\" appendTo=\"body\" />\n <div class=\"designer-toolbox\">\n\n <span style=\"width: 100%;\">\n <input type=\"text\" style=\"width: calc(100% - 30px);\" [(ngModel)]=\"propFilter\"\n [placeholder]=\"'prop.filter' | translate\" />\n <button style=\"width: 25px\" (click)=\"clearPropFilter()\">X</button>\n </span>\n\n @for (section of getToolPropertySections(tool, propFilter); track section.key) {\n <details class=\"prop-section\" [open]=\"section.open\"\n (toggle)=\"onPropertySectionToggle(tool, section.key, $event)\">\n <summary class=\"prop-section-title\">{{ section.label }}</summary>\n <ul class=\"props\">\n @for (toolProp of section.props; track toolProp.key) {\n <li>\n @if (!toolProp.value.hideCaption) {\n <span>{{ toolProp.value?.propertyCaption ||\n toolProp.key[0].toUpperCase() + toolProp.key.slice(1)\n }}</span>\n }\n <br />\n @if (toolProp.value.type == 'button') {\n <p-button (click)=\"toolProp.value.callback(tool.inputs, toolProp.key, null, tool)\"\n class=\"k-button\" [style]=\"{width: '100%'}\">\n {{toolProp.value.propertyCaption}}\n </p-button>\n }\n @if (toolProp.value.type == 'metaEditor' &&\n tool.inputs['componentRef']?.value?.component) {\n <wuic-metadata-editor [hardcodedDatasource]=\"tool.inputs['componentRef']?.value?.component\"\n [saveCallback]=\"metaEditorSave\"></wuic-metadata-editor>\n }\n @if (toolProp.value.type == 'boolean') {\n <input type=\"checkbox\" [(ngModel)]=\"tool.inputs[toolProp.key]\" />\n }\n @if (toolProp.value.type == 'numberToArray') {\n <input style=\"width: 100%;\" type=\"number\" pInputText [ngModel]=\"tool.inputs[toolProp.key]\"\n (ngModelChange)=\"setValue($event, tool, toolProp)\" />\n }\n @if (toolProp.value.type == 'txt_area') {\n <textarea style=\"width: 100%;\" [(ngModel)]=\"tool.inputs[toolProp.key]\"\n [ngModelOptions]=\"{ updateOn: 'blur' }\"\n (ngModelChange)=\"setValue($event, tool, toolProp)\"></textarea>\n }\n @if (toolProp.value.type == 'string') {\n <input style=\"width: 100%;\" type=\"text\" pInputText [(ngModel)]=\"tool.inputs[toolProp.key]\"\n (ngModelChange)=\"setValue($event, tool, toolProp)\" />\n }\n @if (toolProp.value.type == 'color') {\n <p-colorPicker [ngModel]=\"getDesignerColorPickerHex(tool, toolProp)\"\n (ngModelChange)=\"onDesignerColorPickerHexChange($event, tool, toolProp)\" appendTo=\"body\" />\n <div class=\"designer-color-alpha-row\">\n <span class=\"designer-color-alpha-label\">{{ 'designer.alpha' | translate }}</span>\n <input type=\"range\" min=\"0\" max=\"1\" step=\"0.01\"\n [ngModel]=\"getDesignerColorAlpha(tool, toolProp)\"\n (ngModelChange)=\"onDesignerColorAlphaChange($event, tool, toolProp)\" />\n <input type=\"number\" min=\"0\" max=\"1\" step=\"0.01\"\n [ngModel]=\"getDesignerColorAlpha(tool, toolProp)\"\n (ngModelChange)=\"onDesignerColorAlphaChange($event, tool, toolProp)\" />\n </div>\n <input type=\"text\" pInputText [(ngModel)]=\"tool.inputs[toolProp.key]\"\n (ngModelChange)=\"setValue($event, tool, toolProp)\" />\n }\n @if (toolProp.value.type == 'dictionary' && !toolProp.value.async) {\n @if (toolProp.key == 'cssFile') {\n <p-select [style]=\"{width: '100%'}\" appendTo=\"body\" [options]=\"availableProjectCssFiles\"\n [ngModel]=\"tool.inputs[toolProp.key]\" (ngModelChange)=\"onCssFileChanged($event, tool)\" />\n } @else if (toolProp.key == 'cssClass') {\n <p-multiSelect [style]=\"{width: '100%'}\" appendTo=\"body\"\n [options]=\"getCssClassOptionsForTool(tool)\" [ngModel]=\"tool.inputs[toolProp.key]\"\n (ngModelChange)=\"onCssClassChanged($event, tool)\" [filter]=\"true\" [virtualScroll]=\"true\"\n [virtualScrollItemSize]=\"38\" scrollHeight=\"260px\" [showToggleAll]=\"false\" display=\"chip\"\n [maxSelectedLabels]=\"3\" [selectedItemsLabel]=\"'{0} selected'\">\n </p-multiSelect>\n } @else if ((tool.name == 'SELECT' || tool.name == 'MULTISELECT' || tool.name == 'UL') &&\n (toolProp.key == 'valueField' || toolProp.key == 'textField')) {\n <p-select [style]=\"{width: '100%'}\" appendTo=\"body\"\n [options]=\"getDatasourceColumnOptions(tool)\" [ngModel]=\"tool.inputs[toolProp.key]\"\n (ngModelChange)=\"setValue($event, tool, toolProp)\" />\n } @else if (tool.name == 'LABEL' && toolProp.key == 'displayField') {\n <p-select [style]=\"{width: '100%'}\" appendTo=\"body\"\n [options]=\"getDatasourceColumnOptions(tool)\" [ngModel]=\"tool.inputs[toolProp.key]\"\n (ngModelChange)=\"setValue($event, tool, toolProp)\" />\n } @else {\n <p-select [style]=\"{width: '100%'}\" appendTo=\"body\" [options]=\"toolProp.value.values\"\n [(ngModel)]=\"tool.inputs[toolProp.key]\"\n (ngModelChange)=\"setValue($event, tool, toolProp)\" />\n }\n }\n @if (toolProp.value.type == 'dictionary' && toolProp.value.async) {\n <p-select [style]=\"{width: '100%'}\" appendTo=\"body\" [options]=\"toolProp.value.values\"\n [ngModel]=\"tool.inputs[toolProp.key] | async\"\n (ngModelChange)=\"setValue($event, tool, toolProp)\" />\n }\n @if (tool.name == 'DATAREPEATER' && toolProp.key == 'action' &&\n canOpenArchetypeConfigurator(tool)) {\n <button style=\"margin-top: 8px;\" (click)=\"openArchetypeConfigurator(tool)\">\n {{ 'designer.configure' | translate }} {{ (tool.inputs['action'] | async) || '' }}\n </button>\n }\n @if (toolProp.value.type == 'dropped-component-list') {\n <p-select [style]=\"{width: '100%'}\" appendTo=\"body\"\n [placeholder]=\"'select.component' | translate\" [options]=\"elementFilter(toolProp)\"\n [ngModel]=\"getDroppedComponentListModel(tool, toolProp)\"\n (ngModelChange)=\"setValue($event, tool, toolProp)\" optionLabel=\"uniqueName\"\n dataKey=\"uniqueName\" />\n }\n @if (toolProp.value.type == 'autocomplete') {\n <p-autoComplete [style]=\"{width: '100%'}\" appendTo=\"body\"\n [ngModel]=\"tool.inputs[toolProp.key] | async\" [forceSelection]=\"true\"\n (ngModelChange)=\"setValue($event, tool, toolProp)\" [suggestions]=\"tool.suggestions\"\n [optionLabel]=\"toolProp.value.displayField\" [optionValue]=\"toolProp.value.valueField\"\n (completeMethod)=\"search($event, tool, toolProp)\" [dropdown]=\"true\" dropdownMode=\"current\"\n [multiple]=\"toolProp.value.multiple\" />\n }\n @if (toolProp.value.type == 'propertyTree' && shouldShowPropertyTree(tool)) {\n <ul>\n @for (item of $any(tool.inputs[toolProp.key] | async); track item) {\n <li style=\"padding: 0; margin: 0;\">\n @if (item.col && tool.inputs['customProps_' + (tool.inputs['action'] | async)])\n {\n <wuic-field-editor [field]=\"item.col\"\n [record]=\"tool.inputs['customProps_' + (tool.inputs['action'] | async)]\"\n [metaInfo]=\"tool.inputs['datasource']?.component?.value?.metaInfo\"\n [triggerProp]=\"tool.inputs['action']\"></wuic-field-editor>\n }\n </li>\n }\n </ul>\n }\n </li>\n }\n </ul>\n </details>\n }\n </div>\n </div>\n </ng-template>\n <ng-template pTemplate>\n <div class=\"hierarchy-panel\">\n <div class=\"hierarchy-panel-title\">{{ 'designer.canvas_hierarchy' | translate }}</div>\n <div class=\"hierarchy-tree-wrap\">\n <p-tree [value]=\"hierarchyTreeNodes\" [draggableNodes]=\"true\" [droppableNodes]=\"true\"\n selectionMode=\"single\" [(selection)]=\"hierarchySelection\"\n (onNodeSelect)=\"onHierarchyNodeSelect($event)\" draggableScope=\"designer-hierarchy\"\n droppableScope=\"designer-hierarchy\" (onNodeDrop)=\"onHierarchyNodeDrop($event)\">\n <ng-template let-node pTemplate=\"default\">\n <span class=\"hierarchy-node-label\" [class.active]=\"isHierarchyNodeSelected(node)\"\n (mouseenter)=\"onHierarchyNodeMouseEnter(node)\" (mouseleave)=\"onHierarchyNodeMouseLeave()\"\n (contextmenu)=\"showHierarchyNodeContextMenu($event, node, hierarchyCtxMenu)\">\n {{ node.label }}\n </span>\n </ng-template>\n </p-tree>\n </div>\n <p-contextMenu #hierarchyCtxMenu [model]=\"hierarchyCtxItems\" appendTo=\"body\" />\n </div>\n </ng-template>\n </p-splitter>\n </ng-template>\n </p-splitter>\n </ng-template>\n <ng-template pTemplate>\n <div class=\"col dashboard-design\" [class.designer-hide-datasources]=\"hideDatasourcesInCanvas\" pDroppable\n (onDrop)=\"drop($event)\" #canvas (contextmenu)=\"showCtx($event, this)\">\n <wuic-dashboard [dashboardElements]=\"dashboardElements\"></wuic-dashboard>\n <p-contextMenu [target]=\"canvas\" [model]=\"ctxItems | async\" appendTo=\"body\" />\n </div>\n </ng-template>\n </p-splitter>\n </div>\n <div class=\"designer-footer\">\n <div class=\"designer-footer-label\">{{ 'designer.items' | translate }}:</div>\n <div class=\"designer-footer-list\">\n @for (item of droppedDashboardItems; track item.uniqueName) {\n <button type=\"button\" class=\"designer-footer-item\" [class.active]=\"tool?.uniqueName === item?.uniqueName\"\n [attr.data-designer-unique-name]=\"item?.uniqueName\" (mouseenter)=\"setFooterHoverHighlight(item)\"\n (mouseleave)=\"clearFooterHoverHighlight()\" (click)=\"selectDashboardItem(item)\"\n (contextmenu)=\"showFooterContextMenu($event, item, footerCtxMenu)\">\n {{ getDroppedDashboardItemLabel(item) }}\n </button>\n }\n </div>\n <p-contextMenu #footerCtxMenu [model]=\"footerCtxItems\" appendTo=\"body\" />\n </div>\n</div>\n}\n\n@if (noEdit) {\n<wuic-dashboard [dashboardElements]=\"dashboardElements\"></wuic-dashboard>\n}\n\n<wuic-archetype-configurator [visible]=\"showArchetypeConfigurator\"\n (visibleChange)=\"onArchetypeConfigVisibleChange($event)\" [archetype]=\"getActiveArchetypeConfig()\"\n [metaInfo]=\"getArchetypeConfigMetaInfoForTool(archetypeConfigTool)\"\n [value]=\"getArchetypeConfigValueForTool(archetypeConfigTool)\" (applyConfig)=\"onArchetypeConfigApply($event)\">\n</wuic-archetype-configurator>\n\n<wuic-css-sheet-editor [visible]=\"showCssSheetEditor\" (visibleChange)=\"onCssSheetEditorVisibleChange($event)\"\n [sheets]=\"dashboardCssSheets\" [selectedSheetPath]=\"selectedCssEditorSheetPath\" [assetsFolder]=\"currentDashboardRoute\"\n [styleProps]=\"getCssEditorStyleProps()\" (applyEditor)=\"onCssSheetEditorApply($event)\">\n</wuic-css-sheet-editor>\n\n<p-dialog [header]=\"'designer.reopen_dashboard' | translate\" [(visible)]=\"reopenDashboardDialogVisible\" [modal]=\"true\"\n [closable]=\"true\" [draggable]=\"false\" [resizable]=\"false\" [appendTo]=\"'body'\" [style]=\"{ width: '34rem' }\"\n (onHide)=\"cancelReopenDashboardDialog()\">\n <div class=\"route-popup-form\">\n <label for=\"designer-reopen-select\">{{ 'designer.existing_dashboards' | translate }}</label>\n <p-select inputId=\"designer-reopen-select\" [options]=\"savedDashboardOptions\" optionLabel=\"label\" optionValue=\"value\"\n [(ngModel)]=\"reopenDialogSelectedDashboardRoute\" [filter]=\"true\" [showClear]=\"true\" [style]=\"{ width: '100%' }\"\n appendTo=\"body\" />\n\n <div class=\"route-popup-actions\">\n <button type=\"button\" class=\"route-popup-btn\" (click)=\"cancelReopenDashboardDialog()\">{{ 'cancel' | translate\n }}</button>\n <button type=\"button\" class=\"route-popup-btn route-popup-btn-primary\" (click)=\"confirmReopenDashboardDialog()\"\n [disabled]=\"!reopenDialogSelectedDashboardRoute\">{{ 'designer.reopen' | translate }}</button>\n </div>\n </div>\n</p-dialog>", styles: [":host{display:block;width:100%;height:100%;min-width:0}.designer-layout{display:grid;grid-template-rows:auto minmax(0,1fr) auto;height:100%;min-height:0;width:100%;min-width:0}.designer-toolbar{display:flex;align-items:center;flex-wrap:wrap;gap:8px;border:1px solid inherit;border-radius:10px;background:transparent;padding:10px 12px;margin-bottom:8px}.designer-current-dashboard{min-width:220px;font-size:.86rem;font-weight:700;color:inherit}.designer-toolbar-spacer{flex:1 1 auto;min-width:16px}.designer-toolbar-menu-group{display:inline-flex}.designer-toolbar-menu-group button{height:32px;border:1px solid #2f67d8;border-radius:6px;background:#2f67d8;color:#fff;padding:0 10px;font-size:.82rem;cursor:pointer}.designer-toolbar-menu-group button[disabled]{opacity:.6;cursor:not-allowed}.designer-main{min-height:0;min-width:0;width:100%;overflow:hidden;direction:ltr}.designer-main-splitter{height:100%;width:100%;min-width:0;direction:ltr}.designer-toolbox{font-size:3rem}.designer-toolbox ul{list-style-type:none;padding:0;margin:0;width:100%}.designer-toolbox li{float:left;margin-left:5px;margin-top:5px}.designer-toolbox .tool-groups{display:block}.designer-toolbox .tool-group-item{float:none;margin-left:0;margin-top:10px;clear:both}.designer-toolbox .tool-group-title{font-size:.8rem;font-weight:600;text-transform:uppercase;letter-spacing:.04em;opacity:.7;margin:0 0 4px 6px}.designer-toolbox .tool-group-list{display:flex;flex-wrap:wrap;align-items:flex-start;gap:4px}.designer-toolbox .tool-group-list li{float:none;margin-left:0;margin-top:0}.designer-toolbox .props{font-size:1rem}.designer-toolbox .props li{float:none;padding-bottom:15px}.designer-toolbox .prop-section{width:100%;border:1px solid inherit;border-radius:6px;margin-top:8px;background:transparent;padding:6px 8px 2px}.designer-toolbox .prop-section-title{font-size:.82rem;font-weight:600;text-transform:uppercase;letter-spacing:.04em;cursor:pointer;color:inherit;margin-bottom:6px}.palette-panel{height:100%;overflow:auto;width:100%}.designer-color-alpha-row{display:flex;align-items:center;gap:8px;margin-top:6px;margin-bottom:6px}.designer-color-alpha-label{font-size:.8rem;min-width:40px}.designer-color-alpha-row input[type=range]{flex:1 1 auto;min-width:90px}.designer-color-alpha-row input[type=number]{width:72px}.properties-panel{height:100%;overflow:hidden;width:100%;display:flex;flex-direction:column;min-height:0;padding-right:10px;padding-left:10px;padding-top:10px}.properties-panel>.designer-toolbox{flex:1 1 auto;min-height:0;overflow-y:auto;overflow-x:hidden;scrollbar-gutter:stable}:host ::ng-deep .designer-left-bottom-splitter .p-splitterpanel{min-height:0!important;overflow:hidden!important}.hierarchy-panel{height:100%;width:100%;min-height:0;display:flex;flex-direction:column;overflow:hidden;padding:6px}.hierarchy-panel-title{font-size:.82rem;font-weight:600;text-transform:uppercase;letter-spacing:.04em;color:#4b5563;margin:0 0 6px}.hierarchy-tree-wrap{flex:1 1 auto;min-height:0;border:1px solid inherit;border-radius:6px;background:transparent;overflow:auto;padding:4px}:host ::ng-deep .hierarchy-tree-wrap .p-tree{border:none;padding:0;height:100%;background:transparent}:host ::ng-deep .hierarchy-tree-wrap .p-tree-node-content{border:1px solid transparent;border-radius:4px;background:transparent!important;color:inherit!important;padding:0!important}:host ::ng-deep .hierarchy-tree-wrap .p-tree-node-content.p-highlight{border-color:transparent!important;background:transparent!important;color:inherit!important}:host ::ng-deep .hierarchy-tree-wrap .p-tree-node-content:hover{background:transparent!important;color:inherit!important}:host ::ng-deep .hierarchy-tree-wrap .hierarchy-node-label{display:inline-block;border:1px solid inherit;background:transparent;color:inherit;border-radius:4px;padding:2px 8px;font-size:.8rem;line-height:1.25rem;cursor:pointer}:host ::ng-deep .hierarchy-tree-wrap .hierarchy-node-label:hover{border-color:#94a3b8}:host ::ng-deep .hierarchy-tree-wrap .hierarchy-node-label.active{border-color:#3b82f6!important;background:#eff6ff!important;color:#1d4ed8!important}:host ::ng-deep .hierarchy-tree-wrap .p-tree-node-content.p-highlight .hierarchy-node-label{border-color:#3b82f6!important;background:#eff6ff!important;color:#1d4ed8!important}.designer-hierarchy-node{cursor:pointer;border:1px solid transparent;border-radius:4px;padding:2px 6px;font-size:.82rem;line-height:1.25rem}.designer-hierarchy-node-label{cursor:pointer;display:inline-block;width:100%}.designer-hierarchy-node.active{border-color:#3b82f6;background:#eff6ff;color:#1d4ed8}.designer-footer-item.designer-tree-hover-target{border-color:#3b82f6!important;outline:1px solid #3b82f6!important;outline-offset:-1px}.dashboard-design{height:100%;min-height:0;min-width:0;width:100%;overflow:auto;direction:ltr}:host ::ng-deep .dashboard-design.designer-hide-datasources .datasource-descriptor{display:none!important}.current-dashboard-title{font-weight:600;margin-right:12px}.designer-footer{display:flex;align-items:center;gap:8px;padding:6px 8px;border-top:1px solid inherit;background:transparent;min-height:36px;flex:0 0 auto;width:100%;overflow:auto}.designer-footer-label{font-size:.85rem;font-weight:600;color:inherit;white-space:nowrap}.designer-footer-list{display:flex;align-items:center;gap:6px;overflow-x:auto;overflow-y:hidden;white-space:nowrap;width:100%}.designer-footer-item{border:1px solid inherit;background:transparent;color:inherit;border-radius:4px;padding:2px 8px;font-size:.8rem;line-height:1.25rem;cursor:pointer;white-space:nowrap}.designer-footer-item.active{border-color:#3b82f6;background:transparent;color:#1d4ed8}:host ::ng-deep .designer-footer-hover-target{border-color:#3b82f6!important;outline:1px solid #3b82f6!important;outline-offset:-1px;box-shadow:inset 0 0 0 1px #3b82f6!important}.route-popup-form{display:flex;flex-direction:column;gap:8px}.route-popup-form label{font-size:.85rem;font-weight:600;color:#2f3f58}.route-popup-actions{display:flex;justify-content:flex-end;gap:8px;margin-top:10px}.route-popup-btn{border:1px solid inherit;background:transparent;color:inherit;border-radius:6px;padding:7px 12px;font-size:.85rem;cursor:pointer}.route-popup-btn-primary{border-color:#2f67d8;background:#2f67d8;color:#fff}.p-colorpicker{border:1px solid black;border-radius:4px}\n"], dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type: TranslateModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i1.RangeValueAccessor, selector: "input[type=range][formControlName],input[type=range][formControl],input[type=range][ngModel]" }, { kind: "directive", type: i1.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.MinValidator, selector: "input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]", inputs: ["min"] }, { kind: "directive", type: i1.MaxValidator, selector: "input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]", inputs: ["max"] }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: SplitterModule }, { kind: "component", type: i10.Splitter, selector: "p-splitter", inputs: ["styleClass", "panelStyleClass", "panelStyle", "stateStorage", "stateKey", "layout", "gutterSize", "step", "minSizes", "panelSizes"], outputs: ["onResizeEnd", "onResizeStart"] }, { kind: "directive", type: i3.PrimeTemplate, selector: "[pTemplate]", inputs: ["type", "pTemplate"] }, { kind: "ngmodule", type: DragDropModule }, { kind: "directive", type: i12.Draggable, selector: "[pDraggable]", inputs: ["pDraggable", "dragEffect", "dragHandle", "pDraggableDisabled"], outputs: ["onDragStart", "onDragEnd", "onDrag"] }, { kind: "directive", type: i12.Droppable, selector: "[pDroppable]", inputs: ["pDroppable", "pDroppableDisabled", "dropEffect"], outputs: ["onDragEnter", "onDragLeave", "onDrop"] }, { kind: "ngmodule", type: SelectModule }, { kind: "component", type: i6.Select, selector: "p-select", inputs: ["id", "scrollHeight", "filter", "panelStyle", "styleClass", "panelStyleClass", "readonly", "editable", "tabindex", "placeholder", "loadingIcon", "filterPlaceholder", "filterLocale", "inputId", "dataKey", "filterBy", "filterFields", "autofocus", "resetFilterOnHide", "checkmark", "dropdownIcon", "loading", "optionLabel", "optionValue", "optionDisabled", "optionGroupLabel", "optionGroupChildren", "group", "showClear", "emptyFilterMessage", "emptyMessage", "lazy", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "overlayOptions", "ariaFilterLabel", "ariaLabel", "ariaLabelledBy", "filterMatchMode", "tooltip", "tooltipPosition", "tooltipPositionStyle", "tooltipStyleClass", "focusOnHover", "selectOnFocus", "autoOptionFocus", "autofocusFilter", "filterValue", "options", "appendTo", "motionOptions"], outputs: ["onChange", "onFilter", "onFocus", "onBlur", "onClick", "onShow", "onHide", "onClear", "onLazyLoad"] }, { kind: "ngmodule", type: ColorPickerModule }, { kind: "component", type: i2$1.ColorPicker, selector: "p-colorPicker, p-colorpicker, p-color-picker", inputs: ["styleClass", "showTransitionOptions", "hideTransitionOptions", "inline", "format", "tabindex", "inputId", "autoZIndex", "autofocus", "defaultColor", "appendTo", "overlayOptions", "motionOptions"], outputs: ["onChange", "onShow", "onHide"] }, { kind: "ngmodule", type: AutoCompleteModule }, { kind: "component", type: i2$2.AutoComplete, selector: "p-autoComplete, p-autocomplete, p-auto-complete", inputs: ["minLength", "minQueryLength", "delay", "panelStyle", "styleClass", "panelStyleClass", "inputStyle", "inputId", "inputStyleClass", "placeholder", "readonly", "scrollHeight", "lazy", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "autoHighlight", "forceSelection", "type", "autoZIndex", "baseZIndex", "ariaLabel", "dropdownAriaLabel", "ariaLabelledBy", "dropdownIcon", "unique", "group", "completeOnFocus", "showClear", "dropdown", "showEmptyMessage", "dropdownMode", "multiple", "addOnTab", "tabindex", "dataKey", "emptyMessage", "showTransitionOptions", "hideTransitionOptions", "autofocus", "autocomplete", "optionGroupChildren", "optionGroupLabel", "overlayOptions", "suggestions", "optionLabel", "optionValue", "id", "searchMessage", "emptySelectionMessage", "selectionMessage", "autoOptionFocus", "selectOnFocus", "searchLocale", "optionDisabled", "focusOnHover", "typeahead", "addOnBlur", "separator", "appendTo", "motionOptions"], outputs: ["completeMethod", "onSelect", "onUnselect", "onAdd", "onFocus", "onBlur", "onDropdownClick", "onClear", "onInputKeydown", "onKeyUp", "onShow", "onHide", "onLazyLoad"] }, { kind: "component", type: DashboardComponent, selector: "wuic-dashboard", inputs: ["dashboardElements"] }, { kind: "component", type: FieldEditorComponent, selector: "wuic-field-editor", inputs: ["datasource", "record", "field", "metaInfo", "readOnly", "isFilter", "hideLabel", "forceShowLabel", "operator", "nestedIndex", "triggerProp", "tabIndex", "onInlineCellValueChange"] }, { kind: "ngmodule", type: ContextMenuModule }, { kind: "component", type: i14.ContextMenu, selector: "p-contextMenu, p-contextmenu, p-context-menu", inputs: ["model", "triggerEvent", "target", "global", "style", "styleClass", "autoZIndex", "baseZIndex", "id", "breakpoint", "ariaLabel", "ariaLabelledBy", "pressDelay", "appendTo", "motionOptions"], outputs: ["onShow", "onHide"] }, { kind: "component", type: MetadataEditorComponent, selector: "wuic-metadata-editor", inputs: ["datasource", "hardcodedDatasource", "saveCallback", "hideReports", "hideRelatedTableActions", "hideRelatedColumnActions"] }, { kind: "ngmodule", type: MenubarModule }, { kind: "component", type: ArchetypeConfiguratorComponent, selector: "wuic-archetype-configurator", inputs: ["visible", "archetype", "metaInfo", "value"], outputs: ["visibleChange", "applyConfig"] }, { kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i4.Button, selector: "p-button", inputs: ["hostName", "type", "badge", "disabled", "raised", "rounded", "text", "plain", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "iconPos", "icon", "label", "loading", "loadingIcon", "severity", "buttonProps", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "ngmodule", type: MultiSelectModule }, { kind: "component", type: i18.MultiSelect, selector: "p-multiSelect, p-multiselect, p-multi-select", inputs: ["id", "ariaLabel", "styleClass", "panelStyle", "panelStyleClass", "inputId", "readonly", "group", "filter", "filterPlaceHolder", "filterLocale", "overlayVisible", "tabindex", "dataKey", "ariaLabelledBy", "displaySelectedLabel", "maxSelectedLabels", "selectionLimit", "selectedItemsLabel", "showToggleAll", "emptyFilterMessage", "emptyMessage", "resetFilterOnHide", "dropdownIcon", "chipIcon", "optionLabel", "optionValue", "optionDisabled", "optionGroupLabel", "optionGroupChildren", "showHeader", "filterBy", "scrollHeight", "lazy", "virtualScroll", "loading", "virtualScrollItemSize", "loadingIcon", "virtualScrollOptions", "overlayOptions", "ariaFilterLabel", "filterMatchMode", "tooltip", "tooltipPosition", "tooltipPositionStyle", "tooltipStyleClass", "autofocusFilter", "display", "autocomplete", "showClear", "autofocus", "placeholder", "options", "filterValue", "selectAll", "focusOnHover", "filterFields", "selectOnFocus", "autoOptionFocus", "highlightOnSelect", "size", "variant", "fluid", "appendTo", "motionOptions"], outputs: ["onChange", "onFilter", "onFocus", "onBlur", "onClick", "onClear", "onPanelShow", "onPanelHide", "onLazyLoad", "onRemove", "onSelectAllChange"] }, { kind: "component", type: CssSheetEditorComponent, selector: "wuic-css-sheet-editor", inputs: ["visible", "sheets", "selectedSheetPath", "styleProps", "assetsFolder"], outputs: ["visibleChange", "applyEditor"] }, { kind: "ngmodule", type: MenuModule }, { kind: "component", type: i9.Menu, selector: "p-menu", inputs: ["model", "popup", "style", "styleClass", "autoZIndex", "baseZIndex", "showTransitionOptions", "hideTransitionOptions", "ariaLabel", "ariaLabelledBy", "id", "tabindex", "appendTo", "motionOptions"], outputs: ["onShow", "onHide", "onBlur", "onFocus"] }, { kind: "ngmodule", type: DialogModule }, { kind: "component", type: i2.Dialog, selector: "p-dialog", inputs: ["hostName", "header", "draggable", "resizable", "contentStyle", "contentStyleClass", "modal", "closeOnEscape", "dismissableMask", "rtl", "closable", "breakpoints", "styleClass", "maskStyleClass", "maskStyle", "showHeader", "blockScroll", "autoZIndex", "baseZIndex", "minX", "minY", "focusOnShow", "maximizable", "keepInViewport", "focusTrap", "transitionOptions", "maskMotionOptions", "motionOptions", "closeIcon", "closeAriaLabel", "closeTabindex", "minimizeIcon", "maximizeIcon", "closeButtonProps", "maximizeButtonProps", "visible", "style", "position", "role", "appendTo", "content", "contentTemplate", "footerTemplate", "closeIconTemplate", "maximizeIconTemplate", "minimizeIconTemplate", "headlessTemplate"], outputs: ["onShow", "onHide", "visibleChange", "onResizeInit", "onResizeEnd", "onDragEnd", "onMaximize"] }, { kind: "ngmodule", type: TreeModule }, { kind: "component", type: i4$1.Tree, selector: "p-tree", inputs: ["value", "selectionMode", "loadingMode", "selection", "styleClass", "contextMenu", "contextMenuSelectionMode", "contextMenuSelection", "draggableScope", "droppableScope", "draggableNodes", "droppableNodes", "metaKeySelection", "propagateSelectionUp", "propagateSelectionDown", "loading", "loadingIcon", "emptyMessage", "ariaLabel", "togglerAriaLabel", "ariaLabelledBy", "validateDrop", "filter", "filterInputAutoFocus", "filterBy", "filterMode", "filterOptions", "filterPlaceholder", "filteredNodes", "filterLocale", "scrollHeight", "lazy", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "indentation", "_templateMap", "trackBy", "highlightOnSelect"], outputs: ["selectionChange", "contextMenuSelectionChange", "onNodeSelect", "onNodeUnselect", "onNodeExpand", "onNodeCollapse", "onNodeContextMenuSelect", "onNodeDoubleClick", "onNodeDrop", "onLazyLoad", "onScroll", "onScrollIndexChange", "onFilter"] }, { kind: "pipe", type: i8.TranslatePipe, name: "translate" }, { kind: "pipe", type: AsyncPipe, name: "async" }] });
|
|
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" }] });
|
|
11832
13294
|
}
|
|
11833
13295
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImport: i0, type: DesignerComponent, decorators: [{
|
|
11834
13296
|
type: Component,
|
|
11835
|
-
args: [{ selector: 'wuic-designer', imports: [NgClass, TranslateModule, AsyncPipe, FormsModule, SplitterModule, DragDropModule, SelectModule, ColorPickerModule, AutoCompleteModule, DashboardComponent, FieldEditorComponent, ContextMenuModule, MetadataEditorComponent, MenubarModule, ArchetypeConfiguratorComponent, ButtonModule, MultiSelectModule, CssSheetEditorComponent, MenuModule, DialogModule, TreeModule], providers: [TreeDragDropService], template: "<!-- <button pButton type=\"button\" (click)=\"loadDashboard()\" class=\"mr-2\">{{'load' | translate}}</button> -->\n@if (!noEdit) {\n<div class=\"designer-layout\">\n <div class=\"designer-toolbar\">\n <div class=\"designer-current-dashboard\">{{ currentDashboardTitle }}</div>\n\n <div class=\"designer-toolbar-spacer\"></div>\n\n <div class=\"designer-toolbar-menu-group\">\n <button type=\"button\" (click)=\"undo()\" [disabled]=\"!canUndo\">{{ 'designer.undo' | translate }}</button>\n </div>\n\n <div class=\"designer-toolbar-menu-group\">\n <button type=\"button\" (click)=\"redo()\" [disabled]=\"!canRedo\">{{ 'designer.redo' | translate }}</button>\n </div>\n\n <div class=\"designer-toolbar-menu-group\">\n <button type=\"button\" (click)=\"refreshGraphActionsMenuItems(); graphActionsMenu.toggle($event)\">{{ 'designer.actions_menu' | translate }}</button>\n <p-menu #graphActionsMenu [popup]=\"true\" [model]=\"graphActionsMenuItems\" appendTo=\"body\"></p-menu>\n </div>\n\n <div class=\"designer-toolbar-menu-group\">\n <button type=\"button\" (click)=\"openCurrentDashboardInNewTab()\" [disabled]=\"!currentDashboardRoute\">{{\n 'designer.open_current_dashboard' | translate }}</button>\n </div>\n </div>\n <div class=\"designer-main\">\n <p-splitter [style]=\"{ height: '100%' }\" [panelSizes]=\"[15, 85]\" styleClass=\"designer-main-splitter\">\n <ng-template pTemplate>\n <p-splitter layout=\"vertical\" [style]=\"{ height: '100%', width: '100%' }\" [panelSizes]=\"[35, 65]\"\n [minSizes]=\"[20, 30]\" styleClass=\"designer-left-splitter\">\n <ng-template pTemplate>\n <div class=\"col flex designer-toolbox palette-panel\">\n <ul class=\"tool-groups\">\n @for (toolGroup of availableToolGroups; track toolGroup.group) {\n <li class=\"tool-group-item\">\n <div class=\"tool-group-title\">{{ toolGroup.group }}</div>\n <ul class=\"tool-group-list\">\n @for (tool of toolGroup.tools; track tool) {\n <li pDraggable (onDragStart)=\"dragStart(tool)\" (onDragEnd)=\"dragEnd()\" (onDrag)=\"onDrag($event)\"\n (dblclick)=\"dropToolAtRoot(tool)\">\n <i [ngClass]=\"tool.icon\" [title]=\"tool.name\"></i>\n </li>\n }\n </ul>\n </li>\n }\n </ul>\n </div>\n </ng-template>\n <ng-template pTemplate>\n <p-splitter layout=\"vertical\" [style]=\"{ height: '100%', width: '100%' }\" [panelSizes]=\"[68, 32]\"\n [minSizes]=\"[35, 20]\" styleClass=\"designer-left-bottom-splitter\">\n <ng-template pTemplate>\n <div class=\"properties-panel\">\n <p-select [style]=\"{width: '100%'}\" [options]=\"flattenedDashboardElements\" [(ngModel)]=\"tool\"\n optionLabel=\"uniqueName\" dataKey=\"componentId\" appendTo=\"body\" />\n <div class=\"designer-toolbox\">\n\n <span style=\"width: 100%;\">\n <input type=\"text\" style=\"width: calc(100% - 30px);\" [(ngModel)]=\"propFilter\"\n [placeholder]=\"'prop.filter' | translate\" />\n <button style=\"width: 25px\" (click)=\"clearPropFilter()\">X</button>\n </span>\n\n @for (section of getToolPropertySections(tool, propFilter); track section.key) {\n <details class=\"prop-section\" [open]=\"section.open\"\n (toggle)=\"onPropertySectionToggle(tool, section.key, $event)\">\n <summary class=\"prop-section-title\">{{ section.label }}</summary>\n <ul class=\"props\">\n @for (toolProp of section.props; track toolProp.key) {\n <li>\n @if (!toolProp.value.hideCaption) {\n <span>{{ toolProp.value?.propertyCaption ||\n toolProp.key[0].toUpperCase() + toolProp.key.slice(1)\n }}</span>\n }\n <br />\n @if (toolProp.value.type == 'button') {\n <p-button (click)=\"toolProp.value.callback(tool.inputs, toolProp.key, null, tool)\"\n class=\"k-button\" [style]=\"{width: '100%'}\">\n {{toolProp.value.propertyCaption}}\n </p-button>\n }\n @if (toolProp.value.type == 'metaEditor' &&\n tool.inputs['componentRef']?.value?.component) {\n <wuic-metadata-editor [hardcodedDatasource]=\"tool.inputs['componentRef']?.value?.component\"\n [saveCallback]=\"metaEditorSave\"></wuic-metadata-editor>\n }\n @if (toolProp.value.type == 'boolean') {\n <input type=\"checkbox\" [(ngModel)]=\"tool.inputs[toolProp.key]\" />\n }\n @if (toolProp.value.type == 'numberToArray') {\n <input style=\"width: 100%;\" type=\"number\" pInputText [ngModel]=\"tool.inputs[toolProp.key]\"\n (ngModelChange)=\"setValue($event, tool, toolProp)\" />\n }\n @if (toolProp.value.type == 'txt_area') {\n <textarea style=\"width: 100%;\" [(ngModel)]=\"tool.inputs[toolProp.key]\"\n [ngModelOptions]=\"{ updateOn: 'blur' }\"\n (ngModelChange)=\"setValue($event, tool, toolProp)\"></textarea>\n }\n @if (toolProp.value.type == 'string') {\n <input style=\"width: 100%;\" type=\"text\" pInputText [(ngModel)]=\"tool.inputs[toolProp.key]\"\n (ngModelChange)=\"setValue($event, tool, toolProp)\" />\n }\n @if (toolProp.value.type == 'color') {\n <p-colorPicker [ngModel]=\"getDesignerColorPickerHex(tool, toolProp)\"\n (ngModelChange)=\"onDesignerColorPickerHexChange($event, tool, toolProp)\" appendTo=\"body\" />\n <div class=\"designer-color-alpha-row\">\n <span class=\"designer-color-alpha-label\">{{ 'designer.alpha' | translate }}</span>\n <input type=\"range\" min=\"0\" max=\"1\" step=\"0.01\"\n [ngModel]=\"getDesignerColorAlpha(tool, toolProp)\"\n (ngModelChange)=\"onDesignerColorAlphaChange($event, tool, toolProp)\" />\n <input type=\"number\" min=\"0\" max=\"1\" step=\"0.01\"\n [ngModel]=\"getDesignerColorAlpha(tool, toolProp)\"\n (ngModelChange)=\"onDesignerColorAlphaChange($event, tool, toolProp)\" />\n </div>\n <input type=\"text\" pInputText [(ngModel)]=\"tool.inputs[toolProp.key]\"\n (ngModelChange)=\"setValue($event, tool, toolProp)\" />\n }\n @if (toolProp.value.type == 'dictionary' && !toolProp.value.async) {\n @if (toolProp.key == 'cssFile') {\n <p-select [style]=\"{width: '100%'}\" appendTo=\"body\" [options]=\"availableProjectCssFiles\"\n [ngModel]=\"tool.inputs[toolProp.key]\" (ngModelChange)=\"onCssFileChanged($event, tool)\" />\n } @else if (toolProp.key == 'cssClass') {\n <p-multiSelect [style]=\"{width: '100%'}\" appendTo=\"body\"\n [options]=\"getCssClassOptionsForTool(tool)\" [ngModel]=\"tool.inputs[toolProp.key]\"\n (ngModelChange)=\"onCssClassChanged($event, tool)\" [filter]=\"true\" [virtualScroll]=\"true\"\n [virtualScrollItemSize]=\"38\" scrollHeight=\"260px\" [showToggleAll]=\"false\" display=\"chip\"\n [maxSelectedLabels]=\"3\" [selectedItemsLabel]=\"'{0} selected'\">\n </p-multiSelect>\n } @else if ((tool.name == 'SELECT' || tool.name == 'MULTISELECT' || tool.name == 'UL') &&\n (toolProp.key == 'valueField' || toolProp.key == 'textField')) {\n <p-select [style]=\"{width: '100%'}\" appendTo=\"body\"\n [options]=\"getDatasourceColumnOptions(tool)\" [ngModel]=\"tool.inputs[toolProp.key]\"\n (ngModelChange)=\"setValue($event, tool, toolProp)\" />\n } @else if (tool.name == 'LABEL' && toolProp.key == 'displayField') {\n <p-select [style]=\"{width: '100%'}\" appendTo=\"body\"\n [options]=\"getDatasourceColumnOptions(tool)\" [ngModel]=\"tool.inputs[toolProp.key]\"\n (ngModelChange)=\"setValue($event, tool, toolProp)\" />\n } @else {\n <p-select [style]=\"{width: '100%'}\" appendTo=\"body\" [options]=\"toolProp.value.values\"\n [(ngModel)]=\"tool.inputs[toolProp.key]\"\n (ngModelChange)=\"setValue($event, tool, toolProp)\" />\n }\n }\n @if (toolProp.value.type == 'dictionary' && toolProp.value.async) {\n <p-select [style]=\"{width: '100%'}\" appendTo=\"body\" [options]=\"toolProp.value.values\"\n [ngModel]=\"tool.inputs[toolProp.key] | async\"\n (ngModelChange)=\"setValue($event, tool, toolProp)\" />\n }\n @if (tool.name == 'DATAREPEATER' && toolProp.key == 'action' &&\n canOpenArchetypeConfigurator(tool)) {\n <button style=\"margin-top: 8px;\" (click)=\"openArchetypeConfigurator(tool)\">\n {{ 'designer.configure' | translate }} {{ (tool.inputs['action'] | async) || '' }}\n </button>\n }\n @if (toolProp.value.type == 'dropped-component-list') {\n <p-select [style]=\"{width: '100%'}\" appendTo=\"body\"\n [placeholder]=\"'select.component' | translate\" [options]=\"elementFilter(toolProp)\"\n [ngModel]=\"getDroppedComponentListModel(tool, toolProp)\"\n (ngModelChange)=\"setValue($event, tool, toolProp)\" optionLabel=\"uniqueName\"\n dataKey=\"uniqueName\" />\n }\n @if (toolProp.value.type == 'autocomplete') {\n <p-autoComplete [style]=\"{width: '100%'}\" appendTo=\"body\"\n [ngModel]=\"tool.inputs[toolProp.key] | async\" [forceSelection]=\"true\"\n (ngModelChange)=\"setValue($event, tool, toolProp)\" [suggestions]=\"tool.suggestions\"\n [optionLabel]=\"toolProp.value.displayField\" [optionValue]=\"toolProp.value.valueField\"\n (completeMethod)=\"search($event, tool, toolProp)\" [dropdown]=\"true\" dropdownMode=\"current\"\n [multiple]=\"toolProp.value.multiple\" />\n }\n @if (toolProp.value.type == 'propertyTree' && shouldShowPropertyTree(tool)) {\n <ul>\n @for (item of $any(tool.inputs[toolProp.key] | async); track item) {\n <li style=\"padding: 0; margin: 0;\">\n @if (item.col && tool.inputs['customProps_' + (tool.inputs['action'] | async)])\n {\n <wuic-field-editor [field]=\"item.col\"\n [record]=\"tool.inputs['customProps_' + (tool.inputs['action'] | async)]\"\n [metaInfo]=\"tool.inputs['datasource']?.component?.value?.metaInfo\"\n [triggerProp]=\"tool.inputs['action']\"></wuic-field-editor>\n }\n </li>\n }\n </ul>\n }\n </li>\n }\n </ul>\n </details>\n }\n </div>\n </div>\n </ng-template>\n <ng-template pTemplate>\n <div class=\"hierarchy-panel\">\n <div class=\"hierarchy-panel-title\">{{ 'designer.canvas_hierarchy' | translate }}</div>\n <div class=\"hierarchy-tree-wrap\">\n <p-tree [value]=\"hierarchyTreeNodes\" [draggableNodes]=\"true\" [droppableNodes]=\"true\"\n selectionMode=\"single\" [(selection)]=\"hierarchySelection\"\n (onNodeSelect)=\"onHierarchyNodeSelect($event)\" draggableScope=\"designer-hierarchy\"\n droppableScope=\"designer-hierarchy\" (onNodeDrop)=\"onHierarchyNodeDrop($event)\">\n <ng-template let-node pTemplate=\"default\">\n <span class=\"hierarchy-node-label\" [class.active]=\"isHierarchyNodeSelected(node)\"\n (mouseenter)=\"onHierarchyNodeMouseEnter(node)\" (mouseleave)=\"onHierarchyNodeMouseLeave()\"\n (contextmenu)=\"showHierarchyNodeContextMenu($event, node, hierarchyCtxMenu)\">\n {{ node.label }}\n </span>\n </ng-template>\n </p-tree>\n </div>\n <p-contextMenu #hierarchyCtxMenu [model]=\"hierarchyCtxItems\" appendTo=\"body\" />\n </div>\n </ng-template>\n </p-splitter>\n </ng-template>\n </p-splitter>\n </ng-template>\n <ng-template pTemplate>\n <div class=\"col dashboard-design\" [class.designer-hide-datasources]=\"hideDatasourcesInCanvas\" pDroppable\n (onDrop)=\"drop($event)\" #canvas (contextmenu)=\"showCtx($event, this)\">\n <wuic-dashboard [dashboardElements]=\"dashboardElements\"></wuic-dashboard>\n <p-contextMenu [target]=\"canvas\" [model]=\"ctxItems | async\" appendTo=\"body\" />\n </div>\n </ng-template>\n </p-splitter>\n </div>\n <div class=\"designer-footer\">\n <div class=\"designer-footer-label\">{{ 'designer.items' | translate }}:</div>\n <div class=\"designer-footer-list\">\n @for (item of droppedDashboardItems; track item.uniqueName) {\n <button type=\"button\" class=\"designer-footer-item\" [class.active]=\"tool?.uniqueName === item?.uniqueName\"\n [attr.data-designer-unique-name]=\"item?.uniqueName\" (mouseenter)=\"setFooterHoverHighlight(item)\"\n (mouseleave)=\"clearFooterHoverHighlight()\" (click)=\"selectDashboardItem(item)\"\n (contextmenu)=\"showFooterContextMenu($event, item, footerCtxMenu)\">\n {{ getDroppedDashboardItemLabel(item) }}\n </button>\n }\n </div>\n <p-contextMenu #footerCtxMenu [model]=\"footerCtxItems\" appendTo=\"body\" />\n </div>\n</div>\n}\n\n@if (noEdit) {\n<wuic-dashboard [dashboardElements]=\"dashboardElements\"></wuic-dashboard>\n}\n\n<wuic-archetype-configurator [visible]=\"showArchetypeConfigurator\"\n (visibleChange)=\"onArchetypeConfigVisibleChange($event)\" [archetype]=\"getActiveArchetypeConfig()\"\n [metaInfo]=\"getArchetypeConfigMetaInfoForTool(archetypeConfigTool)\"\n [value]=\"getArchetypeConfigValueForTool(archetypeConfigTool)\" (applyConfig)=\"onArchetypeConfigApply($event)\">\n</wuic-archetype-configurator>\n\n<wuic-css-sheet-editor [visible]=\"showCssSheetEditor\" (visibleChange)=\"onCssSheetEditorVisibleChange($event)\"\n [sheets]=\"dashboardCssSheets\" [selectedSheetPath]=\"selectedCssEditorSheetPath\" [assetsFolder]=\"currentDashboardRoute\"\n [styleProps]=\"getCssEditorStyleProps()\" (applyEditor)=\"onCssSheetEditorApply($event)\">\n</wuic-css-sheet-editor>\n\n<p-dialog [header]=\"'designer.reopen_dashboard' | translate\" [(visible)]=\"reopenDashboardDialogVisible\" [modal]=\"true\"\n [closable]=\"true\" [draggable]=\"false\" [resizable]=\"false\" [appendTo]=\"'body'\" [style]=\"{ width: '34rem' }\"\n (onHide)=\"cancelReopenDashboardDialog()\">\n <div class=\"route-popup-form\">\n <label for=\"designer-reopen-select\">{{ 'designer.existing_dashboards' | translate }}</label>\n <p-select inputId=\"designer-reopen-select\" [options]=\"savedDashboardOptions\" optionLabel=\"label\" optionValue=\"value\"\n [(ngModel)]=\"reopenDialogSelectedDashboardRoute\" [filter]=\"true\" [showClear]=\"true\" [style]=\"{ width: '100%' }\"\n appendTo=\"body\" />\n\n <div class=\"route-popup-actions\">\n <button type=\"button\" class=\"route-popup-btn\" (click)=\"cancelReopenDashboardDialog()\">{{ 'cancel' | translate\n }}</button>\n <button type=\"button\" class=\"route-popup-btn route-popup-btn-primary\" (click)=\"confirmReopenDashboardDialog()\"\n [disabled]=\"!reopenDialogSelectedDashboardRoute\">{{ 'designer.reopen' | translate }}</button>\n </div>\n </div>\n</p-dialog>", styles: [":host{display:block;width:100%;height:100%;min-width:0}.designer-layout{display:grid;grid-template-rows:auto minmax(0,1fr) auto;height:100%;min-height:0;width:100%;min-width:0}.designer-toolbar{display:flex;align-items:center;flex-wrap:wrap;gap:8px;border:1px solid inherit;border-radius:10px;background:transparent;padding:10px 12px;margin-bottom:8px}.designer-current-dashboard{min-width:220px;font-size:.86rem;font-weight:700;color:inherit}.designer-toolbar-spacer{flex:1 1 auto;min-width:16px}.designer-toolbar-menu-group{display:inline-flex}.designer-toolbar-menu-group button{height:32px;border:1px solid #2f67d8;border-radius:6px;background:#2f67d8;color:#fff;padding:0 10px;font-size:.82rem;cursor:pointer}.designer-toolbar-menu-group button[disabled]{opacity:.6;cursor:not-allowed}.designer-main{min-height:0;min-width:0;width:100%;overflow:hidden;direction:ltr}.designer-main-splitter{height:100%;width:100%;min-width:0;direction:ltr}.designer-toolbox{font-size:3rem}.designer-toolbox ul{list-style-type:none;padding:0;margin:0;width:100%}.designer-toolbox li{float:left;margin-left:5px;margin-top:5px}.designer-toolbox .tool-groups{display:block}.designer-toolbox .tool-group-item{float:none;margin-left:0;margin-top:10px;clear:both}.designer-toolbox .tool-group-title{font-size:.8rem;font-weight:600;text-transform:uppercase;letter-spacing:.04em;opacity:.7;margin:0 0 4px 6px}.designer-toolbox .tool-group-list{display:flex;flex-wrap:wrap;align-items:flex-start;gap:4px}.designer-toolbox .tool-group-list li{float:none;margin-left:0;margin-top:0}.designer-toolbox .props{font-size:1rem}.designer-toolbox .props li{float:none;padding-bottom:15px}.designer-toolbox .prop-section{width:100%;border:1px solid inherit;border-radius:6px;margin-top:8px;background:transparent;padding:6px 8px 2px}.designer-toolbox .prop-section-title{font-size:.82rem;font-weight:600;text-transform:uppercase;letter-spacing:.04em;cursor:pointer;color:inherit;margin-bottom:6px}.palette-panel{height:100%;overflow:auto;width:100%}.designer-color-alpha-row{display:flex;align-items:center;gap:8px;margin-top:6px;margin-bottom:6px}.designer-color-alpha-label{font-size:.8rem;min-width:40px}.designer-color-alpha-row input[type=range]{flex:1 1 auto;min-width:90px}.designer-color-alpha-row input[type=number]{width:72px}.properties-panel{height:100%;overflow:hidden;width:100%;display:flex;flex-direction:column;min-height:0;padding-right:10px;padding-left:10px;padding-top:10px}.properties-panel>.designer-toolbox{flex:1 1 auto;min-height:0;overflow-y:auto;overflow-x:hidden;scrollbar-gutter:stable}:host ::ng-deep .designer-left-bottom-splitter .p-splitterpanel{min-height:0!important;overflow:hidden!important}.hierarchy-panel{height:100%;width:100%;min-height:0;display:flex;flex-direction:column;overflow:hidden;padding:6px}.hierarchy-panel-title{font-size:.82rem;font-weight:600;text-transform:uppercase;letter-spacing:.04em;color:#4b5563;margin:0 0 6px}.hierarchy-tree-wrap{flex:1 1 auto;min-height:0;border:1px solid inherit;border-radius:6px;background:transparent;overflow:auto;padding:4px}:host ::ng-deep .hierarchy-tree-wrap .p-tree{border:none;padding:0;height:100%;background:transparent}:host ::ng-deep .hierarchy-tree-wrap .p-tree-node-content{border:1px solid transparent;border-radius:4px;background:transparent!important;color:inherit!important;padding:0!important}:host ::ng-deep .hierarchy-tree-wrap .p-tree-node-content.p-highlight{border-color:transparent!important;background:transparent!important;color:inherit!important}:host ::ng-deep .hierarchy-tree-wrap .p-tree-node-content:hover{background:transparent!important;color:inherit!important}:host ::ng-deep .hierarchy-tree-wrap .hierarchy-node-label{display:inline-block;border:1px solid inherit;background:transparent;color:inherit;border-radius:4px;padding:2px 8px;font-size:.8rem;line-height:1.25rem;cursor:pointer}:host ::ng-deep .hierarchy-tree-wrap .hierarchy-node-label:hover{border-color:#94a3b8}:host ::ng-deep .hierarchy-tree-wrap .hierarchy-node-label.active{border-color:#3b82f6!important;background:#eff6ff!important;color:#1d4ed8!important}:host ::ng-deep .hierarchy-tree-wrap .p-tree-node-content.p-highlight .hierarchy-node-label{border-color:#3b82f6!important;background:#eff6ff!important;color:#1d4ed8!important}.designer-hierarchy-node{cursor:pointer;border:1px solid transparent;border-radius:4px;padding:2px 6px;font-size:.82rem;line-height:1.25rem}.designer-hierarchy-node-label{cursor:pointer;display:inline-block;width:100%}.designer-hierarchy-node.active{border-color:#3b82f6;background:#eff6ff;color:#1d4ed8}.designer-footer-item.designer-tree-hover-target{border-color:#3b82f6!important;outline:1px solid #3b82f6!important;outline-offset:-1px}.dashboard-design{height:100%;min-height:0;min-width:0;width:100%;overflow:auto;direction:ltr}:host ::ng-deep .dashboard-design.designer-hide-datasources .datasource-descriptor{display:none!important}.current-dashboard-title{font-weight:600;margin-right:12px}.designer-footer{display:flex;align-items:center;gap:8px;padding:6px 8px;border-top:1px solid inherit;background:transparent;min-height:36px;flex:0 0 auto;width:100%;overflow:auto}.designer-footer-label{font-size:.85rem;font-weight:600;color:inherit;white-space:nowrap}.designer-footer-list{display:flex;align-items:center;gap:6px;overflow-x:auto;overflow-y:hidden;white-space:nowrap;width:100%}.designer-footer-item{border:1px solid inherit;background:transparent;color:inherit;border-radius:4px;padding:2px 8px;font-size:.8rem;line-height:1.25rem;cursor:pointer;white-space:nowrap}.designer-footer-item.active{border-color:#3b82f6;background:transparent;color:#1d4ed8}:host ::ng-deep .designer-footer-hover-target{border-color:#3b82f6!important;outline:1px solid #3b82f6!important;outline-offset:-1px;box-shadow:inset 0 0 0 1px #3b82f6!important}.route-popup-form{display:flex;flex-direction:column;gap:8px}.route-popup-form label{font-size:.85rem;font-weight:600;color:#2f3f58}.route-popup-actions{display:flex;justify-content:flex-end;gap:8px;margin-top:10px}.route-popup-btn{border:1px solid inherit;background:transparent;color:inherit;border-radius:6px;padding:7px 12px;font-size:.85rem;cursor:pointer}.route-popup-btn-primary{border-color:#2f67d8;background:#2f67d8;color:#fff}.p-colorpicker{border:1px solid black;border-radius:4px}\n"] }]
|
|
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"] }]
|
|
11836
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: [{
|
|
11837
13299
|
type: HostListener,
|
|
11838
13300
|
args: ['window:hashchange']
|
|
@@ -11854,7 +13316,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.9", ngImpor
|
|
|
11854
13316
|
}], onWindowChange: [{
|
|
11855
13317
|
type: HostListener,
|
|
11856
13318
|
args: ['window:change']
|
|
13319
|
+
}], canvasRef: [{
|
|
13320
|
+
type: ViewChild,
|
|
13321
|
+
args: ['canvas']
|
|
11857
13322
|
}] } });
|
|
11858
13323
|
|
|
11859
13324
|
export { DesignerComponent };
|
|
11860
|
-
//# sourceMappingURL=wuic-framework-lib-designer.component-
|
|
13325
|
+
//# sourceMappingURL=wuic-framework-lib-designer.component-x10ZzUEy.mjs.map
|