@memberjunction/ng-dashboard-viewer 6.2.0-edge.2 → 6.2.0-edge.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +165 -17
- package/dist/lib/config-dialogs/confirm-dialog.component.d.ts +3 -4
- package/dist/lib/config-dialogs/confirm-dialog.component.d.ts.map +1 -1
- package/dist/lib/config-dialogs/confirm-dialog.component.js +3 -2
- package/dist/lib/config-dialogs/confirm-dialog.component.js.map +1 -1
- package/dist/lib/config-panels/artifact-config-panel.component.d.ts.map +1 -1
- package/dist/lib/config-panels/artifact-config-panel.component.js +59 -50
- package/dist/lib/config-panels/artifact-config-panel.component.js.map +1 -1
- package/dist/lib/config-panels/base-config-panel.d.ts +8 -3
- package/dist/lib/config-panels/base-config-panel.d.ts.map +1 -1
- package/dist/lib/config-panels/base-config-panel.js +10 -3
- package/dist/lib/config-panels/base-config-panel.js.map +1 -1
- package/dist/lib/config-panels/query-config-panel.component.js +124 -99
- package/dist/lib/config-panels/query-config-panel.component.js.map +1 -1
- package/dist/lib/config-panels/view-config-panel.component.js +111 -103
- package/dist/lib/config-panels/view-config-panel.component.js.map +1 -1
- package/dist/lib/config-panels/weburl-config-panel.component.js +89 -80
- package/dist/lib/config-panels/weburl-config-panel.component.js.map +1 -1
- package/dist/lib/dashboard-browser/dashboard-browser.component.d.ts +79 -28
- package/dist/lib/dashboard-browser/dashboard-browser.component.d.ts.map +1 -1
- package/dist/lib/dashboard-browser/dashboard-browser.component.js +446 -447
- package/dist/lib/dashboard-browser/dashboard-browser.component.js.map +1 -1
- package/dist/lib/dashboard-card/dashboard-card.component.d.ts +69 -0
- package/dist/lib/dashboard-card/dashboard-card.component.d.ts.map +1 -0
- package/dist/lib/dashboard-card/dashboard-card.component.js +281 -0
- package/dist/lib/dashboard-card/dashboard-card.component.js.map +1 -0
- package/dist/lib/dashboard-card/dashboard-card.helpers.d.ts +18 -0
- package/dist/lib/dashboard-card/dashboard-card.helpers.d.ts.map +1 -0
- package/dist/lib/dashboard-card/dashboard-card.helpers.js +43 -0
- package/dist/lib/dashboard-card/dashboard-card.helpers.js.map +1 -0
- package/dist/lib/dashboard-card/layout-preview-cache.d.ts +15 -0
- package/dist/lib/dashboard-card/layout-preview-cache.d.ts.map +1 -0
- package/dist/lib/dashboard-card/layout-preview-cache.js +22 -0
- package/dist/lib/dashboard-card/layout-preview-cache.js.map +1 -0
- package/dist/lib/dashboard-viewer/dashboard-viewer.component.d.ts +69 -12
- package/dist/lib/dashboard-viewer/dashboard-viewer.component.d.ts.map +1 -1
- package/dist/lib/dashboard-viewer/dashboard-viewer.component.js +173 -145
- package/dist/lib/dashboard-viewer/dashboard-viewer.component.js.map +1 -1
- package/dist/lib/dashboard-viewer.module.d.ts +24 -20
- package/dist/lib/dashboard-viewer.module.d.ts.map +1 -1
- package/dist/lib/dashboard-viewer.module.js +42 -5
- package/dist/lib/dashboard-viewer.module.js.map +1 -1
- package/dist/lib/dialogs/add-panel-dialog/add-panel-dialog.component.d.ts +7 -12
- package/dist/lib/dialogs/add-panel-dialog/add-panel-dialog.component.d.ts.map +1 -1
- package/dist/lib/dialogs/add-panel-dialog/add-panel-dialog.component.js +3 -1
- package/dist/lib/dialogs/add-panel-dialog/add-panel-dialog.component.js.map +1 -1
- package/dist/lib/dialogs/dashboard-name-dialog/dashboard-name-dialog.component.d.ts +54 -0
- package/dist/lib/dialogs/dashboard-name-dialog/dashboard-name-dialog.component.d.ts.map +1 -0
- package/dist/lib/dialogs/dashboard-name-dialog/dashboard-name-dialog.component.js +156 -0
- package/dist/lib/dialogs/dashboard-name-dialog/dashboard-name-dialog.component.js.map +1 -0
- package/dist/lib/dialogs/edit-part-dialog/edit-part-dialog.component.d.ts +5 -11
- package/dist/lib/dialogs/edit-part-dialog/edit-part-dialog.component.d.ts.map +1 -1
- package/dist/lib/dialogs/edit-part-dialog/edit-part-dialog.component.js +3 -1
- package/dist/lib/dialogs/edit-part-dialog/edit-part-dialog.component.js.map +1 -1
- package/dist/lib/dialogs/part-dialog/dashboard-part-dialog.component.d.ts +147 -0
- package/dist/lib/dialogs/part-dialog/dashboard-part-dialog.component.d.ts.map +1 -0
- package/dist/lib/dialogs/part-dialog/dashboard-part-dialog.component.js +536 -0
- package/dist/lib/dialogs/part-dialog/dashboard-part-dialog.component.js.map +1 -0
- package/dist/lib/layout-preview/dashboard-layout-preview-node.component.d.ts +20 -0
- package/dist/lib/layout-preview/dashboard-layout-preview-node.component.d.ts.map +1 -0
- package/dist/lib/layout-preview/dashboard-layout-preview-node.component.js +80 -0
- package/dist/lib/layout-preview/dashboard-layout-preview-node.component.js.map +1 -0
- package/dist/lib/layout-preview/dashboard-layout-preview.component.d.ts +28 -0
- package/dist/lib/layout-preview/dashboard-layout-preview.component.d.ts.map +1 -0
- package/dist/lib/layout-preview/dashboard-layout-preview.component.js +62 -0
- package/dist/lib/layout-preview/dashboard-layout-preview.component.js.map +1 -0
- package/dist/lib/layout-preview/dashboard-layout-preview.d.ts +33 -0
- package/dist/lib/layout-preview/dashboard-layout-preview.d.ts.map +1 -0
- package/dist/lib/layout-preview/dashboard-layout-preview.js +138 -0
- package/dist/lib/layout-preview/dashboard-layout-preview.js.map +1 -0
- package/dist/lib/models/dashboard-layout-editor.d.ts +78 -0
- package/dist/lib/models/dashboard-layout-editor.d.ts.map +1 -0
- package/dist/lib/models/dashboard-layout-editor.js +374 -0
- package/dist/lib/models/dashboard-layout-editor.js.map +1 -0
- package/dist/lib/models/remove-part-confirm.d.ts +8 -0
- package/dist/lib/models/remove-part-confirm.d.ts.map +1 -0
- package/dist/lib/models/remove-part-confirm.js +23 -0
- package/dist/lib/models/remove-part-confirm.js.map +1 -0
- package/dist/lib/parts/artifact-part.component.js +2 -2
- package/dist/lib/parts/artifact-part.component.js.map +1 -1
- package/dist/lib/parts/query-part.component.js +2 -2
- package/dist/lib/parts/query-part.component.js.map +1 -1
- package/dist/lib/parts/view-part.component.js +2 -2
- package/dist/lib/parts/view-part.component.js.map +1 -1
- package/dist/lib/parts/weburl-part.component.js +2 -2
- package/dist/lib/parts/weburl-part.component.js.map +1 -1
- package/dist/lib/services/golden-layout-wrapper.service.d.ts +40 -0
- package/dist/lib/services/golden-layout-wrapper.service.d.ts.map +1 -1
- package/dist/lib/services/golden-layout-wrapper.service.js +117 -9
- package/dist/lib/services/golden-layout-wrapper.service.js.map +1 -1
- package/dist/lib/services/stack-part-actions.d.ts +11 -0
- package/dist/lib/services/stack-part-actions.d.ts.map +1 -0
- package/dist/lib/services/stack-part-actions.js +41 -0
- package/dist/lib/services/stack-part-actions.js.map +1 -0
- package/dist/public-api.d.ts +9 -0
- package/dist/public-api.d.ts.map +1 -1
- package/dist/public-api.js +13 -0
- package/dist/public-api.js.map +1 -1
- package/package.json +13 -13
|
@@ -6,6 +6,7 @@ import { BaseAngularComponent } from '@memberjunction/ng-base-types';
|
|
|
6
6
|
import { DashboardEngine } from '@memberjunction/core-entities';
|
|
7
7
|
import { CreateDefaultDashboardConfig, GeneratePanelId, ExtractPanelsFromLayout, FindPanelInLayout } from '../models/dashboard-types';
|
|
8
8
|
import { GoldenLayoutWrapperService } from '../services/golden-layout-wrapper.service';
|
|
9
|
+
import { InsertPanel, FindPanelPath, DescribePath } from '../models/dashboard-layout-editor';
|
|
9
10
|
import { BaseDashboardPart } from '../parts/base-dashboard-part';
|
|
10
11
|
import * as i0 from "@angular/core";
|
|
11
12
|
import * as i1 from "@memberjunction/ng-shared-generic";
|
|
@@ -110,13 +111,15 @@ function DashboardViewerComponent_Conditional_3_Template(rf, ctx) { if (rf & 1)
|
|
|
110
111
|
function DashboardViewerComponent_Conditional_6_Template(rf, ctx) { if (rf & 1) {
|
|
111
112
|
const _r7 = i0.ɵɵgetCurrentView();
|
|
112
113
|
i0.ɵɵelementStart(0, "mj-empty-state", 27);
|
|
113
|
-
i0.ɵɵlistener("Action", function DashboardViewerComponent_Conditional_6_Template_mj_empty_state_Action_0_listener() { i0.ɵɵrestoreView(_r7); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.
|
|
114
|
+
i0.ɵɵlistener("Action", function DashboardViewerComponent_Conditional_6_Template_mj_empty_state_Action_0_listener() { i0.ɵɵrestoreView(_r7); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.OnAddPanelClick()); });
|
|
114
115
|
i0.ɵɵelementEnd();
|
|
115
116
|
} if (rf & 2) {
|
|
116
117
|
const ctx_r1 = i0.ɵɵnextContext();
|
|
117
|
-
i0.ɵɵproperty("ActionText", ctx_r1.
|
|
118
|
+
i0.ɵɵproperty("ActionText", ctx_r1.IsEditing || ctx_r1.CanEdit ? "Add part" : "");
|
|
118
119
|
} }
|
|
119
120
|
const LAYOUT_CONTAINER_SIZE_TIMEOUT_MS = 10_000;
|
|
121
|
+
/** Time a layout build waits after its parts are created, before its final size update. */
|
|
122
|
+
const LAYOUT_SETTLE_DELAY_MS = 100;
|
|
120
123
|
/**
|
|
121
124
|
* Main dashboard viewer component.
|
|
122
125
|
* Renders a configurable dashboard with draggable/resizable panels using Golden Layout.
|
|
@@ -179,7 +182,6 @@ export class DashboardViewerComponent extends BaseAngularComponent {
|
|
|
179
182
|
this._isEditing = value;
|
|
180
183
|
// When isEditing changes (and layout exists), reinitialize to apply GL settings
|
|
181
184
|
if (value !== previous && this._glService) {
|
|
182
|
-
console.log('[DashboardViewer] isEditing changed from', previous, 'to', value);
|
|
183
185
|
this.updatePanelEditModes();
|
|
184
186
|
}
|
|
185
187
|
}
|
|
@@ -194,6 +196,11 @@ export class DashboardViewerComponent extends BaseAngularComponent {
|
|
|
194
196
|
set isEditing(value) {
|
|
195
197
|
this.IsEditing = value;
|
|
196
198
|
}
|
|
199
|
+
/**
|
|
200
|
+
* Whether the user may edit this dashboard. An empty dashboard then offers Add part in view mode
|
|
201
|
+
* too; the host decides how to get into edit mode.
|
|
202
|
+
*/
|
|
203
|
+
CanEdit = false;
|
|
197
204
|
/** Whether to show the toolbar */
|
|
198
205
|
_showToolbar = true;
|
|
199
206
|
set ShowToolbar(value) {
|
|
@@ -405,6 +412,8 @@ export class DashboardViewerComponent extends BaseAngularComponent {
|
|
|
405
412
|
set hasUnsavedChanges(value) {
|
|
406
413
|
this.HasUnsavedChanges = value;
|
|
407
414
|
}
|
|
415
|
+
/** The saved configuration (UIConfigDetails) the layout was loaded from or last saved as. */
|
|
416
|
+
_loadedConfigDetails = null;
|
|
408
417
|
/**
|
|
409
418
|
* Helper to check if layout has any panels (for template use).
|
|
410
419
|
* Panels are stored in componentState within the layout tree.
|
|
@@ -425,7 +434,8 @@ export class DashboardViewerComponent extends BaseAngularComponent {
|
|
|
425
434
|
_resolveLayoutReady = null;
|
|
426
435
|
_rejectLayoutReady = null;
|
|
427
436
|
_layoutInitGeneration = 0;
|
|
428
|
-
|
|
437
|
+
/** Number of the latest layout build. A build that is no longer the latest stops at its next step. */
|
|
438
|
+
_layoutBuild = 0;
|
|
429
439
|
_resolveDeferredLayoutInit = null;
|
|
430
440
|
_rejectDeferredLayoutInit = null;
|
|
431
441
|
_deferredLayoutInitTimer = null;
|
|
@@ -450,7 +460,6 @@ export class DashboardViewerComponent extends BaseAngularComponent {
|
|
|
450
460
|
const generation = this._layoutInitGeneration;
|
|
451
461
|
this.resolveLayoutReady(generation);
|
|
452
462
|
this._layoutInitGeneration++;
|
|
453
|
-
this.clearLayoutReadyTimer();
|
|
454
463
|
this.cancelDeferredLayoutInit();
|
|
455
464
|
this.destroyLayout();
|
|
456
465
|
}
|
|
@@ -470,17 +479,19 @@ export class DashboardViewerComponent extends BaseAngularComponent {
|
|
|
470
479
|
return this.ToggleEditMode();
|
|
471
480
|
}
|
|
472
481
|
/**
|
|
473
|
-
*
|
|
474
|
-
*
|
|
482
|
+
* Adds a panel and returns its id, or null when nothing was added (no layout yet, or an unknown
|
|
483
|
+
* part type). With a location, the layout editor places the panel there and the layout is
|
|
484
|
+
* rebuilt; without one, Golden Layout adds the panel to its first stack.
|
|
485
|
+
* @throws LayoutEditError when the location is not valid or names a panel that is not in the layout.
|
|
475
486
|
*/
|
|
476
487
|
async AddPanel(partTypeId, panelConfig, title, icon, location) {
|
|
477
488
|
if (!this.config || !this._glService) {
|
|
478
|
-
return;
|
|
489
|
+
return null;
|
|
479
490
|
}
|
|
480
491
|
const partType = this.PartTypes.find(pt => UUIDsEqual(pt.ID, partTypeId));
|
|
481
492
|
if (!partType) {
|
|
482
493
|
this.error.emit({ message: `Unknown panel type: ${partTypeId}` });
|
|
483
|
-
return;
|
|
494
|
+
return null;
|
|
484
495
|
}
|
|
485
496
|
const panel = {
|
|
486
497
|
id: GeneratePanelId(),
|
|
@@ -489,15 +500,19 @@ export class DashboardViewerComponent extends BaseAngularComponent {
|
|
|
489
500
|
icon: icon || partType.Icon || 'fa-solid fa-window-maximize',
|
|
490
501
|
config: panelConfig
|
|
491
502
|
};
|
|
503
|
+
if (location) {
|
|
504
|
+
await this.ApplyLayout(InsertPanel(this._glService.getLayoutConfig() ?? this.config.layout, panel, location));
|
|
505
|
+
return panel.id;
|
|
506
|
+
}
|
|
492
507
|
// Add to Golden Layout - panel data stored in componentState
|
|
493
|
-
this._glService.addPanel(panel
|
|
508
|
+
this._glService.addPanel(panel);
|
|
494
509
|
// Sync the layout config from Golden Layout to capture the new panel
|
|
495
|
-
// The layout IS the source of truth - it contains the panel in componentState
|
|
496
510
|
const currentLayout = this._glService.getLayoutConfig();
|
|
497
511
|
if (currentLayout) {
|
|
498
512
|
this.config.layout = currentLayout;
|
|
499
513
|
}
|
|
500
514
|
this.markDirty();
|
|
515
|
+
return panel.id;
|
|
501
516
|
}
|
|
502
517
|
/** @deprecated Use {@link AddPanel}. */
|
|
503
518
|
async addPanel(partTypeId, panelConfig, title, icon, location) {
|
|
@@ -546,6 +561,7 @@ export class DashboardViewerComponent extends BaseAngularComponent {
|
|
|
546
561
|
const saved = await this._dashboard.Save();
|
|
547
562
|
if (saved) {
|
|
548
563
|
this.HasUnsavedChanges = false;
|
|
564
|
+
this._loadedConfigDetails = this._dashboard.UIConfigDetails ?? null;
|
|
549
565
|
this.DashboardSaved.emit(this._dashboard);
|
|
550
566
|
}
|
|
551
567
|
return saved;
|
|
@@ -573,6 +589,32 @@ export class DashboardViewerComponent extends BaseAngularComponent {
|
|
|
573
589
|
async refreshAllPanels() {
|
|
574
590
|
return this.RefreshAllPanels();
|
|
575
591
|
}
|
|
592
|
+
/**
|
|
593
|
+
* Shows `layout`: stores it as the dashboard's layout, rebuilds the panels, marks the dashboard
|
|
594
|
+
* changed and emits a `layout` config change. Build the layout with the layout editor functions.
|
|
595
|
+
* Rejects when the rebuild fails, for example when the container stays at zero size.
|
|
596
|
+
*/
|
|
597
|
+
async ApplyLayout(layout) {
|
|
598
|
+
if (!this.config)
|
|
599
|
+
return;
|
|
600
|
+
this.config.layout = layout;
|
|
601
|
+
try {
|
|
602
|
+
if (this._glService) {
|
|
603
|
+
await this.initializeLayout();
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
finally {
|
|
607
|
+
// After the rebuild, so an auto-save reads the new layout back from Golden Layout.
|
|
608
|
+
// The layout has changed even when the rebuild fails.
|
|
609
|
+
this.markDirty();
|
|
610
|
+
}
|
|
611
|
+
this.configChanged.emit({ config: this.config, changeType: 'layout' });
|
|
612
|
+
}
|
|
613
|
+
/** The panel's address in the layout, for example `row/1 › tab 0`, or null when it is not in the layout. */
|
|
614
|
+
GetPanelPath(panelId) {
|
|
615
|
+
const path = FindPanelPath(this.config?.layout ?? null, panelId);
|
|
616
|
+
return path ? DescribePath(path) : null;
|
|
617
|
+
}
|
|
576
618
|
/**
|
|
577
619
|
* Resolves when the current dashboard's Golden Layout instance has initialized
|
|
578
620
|
* against a non-zero container and completed its first size update.
|
|
@@ -584,6 +626,42 @@ export class DashboardViewerComponent extends BaseAngularComponent {
|
|
|
584
626
|
waitForLayoutReady() {
|
|
585
627
|
return this.WaitForLayoutReady();
|
|
586
628
|
}
|
|
629
|
+
/**
|
|
630
|
+
* True when the dashboard's saved layout differs from the one this viewer last loaded or saved,
|
|
631
|
+
* for example after another tab saved the dashboard.
|
|
632
|
+
* @param dashboard A saved copy of the shown dashboard. Defaults to the shown dashboard.
|
|
633
|
+
*/
|
|
634
|
+
HasNewerSavedLayout(dashboard = this._dashboard) {
|
|
635
|
+
return !!dashboard && (dashboard.UIConfigDetails ?? null) !== this._loadedConfigDetails;
|
|
636
|
+
}
|
|
637
|
+
/**
|
|
638
|
+
* Shows the dashboard's saved layout again and drops unsaved layout changes. The current layout
|
|
639
|
+
* is removed at once, so an edit-mode change made right after the call applies to the new
|
|
640
|
+
* layout. A hidden viewer builds the layout when its container gets a size, with no time limit.
|
|
641
|
+
* @param dashboard A saved copy of the shown dashboard to show instead. Defaults to the shown dashboard.
|
|
642
|
+
*/
|
|
643
|
+
ReloadFromSaved(dashboard = this._dashboard) {
|
|
644
|
+
if (!dashboard) {
|
|
645
|
+
return Promise.resolve();
|
|
646
|
+
}
|
|
647
|
+
this._dashboard = dashboard;
|
|
648
|
+
this.HasUnsavedChanges = false;
|
|
649
|
+
this.destroyLayout();
|
|
650
|
+
return this.onDashboardChanged(false);
|
|
651
|
+
}
|
|
652
|
+
/**
|
|
653
|
+
* Takes another copy of the shown dashboard without rebuilding the layout, for example after
|
|
654
|
+
* DashboardEngine reloaded its dashboards. Later saves write that copy. Returns false, and changes
|
|
655
|
+
* nothing, when the copy is of another dashboard or its saved layout differs from the one this
|
|
656
|
+
* viewer last loaded or saved (use ReloadFromSaved then).
|
|
657
|
+
*/
|
|
658
|
+
UseSavedCopy(dashboard) {
|
|
659
|
+
if (!this._dashboard || !UUIDsEqual(dashboard.ID, this._dashboard.ID) || this.HasNewerSavedLayout(dashboard)) {
|
|
660
|
+
return false;
|
|
661
|
+
}
|
|
662
|
+
this._dashboard = dashboard;
|
|
663
|
+
return true;
|
|
664
|
+
}
|
|
587
665
|
/**
|
|
588
666
|
* Get the current configuration
|
|
589
667
|
*/
|
|
@@ -628,8 +706,9 @@ export class DashboardViewerComponent extends BaseAngularComponent {
|
|
|
628
706
|
* Update a panel's configuration.
|
|
629
707
|
* Since panels live in componentState within the layout, we need to
|
|
630
708
|
* update the layout tree directly or reinitialize with updated data.
|
|
709
|
+
* Resolves once the layout is rebuilt with the updated panel; rejects when the rebuild fails.
|
|
631
710
|
*/
|
|
632
|
-
UpdatePanelConfig(panelId, newConfig, title, icon) {
|
|
711
|
+
async UpdatePanelConfig(panelId, newConfig, title, icon) {
|
|
633
712
|
if (!this.config || !this._glService)
|
|
634
713
|
return;
|
|
635
714
|
// Get current layout which contains all panel data
|
|
@@ -641,7 +720,7 @@ export class DashboardViewerComponent extends BaseAngularComponent {
|
|
|
641
720
|
this.config.layout = currentLayout;
|
|
642
721
|
this.markDirty();
|
|
643
722
|
// Reinitialize layout to reflect the updated panel
|
|
644
|
-
this.initializeLayout();
|
|
723
|
+
await this.initializeLayout();
|
|
645
724
|
}
|
|
646
725
|
/** @deprecated Use {@link UpdatePanelConfig}. */
|
|
647
726
|
updatePanelConfig(panelId, newConfig, title, icon) {
|
|
@@ -696,7 +775,7 @@ export class DashboardViewerComponent extends BaseAngularComponent {
|
|
|
696
775
|
return this.OnAddPanelClick();
|
|
697
776
|
}
|
|
698
777
|
/**
|
|
699
|
-
* Handle "Open in Tab" button click - emits event for parent to open dashboard in
|
|
778
|
+
* Handle "Open in Tab" button click - emits event for the parent to open the dashboard in a dashboard tab
|
|
700
779
|
*/
|
|
701
780
|
OnOpenInTabClick() {
|
|
702
781
|
if (this._dashboard) {
|
|
@@ -756,13 +835,19 @@ export class DashboardViewerComponent extends BaseAngularComponent {
|
|
|
756
835
|
this.cdr.detectChanges();
|
|
757
836
|
}
|
|
758
837
|
}
|
|
759
|
-
|
|
838
|
+
/**
|
|
839
|
+
* Builds the layout from the dashboard's saved configuration.
|
|
840
|
+
* @param limitSizeWait When true, a container that stays at zero size fails the build after
|
|
841
|
+
* LAYOUT_CONTAINER_SIZE_TIMEOUT_MS. When false, the build waits until the container gets a size.
|
|
842
|
+
*/
|
|
843
|
+
async onDashboardChanged(limitSizeWait = true) {
|
|
760
844
|
if (!this._dashboard)
|
|
761
845
|
return;
|
|
762
846
|
const generation = this.startLayoutReadyCycle();
|
|
763
847
|
try {
|
|
764
848
|
// Parse or create config
|
|
765
849
|
this.config = this.parseOrCreateConfig();
|
|
850
|
+
this._loadedConfigDetails = this._dashboard.UIConfigDetails ?? null;
|
|
766
851
|
// Wait for part types to be loaded before initializing layout
|
|
767
852
|
// This ensures partTypes array is populated when createPanelComponent is called
|
|
768
853
|
if (this._partTypesLoaded) {
|
|
@@ -773,7 +858,7 @@ export class DashboardViewerComponent extends BaseAngularComponent {
|
|
|
773
858
|
return;
|
|
774
859
|
}
|
|
775
860
|
// Initialize layout
|
|
776
|
-
await this.initializeLayout(generation);
|
|
861
|
+
await this.initializeLayout(generation, limitSizeWait);
|
|
777
862
|
}
|
|
778
863
|
catch (err) {
|
|
779
864
|
const error = err instanceof Error ? err : new Error(String(err));
|
|
@@ -803,10 +888,11 @@ export class DashboardViewerComponent extends BaseAngularComponent {
|
|
|
803
888
|
// ========================================
|
|
804
889
|
// Private Methods - Layout
|
|
805
890
|
// ========================================
|
|
806
|
-
async initializeLayout(generation = this._layoutInitGeneration) {
|
|
891
|
+
async initializeLayout(generation = this._layoutInitGeneration, limitSizeWait = true) {
|
|
807
892
|
if (!this.config || !this.LayoutContainer?.nativeElement) {
|
|
808
893
|
return;
|
|
809
894
|
}
|
|
895
|
+
const build = ++this._layoutBuild;
|
|
810
896
|
const el = this.LayoutContainer.nativeElement;
|
|
811
897
|
const rect = el.getBoundingClientRect();
|
|
812
898
|
if (rect.width === 0 || rect.height === 0) {
|
|
@@ -819,8 +905,8 @@ export class DashboardViewerComponent extends BaseAngularComponent {
|
|
|
819
905
|
const reason = 'layout container has zero size';
|
|
820
906
|
this.emitLayoutLifecycle('waiting-for-size', reason);
|
|
821
907
|
this.LayoutDeferred.emit({ reason });
|
|
822
|
-
await this.waitForLayoutContainerSize(el, generation);
|
|
823
|
-
if (
|
|
908
|
+
await this.waitForLayoutContainerSize(el, generation, limitSizeWait);
|
|
909
|
+
if (!this.isCurrentLayoutBuild(build, generation)) {
|
|
824
910
|
return;
|
|
825
911
|
}
|
|
826
912
|
}
|
|
@@ -830,14 +916,15 @@ export class DashboardViewerComponent extends BaseAngularComponent {
|
|
|
830
916
|
// Destroy existing layout
|
|
831
917
|
this.destroyLayout();
|
|
832
918
|
// Create new Golden Layout service
|
|
833
|
-
|
|
919
|
+
const glService = new GoldenLayoutWrapperService();
|
|
920
|
+
this._glService = glService;
|
|
834
921
|
// Subscribe to layout events
|
|
835
922
|
this.subscribeToLayoutEvents();
|
|
836
923
|
const pendingPanelCreations = [];
|
|
837
924
|
// Panel factory - called by GL when it binds a component
|
|
838
925
|
// The panel comes directly from GL's componentState (single source of truth)
|
|
839
926
|
const panelFactory = (panel, container) => {
|
|
840
|
-
const creation = this.createPanelComponent(panel, container);
|
|
927
|
+
const creation = this.createPanelComponent(panel, container, glService);
|
|
841
928
|
pendingPanelCreations.push(creation.catch(error => {
|
|
842
929
|
console.error('[DashboardViewer] Failed to create panel component:', error);
|
|
843
930
|
}));
|
|
@@ -854,14 +941,8 @@ export class DashboardViewerComponent extends BaseAngularComponent {
|
|
|
854
941
|
// However, Angular needs time to complete change detection and render
|
|
855
942
|
// the dynamic components. A single delayed updateSize() ensures GL
|
|
856
943
|
// recalculates dimensions after Angular has finished rendering.
|
|
857
|
-
await
|
|
858
|
-
|
|
859
|
-
this._layoutReadyTimer = setTimeout(() => {
|
|
860
|
-
this._layoutReadyTimer = null;
|
|
861
|
-
resolve();
|
|
862
|
-
}, 100);
|
|
863
|
-
});
|
|
864
|
-
if (generation !== this._layoutInitGeneration) {
|
|
944
|
+
await this.waitForLayoutToSettle();
|
|
945
|
+
if (!this.isCurrentLayoutBuild(build, generation)) {
|
|
865
946
|
return;
|
|
866
947
|
}
|
|
867
948
|
this._glService?.updateSize();
|
|
@@ -877,8 +958,9 @@ export class DashboardViewerComponent extends BaseAngularComponent {
|
|
|
877
958
|
* (re)initialize. Used when initializeLayout() is called while the container is
|
|
878
959
|
* still 0×0 — e.g. during a cached-component reattach on browser back/forward —
|
|
879
960
|
* where GoldenLayout would otherwise bind panels that never receive a 'show' event.
|
|
961
|
+
* With `limitSizeWait`, the wait fails after LAYOUT_CONTAINER_SIZE_TIMEOUT_MS.
|
|
880
962
|
*/
|
|
881
|
-
waitForLayoutContainerSize(el, generation) {
|
|
963
|
+
waitForLayoutContainerSize(el, generation, limitSizeWait = true) {
|
|
882
964
|
this.cancelDeferredLayoutInit();
|
|
883
965
|
return new Promise((resolve, reject) => {
|
|
884
966
|
this._resolveDeferredLayoutInit = resolve;
|
|
@@ -900,10 +982,12 @@ export class DashboardViewerComponent extends BaseAngularComponent {
|
|
|
900
982
|
resolve();
|
|
901
983
|
return;
|
|
902
984
|
}
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
985
|
+
if (limitSizeWait) {
|
|
986
|
+
this._deferredLayoutInitTimer = setTimeout(() => {
|
|
987
|
+
const error = new Error(`Dashboard layout container stayed at zero size for ${LAYOUT_CONTAINER_SIZE_TIMEOUT_MS}ms`);
|
|
988
|
+
this.failDeferredLayoutInit(error);
|
|
989
|
+
}, LAYOUT_CONTAINER_SIZE_TIMEOUT_MS);
|
|
990
|
+
}
|
|
907
991
|
ro.observe(el);
|
|
908
992
|
this._layoutSizeObserver = ro;
|
|
909
993
|
});
|
|
@@ -938,9 +1022,19 @@ export class DashboardViewerComponent extends BaseAngularComponent {
|
|
|
938
1022
|
this._deferredLayoutInitTimer = null;
|
|
939
1023
|
}
|
|
940
1024
|
}
|
|
1025
|
+
/** True while the build is the latest layout build and its layout cycle is still current. */
|
|
1026
|
+
isCurrentLayoutBuild(build, generation) {
|
|
1027
|
+
return build === this._layoutBuild && generation === this._layoutInitGeneration;
|
|
1028
|
+
}
|
|
1029
|
+
/**
|
|
1030
|
+
* Resolves after LAYOUT_SETTLE_DELAY_MS. Each build waits on its own timer; a build that a newer
|
|
1031
|
+
* build replaced stops after the wait.
|
|
1032
|
+
*/
|
|
1033
|
+
waitForLayoutToSettle() {
|
|
1034
|
+
return new Promise(resolve => setTimeout(resolve, LAYOUT_SETTLE_DELAY_MS));
|
|
1035
|
+
}
|
|
941
1036
|
startLayoutReadyCycle() {
|
|
942
1037
|
const generation = ++this._layoutInitGeneration;
|
|
943
|
-
this.clearLayoutReadyTimer();
|
|
944
1038
|
this.cancelDeferredLayoutInit();
|
|
945
1039
|
this._resolveLayoutReady?.();
|
|
946
1040
|
this._layoutReadyPromise = new Promise((resolve, reject) => {
|
|
@@ -967,12 +1061,6 @@ export class DashboardViewerComponent extends BaseAngularComponent {
|
|
|
967
1061
|
this._resolveLayoutReady = null;
|
|
968
1062
|
this._rejectLayoutReady = null;
|
|
969
1063
|
}
|
|
970
|
-
clearLayoutReadyTimer() {
|
|
971
|
-
if (this._layoutReadyTimer) {
|
|
972
|
-
clearTimeout(this._layoutReadyTimer);
|
|
973
|
-
this._layoutReadyTimer = null;
|
|
974
|
-
}
|
|
975
|
-
}
|
|
976
1064
|
emitLayoutLifecycle(state, reason, error) {
|
|
977
1065
|
this.LayoutLifecycle.emit({
|
|
978
1066
|
state,
|
|
@@ -1016,6 +1104,20 @@ export class DashboardViewerComponent extends BaseAngularComponent {
|
|
|
1016
1104
|
.subscribe((panelId) => {
|
|
1017
1105
|
this.onPanelSelected(panelId);
|
|
1018
1106
|
});
|
|
1107
|
+
this._glService.OnStackAction
|
|
1108
|
+
.pipe(takeUntil(this._destroy$))
|
|
1109
|
+
.subscribe((event) => this.onStackAction(event));
|
|
1110
|
+
}
|
|
1111
|
+
/** Asks the host to edit or remove the part a stack header button acts on. */
|
|
1112
|
+
onStackAction(event) {
|
|
1113
|
+
switch (event.Action) {
|
|
1114
|
+
case 'edit':
|
|
1115
|
+
this.onConfigurePart(event.PanelId);
|
|
1116
|
+
break;
|
|
1117
|
+
case 'remove':
|
|
1118
|
+
this.onRemovePart(event.PanelId);
|
|
1119
|
+
break;
|
|
1120
|
+
}
|
|
1019
1121
|
}
|
|
1020
1122
|
onLayoutChanged(event) {
|
|
1021
1123
|
if (this.config) {
|
|
@@ -1054,24 +1156,27 @@ export class DashboardViewerComponent extends BaseAngularComponent {
|
|
|
1054
1156
|
/**
|
|
1055
1157
|
* Create a panel component from the DashboardPanel data.
|
|
1056
1158
|
* Panel comes directly from GL's componentState - no lookup needed.
|
|
1159
|
+
* @param glService The layout that asks for the part. When it is no longer the viewer's layout
|
|
1160
|
+
* once the part is created (a newer build replaced it, or it was removed), the part is destroyed.
|
|
1057
1161
|
*/
|
|
1058
|
-
async createPanelComponent(panel, container) {
|
|
1162
|
+
async createPanelComponent(panel, container, glService) {
|
|
1059
1163
|
const partType = this.PartTypes.find(pt => UUIDsEqual(pt.ID, panel.partTypeId));
|
|
1060
|
-
// Create the panel wrapper
|
|
1164
|
+
// Create the panel wrapper; the Golden Layout tab shows the part's title
|
|
1061
1165
|
const wrapper = document.createElement('div');
|
|
1062
1166
|
wrapper.className = 'dashboard-part-wrapper';
|
|
1063
1167
|
wrapper.style.cssText = 'display: flex; flex-direction: column; height: 100%; background: var(--mj-bg-surface);';
|
|
1064
|
-
// Only show header in edit mode - GL tabs already display the title in view mode
|
|
1065
|
-
if (this.IsEditing) {
|
|
1066
|
-
const header = this.createPartHeader(panel, panel.id);
|
|
1067
|
-
wrapper.appendChild(header);
|
|
1068
|
-
}
|
|
1069
1168
|
// Create content area
|
|
1070
1169
|
const content = document.createElement('div');
|
|
1071
1170
|
content.className = 'dashboard-part-content';
|
|
1072
1171
|
content.style.cssText = 'flex: 1; overflow: auto; min-height: 0;';
|
|
1073
1172
|
// Try to create dynamic component via ClassFactory
|
|
1074
1173
|
const componentRef = await this.createDynamicPartComponent(panel, partType, content);
|
|
1174
|
+
if (glService !== this._glService) {
|
|
1175
|
+
if (componentRef) {
|
|
1176
|
+
this.releasePartComponent(componentRef);
|
|
1177
|
+
}
|
|
1178
|
+
return;
|
|
1179
|
+
}
|
|
1075
1180
|
if (!componentRef) {
|
|
1076
1181
|
// Fallback to static rendering if no DriverClass or component creation failed
|
|
1077
1182
|
this.renderPartContent(panel, content, partType);
|
|
@@ -1128,90 +1233,6 @@ export class DashboardViewerComponent extends BaseAngularComponent {
|
|
|
1128
1233
|
return null;
|
|
1129
1234
|
}
|
|
1130
1235
|
}
|
|
1131
|
-
createPartHeader(panel, panelId) {
|
|
1132
|
-
const header = document.createElement('div');
|
|
1133
|
-
header.className = 'dashboard-part-header';
|
|
1134
|
-
header.style.cssText = `
|
|
1135
|
-
display: flex;
|
|
1136
|
-
align-items: center;
|
|
1137
|
-
gap: 8px;
|
|
1138
|
-
padding: 10px 12px;
|
|
1139
|
-
background: var(--mj-bg-surface-card);
|
|
1140
|
-
border-bottom: 1px solid var(--mj-border-default);
|
|
1141
|
-
min-height: 40px;
|
|
1142
|
-
`;
|
|
1143
|
-
// Icon and title
|
|
1144
|
-
const titleSection = document.createElement('div');
|
|
1145
|
-
titleSection.style.cssText = 'display: flex; align-items: center; gap: 8px; flex: 1; min-width: 0;';
|
|
1146
|
-
titleSection.innerHTML = `
|
|
1147
|
-
<i class="${EscapeHTML(panel.icon || 'fa-solid fa-puzzle-piece')}" style="color: var(--mj-brand-primary); font-size: 14px;"></i>
|
|
1148
|
-
<span style="font-weight: 500; font-size: 14px; color: var(--mj-text-primary); overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">${EscapeHTML(panel.title)}</span>
|
|
1149
|
-
`;
|
|
1150
|
-
header.appendChild(titleSection);
|
|
1151
|
-
// Action buttons (only in edit mode)
|
|
1152
|
-
if (this.IsEditing) {
|
|
1153
|
-
const actions = document.createElement('div');
|
|
1154
|
-
actions.style.cssText = 'display: flex; gap: 4px;';
|
|
1155
|
-
// Configure button
|
|
1156
|
-
const configBtn = document.createElement('button');
|
|
1157
|
-
configBtn.className = 'part-action-btn';
|
|
1158
|
-
configBtn.title = 'Configure';
|
|
1159
|
-
configBtn.style.cssText = `
|
|
1160
|
-
width: 28px;
|
|
1161
|
-
height: 28px;
|
|
1162
|
-
display: flex;
|
|
1163
|
-
align-items: center;
|
|
1164
|
-
justify-content: center;
|
|
1165
|
-
border: none;
|
|
1166
|
-
border-radius: 4px;
|
|
1167
|
-
background: transparent;
|
|
1168
|
-
color: var(--mj-text-secondary);
|
|
1169
|
-
cursor: pointer;
|
|
1170
|
-
transition: all 0.15s;
|
|
1171
|
-
`;
|
|
1172
|
-
configBtn.innerHTML = '<i class="fa-solid fa-cog" style="font-size: 12px;"></i>';
|
|
1173
|
-
configBtn.addEventListener('click', () => this.onConfigurePart(panelId));
|
|
1174
|
-
configBtn.addEventListener('mouseenter', () => {
|
|
1175
|
-
configBtn.style.background = 'var(--mj-border-default)';
|
|
1176
|
-
configBtn.style.color = 'var(--mj-text-primary)';
|
|
1177
|
-
});
|
|
1178
|
-
configBtn.addEventListener('mouseleave', () => {
|
|
1179
|
-
configBtn.style.background = 'transparent';
|
|
1180
|
-
configBtn.style.color = 'var(--mj-text-secondary)';
|
|
1181
|
-
});
|
|
1182
|
-
// Remove button
|
|
1183
|
-
const removeBtn = document.createElement('button');
|
|
1184
|
-
removeBtn.className = 'part-action-btn';
|
|
1185
|
-
removeBtn.title = 'Remove';
|
|
1186
|
-
removeBtn.style.cssText = `
|
|
1187
|
-
width: 28px;
|
|
1188
|
-
height: 28px;
|
|
1189
|
-
display: flex;
|
|
1190
|
-
align-items: center;
|
|
1191
|
-
justify-content: center;
|
|
1192
|
-
border: none;
|
|
1193
|
-
border-radius: 4px;
|
|
1194
|
-
background: transparent;
|
|
1195
|
-
color: var(--mj-text-secondary);
|
|
1196
|
-
cursor: pointer;
|
|
1197
|
-
transition: all 0.15s;
|
|
1198
|
-
`;
|
|
1199
|
-
removeBtn.innerHTML = '<i class="fa-solid fa-times" style="font-size: 12px;"></i>';
|
|
1200
|
-
removeBtn.addEventListener('click', () => this.onRemovePart(panelId));
|
|
1201
|
-
removeBtn.addEventListener('mouseenter', () => {
|
|
1202
|
-
removeBtn.style.background = 'color-mix(in srgb, var(--mj-status-error) 10%, transparent)';
|
|
1203
|
-
removeBtn.style.color = 'var(--mj-status-error)';
|
|
1204
|
-
});
|
|
1205
|
-
removeBtn.addEventListener('mouseleave', () => {
|
|
1206
|
-
removeBtn.style.background = 'transparent';
|
|
1207
|
-
removeBtn.style.color = 'var(--mj-text-secondary)';
|
|
1208
|
-
});
|
|
1209
|
-
actions.appendChild(configBtn);
|
|
1210
|
-
actions.appendChild(removeBtn);
|
|
1211
|
-
header.appendChild(actions);
|
|
1212
|
-
}
|
|
1213
|
-
return header;
|
|
1214
|
-
}
|
|
1215
1236
|
renderPartContent(panel, container, partType) {
|
|
1216
1237
|
const config = panel.config;
|
|
1217
1238
|
switch (config?.type) {
|
|
@@ -1238,7 +1259,7 @@ export class DashboardViewerComponent extends BaseAngularComponent {
|
|
|
1238
1259
|
<div style="display: flex; flex-direction: column; align-items: center; justify-content: center; height: 100%; color: var(--mj-text-secondary); text-align: center; padding: 24px;">
|
|
1239
1260
|
<i class="fa-solid fa-globe" style="font-size: 48px; color: var(--mj-text-muted); margin-bottom: 16px;"></i>
|
|
1240
1261
|
<h4 style="margin: 0 0 8px 0; color: var(--mj-text-primary);">No URL Configured</h4>
|
|
1241
|
-
<p style="margin: 0; font-size: 13px;">
|
|
1262
|
+
<p style="margin: 0; font-size: 13px;">Use Edit part to set a URL.</p>
|
|
1242
1263
|
</div>
|
|
1243
1264
|
`;
|
|
1244
1265
|
return;
|
|
@@ -1270,7 +1291,7 @@ export class DashboardViewerComponent extends BaseAngularComponent {
|
|
|
1270
1291
|
<div style="display: flex; flex-direction: column; align-items: center; justify-content: center; height: 100%; color: var(--mj-text-secondary); text-align: center; padding: 24px;">
|
|
1271
1292
|
<i class="fa-solid fa-table" style="font-size: 48px; color: var(--mj-text-muted); margin-bottom: 16px;"></i>
|
|
1272
1293
|
<h4 style="margin: 0 0 8px 0; color: var(--mj-text-primary);">No View Selected</h4>
|
|
1273
|
-
<p style="margin: 0; font-size: 13px;">
|
|
1294
|
+
<p style="margin: 0; font-size: 13px;">Use Edit part to choose a view.</p>
|
|
1274
1295
|
</div>
|
|
1275
1296
|
`;
|
|
1276
1297
|
return;
|
|
@@ -1306,7 +1327,7 @@ export class DashboardViewerComponent extends BaseAngularComponent {
|
|
|
1306
1327
|
<div style="display: flex; flex-direction: column; align-items: center; justify-content: center; height: 100%; color: var(--mj-text-secondary); text-align: center; padding: 24px;">
|
|
1307
1328
|
<i class="fa-solid fa-database" style="font-size: 48px; color: var(--mj-text-muted); margin-bottom: 16px;"></i>
|
|
1308
1329
|
<h4 style="margin: 0 0 8px 0; color: var(--mj-text-primary);">No Query Selected</h4>
|
|
1309
|
-
<p style="margin: 0; font-size: 13px;">
|
|
1330
|
+
<p style="margin: 0; font-size: 13px;">Use Edit part to choose a query.</p>
|
|
1310
1331
|
</div>
|
|
1311
1332
|
`;
|
|
1312
1333
|
return;
|
|
@@ -1340,7 +1361,7 @@ export class DashboardViewerComponent extends BaseAngularComponent {
|
|
|
1340
1361
|
<div style="display: flex; flex-direction: column; align-items: center; justify-content: center; height: 100%; color: var(--mj-text-secondary); text-align: center; padding: 24px;">
|
|
1341
1362
|
<i class="fa-solid fa-cube" style="font-size: 48px; color: var(--mj-text-muted); margin-bottom: 16px;"></i>
|
|
1342
1363
|
<h4 style="margin: 0 0 8px 0; color: var(--mj-text-primary);">No Artifact Selected</h4>
|
|
1343
|
-
<p style="margin: 0; font-size: 13px;">
|
|
1364
|
+
<p style="margin: 0; font-size: 13px;">Use Edit part to choose an artifact.</p>
|
|
1344
1365
|
</div>
|
|
1345
1366
|
`;
|
|
1346
1367
|
return;
|
|
@@ -1387,14 +1408,15 @@ export class DashboardViewerComponent extends BaseAngularComponent {
|
|
|
1387
1408
|
});
|
|
1388
1409
|
}
|
|
1389
1410
|
onRemovePart(panelId) {
|
|
1390
|
-
// Emit event for parent to show confirmation dialog
|
|
1411
|
+
// Emit event for parent to show confirmation dialog, with the part's title and part type name
|
|
1391
1412
|
const panel = FindPanelInLayout(this.config?.layout ?? null, panelId);
|
|
1392
1413
|
this.PanelInteraction.emit({
|
|
1393
1414
|
panelId,
|
|
1394
1415
|
interactionType: 'custom',
|
|
1395
1416
|
payload: {
|
|
1396
1417
|
action: 'remove-part-requested',
|
|
1397
|
-
panelTitle: panel?.title || 'this part'
|
|
1418
|
+
panelTitle: panel?.title || 'this part',
|
|
1419
|
+
partTypeName: this.GetPartTypeForPanel(panelId)?.Name ?? null
|
|
1398
1420
|
}
|
|
1399
1421
|
});
|
|
1400
1422
|
}
|
|
@@ -1413,12 +1435,16 @@ export class DashboardViewerComponent extends BaseAngularComponent {
|
|
|
1413
1435
|
if (entry) {
|
|
1414
1436
|
// Destroy the Angular component if present
|
|
1415
1437
|
if (entry.componentRef) {
|
|
1416
|
-
this.
|
|
1417
|
-
entry.componentRef.destroy();
|
|
1438
|
+
this.releasePartComponent(entry.componentRef);
|
|
1418
1439
|
}
|
|
1419
1440
|
this._panelComponents.delete(panelId);
|
|
1420
1441
|
}
|
|
1421
1442
|
}
|
|
1443
|
+
/** Detaches a part component from change detection and destroys it. */
|
|
1444
|
+
releasePartComponent(componentRef) {
|
|
1445
|
+
this.appRef.detachView(componentRef.hostView);
|
|
1446
|
+
componentRef.destroy();
|
|
1447
|
+
}
|
|
1422
1448
|
updatePanelEditModes() {
|
|
1423
1449
|
// Update IsEditing on all dynamic components
|
|
1424
1450
|
this._panelComponents.forEach((entry) => {
|
|
@@ -1453,7 +1479,7 @@ export class DashboardViewerComponent extends BaseAngularComponent {
|
|
|
1453
1479
|
} if (rf & 2) {
|
|
1454
1480
|
let _t;
|
|
1455
1481
|
i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.LayoutContainer = _t.first);
|
|
1456
|
-
} }, inputs: { Dashboard: "Dashboard", dashboard: "dashboard", DashboardId: "DashboardId", dashboardId: "dashboardId", IsEditing: "IsEditing", isEditing: "isEditing", ShowToolbar: "ShowToolbar", showToolbar: "showToolbar", AutoSave: "AutoSave", autoSave: "autoSave", ShowBreadcrumb: "ShowBreadcrumb", showBreadcrumb: "showBreadcrumb", ShowOpenInTabButton: "ShowOpenInTabButton", showOpenInTabButton: "showOpenInTabButton", ShowEditButton: "ShowEditButton", showEditButton: "showEditButton", Categories: "Categories" }, outputs: { configChanged: "configChanged", NavigationRequested: "NavigationRequested", navigationRequested: "navigationRequested", PanelInteraction: "PanelInteraction", panelInteraction: "panelInteraction", DashboardSaved: "DashboardSaved", dashboardSaved: "dashboardSaved", error: "error", EditModeChanged: "EditModeChanged", editModeChanged: "editModeChanged", BreadcrumbNavigate: "BreadcrumbNavigate", breadcrumbNavigate: "breadcrumbNavigate", OpenInTab: "OpenInTab", openInTab: "openInTab", LayoutLifecycle: "LayoutLifecycle", layoutLifecycle: "layoutLifecycle", LayoutReady: "LayoutReady", layoutReady: "layoutReady", LayoutDeferred: "LayoutDeferred", layoutDeferred: "layoutDeferred" }, standalone: false, features: [i0.ɵɵInheritDefinitionFeature], decls: 7, vars: 8, consts: [["layoutContainer", ""], [1, "dashboard-viewer"], ["Size", "large", "RootIcon", "fa-solid fa-gauge-high", "RootLabel", "Dashboards", 3, "Categories", "CurrentCategoryId", "CurrentDashboard", "ShowDashboardName", "AllowDragDrop"], [1, "dashboard-toolbar"], [1, "loading-overlay"], [1, "layout-container"], ["Icon", "fa-solid fa-
|
|
1482
|
+
} }, inputs: { Dashboard: "Dashboard", dashboard: "dashboard", DashboardId: "DashboardId", dashboardId: "dashboardId", IsEditing: "IsEditing", isEditing: "isEditing", CanEdit: "CanEdit", ShowToolbar: "ShowToolbar", showToolbar: "showToolbar", AutoSave: "AutoSave", autoSave: "autoSave", ShowBreadcrumb: "ShowBreadcrumb", showBreadcrumb: "showBreadcrumb", ShowOpenInTabButton: "ShowOpenInTabButton", showOpenInTabButton: "showOpenInTabButton", ShowEditButton: "ShowEditButton", showEditButton: "showEditButton", Categories: "Categories" }, outputs: { configChanged: "configChanged", NavigationRequested: "NavigationRequested", navigationRequested: "navigationRequested", PanelInteraction: "PanelInteraction", panelInteraction: "panelInteraction", DashboardSaved: "DashboardSaved", dashboardSaved: "dashboardSaved", error: "error", EditModeChanged: "EditModeChanged", editModeChanged: "editModeChanged", BreadcrumbNavigate: "BreadcrumbNavigate", breadcrumbNavigate: "breadcrumbNavigate", OpenInTab: "OpenInTab", openInTab: "openInTab", LayoutLifecycle: "LayoutLifecycle", layoutLifecycle: "layoutLifecycle", LayoutReady: "LayoutReady", layoutReady: "layoutReady", LayoutDeferred: "LayoutDeferred", layoutDeferred: "layoutDeferred" }, standalone: false, features: [i0.ɵɵInheritDefinitionFeature], decls: 7, vars: 8, consts: [["layoutContainer", ""], [1, "dashboard-viewer"], ["Size", "large", "RootIcon", "fa-solid fa-gauge-high", "RootLabel", "Dashboards", 3, "Categories", "CurrentCategoryId", "CurrentDashboard", "ShowDashboardName", "AllowDragDrop"], [1, "dashboard-toolbar"], [1, "loading-overlay"], [1, "layout-container"], ["Icon", "fa-solid fa-table-columns", "Title", "No parts yet", "Message", "Add a view, query, artifact, or web page to get started.", "ActionIcon", "fa-solid fa-plus", 1, "dashboard-empty", 3, "ActionText"], ["Size", "large", "RootIcon", "fa-solid fa-gauge-high", "RootLabel", "Dashboards", 3, "Navigate", "Categories", "CurrentCategoryId", "CurrentDashboard", "ShowDashboardName", "AllowDragDrop"], [1, "toolbar-left"], [1, "dashboard-title"], [1, "toolbar-center"], [1, "unsaved-indicator"], [1, "toolbar-right"], ["title", "Open in a dashboard tab", 1, "toolbar-button"], [1, "toolbar-button"], [1, "toolbar-button", 3, "active"], [1, "toolbar-button", "primary"], [1, "fa-solid", "fa-chart-line"], [1, "fa-solid", "fa-circle"], ["title", "Open in a dashboard tab", 1, "toolbar-button", 3, "click"], [1, "fa-solid", "fa-up-right-from-square"], [1, "toolbar-button", 3, "click"], [1, "fa-solid", "fa-plus"], [1, "fa-solid", "fa-edit"], [1, "toolbar-button", "primary", 3, "click"], [1, "fa-solid", "fa-save"], ["text", "Loading dashboard..."], ["Icon", "fa-solid fa-table-columns", "Title", "No parts yet", "Message", "Add a view, query, artifact, or web page to get started.", "ActionIcon", "fa-solid fa-plus", 1, "dashboard-empty", 3, "Action", "ActionText"]], template: function DashboardViewerComponent_Template(rf, ctx) { if (rf & 1) {
|
|
1457
1483
|
i0.ɵɵelementStart(0, "div", 1);
|
|
1458
1484
|
i0.ɵɵconditionalCreate(1, DashboardViewerComponent_Conditional_1_Template, 1, 5, "mj-dashboard-breadcrumb", 2);
|
|
1459
1485
|
i0.ɵɵconditionalCreate(2, DashboardViewerComponent_Conditional_2_Template, 10, 6, "div", 3);
|
|
@@ -1470,12 +1496,12 @@ export class DashboardViewerComponent extends BaseAngularComponent {
|
|
|
1470
1496
|
i0.ɵɵadvance();
|
|
1471
1497
|
i0.ɵɵconditional(ctx.isLoading ? 3 : -1);
|
|
1472
1498
|
i0.ɵɵadvance(3);
|
|
1473
|
-
i0.ɵɵconditional(!ctx.
|
|
1474
|
-
} }, dependencies: [i1.LoadingComponent, i2.MJEmptyStateComponent, i3.DashboardBreadcrumbComponent], styles: ["/**\n * Dashboard Viewer Component Styles\n *\n * IMPORTANT: For Golden Layout tabs to display correctly, you must import\n * Golden Layout's CSS in your application's global styles:\n *\n * @import 'golden-layout/dist/css/goldenlayout-base.css';\n *\n * Or add to your angular.json styles array:\n * \"node_modules/golden-layout/dist/css/goldenlayout-base.css\"\n */\n\n:host {\n display: block;\n width: 100%;\n height: 100%;\n position: relative;\n}\n\n.dashboard-viewer {\n display: flex;\n flex-direction: column;\n width: 100%;\n height: 100%;\n background: var(--dashboard-bg, var(--mj-bg-surface-card));\n}\n\n.dashboard-viewer.editing .layout-container {\n outline: 2px dashed var(--dashboard-edit-outline, var(--mj-brand-primary));\n outline-offset: -2px;\n}\n\n/* ========================================\n Toolbar\n ======================================== */\n\n.dashboard-toolbar {\n flex: 0 0 48px;\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 16px;\n background: var(--dashboard-toolbar-bg, var(--mj-bg-surface));\n border-bottom: 1px solid var(--dashboard-border, var(--mj-border-default));\n box-sizing: border-box;\n z-index: 10;\n}\n\n.toolbar-left,\n.toolbar-center,\n.toolbar-right {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.toolbar-left {\n flex: 1;\n}\n\n.toolbar-center {\n flex: 0 0 auto;\n}\n\n.toolbar-right {\n flex: 1;\n justify-content: flex-end;\n}\n\n.dashboard-title {\n font-size: 16px;\n font-weight: 500;\n color: var(--dashboard-title-color, var(--mj-text-primary));\n}\n\n.dashboard-title i {\n margin-right: 8px;\n color: var(--dashboard-title-icon, var(--mj-brand-primary));\n}\n\n.unsaved-indicator {\n font-size: 12px;\n color: var(--dashboard-unsaved-color, var(--mj-status-warning));\n display: flex;\n align-items: center;\n gap: 4px;\n}\n\n.unsaved-indicator i {\n font-size: 8px;\n}\n\n.toolbar-button {\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 6px 12px;\n border: 1px solid var(--dashboard-button-border, var(--mj-border-strong));\n border-radius: 4px;\n background: var(--dashboard-button-bg, var(--mj-bg-surface));\n color: var(--dashboard-button-color, var(--mj-text-primary));\n font-size: 13px;\n cursor: pointer;\n transition: all 0.2s ease;\n}\n\n.toolbar-button:hover {\n background: var(--dashboard-button-hover-bg, var(--mj-bg-surface-card));\n border-color: var(--dashboard-button-hover-border, var(--mj-border-strong));\n}\n\n.toolbar-button.active {\n background: var(--dashboard-button-active-bg, color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface)));\n border-color: var(--dashboard-button-active-border, var(--mj-brand-primary));\n color: var(--dashboard-button-active-color, var(--mj-brand-primary));\n}\n\n.toolbar-button.primary {\n background: var(--dashboard-button-primary-bg, var(--mj-brand-primary));\n border-color: var(--dashboard-button-primary-border, var(--mj-brand-primary-hover));\n color: var(--dashboard-button-primary-color, var(--mj-text-inverse));\n}\n\n.toolbar-button.primary:hover {\n background: var(--dashboard-button-primary-hover-bg, var(--mj-brand-primary-hover));\n}\n\n.toolbar-button i {\n font-size: 14px;\n}\n\n/* ========================================\n Layout Container\n ======================================== */\n\n/* Layout container fills remaining space in the flex column */\n.layout-container {\n flex: 1;\n position: relative;\n overflow: hidden;\n min-height: 0;\n}\n\n/* ========================================\n Dashboard Panel Content (our wrapper)\n ======================================== */\n\n/* Dashboard panel wrapper (header + content) */\n:host ::ng-deep .dashboard-part-wrapper {\n display: flex;\n flex-direction: column;\n height: 100%;\n background: var(--mj-bg-surface);\n}\n\n/* Dashboard part header */\n:host ::ng-deep .dashboard-part-header {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 10px 12px;\n background: var(--mj-bg-surface-card);\n border-bottom: 1px solid var(--mj-border-default);\n min-height: 40px;\n flex-shrink: 0;\n}\n\n/* Dashboard part content */\n:host ::ng-deep .dashboard-part-content {\n flex: 1;\n overflow: auto;\n min-height: 0;\n}\n\n/* ========================================\n Loading Overlay\n ======================================== */\n\n.loading-overlay {\n position: absolute;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n background: color-mix(in srgb, var(--mj-bg-surface) 90%, transparent);\n z-index: 100;\n}\n\n/* ========================================\n Empty State\n ======================================== */\n\n/* Empty state centered in the dashboard pane (mj-empty-state centers its own\n content; absolute positioning keeps it overlaid on the layout container). */\n.dashboard-empty {\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n max-width: 400px;\n}\n\n/* ========================================\n Panel Error State\n ======================================== */\n\n:host ::ng-deep .panel-error {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n height: 100%;\n padding: 24px;\n text-align: center;\n color: var(--dashboard-error-color, var(--mj-status-error));\n background: var(--dashboard-error-bg, color-mix(in srgb, var(--mj-status-error) 10%, var(--mj-bg-surface)));\n}\n\n:host ::ng-deep .panel-error i {\n font-size: 32px;\n margin-bottom: 12px;\n}\n\n:host ::ng-deep .panel-error span {\n font-size: 14px;\n}\n\n/* ========================================\n Golden Layout Overrides\n These require ViewEncapsulation.None to work on dynamic GL elements\n ======================================== */\n\n/* Override shell's hide-tab-bar rule that hides GL headers when dashboard is inside shell */\n/* The shell hides .lm_header when mj-tab-container has hide-tab-bar class */\n/* We need to ensure OUR Golden Layout headers are always visible */\nmj-dashboard-viewer .lm_header {\n display: block !important;\n}\n\n/* Ensure GL root container fills available space */\nmj-dashboard-viewer .lm_goldenlayout {\n width: 100% !important;\n height: 100% !important;\n}\n\n/* Ensure the root layout item fills space */\nmj-dashboard-viewer .lm_root {\n width: 100% !important;\n height: 100% !important;\n}\n\n/* Ensure proper box-sizing for all GL layout elements */\nmj-dashboard-viewer .lm_item,\nmj-dashboard-viewer .lm_content,\nmj-dashboard-viewer .lm_stack,\nmj-dashboard-viewer .lm_row,\nmj-dashboard-viewer .lm_column {\n box-sizing: border-box !important;\n}\n\n/* Ensure layout items don't overflow */\nmj-dashboard-viewer .lm_item {\n overflow: hidden !important;\n}\n\n/* Fix for .lm_items - the content container inside stacks */\nmj-dashboard-viewer .lm_items {\n width: 100% !important;\n height: calc(100% - 38px) !important; /* Account for header height (38px) */\n box-sizing: border-box !important;\n position: relative !important;\n}\n\n/* When tabs are maximized, no header visible */\nmj-dashboard-viewer .lm_stack.lm_maximised > .lm_items {\n height: 100% !important;\n}\n\n/* Target the ComponentItem div inside lm_items */\nmj-dashboard-viewer .lm_items > div {\n width: 100% !important;\n height: 100% !important;\n box-sizing: border-box !important;\n flex-direction: column !important;\n}\n\n/* Only apply flex display to the active/visible tab content div */\nmj-dashboard-viewer .lm_items > div:not([style*=\"display: none\"]) {\n display: flex !important;\n}\n\n/* Clearfix for float-based row layout */\nmj-dashboard-viewer .lm_row::after {\n content: \"\" !important;\n display: table !important;\n clear: both !important;\n}\n\n/* Force content children to respect parent bounds */\nmj-dashboard-viewer .lm_content > * {\n max-width: 100% !important;\n width: 100% !important;\n}\n\nmj-dashboard-viewer .lm_content {\n background: var(--mj-bg-surface) !important;\n display: flex !important;\n flex-direction: column !important;\n height: 100% !important;\n width: 100% !important;\n}\n\nmj-dashboard-viewer .lm_item_container {\n background: var(--mj-bg-surface) !important;\n}\n\n/* Tab header styling */\nmj-dashboard-viewer .lm_header {\n height: 38px !important;\n padding-top: 2px !important;\n padding-left: 4px !important;\n background: var(--mj-bg-surface-card) !important;\n border-bottom: 1px solid var(--mj-border-default) !important;\n overflow: visible !important;\n box-sizing: border-box !important;\n}\n\nmj-dashboard-viewer .lm_tabs {\n height: 36px !important;\n}\n\n/* Hide Golden Layout window controls (popout, maximize) */\nmj-dashboard-viewer .lm_controls {\n display: none !important;\n}\n\n/* Tab styling */\nmj-dashboard-viewer .lm_header .lm_tab {\n padding: 0 16px 0 28px !important; /* Extra left padding for icon */\n font-size: 13px !important;\n height: 35px !important;\n line-height: 35px !important;\n box-sizing: border-box !important;\n cursor: pointer !important;\n user-select: none !important;\n background: transparent !important;\n border: none !important;\n border-bottom: 1px solid var(--mj-border-default) !important;\n transition: all 0.15s ease !important;\n position: relative !important;\n z-index: 1 !important;\n margin-right: 1px !important;\n}\n\nmj-dashboard-viewer .lm_header .lm_tab:hover {\n background: var(--mj-bg-surface-sunken) !important;\n}\n\nmj-dashboard-viewer .lm_header .lm_tab.lm_active {\n background: var(--mj-bg-surface) !important;\n height: 36px !important;\n margin-bottom: -1px !important;\n margin-right: 0 !important;\n border: 1px solid var(--mj-border-default) !important;\n border-bottom-color: var(--mj-bg-surface) !important;\n border-radius: 4px 4px 0 0 !important;\n z-index: 2 !important;\n}\n\nmj-dashboard-viewer .lm_title {\n cursor: pointer !important;\n user-select: none !important;\n}\n\n/* Panel icon styling - icon is direct child of .lm_tab to preserve GL's drag/drop */\n/* Positioned absolutely in the left padding area so it doesn't affect GL's title structure */\nmj-dashboard-viewer .lm_header .lm_tab .panel-icon {\n position: absolute !important;\n left: 10px !important;\n top: 50% !important;\n transform: translateY(-30%) !important;\n font-family: \"Font Awesome 6 Free\", \"Font Awesome 6 Pro\", \"Font Awesome 5 Free\", \"Font Awesome 5 Pro\", FontAwesome !important;\n font-weight: 900 !important; /* Required for solid icons */\n font-size: 12px !important;\n color: var(--mj-brand-primary) !important;\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n}\n\nmj-dashboard-viewer .lm_close_tab {\n position: absolute !important;\n right: 4px !important;\n top: 50% !important;\n transform: translateY(-50%) !important;\n width: 16px !important;\n height: 16px !important;\n cursor: pointer !important;\n opacity: 0 !important;\n transition: all 0.15s ease !important;\n}\n\nmj-dashboard-viewer .lm_header .lm_tab:hover .lm_close_tab {\n opacity: 0.7 !important;\n}\n\nmj-dashboard-viewer .lm_close_tab:hover {\n opacity: 1 !important;\n color: var(--mj-status-error) !important;\n}\n\n/* Add padding for close button when editing */\nmj-dashboard-viewer .dashboard-viewer.editing .lm_header .lm_tab {\n padding-right: 24px !important;\n}\n\n/* ========================================\n View Mode (Not Editing) - Lock Layout\n ======================================== */\n\n/* Hide close buttons on tabs when not editing */\nmj-dashboard-viewer .dashboard-viewer:not(.editing) .lm_close_tab {\n display: none !important;\n}\n\n/* Remove extra padding for close button when not editing */\nmj-dashboard-viewer .dashboard-viewer:not(.editing) .lm_header .lm_tab {\n padding-right: 16px !important;\n}\n\n/* Disable splitter dragging when not editing */\nmj-dashboard-viewer .dashboard-viewer:not(.editing) .lm_splitter {\n pointer-events: none !important;\n cursor: default !important;\n}\n\n/* Hide splitter drag handle visual when not editing */\nmj-dashboard-viewer .dashboard-viewer:not(.editing) .lm_splitter .lm_drag_handle {\n display: none !important;\n}\n\n/* Disable tab dragging cursor when not editing */\nmj-dashboard-viewer .dashboard-viewer:not(.editing) .lm_header .lm_tab {\n cursor: default !important;\n}\n\nmj-dashboard-viewer .dashboard-viewer:not(.editing) .lm_title {\n cursor: default !important;\n}\n"], encapsulation: 2 });
|
|
1499
|
+
i0.ɵɵconditional(!ctx.HasPanels && !ctx.isLoading ? 6 : -1);
|
|
1500
|
+
} }, dependencies: [i1.LoadingComponent, i2.MJEmptyStateComponent, i3.DashboardBreadcrumbComponent], styles: ["/**\n * Dashboard Viewer Component Styles\n *\n * IMPORTANT: For Golden Layout tabs to display correctly, you must import\n * Golden Layout's CSS in your application's global styles:\n *\n * @import 'golden-layout/dist/css/goldenlayout-base.css';\n *\n * Or add to your angular.json styles array:\n * \"node_modules/golden-layout/dist/css/goldenlayout-base.css\"\n */\n\n:host {\n display: block;\n width: 100%;\n height: 100%;\n position: relative;\n}\n\n.dashboard-viewer {\n display: flex;\n flex-direction: column;\n width: 100%;\n height: 100%;\n background: var(--dashboard-bg, var(--mj-bg-surface-card));\n}\n\n/* ========================================\n Toolbar\n ======================================== */\n\n.dashboard-toolbar {\n flex: 0 0 48px;\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 16px;\n background: var(--dashboard-toolbar-bg, var(--mj-bg-surface));\n border-bottom: 1px solid var(--dashboard-border, var(--mj-border-default));\n box-sizing: border-box;\n z-index: 10;\n}\n\n.toolbar-left,\n.toolbar-center,\n.toolbar-right {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.toolbar-left {\n flex: 1;\n}\n\n.toolbar-center {\n flex: 0 0 auto;\n}\n\n.toolbar-right {\n flex: 1;\n justify-content: flex-end;\n}\n\n.dashboard-title {\n font-size: 16px;\n font-weight: 500;\n color: var(--dashboard-title-color, var(--mj-text-primary));\n}\n\n.dashboard-title i {\n margin-right: 8px;\n color: var(--dashboard-title-icon, var(--mj-brand-primary));\n}\n\n.unsaved-indicator {\n font-size: 12px;\n color: var(--dashboard-unsaved-color, var(--mj-status-warning));\n display: flex;\n align-items: center;\n gap: 4px;\n}\n\n.unsaved-indicator i {\n font-size: 8px;\n}\n\n.toolbar-button {\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 6px 12px;\n border: 1px solid var(--dashboard-button-border, var(--mj-border-strong));\n border-radius: 4px;\n background: var(--dashboard-button-bg, var(--mj-bg-surface));\n color: var(--dashboard-button-color, var(--mj-text-primary));\n font-size: 13px;\n cursor: pointer;\n transition: all 0.2s ease;\n}\n\n.toolbar-button:hover {\n background: var(--dashboard-button-hover-bg, var(--mj-bg-surface-card));\n border-color: var(--dashboard-button-hover-border, var(--mj-border-strong));\n}\n\n.toolbar-button.active {\n background: var(--dashboard-button-active-bg, color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface)));\n border-color: var(--dashboard-button-active-border, var(--mj-brand-primary));\n color: var(--dashboard-button-active-color, var(--mj-brand-primary));\n}\n\n.toolbar-button.primary {\n background: var(--dashboard-button-primary-bg, var(--mj-brand-primary));\n border-color: var(--dashboard-button-primary-border, var(--mj-brand-primary-hover));\n color: var(--dashboard-button-primary-color, var(--mj-text-inverse));\n}\n\n.toolbar-button.primary:hover {\n background: var(--dashboard-button-primary-hover-bg, var(--mj-brand-primary-hover));\n}\n\n.toolbar-button i {\n font-size: 14px;\n}\n\n/* ========================================\n Layout Container\n ======================================== */\n\n/* Layout container fills remaining space in the flex column */\n.layout-container {\n flex: 1;\n position: relative;\n overflow: hidden;\n min-height: 0;\n}\n\n/* ========================================\n Dashboard Panel Content (our wrapper)\n ======================================== */\n\n/* Dashboard panel wrapper (holds the part content) */\n:host ::ng-deep .dashboard-part-wrapper {\n display: flex;\n flex-direction: column;\n height: 100%;\n background: var(--mj-bg-surface);\n}\n\n/* Dashboard part content */\n:host ::ng-deep .dashboard-part-content {\n flex: 1;\n overflow: auto;\n min-height: 0;\n}\n\n/* ========================================\n Loading Overlay\n ======================================== */\n\n.loading-overlay {\n position: absolute;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n background: color-mix(in srgb, var(--mj-bg-surface) 90%, transparent);\n z-index: 100;\n}\n\n/* ========================================\n Empty State\n ======================================== */\n\n/* Empty state centered in the dashboard pane (mj-empty-state centers its own\n content; absolute positioning keeps it overlaid on the layout container). */\n.dashboard-empty {\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n max-width: 400px;\n}\n\n/* ========================================\n Panel Error State\n ======================================== */\n\n:host ::ng-deep .panel-error {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n height: 100%;\n padding: 24px;\n text-align: center;\n color: var(--dashboard-error-color, var(--mj-status-error));\n background: var(--dashboard-error-bg, color-mix(in srgb, var(--mj-status-error) 10%, var(--mj-bg-surface)));\n}\n\n:host ::ng-deep .panel-error i {\n font-size: 32px;\n margin-bottom: 12px;\n}\n\n:host ::ng-deep .panel-error span {\n font-size: 14px;\n}\n\n/* ========================================\n Golden Layout Overrides\n These require ViewEncapsulation.None to work on dynamic GL elements\n ======================================== */\n\n/* Override shell's hide-tab-bar rule that hides GL headers when dashboard is inside shell */\n/* The shell hides .lm_header when mj-tab-container has hide-tab-bar class */\n/* We need to ensure OUR Golden Layout headers are always visible */\nmj-dashboard-viewer .lm_header {\n display: block !important;\n}\n\n/* Ensure GL root container fills available space */\nmj-dashboard-viewer .lm_goldenlayout {\n width: 100% !important;\n height: 100% !important;\n}\n\n/* Ensure the root layout item fills space */\nmj-dashboard-viewer .lm_root {\n width: 100% !important;\n height: 100% !important;\n}\n\n/* Ensure proper box-sizing for all GL layout elements */\nmj-dashboard-viewer .lm_item,\nmj-dashboard-viewer .lm_content,\nmj-dashboard-viewer .lm_stack,\nmj-dashboard-viewer .lm_row,\nmj-dashboard-viewer .lm_column {\n box-sizing: border-box !important;\n}\n\n/* Ensure layout items don't overflow */\nmj-dashboard-viewer .lm_item {\n overflow: hidden !important;\n}\n\n/* Fix for .lm_items - the content container inside stacks (its height is set with the stack header below) */\nmj-dashboard-viewer .lm_items {\n width: 100% !important;\n box-sizing: border-box !important;\n position: relative !important;\n}\n\n/* When tabs are maximized, no header visible */\nmj-dashboard-viewer .lm_stack.lm_maximised > .lm_items {\n height: 100% !important;\n}\n\n/* Target the ComponentItem div inside lm_items */\nmj-dashboard-viewer .lm_items > div {\n width: 100% !important;\n height: 100% !important;\n box-sizing: border-box !important;\n flex-direction: column !important;\n}\n\n/* Only apply flex display to the active/visible tab content div */\nmj-dashboard-viewer .lm_items > div:not([style*=\"display: none\"]) {\n display: flex !important;\n}\n\n/* Clearfix for float-based row layout */\nmj-dashboard-viewer .lm_row::after {\n content: \"\" !important;\n display: table !important;\n clear: both !important;\n}\n\n/* Force content children to respect parent bounds */\nmj-dashboard-viewer .lm_content > * {\n max-width: 100% !important;\n width: 100% !important;\n}\n\nmj-dashboard-viewer .lm_content {\n background: var(--mj-bg-surface) !important;\n display: flex !important;\n flex-direction: column !important;\n height: 100% !important;\n width: 100% !important;\n}\n\nmj-dashboard-viewer .lm_item_container {\n background: var(--mj-bg-surface) !important;\n}\n\n/* ===== Golden Layout: part tabs and part buttons =====\n The shell's tab-container and theme rules reach this nested layout. These selectors add\n .dashboard-viewer to outrank them, with !important where those rules use it. */\n\n/* Stack header: tab track on the left, part buttons on the right (DASHBOARD_STACK_HEADER_HEIGHT) */\nmj-dashboard-viewer .dashboard-viewer .lm_header {\n display: block !important;\n height: 44px !important;\n max-height: none !important; /* shell.component.css caps nested headers at 40px */\n padding: 0 !important;\n background: var(--mj-bg-surface) !important;\n border-bottom: 1px solid var(--mj-border-default) !important;\n overflow: visible !important;\n box-sizing: border-box !important;\n z-index: auto; /* no stacking context, so the tab dropdown list can rise above the part */\n}\nmj-dashboard-viewer .dashboard-viewer .lm_items {\n height: calc(100% - 44px) !important;\n}\n\n/* The tab track, styled like mj-tab-nav. Golden Layout counts each tab's width and right margin; the rest of the\n spacing beside the tabs is DASHBOARD_TAB_CONTROL_OFFSET (78px): the track's 12px inset, 1px borders and 4px left\n padding, an 8px gap, the 34px tab dropdown button and its 6px gap, and the controls' 12px inset. */\nmj-dashboard-viewer .dashboard-viewer .lm_header .lm_tabs {\n top: 5px !important;\n left: var(--mj-space-3) !important;\n right: auto !important; /* shell sets left:0; right:0; overflow-x:auto */\n height: auto !important;\n overflow: visible !important;\n padding: var(--mj-space-1) 0 var(--mj-space-1) var(--mj-space-1); /* the last tab's margin closes the right side */\n background: var(--mj-bg-surface-card);\n border: 1px solid var(--mj-border-default);\n border-radius: var(--mj-radius-lg);\n}\nmj-dashboard-viewer .dashboard-viewer .lm_header .lm_tab {\n display: inline-flex !important;\n align-items: center !important;\n gap: var(--mj-space-2) !important;\n flex: 0 0 auto !important;\n height: auto !important;\n margin: 0 var(--mj-space-1) 0 0 !important;\n padding: 4px var(--mj-space-3) !important;\n font-family: inherit !important;\n font-size: 13px !important;\n font-weight: 500 !important;\n line-height: 1.2 !important;\n color: var(--mj-text-secondary) !important;\n background: transparent !important;\n border: none !important;\n border-radius: var(--mj-radius-md) !important;\n box-shadow: none !important;\n cursor: pointer !important;\n transition: background-color var(--mj-transition-fast), color var(--mj-transition-fast) !important;\n}\nmj-dashboard-viewer .dashboard-viewer .lm_header .lm_tab::after { content: none !important; } /* shell pipe separators */\nmj-dashboard-viewer .dashboard-viewer .lm_header .lm_tab:hover:not(.lm_active) {\n background: color-mix(in srgb, var(--mj-bg-surface) 70%, transparent) !important;\n color: var(--mj-text-primary) !important;\n}\n/* The shell's active tab rule is as specific as the tab rule above, so the tab's box is repeated here */\nmj-dashboard-viewer .dashboard-viewer .lm_header .lm_tab.lm_active {\n height: auto !important;\n margin: 0 var(--mj-space-1) 0 0 !important;\n border: none !important;\n border-radius: var(--mj-radius-md) !important;\n background: var(--mj-bg-surface) !important;\n color: var(--mj-brand-primary) !important;\n box-shadow: var(--mj-shadow-sm) !important;\n}\nmj-dashboard-viewer .dashboard-viewer .lm_header .lm_tab:focus-visible {\n outline: 2px solid var(--mj-border-focus) !important;\n outline-offset: -2px !important;\n}\nmj-dashboard-viewer .dashboard-viewer.editing .lm_header .lm_tab { cursor: grab !important; }\nmj-dashboard-viewer .dashboard-viewer .lm_header .lm_title {\n cursor: inherit !important;\n line-height: normal !important;\n max-width: 220px;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\nmj-dashboard-viewer .dashboard-viewer .lm_header .lm_tab .panel-icon { /* the part's icon */\n position: static !important;\n width: auto !important;\n height: auto !important;\n transform: none !important;\n font-size: 13px !important;\n color: inherit !important;\n}\n\n/* Parts are removed only through Remove (with a confirmation): hide GL's close buttons */\nmj-dashboard-viewer .dashboard-viewer .lm_close_tab,\nmj-dashboard-viewer .dashboard-viewer .lm_controls .lm_close,\nmj-dashboard-viewer .dashboard-viewer .lm_controls .lm_popout,\nmj-dashboard-viewer .dashboard-viewer .lm_controls .lm_maximise { display: none !important; }\n\n/* Part buttons (Edit part, Remove) in the header's controls area */\nmj-dashboard-viewer .dashboard-viewer .lm_header .lm_controls {\n top: 0 !important;\n bottom: 0 !important;\n right: var(--mj-space-3) !important;\n z-index: 3; /* above tabs that run into it (the shell gives tabs 1 and 2), under the tab dropdown list */\n align-items: center;\n gap: 6px;\n}\nmj-dashboard-viewer .dashboard-viewer .lm_header .lm_controls > .dashboard-stack-actions {\n display: flex !important;\n align-items: center;\n gap: 6px;\n width: auto !important; /* GL base: .lm_controls > * is 18x18 and floats */\n height: auto !important;\n float: none !important;\n opacity: 1 !important; /* MJ theme: .lm_controls > * is at 0.6 */\n cursor: default !important;\n}\n\n/* Golden Layout's tab dropdown button, sized like the part buttons and placed left of them. It sits outside the\n controls' measured width, because Golden Layout shows it only after it measures the tabs. Golden Layout shows\n and hides it with an inline display, so display here carries no !important. */\nmj-dashboard-viewer .dashboard-viewer .lm_header .lm_controls > .lm_tabdropdown {\n display: inline-flex;\n position: absolute;\n top: 0;\n bottom: 0;\n right: 100%;\n margin: auto 6px auto 0;\n width: 32px !important; /* GL base: .lm_controls > * is 18x18 and floats */\n height: 32px !important;\n float: none !important;\n align-items: center;\n justify-content: center;\n border: 1px solid transparent;\n border-radius: var(--mj-radius-md);\n color: var(--mj-text-secondary);\n opacity: 1 !important; /* MJ theme: .lm_controls > * is at 0.6 */\n cursor: pointer;\n}\nmj-dashboard-viewer .dashboard-viewer .lm_header .lm_controls > .lm_tabdropdown:hover {\n background-color: var(--mj-bg-surface-hover);\n color: var(--mj-text-primary);\n}\nmj-dashboard-viewer .dashboard-viewer .lm_header .lm_controls > .lm_tabdropdown::before {\n content: '\\f078'; /* chevron-down, in place of GL's border triangle */\n width: auto;\n height: auto;\n border: none;\n font-family: 'Font Awesome 6 Free';\n font-weight: 900;\n font-size: 12px;\n line-height: 1;\n color: inherit;\n}\n\n/* Tabs that do not fit: Golden Layout's tab dropdown list, as a menu below the header. Golden Layout shows and\n hides it with an inline display, so display here carries no !important. */\nmj-dashboard-viewer .dashboard-viewer .lm_header .lm_tabdropdown_list {\n display: flex;\n flex-direction: column;\n top: calc(100% + var(--mj-space-1));\n right: var(--mj-space-3);\n z-index: var(--mj-z-dropdown);\n min-width: 200px;\n max-height: 290px;\n overflow-y: auto;\n padding: var(--mj-space-1);\n background: var(--mj-bg-surface-elevated);\n border: 1px solid var(--mj-border-default);\n border-radius: var(--mj-radius-md);\n box-shadow: var(--mj-shadow-md);\n}\nmj-dashboard-viewer .dashboard-viewer .lm_header .lm_tabdropdown_list .lm_tab {\n display: flex !important;\n margin: 0 !important;\n padding: 6px var(--mj-space-2) !important;\n border-radius: var(--mj-radius-sm) !important;\n background: transparent !important;\n box-shadow: none !important;\n}\nmj-dashboard-viewer .dashboard-viewer .lm_header .lm_tabdropdown_list .lm_tab:hover:not(.lm_active) {\n background: var(--mj-bg-surface-hover) !important;\n}\nmj-dashboard-viewer .dashboard-viewer .lm_header .lm_tabdropdown_list .lm_title {\n width: auto; /* GL base: 100px */\n}\n\n/* Each stack (a panel of part tabs) is a card */\nmj-dashboard-viewer .dashboard-viewer .lm_stack {\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default) !important;\n border-radius: var(--mj-radius-lg);\n}\n\n/* ========================================\n View Mode (Not Editing) - Lock Layout\n ======================================== */\n\n/* Disable splitter dragging when not editing */\nmj-dashboard-viewer .dashboard-viewer:not(.editing) .lm_splitter {\n pointer-events: none !important;\n cursor: default !important;\n}\n\n/* Hide splitter drag handle visual when not editing */\nmj-dashboard-viewer .dashboard-viewer:not(.editing) .lm_splitter .lm_drag_handle {\n display: none !important;\n}\n\n/* Disable tab dragging cursor when not editing */\nmj-dashboard-viewer .dashboard-viewer:not(.editing) .lm_header .lm_tab {\n cursor: default !important;\n}\n\nmj-dashboard-viewer .dashboard-viewer:not(.editing) .lm_title {\n cursor: default !important;\n}\n"], encapsulation: 2 });
|
|
1475
1501
|
}
|
|
1476
1502
|
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(DashboardViewerComponent, [{
|
|
1477
1503
|
type: Component,
|
|
1478
|
-
args: [{ standalone: false, selector: 'mj-dashboard-viewer', encapsulation: ViewEncapsulation.None, template: "<!-- Dashboard Viewer Component -->\n<div class=\"dashboard-viewer\" [class.editing]=\"isEditing\" [class.has-toolbar]=\"shouldShowToolbar\">\n <!-- Breadcrumb Navigation (hidden in edit mode) -->\n @if (showBreadcrumb && !isEditing && dashboard) {\n <mj-dashboard-breadcrumb\n [Categories]=\"Categories\"\n [CurrentCategoryId]=\"dashboard.CategoryID\"\n [CurrentDashboard]=\"dashboard\"\n [ShowDashboardName]=\"true\"\n [AllowDragDrop]=\"false\"\n Size=\"large\"\n RootIcon=\"fa-solid fa-gauge-high\"\n RootLabel=\"Dashboards\"\n (Navigate)=\"onBreadcrumbNavigate($event)\">\n </mj-dashboard-breadcrumb>\n }\n\n <!-- Toolbar (auto-hides when all elements are disabled) -->\n @if (shouldShowToolbar) {\n <div class=\"dashboard-toolbar\">\n <div class=\"toolbar-left\">\n @if (dashboard && (isEditing || !showBreadcrumb)) {\n <span class=\"dashboard-title\">\n <i class=\"fa-solid fa-chart-line\"></i>\n {{ dashboard.Name }}\n </span>\n }\n </div>\n <div class=\"toolbar-center\">\n @if (hasUnsavedChanges && isEditing) {\n <span class=\"unsaved-indicator\">\n <i class=\"fa-solid fa-circle\"></i>\n Unsaved changes\n </span>\n }\n </div>\n <div class=\"toolbar-right\">\n <!-- Open in New Tab button (when embedded) -->\n @if (showOpenInTabButton && !isEditing) {\n <button\n class=\"toolbar-button\"\n (click)=\"onOpenInTabClick()\"\n title=\"Open in its own tab\">\n <i class=\"fa-solid fa-up-right-from-square\"></i>\n Open in Tab\n </button>\n }\n @if (isEditing) {\n <button\n class=\"toolbar-button\"\n (click)=\"onAddPanelClick()\">\n <i class=\"fa-solid fa-plus\"></i>\n Add Part\n </button>\n }\n @if (showEditButton) {\n <button\n class=\"toolbar-button\"\n [class.active]=\"isEditing\"\n (click)=\"toggleEditMode()\">\n <i class=\"fa-solid fa-edit\"></i>\n {{ isEditing ? 'Editing' : 'Edit' }}\n </button>\n }\n @if (isEditing && hasUnsavedChanges) {\n <button\n class=\"toolbar-button primary\"\n (click)=\"save()\">\n <i class=\"fa-solid fa-save\"></i>\n Save\n </button>\n }\n </div>\n </div>\n }\n\n <!-- Loading Overlay -->\n @if (isLoading) {\n <div class=\"loading-overlay\">\n <mj-loading text=\"Loading dashboard...\"></mj-loading>\n </div>\n }\n\n <!-- Layout Container -->\n <div class=\"layout-container\" #layoutContainer>\n <!-- Golden Layout will render panels here -->\n </div>\n\n <!-- Empty State -->\n @if (!hasPanels && !isLoading) {\n <mj-empty-state\n class=\"dashboard-empty\"\n Icon=\"fa-solid fa-layer-group\"\n Title=\"No parts configured\"\n Message=\"This dashboard has no parts yet. Add your first part to start visualizing your data.\"\n [ActionText]=\"isEditing ? 'Add Your First Part' : ''\"\n ActionIcon=\"fa-solid fa-plus\"\n (Action)=\"onAddPanelClick()\" />\n }\n</div>\n", styles: ["/**\n * Dashboard Viewer Component Styles\n *\n * IMPORTANT: For Golden Layout tabs to display correctly, you must import\n * Golden Layout's CSS in your application's global styles:\n *\n * @import 'golden-layout/dist/css/goldenlayout-base.css';\n *\n * Or add to your angular.json styles array:\n * \"node_modules/golden-layout/dist/css/goldenlayout-base.css\"\n */\n\n:host {\n display: block;\n width: 100%;\n height: 100%;\n position: relative;\n}\n\n.dashboard-viewer {\n display: flex;\n flex-direction: column;\n width: 100%;\n height: 100%;\n background: var(--dashboard-bg, var(--mj-bg-surface-card));\n}\n\n.dashboard-viewer.editing .layout-container {\n outline: 2px dashed var(--dashboard-edit-outline, var(--mj-brand-primary));\n outline-offset: -2px;\n}\n\n/* ========================================\n Toolbar\n ======================================== */\n\n.dashboard-toolbar {\n flex: 0 0 48px;\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 16px;\n background: var(--dashboard-toolbar-bg, var(--mj-bg-surface));\n border-bottom: 1px solid var(--dashboard-border, var(--mj-border-default));\n box-sizing: border-box;\n z-index: 10;\n}\n\n.toolbar-left,\n.toolbar-center,\n.toolbar-right {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.toolbar-left {\n flex: 1;\n}\n\n.toolbar-center {\n flex: 0 0 auto;\n}\n\n.toolbar-right {\n flex: 1;\n justify-content: flex-end;\n}\n\n.dashboard-title {\n font-size: 16px;\n font-weight: 500;\n color: var(--dashboard-title-color, var(--mj-text-primary));\n}\n\n.dashboard-title i {\n margin-right: 8px;\n color: var(--dashboard-title-icon, var(--mj-brand-primary));\n}\n\n.unsaved-indicator {\n font-size: 12px;\n color: var(--dashboard-unsaved-color, var(--mj-status-warning));\n display: flex;\n align-items: center;\n gap: 4px;\n}\n\n.unsaved-indicator i {\n font-size: 8px;\n}\n\n.toolbar-button {\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 6px 12px;\n border: 1px solid var(--dashboard-button-border, var(--mj-border-strong));\n border-radius: 4px;\n background: var(--dashboard-button-bg, var(--mj-bg-surface));\n color: var(--dashboard-button-color, var(--mj-text-primary));\n font-size: 13px;\n cursor: pointer;\n transition: all 0.2s ease;\n}\n\n.toolbar-button:hover {\n background: var(--dashboard-button-hover-bg, var(--mj-bg-surface-card));\n border-color: var(--dashboard-button-hover-border, var(--mj-border-strong));\n}\n\n.toolbar-button.active {\n background: var(--dashboard-button-active-bg, color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface)));\n border-color: var(--dashboard-button-active-border, var(--mj-brand-primary));\n color: var(--dashboard-button-active-color, var(--mj-brand-primary));\n}\n\n.toolbar-button.primary {\n background: var(--dashboard-button-primary-bg, var(--mj-brand-primary));\n border-color: var(--dashboard-button-primary-border, var(--mj-brand-primary-hover));\n color: var(--dashboard-button-primary-color, var(--mj-text-inverse));\n}\n\n.toolbar-button.primary:hover {\n background: var(--dashboard-button-primary-hover-bg, var(--mj-brand-primary-hover));\n}\n\n.toolbar-button i {\n font-size: 14px;\n}\n\n/* ========================================\n Layout Container\n ======================================== */\n\n/* Layout container fills remaining space in the flex column */\n.layout-container {\n flex: 1;\n position: relative;\n overflow: hidden;\n min-height: 0;\n}\n\n/* ========================================\n Dashboard Panel Content (our wrapper)\n ======================================== */\n\n/* Dashboard panel wrapper (header + content) */\n:host ::ng-deep .dashboard-part-wrapper {\n display: flex;\n flex-direction: column;\n height: 100%;\n background: var(--mj-bg-surface);\n}\n\n/* Dashboard part header */\n:host ::ng-deep .dashboard-part-header {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 10px 12px;\n background: var(--mj-bg-surface-card);\n border-bottom: 1px solid var(--mj-border-default);\n min-height: 40px;\n flex-shrink: 0;\n}\n\n/* Dashboard part content */\n:host ::ng-deep .dashboard-part-content {\n flex: 1;\n overflow: auto;\n min-height: 0;\n}\n\n/* ========================================\n Loading Overlay\n ======================================== */\n\n.loading-overlay {\n position: absolute;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n background: color-mix(in srgb, var(--mj-bg-surface) 90%, transparent);\n z-index: 100;\n}\n\n/* ========================================\n Empty State\n ======================================== */\n\n/* Empty state centered in the dashboard pane (mj-empty-state centers its own\n content; absolute positioning keeps it overlaid on the layout container). */\n.dashboard-empty {\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n max-width: 400px;\n}\n\n/* ========================================\n Panel Error State\n ======================================== */\n\n:host ::ng-deep .panel-error {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n height: 100%;\n padding: 24px;\n text-align: center;\n color: var(--dashboard-error-color, var(--mj-status-error));\n background: var(--dashboard-error-bg, color-mix(in srgb, var(--mj-status-error) 10%, var(--mj-bg-surface)));\n}\n\n:host ::ng-deep .panel-error i {\n font-size: 32px;\n margin-bottom: 12px;\n}\n\n:host ::ng-deep .panel-error span {\n font-size: 14px;\n}\n\n/* ========================================\n Golden Layout Overrides\n These require ViewEncapsulation.None to work on dynamic GL elements\n ======================================== */\n\n/* Override shell's hide-tab-bar rule that hides GL headers when dashboard is inside shell */\n/* The shell hides .lm_header when mj-tab-container has hide-tab-bar class */\n/* We need to ensure OUR Golden Layout headers are always visible */\nmj-dashboard-viewer .lm_header {\n display: block !important;\n}\n\n/* Ensure GL root container fills available space */\nmj-dashboard-viewer .lm_goldenlayout {\n width: 100% !important;\n height: 100% !important;\n}\n\n/* Ensure the root layout item fills space */\nmj-dashboard-viewer .lm_root {\n width: 100% !important;\n height: 100% !important;\n}\n\n/* Ensure proper box-sizing for all GL layout elements */\nmj-dashboard-viewer .lm_item,\nmj-dashboard-viewer .lm_content,\nmj-dashboard-viewer .lm_stack,\nmj-dashboard-viewer .lm_row,\nmj-dashboard-viewer .lm_column {\n box-sizing: border-box !important;\n}\n\n/* Ensure layout items don't overflow */\nmj-dashboard-viewer .lm_item {\n overflow: hidden !important;\n}\n\n/* Fix for .lm_items - the content container inside stacks */\nmj-dashboard-viewer .lm_items {\n width: 100% !important;\n height: calc(100% - 38px) !important; /* Account for header height (38px) */\n box-sizing: border-box !important;\n position: relative !important;\n}\n\n/* When tabs are maximized, no header visible */\nmj-dashboard-viewer .lm_stack.lm_maximised > .lm_items {\n height: 100% !important;\n}\n\n/* Target the ComponentItem div inside lm_items */\nmj-dashboard-viewer .lm_items > div {\n width: 100% !important;\n height: 100% !important;\n box-sizing: border-box !important;\n flex-direction: column !important;\n}\n\n/* Only apply flex display to the active/visible tab content div */\nmj-dashboard-viewer .lm_items > div:not([style*=\"display: none\"]) {\n display: flex !important;\n}\n\n/* Clearfix for float-based row layout */\nmj-dashboard-viewer .lm_row::after {\n content: \"\" !important;\n display: table !important;\n clear: both !important;\n}\n\n/* Force content children to respect parent bounds */\nmj-dashboard-viewer .lm_content > * {\n max-width: 100% !important;\n width: 100% !important;\n}\n\nmj-dashboard-viewer .lm_content {\n background: var(--mj-bg-surface) !important;\n display: flex !important;\n flex-direction: column !important;\n height: 100% !important;\n width: 100% !important;\n}\n\nmj-dashboard-viewer .lm_item_container {\n background: var(--mj-bg-surface) !important;\n}\n\n/* Tab header styling */\nmj-dashboard-viewer .lm_header {\n height: 38px !important;\n padding-top: 2px !important;\n padding-left: 4px !important;\n background: var(--mj-bg-surface-card) !important;\n border-bottom: 1px solid var(--mj-border-default) !important;\n overflow: visible !important;\n box-sizing: border-box !important;\n}\n\nmj-dashboard-viewer .lm_tabs {\n height: 36px !important;\n}\n\n/* Hide Golden Layout window controls (popout, maximize) */\nmj-dashboard-viewer .lm_controls {\n display: none !important;\n}\n\n/* Tab styling */\nmj-dashboard-viewer .lm_header .lm_tab {\n padding: 0 16px 0 28px !important; /* Extra left padding for icon */\n font-size: 13px !important;\n height: 35px !important;\n line-height: 35px !important;\n box-sizing: border-box !important;\n cursor: pointer !important;\n user-select: none !important;\n background: transparent !important;\n border: none !important;\n border-bottom: 1px solid var(--mj-border-default) !important;\n transition: all 0.15s ease !important;\n position: relative !important;\n z-index: 1 !important;\n margin-right: 1px !important;\n}\n\nmj-dashboard-viewer .lm_header .lm_tab:hover {\n background: var(--mj-bg-surface-sunken) !important;\n}\n\nmj-dashboard-viewer .lm_header .lm_tab.lm_active {\n background: var(--mj-bg-surface) !important;\n height: 36px !important;\n margin-bottom: -1px !important;\n margin-right: 0 !important;\n border: 1px solid var(--mj-border-default) !important;\n border-bottom-color: var(--mj-bg-surface) !important;\n border-radius: 4px 4px 0 0 !important;\n z-index: 2 !important;\n}\n\nmj-dashboard-viewer .lm_title {\n cursor: pointer !important;\n user-select: none !important;\n}\n\n/* Panel icon styling - icon is direct child of .lm_tab to preserve GL's drag/drop */\n/* Positioned absolutely in the left padding area so it doesn't affect GL's title structure */\nmj-dashboard-viewer .lm_header .lm_tab .panel-icon {\n position: absolute !important;\n left: 10px !important;\n top: 50% !important;\n transform: translateY(-30%) !important;\n font-family: \"Font Awesome 6 Free\", \"Font Awesome 6 Pro\", \"Font Awesome 5 Free\", \"Font Awesome 5 Pro\", FontAwesome !important;\n font-weight: 900 !important; /* Required for solid icons */\n font-size: 12px !important;\n color: var(--mj-brand-primary) !important;\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n}\n\nmj-dashboard-viewer .lm_close_tab {\n position: absolute !important;\n right: 4px !important;\n top: 50% !important;\n transform: translateY(-50%) !important;\n width: 16px !important;\n height: 16px !important;\n cursor: pointer !important;\n opacity: 0 !important;\n transition: all 0.15s ease !important;\n}\n\nmj-dashboard-viewer .lm_header .lm_tab:hover .lm_close_tab {\n opacity: 0.7 !important;\n}\n\nmj-dashboard-viewer .lm_close_tab:hover {\n opacity: 1 !important;\n color: var(--mj-status-error) !important;\n}\n\n/* Add padding for close button when editing */\nmj-dashboard-viewer .dashboard-viewer.editing .lm_header .lm_tab {\n padding-right: 24px !important;\n}\n\n/* ========================================\n View Mode (Not Editing) - Lock Layout\n ======================================== */\n\n/* Hide close buttons on tabs when not editing */\nmj-dashboard-viewer .dashboard-viewer:not(.editing) .lm_close_tab {\n display: none !important;\n}\n\n/* Remove extra padding for close button when not editing */\nmj-dashboard-viewer .dashboard-viewer:not(.editing) .lm_header .lm_tab {\n padding-right: 16px !important;\n}\n\n/* Disable splitter dragging when not editing */\nmj-dashboard-viewer .dashboard-viewer:not(.editing) .lm_splitter {\n pointer-events: none !important;\n cursor: default !important;\n}\n\n/* Hide splitter drag handle visual when not editing */\nmj-dashboard-viewer .dashboard-viewer:not(.editing) .lm_splitter .lm_drag_handle {\n display: none !important;\n}\n\n/* Disable tab dragging cursor when not editing */\nmj-dashboard-viewer .dashboard-viewer:not(.editing) .lm_header .lm_tab {\n cursor: default !important;\n}\n\nmj-dashboard-viewer .dashboard-viewer:not(.editing) .lm_title {\n cursor: default !important;\n}\n"] }]
|
|
1504
|
+
args: [{ standalone: false, selector: 'mj-dashboard-viewer', encapsulation: ViewEncapsulation.None, template: "<!-- Dashboard Viewer Component -->\n<div class=\"dashboard-viewer\" [class.editing]=\"isEditing\" [class.has-toolbar]=\"shouldShowToolbar\">\n <!-- Breadcrumb Navigation (hidden in edit mode) -->\n @if (showBreadcrumb && !isEditing && dashboard) {\n <mj-dashboard-breadcrumb\n [Categories]=\"Categories\"\n [CurrentCategoryId]=\"dashboard.CategoryID\"\n [CurrentDashboard]=\"dashboard\"\n [ShowDashboardName]=\"true\"\n [AllowDragDrop]=\"false\"\n Size=\"large\"\n RootIcon=\"fa-solid fa-gauge-high\"\n RootLabel=\"Dashboards\"\n (Navigate)=\"onBreadcrumbNavigate($event)\">\n </mj-dashboard-breadcrumb>\n }\n\n <!-- Toolbar (auto-hides when all elements are disabled) -->\n @if (shouldShowToolbar) {\n <div class=\"dashboard-toolbar\">\n <div class=\"toolbar-left\">\n @if (dashboard && (isEditing || !showBreadcrumb)) {\n <span class=\"dashboard-title\">\n <i class=\"fa-solid fa-chart-line\"></i>\n {{ dashboard.Name }}\n </span>\n }\n </div>\n <div class=\"toolbar-center\">\n @if (hasUnsavedChanges && isEditing) {\n <span class=\"unsaved-indicator\">\n <i class=\"fa-solid fa-circle\"></i>\n Unsaved changes\n </span>\n }\n </div>\n <div class=\"toolbar-right\">\n <!-- Open in New Tab button (when embedded) -->\n @if (showOpenInTabButton && !isEditing) {\n <button\n class=\"toolbar-button\"\n (click)=\"onOpenInTabClick()\"\n title=\"Open in a dashboard tab\">\n <i class=\"fa-solid fa-up-right-from-square\"></i>\n Open in Tab\n </button>\n }\n @if (isEditing) {\n <button\n class=\"toolbar-button\"\n (click)=\"onAddPanelClick()\">\n <i class=\"fa-solid fa-plus\"></i>\n Add Part\n </button>\n }\n @if (showEditButton) {\n <button\n class=\"toolbar-button\"\n [class.active]=\"isEditing\"\n (click)=\"toggleEditMode()\">\n <i class=\"fa-solid fa-edit\"></i>\n {{ isEditing ? 'Editing' : 'Edit' }}\n </button>\n }\n @if (isEditing && hasUnsavedChanges) {\n <button\n class=\"toolbar-button primary\"\n (click)=\"save()\">\n <i class=\"fa-solid fa-save\"></i>\n Save\n </button>\n }\n </div>\n </div>\n }\n\n <!-- Loading Overlay -->\n @if (isLoading) {\n <div class=\"loading-overlay\">\n <mj-loading text=\"Loading dashboard...\"></mj-loading>\n </div>\n }\n\n <!-- Layout Container -->\n <div class=\"layout-container\" #layoutContainer>\n <!-- Golden Layout will render panels here -->\n </div>\n\n <!-- Empty State: Add part shows in edit mode, and in view mode to a user who can edit -->\n @if (!HasPanels && !isLoading) {\n <mj-empty-state\n class=\"dashboard-empty\"\n Icon=\"fa-solid fa-table-columns\"\n Title=\"No parts yet\"\n Message=\"Add a view, query, artifact, or web page to get started.\"\n [ActionText]=\"IsEditing || CanEdit ? 'Add part' : ''\"\n ActionIcon=\"fa-solid fa-plus\"\n (Action)=\"OnAddPanelClick()\" />\n }\n</div>\n", styles: ["/**\n * Dashboard Viewer Component Styles\n *\n * IMPORTANT: For Golden Layout tabs to display correctly, you must import\n * Golden Layout's CSS in your application's global styles:\n *\n * @import 'golden-layout/dist/css/goldenlayout-base.css';\n *\n * Or add to your angular.json styles array:\n * \"node_modules/golden-layout/dist/css/goldenlayout-base.css\"\n */\n\n:host {\n display: block;\n width: 100%;\n height: 100%;\n position: relative;\n}\n\n.dashboard-viewer {\n display: flex;\n flex-direction: column;\n width: 100%;\n height: 100%;\n background: var(--dashboard-bg, var(--mj-bg-surface-card));\n}\n\n/* ========================================\n Toolbar\n ======================================== */\n\n.dashboard-toolbar {\n flex: 0 0 48px;\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 8px 16px;\n background: var(--dashboard-toolbar-bg, var(--mj-bg-surface));\n border-bottom: 1px solid var(--dashboard-border, var(--mj-border-default));\n box-sizing: border-box;\n z-index: 10;\n}\n\n.toolbar-left,\n.toolbar-center,\n.toolbar-right {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.toolbar-left {\n flex: 1;\n}\n\n.toolbar-center {\n flex: 0 0 auto;\n}\n\n.toolbar-right {\n flex: 1;\n justify-content: flex-end;\n}\n\n.dashboard-title {\n font-size: 16px;\n font-weight: 500;\n color: var(--dashboard-title-color, var(--mj-text-primary));\n}\n\n.dashboard-title i {\n margin-right: 8px;\n color: var(--dashboard-title-icon, var(--mj-brand-primary));\n}\n\n.unsaved-indicator {\n font-size: 12px;\n color: var(--dashboard-unsaved-color, var(--mj-status-warning));\n display: flex;\n align-items: center;\n gap: 4px;\n}\n\n.unsaved-indicator i {\n font-size: 8px;\n}\n\n.toolbar-button {\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 6px 12px;\n border: 1px solid var(--dashboard-button-border, var(--mj-border-strong));\n border-radius: 4px;\n background: var(--dashboard-button-bg, var(--mj-bg-surface));\n color: var(--dashboard-button-color, var(--mj-text-primary));\n font-size: 13px;\n cursor: pointer;\n transition: all 0.2s ease;\n}\n\n.toolbar-button:hover {\n background: var(--dashboard-button-hover-bg, var(--mj-bg-surface-card));\n border-color: var(--dashboard-button-hover-border, var(--mj-border-strong));\n}\n\n.toolbar-button.active {\n background: var(--dashboard-button-active-bg, color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface)));\n border-color: var(--dashboard-button-active-border, var(--mj-brand-primary));\n color: var(--dashboard-button-active-color, var(--mj-brand-primary));\n}\n\n.toolbar-button.primary {\n background: var(--dashboard-button-primary-bg, var(--mj-brand-primary));\n border-color: var(--dashboard-button-primary-border, var(--mj-brand-primary-hover));\n color: var(--dashboard-button-primary-color, var(--mj-text-inverse));\n}\n\n.toolbar-button.primary:hover {\n background: var(--dashboard-button-primary-hover-bg, var(--mj-brand-primary-hover));\n}\n\n.toolbar-button i {\n font-size: 14px;\n}\n\n/* ========================================\n Layout Container\n ======================================== */\n\n/* Layout container fills remaining space in the flex column */\n.layout-container {\n flex: 1;\n position: relative;\n overflow: hidden;\n min-height: 0;\n}\n\n/* ========================================\n Dashboard Panel Content (our wrapper)\n ======================================== */\n\n/* Dashboard panel wrapper (holds the part content) */\n:host ::ng-deep .dashboard-part-wrapper {\n display: flex;\n flex-direction: column;\n height: 100%;\n background: var(--mj-bg-surface);\n}\n\n/* Dashboard part content */\n:host ::ng-deep .dashboard-part-content {\n flex: 1;\n overflow: auto;\n min-height: 0;\n}\n\n/* ========================================\n Loading Overlay\n ======================================== */\n\n.loading-overlay {\n position: absolute;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n background: color-mix(in srgb, var(--mj-bg-surface) 90%, transparent);\n z-index: 100;\n}\n\n/* ========================================\n Empty State\n ======================================== */\n\n/* Empty state centered in the dashboard pane (mj-empty-state centers its own\n content; absolute positioning keeps it overlaid on the layout container). */\n.dashboard-empty {\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n max-width: 400px;\n}\n\n/* ========================================\n Panel Error State\n ======================================== */\n\n:host ::ng-deep .panel-error {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n height: 100%;\n padding: 24px;\n text-align: center;\n color: var(--dashboard-error-color, var(--mj-status-error));\n background: var(--dashboard-error-bg, color-mix(in srgb, var(--mj-status-error) 10%, var(--mj-bg-surface)));\n}\n\n:host ::ng-deep .panel-error i {\n font-size: 32px;\n margin-bottom: 12px;\n}\n\n:host ::ng-deep .panel-error span {\n font-size: 14px;\n}\n\n/* ========================================\n Golden Layout Overrides\n These require ViewEncapsulation.None to work on dynamic GL elements\n ======================================== */\n\n/* Override shell's hide-tab-bar rule that hides GL headers when dashboard is inside shell */\n/* The shell hides .lm_header when mj-tab-container has hide-tab-bar class */\n/* We need to ensure OUR Golden Layout headers are always visible */\nmj-dashboard-viewer .lm_header {\n display: block !important;\n}\n\n/* Ensure GL root container fills available space */\nmj-dashboard-viewer .lm_goldenlayout {\n width: 100% !important;\n height: 100% !important;\n}\n\n/* Ensure the root layout item fills space */\nmj-dashboard-viewer .lm_root {\n width: 100% !important;\n height: 100% !important;\n}\n\n/* Ensure proper box-sizing for all GL layout elements */\nmj-dashboard-viewer .lm_item,\nmj-dashboard-viewer .lm_content,\nmj-dashboard-viewer .lm_stack,\nmj-dashboard-viewer .lm_row,\nmj-dashboard-viewer .lm_column {\n box-sizing: border-box !important;\n}\n\n/* Ensure layout items don't overflow */\nmj-dashboard-viewer .lm_item {\n overflow: hidden !important;\n}\n\n/* Fix for .lm_items - the content container inside stacks (its height is set with the stack header below) */\nmj-dashboard-viewer .lm_items {\n width: 100% !important;\n box-sizing: border-box !important;\n position: relative !important;\n}\n\n/* When tabs are maximized, no header visible */\nmj-dashboard-viewer .lm_stack.lm_maximised > .lm_items {\n height: 100% !important;\n}\n\n/* Target the ComponentItem div inside lm_items */\nmj-dashboard-viewer .lm_items > div {\n width: 100% !important;\n height: 100% !important;\n box-sizing: border-box !important;\n flex-direction: column !important;\n}\n\n/* Only apply flex display to the active/visible tab content div */\nmj-dashboard-viewer .lm_items > div:not([style*=\"display: none\"]) {\n display: flex !important;\n}\n\n/* Clearfix for float-based row layout */\nmj-dashboard-viewer .lm_row::after {\n content: \"\" !important;\n display: table !important;\n clear: both !important;\n}\n\n/* Force content children to respect parent bounds */\nmj-dashboard-viewer .lm_content > * {\n max-width: 100% !important;\n width: 100% !important;\n}\n\nmj-dashboard-viewer .lm_content {\n background: var(--mj-bg-surface) !important;\n display: flex !important;\n flex-direction: column !important;\n height: 100% !important;\n width: 100% !important;\n}\n\nmj-dashboard-viewer .lm_item_container {\n background: var(--mj-bg-surface) !important;\n}\n\n/* ===== Golden Layout: part tabs and part buttons =====\n The shell's tab-container and theme rules reach this nested layout. These selectors add\n .dashboard-viewer to outrank them, with !important where those rules use it. */\n\n/* Stack header: tab track on the left, part buttons on the right (DASHBOARD_STACK_HEADER_HEIGHT) */\nmj-dashboard-viewer .dashboard-viewer .lm_header {\n display: block !important;\n height: 44px !important;\n max-height: none !important; /* shell.component.css caps nested headers at 40px */\n padding: 0 !important;\n background: var(--mj-bg-surface) !important;\n border-bottom: 1px solid var(--mj-border-default) !important;\n overflow: visible !important;\n box-sizing: border-box !important;\n z-index: auto; /* no stacking context, so the tab dropdown list can rise above the part */\n}\nmj-dashboard-viewer .dashboard-viewer .lm_items {\n height: calc(100% - 44px) !important;\n}\n\n/* The tab track, styled like mj-tab-nav. Golden Layout counts each tab's width and right margin; the rest of the\n spacing beside the tabs is DASHBOARD_TAB_CONTROL_OFFSET (78px): the track's 12px inset, 1px borders and 4px left\n padding, an 8px gap, the 34px tab dropdown button and its 6px gap, and the controls' 12px inset. */\nmj-dashboard-viewer .dashboard-viewer .lm_header .lm_tabs {\n top: 5px !important;\n left: var(--mj-space-3) !important;\n right: auto !important; /* shell sets left:0; right:0; overflow-x:auto */\n height: auto !important;\n overflow: visible !important;\n padding: var(--mj-space-1) 0 var(--mj-space-1) var(--mj-space-1); /* the last tab's margin closes the right side */\n background: var(--mj-bg-surface-card);\n border: 1px solid var(--mj-border-default);\n border-radius: var(--mj-radius-lg);\n}\nmj-dashboard-viewer .dashboard-viewer .lm_header .lm_tab {\n display: inline-flex !important;\n align-items: center !important;\n gap: var(--mj-space-2) !important;\n flex: 0 0 auto !important;\n height: auto !important;\n margin: 0 var(--mj-space-1) 0 0 !important;\n padding: 4px var(--mj-space-3) !important;\n font-family: inherit !important;\n font-size: 13px !important;\n font-weight: 500 !important;\n line-height: 1.2 !important;\n color: var(--mj-text-secondary) !important;\n background: transparent !important;\n border: none !important;\n border-radius: var(--mj-radius-md) !important;\n box-shadow: none !important;\n cursor: pointer !important;\n transition: background-color var(--mj-transition-fast), color var(--mj-transition-fast) !important;\n}\nmj-dashboard-viewer .dashboard-viewer .lm_header .lm_tab::after { content: none !important; } /* shell pipe separators */\nmj-dashboard-viewer .dashboard-viewer .lm_header .lm_tab:hover:not(.lm_active) {\n background: color-mix(in srgb, var(--mj-bg-surface) 70%, transparent) !important;\n color: var(--mj-text-primary) !important;\n}\n/* The shell's active tab rule is as specific as the tab rule above, so the tab's box is repeated here */\nmj-dashboard-viewer .dashboard-viewer .lm_header .lm_tab.lm_active {\n height: auto !important;\n margin: 0 var(--mj-space-1) 0 0 !important;\n border: none !important;\n border-radius: var(--mj-radius-md) !important;\n background: var(--mj-bg-surface) !important;\n color: var(--mj-brand-primary) !important;\n box-shadow: var(--mj-shadow-sm) !important;\n}\nmj-dashboard-viewer .dashboard-viewer .lm_header .lm_tab:focus-visible {\n outline: 2px solid var(--mj-border-focus) !important;\n outline-offset: -2px !important;\n}\nmj-dashboard-viewer .dashboard-viewer.editing .lm_header .lm_tab { cursor: grab !important; }\nmj-dashboard-viewer .dashboard-viewer .lm_header .lm_title {\n cursor: inherit !important;\n line-height: normal !important;\n max-width: 220px;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\nmj-dashboard-viewer .dashboard-viewer .lm_header .lm_tab .panel-icon { /* the part's icon */\n position: static !important;\n width: auto !important;\n height: auto !important;\n transform: none !important;\n font-size: 13px !important;\n color: inherit !important;\n}\n\n/* Parts are removed only through Remove (with a confirmation): hide GL's close buttons */\nmj-dashboard-viewer .dashboard-viewer .lm_close_tab,\nmj-dashboard-viewer .dashboard-viewer .lm_controls .lm_close,\nmj-dashboard-viewer .dashboard-viewer .lm_controls .lm_popout,\nmj-dashboard-viewer .dashboard-viewer .lm_controls .lm_maximise { display: none !important; }\n\n/* Part buttons (Edit part, Remove) in the header's controls area */\nmj-dashboard-viewer .dashboard-viewer .lm_header .lm_controls {\n top: 0 !important;\n bottom: 0 !important;\n right: var(--mj-space-3) !important;\n z-index: 3; /* above tabs that run into it (the shell gives tabs 1 and 2), under the tab dropdown list */\n align-items: center;\n gap: 6px;\n}\nmj-dashboard-viewer .dashboard-viewer .lm_header .lm_controls > .dashboard-stack-actions {\n display: flex !important;\n align-items: center;\n gap: 6px;\n width: auto !important; /* GL base: .lm_controls > * is 18x18 and floats */\n height: auto !important;\n float: none !important;\n opacity: 1 !important; /* MJ theme: .lm_controls > * is at 0.6 */\n cursor: default !important;\n}\n\n/* Golden Layout's tab dropdown button, sized like the part buttons and placed left of them. It sits outside the\n controls' measured width, because Golden Layout shows it only after it measures the tabs. Golden Layout shows\n and hides it with an inline display, so display here carries no !important. */\nmj-dashboard-viewer .dashboard-viewer .lm_header .lm_controls > .lm_tabdropdown {\n display: inline-flex;\n position: absolute;\n top: 0;\n bottom: 0;\n right: 100%;\n margin: auto 6px auto 0;\n width: 32px !important; /* GL base: .lm_controls > * is 18x18 and floats */\n height: 32px !important;\n float: none !important;\n align-items: center;\n justify-content: center;\n border: 1px solid transparent;\n border-radius: var(--mj-radius-md);\n color: var(--mj-text-secondary);\n opacity: 1 !important; /* MJ theme: .lm_controls > * is at 0.6 */\n cursor: pointer;\n}\nmj-dashboard-viewer .dashboard-viewer .lm_header .lm_controls > .lm_tabdropdown:hover {\n background-color: var(--mj-bg-surface-hover);\n color: var(--mj-text-primary);\n}\nmj-dashboard-viewer .dashboard-viewer .lm_header .lm_controls > .lm_tabdropdown::before {\n content: '\\f078'; /* chevron-down, in place of GL's border triangle */\n width: auto;\n height: auto;\n border: none;\n font-family: 'Font Awesome 6 Free';\n font-weight: 900;\n font-size: 12px;\n line-height: 1;\n color: inherit;\n}\n\n/* Tabs that do not fit: Golden Layout's tab dropdown list, as a menu below the header. Golden Layout shows and\n hides it with an inline display, so display here carries no !important. */\nmj-dashboard-viewer .dashboard-viewer .lm_header .lm_tabdropdown_list {\n display: flex;\n flex-direction: column;\n top: calc(100% + var(--mj-space-1));\n right: var(--mj-space-3);\n z-index: var(--mj-z-dropdown);\n min-width: 200px;\n max-height: 290px;\n overflow-y: auto;\n padding: var(--mj-space-1);\n background: var(--mj-bg-surface-elevated);\n border: 1px solid var(--mj-border-default);\n border-radius: var(--mj-radius-md);\n box-shadow: var(--mj-shadow-md);\n}\nmj-dashboard-viewer .dashboard-viewer .lm_header .lm_tabdropdown_list .lm_tab {\n display: flex !important;\n margin: 0 !important;\n padding: 6px var(--mj-space-2) !important;\n border-radius: var(--mj-radius-sm) !important;\n background: transparent !important;\n box-shadow: none !important;\n}\nmj-dashboard-viewer .dashboard-viewer .lm_header .lm_tabdropdown_list .lm_tab:hover:not(.lm_active) {\n background: var(--mj-bg-surface-hover) !important;\n}\nmj-dashboard-viewer .dashboard-viewer .lm_header .lm_tabdropdown_list .lm_title {\n width: auto; /* GL base: 100px */\n}\n\n/* Each stack (a panel of part tabs) is a card */\nmj-dashboard-viewer .dashboard-viewer .lm_stack {\n background: var(--mj-bg-surface);\n border: 1px solid var(--mj-border-default) !important;\n border-radius: var(--mj-radius-lg);\n}\n\n/* ========================================\n View Mode (Not Editing) - Lock Layout\n ======================================== */\n\n/* Disable splitter dragging when not editing */\nmj-dashboard-viewer .dashboard-viewer:not(.editing) .lm_splitter {\n pointer-events: none !important;\n cursor: default !important;\n}\n\n/* Hide splitter drag handle visual when not editing */\nmj-dashboard-viewer .dashboard-viewer:not(.editing) .lm_splitter .lm_drag_handle {\n display: none !important;\n}\n\n/* Disable tab dragging cursor when not editing */\nmj-dashboard-viewer .dashboard-viewer:not(.editing) .lm_header .lm_tab {\n cursor: default !important;\n}\n\nmj-dashboard-viewer .dashboard-viewer:not(.editing) .lm_title {\n cursor: default !important;\n}\n"] }]
|
|
1479
1505
|
}], () => [{ type: i0.ChangeDetectorRef }, { type: i0.ApplicationRef }, { type: i0.Injector }, { type: i0.EnvironmentInjector }], { Dashboard: [{
|
|
1480
1506
|
type: Input
|
|
1481
1507
|
}], dashboard: [{
|
|
@@ -1488,6 +1514,8 @@ export class DashboardViewerComponent extends BaseAngularComponent {
|
|
|
1488
1514
|
type: Input
|
|
1489
1515
|
}], isEditing: [{
|
|
1490
1516
|
type: Input
|
|
1517
|
+
}], CanEdit: [{
|
|
1518
|
+
type: Input
|
|
1491
1519
|
}], ShowToolbar: [{
|
|
1492
1520
|
type: Input
|
|
1493
1521
|
}], showToolbar: [{
|
|
@@ -1554,5 +1582,5 @@ export class DashboardViewerComponent extends BaseAngularComponent {
|
|
|
1554
1582
|
type: ViewChild,
|
|
1555
1583
|
args: ['layoutContainer', { static: true }]
|
|
1556
1584
|
}] }); })();
|
|
1557
|
-
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(DashboardViewerComponent, { className: "DashboardViewerComponent", filePath: "src/lib/dashboard-viewer/dashboard-viewer.component.ts", lineNumber:
|
|
1585
|
+
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(DashboardViewerComponent, { className: "DashboardViewerComponent", filePath: "src/lib/dashboard-viewer/dashboard-viewer.component.ts", lineNumber: 76 }); })();
|
|
1558
1586
|
//# sourceMappingURL=dashboard-viewer.component.js.map
|