@memberjunction/ng-dashboards 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/dist/AI/components/analytics/realtime/realtime-transcripts-data.d.ts +4 -3
- package/dist/AI/components/analytics/realtime/realtime-transcripts-data.d.ts.map +1 -1
- package/dist/AI/components/analytics/realtime/realtime-transcripts-data.js +8 -5
- package/dist/AI/components/analytics/realtime/realtime-transcripts-data.js.map +1 -1
- package/dist/AI/components/duplicates/compare-value.component.d.ts +67 -0
- package/dist/AI/components/duplicates/compare-value.component.d.ts.map +1 -0
- package/dist/AI/components/duplicates/compare-value.component.js +168 -0
- package/dist/AI/components/duplicates/compare-value.component.js.map +1 -0
- package/dist/AI/components/duplicates/duplicate-detection-resource.component.d.ts +49 -3
- package/dist/AI/components/duplicates/duplicate-detection-resource.component.d.ts.map +1 -1
- package/dist/AI/components/duplicates/duplicate-detection-resource.component.js +287 -169
- package/dist/AI/components/duplicates/duplicate-detection-resource.component.js.map +1 -1
- package/dist/AI/components/duplicates/duplicate-detection-run-scope.d.ts +77 -0
- package/dist/AI/components/duplicates/duplicate-detection-run-scope.d.ts.map +1 -0
- package/dist/AI/components/duplicates/duplicate-detection-run-scope.js +116 -0
- package/dist/AI/components/duplicates/duplicate-detection-run-scope.js.map +1 -0
- package/dist/AI/components/prompts/model-prompt-priority-matrix.component.d.ts +1 -0
- package/dist/AI/components/prompts/model-prompt-priority-matrix.component.d.ts.map +1 -1
- package/dist/AI/components/prompts/model-prompt-priority-matrix.component.js +16 -4
- package/dist/AI/components/prompts/model-prompt-priority-matrix.component.js.map +1 -1
- package/dist/AI/components/tags/tags-resource.component.d.ts +2 -0
- package/dist/AI/components/tags/tags-resource.component.d.ts.map +1 -1
- package/dist/AI/components/tags/tags-resource.component.js +12 -6
- package/dist/AI/components/tags/tags-resource.component.js.map +1 -1
- package/dist/AI/components/vectors/vector-index-auto.d.ts +29 -0
- package/dist/AI/components/vectors/vector-index-auto.d.ts.map +1 -0
- package/dist/AI/components/vectors/vector-index-auto.js +59 -0
- package/dist/AI/components/vectors/vector-index-auto.js.map +1 -0
- package/dist/AI/components/vectors/vector-management-resource.component.d.ts +9 -0
- package/dist/AI/components/vectors/vector-management-resource.component.d.ts.map +1 -1
- package/dist/AI/components/vectors/vector-management-resource.component.js +65 -20
- package/dist/AI/components/vectors/vector-management-resource.component.js.map +1 -1
- package/dist/Actions/components/actions-overview.component.d.ts.map +1 -1
- package/dist/Actions/components/actions-overview.component.js +7 -3
- package/dist/Actions/components/actions-overview.component.js.map +1 -1
- package/dist/Actions/components/categories-list-view.component.d.ts.map +1 -1
- package/dist/Actions/components/categories-list-view.component.js +3 -2
- package/dist/Actions/components/categories-list-view.component.js.map +1 -1
- package/dist/ComponentStudio/component-studio-dashboard.component.d.ts.map +1 -1
- package/dist/ComponentStudio/component-studio-dashboard.component.js +1 -0
- package/dist/ComponentStudio/component-studio-dashboard.component.js.map +1 -1
- package/dist/ComponentStudio/components/ai-assistant/ai-assistant-panel.component.d.ts +0 -3
- package/dist/ComponentStudio/components/ai-assistant/ai-assistant-panel.component.d.ts.map +1 -1
- package/dist/ComponentStudio/components/ai-assistant/ai-assistant-panel.component.js +5 -14
- package/dist/ComponentStudio/components/ai-assistant/ai-assistant-panel.component.js.map +1 -1
- package/dist/ComponentStudio/components/artifact-load-dialog.component.d.ts.map +1 -1
- package/dist/ComponentStudio/components/artifact-load-dialog.component.js +1 -0
- package/dist/ComponentStudio/components/artifact-load-dialog.component.js.map +1 -1
- package/dist/ComponentStudio/components/artifact-selection-dialog.component.d.ts.map +1 -1
- package/dist/ComponentStudio/components/artifact-selection-dialog.component.js +1 -0
- package/dist/ComponentStudio/components/artifact-selection-dialog.component.js.map +1 -1
- package/dist/ComponentStudio/components/form-override-dialog.component.d.ts +12 -1
- package/dist/ComponentStudio/components/form-override-dialog.component.d.ts.map +1 -1
- package/dist/ComponentStudio/components/form-override-dialog.component.js +128 -81
- package/dist/ComponentStudio/components/form-override-dialog.component.js.map +1 -1
- package/dist/Credentials/components/credentials-list-resource.component.d.ts +2 -0
- package/dist/Credentials/components/credentials-list-resource.component.d.ts.map +1 -1
- package/dist/Credentials/components/credentials-list-resource.component.js +12 -10
- package/dist/Credentials/components/credentials-list-resource.component.js.map +1 -1
- package/dist/DashboardBrowser/dashboard-browser-agent-context.d.ts +14 -43
- package/dist/DashboardBrowser/dashboard-browser-agent-context.d.ts.map +1 -1
- package/dist/DashboardBrowser/dashboard-browser-agent-context.js +6 -29
- package/dist/DashboardBrowser/dashboard-browser-agent-context.js.map +1 -1
- package/dist/DashboardBrowser/dashboard-browser-resource.component.d.ts +174 -321
- package/dist/DashboardBrowser/dashboard-browser-resource.component.d.ts.map +1 -1
- package/dist/DashboardBrowser/dashboard-browser-resource.component.js +522 -1195
- package/dist/DashboardBrowser/dashboard-browser-resource.component.js.map +1 -1
- package/dist/DashboardBrowser/dashboard-library-filter.d.ts +116 -0
- package/dist/DashboardBrowser/dashboard-library-filter.d.ts.map +1 -0
- package/dist/DashboardBrowser/dashboard-library-filter.js +277 -0
- package/dist/DashboardBrowser/dashboard-library-filter.js.map +1 -0
- package/dist/DashboardBrowser/dashboard-share-dialog.component.d.ts +1 -1
- package/dist/DashboardBrowser/dashboard-share-dialog.component.js.map +1 -1
- package/dist/DashboardBrowser/dashboards-agent-tools.d.ts +26 -0
- package/dist/DashboardBrowser/dashboards-agent-tools.d.ts.map +1 -0
- package/dist/DashboardBrowser/dashboards-agent-tools.js +32 -0
- package/dist/DashboardBrowser/dashboards-agent-tools.js.map +1 -0
- package/dist/DashboardBrowser/dashboards-categories-resource.component.d.ts +58 -0
- package/dist/DashboardBrowser/dashboards-categories-resource.component.d.ts.map +1 -0
- package/dist/DashboardBrowser/dashboards-categories-resource.component.js +391 -0
- package/dist/DashboardBrowser/dashboards-categories-resource.component.js.map +1 -0
- package/dist/DashboardBrowser/dashboards-categories.helpers.d.ts +37 -0
- package/dist/DashboardBrowser/dashboards-categories.helpers.d.ts.map +1 -0
- package/dist/DashboardBrowser/dashboards-categories.helpers.js +68 -0
- package/dist/DashboardBrowser/dashboards-categories.helpers.js.map +1 -0
- package/dist/DashboardEditor/dashboard-editor.component.d.ts +295 -0
- package/dist/DashboardEditor/dashboard-editor.component.d.ts.map +1 -0
- package/dist/DashboardEditor/dashboard-editor.component.js +1022 -0
- package/dist/DashboardEditor/dashboard-editor.component.js.map +1 -0
- package/dist/DashboardEditor/dashboard-editor.types.d.ts +23 -0
- package/dist/DashboardEditor/dashboard-editor.types.d.ts.map +1 -0
- package/dist/DashboardEditor/dashboard-editor.types.js +3 -0
- package/dist/DashboardEditor/dashboard-editor.types.js.map +1 -0
- package/dist/DashboardEditor/dashboard-viewer-factory.d.ts +11 -0
- package/dist/DashboardEditor/dashboard-viewer-factory.d.ts.map +1 -0
- package/dist/DashboardEditor/dashboard-viewer-factory.js +17 -0
- package/dist/DashboardEditor/dashboard-viewer-factory.js.map +1 -0
- package/dist/DataExplorer/data-explorer-dashboard.component.d.ts +30 -0
- package/dist/DataExplorer/data-explorer-dashboard.component.d.ts.map +1 -1
- package/dist/DataExplorer/data-explorer-dashboard.component.js +349 -232
- package/dist/DataExplorer/data-explorer-dashboard.component.js.map +1 -1
- package/dist/DataExplorer/services/explorer-state.service.d.ts +1 -1
- package/dist/DataExplorer/services/explorer-state.service.js +2 -5
- package/dist/DataExplorer/services/explorer-state.service.js.map +1 -1
- package/dist/FormBuilder/form-builder-resource.component.d.ts +0 -14
- package/dist/FormBuilder/form-builder-resource.component.d.ts.map +1 -1
- package/dist/FormBuilder/form-builder-resource.component.js +7 -29
- package/dist/FormBuilder/form-builder-resource.component.js.map +1 -1
- package/dist/Home/home-agent-context.d.ts +29 -10
- package/dist/Home/home-agent-context.d.ts.map +1 -1
- package/dist/Home/home-agent-context.js +50 -24
- package/dist/Home/home-agent-context.js.map +1 -1
- package/dist/Home/home-dashboard-switcher.component.d.ts +95 -0
- package/dist/Home/home-dashboard-switcher.component.d.ts.map +1 -0
- package/dist/Home/home-dashboard-switcher.component.js +442 -0
- package/dist/Home/home-dashboard-switcher.component.js.map +1 -0
- package/dist/Home/home-dashboard.component.d.ts +330 -17
- package/dist/Home/home-dashboard.component.d.ts.map +1 -1
- package/dist/Home/home-dashboard.component.js +1702 -1014
- package/dist/Home/home-dashboard.component.js.map +1 -1
- package/dist/Home/home-menus.d.ts +38 -0
- package/dist/Home/home-menus.d.ts.map +1 -0
- package/dist/Home/home-menus.js +53 -0
- package/dist/Home/home-menus.js.map +1 -0
- package/dist/Home/home-pinned-dashboards.d.ts +33 -0
- package/dist/Home/home-pinned-dashboards.d.ts.map +1 -0
- package/dist/Home/home-pinned-dashboards.js +76 -0
- package/dist/Home/home-pinned-dashboards.js.map +1 -0
- package/dist/PredictiveStudio/components/ps-production.component.d.ts +5 -1
- package/dist/PredictiveStudio/components/ps-production.component.d.ts.map +1 -1
- package/dist/PredictiveStudio/components/ps-production.component.js +64 -41
- package/dist/PredictiveStudio/components/ps-production.component.js.map +1 -1
- package/dist/QueryBrowser/query-browser-resource.component.d.ts.map +1 -1
- package/dist/QueryBrowser/query-browser-resource.component.js +3 -2
- package/dist/QueryBrowser/query-browser-resource.component.js.map +1 -1
- package/dist/RealtimeRecordings/realtime-recordings-dashboard.component.js +2 -2
- package/dist/RealtimeRecordings/realtime-recordings-dashboard.component.js.map +1 -1
- package/dist/TelephonyOperations/telephony-operations-agent-context.d.ts +107 -0
- package/dist/TelephonyOperations/telephony-operations-agent-context.d.ts.map +1 -0
- package/dist/TelephonyOperations/telephony-operations-agent-context.js +216 -0
- package/dist/TelephonyOperations/telephony-operations-agent-context.js.map +1 -0
- package/dist/TelephonyOperations/telephony-operations-dashboard.component.d.ts +58 -0
- package/dist/TelephonyOperations/telephony-operations-dashboard.component.d.ts.map +1 -0
- package/dist/TelephonyOperations/telephony-operations-dashboard.component.js +812 -0
- package/dist/TelephonyOperations/telephony-operations-dashboard.component.js.map +1 -0
- package/dist/Testing/components/testing-explorer.component.d.ts.map +1 -1
- package/dist/Testing/components/testing-explorer.component.js +7 -7
- package/dist/Testing/components/testing-explorer.component.js.map +1 -1
- package/dist/WorkQueue/components/work-queue-bindings.component.d.ts +37 -0
- package/dist/WorkQueue/components/work-queue-bindings.component.d.ts.map +1 -0
- package/dist/WorkQueue/components/work-queue-bindings.component.js +210 -0
- package/dist/WorkQueue/components/work-queue-bindings.component.js.map +1 -0
- package/dist/WorkQueue/components/work-queue-dead-letters.component.d.ts +70 -0
- package/dist/WorkQueue/components/work-queue-dead-letters.component.d.ts.map +1 -0
- package/dist/WorkQueue/components/work-queue-dead-letters.component.js +496 -0
- package/dist/WorkQueue/components/work-queue-dead-letters.component.js.map +1 -0
- package/dist/WorkQueue/components/work-queue-discard-dialog.component.d.ts +22 -0
- package/dist/WorkQueue/components/work-queue-discard-dialog.component.d.ts.map +1 -0
- package/dist/WorkQueue/components/work-queue-discard-dialog.component.js +99 -0
- package/dist/WorkQueue/components/work-queue-discard-dialog.component.js.map +1 -0
- package/dist/WorkQueue/components/work-queue-overview.component.d.ts +29 -0
- package/dist/WorkQueue/components/work-queue-overview.component.d.ts.map +1 -0
- package/dist/WorkQueue/components/work-queue-overview.component.js +278 -0
- package/dist/WorkQueue/components/work-queue-overview.component.js.map +1 -0
- package/dist/WorkQueue/components/work-queue-partitions.component.d.ts +59 -0
- package/dist/WorkQueue/components/work-queue-partitions.component.d.ts.map +1 -0
- package/dist/WorkQueue/components/work-queue-partitions.component.js +359 -0
- package/dist/WorkQueue/components/work-queue-partitions.component.js.map +1 -0
- package/dist/WorkQueue/services/work-queue-operator.service.d.ts +22 -0
- package/dist/WorkQueue/services/work-queue-operator.service.d.ts.map +1 -0
- package/dist/WorkQueue/services/work-queue-operator.service.js +44 -0
- package/dist/WorkQueue/services/work-queue-operator.service.js.map +1 -0
- package/dist/WorkQueue/work-queue-agent-context.d.ts +67 -0
- package/dist/WorkQueue/work-queue-agent-context.d.ts.map +1 -0
- package/dist/WorkQueue/work-queue-agent-context.js +119 -0
- package/dist/WorkQueue/work-queue-agent-context.js.map +1 -0
- package/dist/WorkQueue/work-queue-dashboard.component.d.ts +94 -0
- package/dist/WorkQueue/work-queue-dashboard.component.d.ts.map +1 -0
- package/dist/WorkQueue/work-queue-dashboard.component.js +545 -0
- package/dist/WorkQueue/work-queue-dashboard.component.js.map +1 -0
- package/dist/WorkQueue/work-queue-types.d.ts +40 -0
- package/dist/WorkQueue/work-queue-types.d.ts.map +1 -0
- package/dist/WorkQueue/work-queue-types.js +2 -0
- package/dist/WorkQueue/work-queue-types.js.map +1 -0
- package/dist/ai-dashboards.module.d.ts +21 -20
- package/dist/ai-dashboards.module.d.ts.map +1 -1
- package/dist/ai-dashboards.module.js +4 -1
- package/dist/ai-dashboards.module.js.map +1 -1
- package/dist/core-dashboards.module.d.ts +69 -61
- package/dist/core-dashboards.module.d.ts.map +1 -1
- package/dist/core-dashboards.module.js +29 -2
- package/dist/core-dashboards.module.js.map +1 -1
- package/dist/data-explorer-dashboards.module.d.ts +1 -1
- package/dist/data-explorer-dashboards.module.d.ts.map +1 -1
- package/dist/data-explorer-dashboards.module.js +3 -1
- package/dist/data-explorer-dashboards.module.js.map +1 -1
- package/dist/module.d.ts +14 -13
- package/dist/module.d.ts.map +1 -1
- package/dist/module.js +7 -0
- package/dist/module.js.map +1 -1
- package/dist/public-api.d.ts +6 -0
- package/dist/public-api.d.ts.map +1 -1
- package/dist/public-api.js +9 -0
- package/dist/public-api.js.map +1 -1
- package/dist/shared/dashboard-library-changes.d.ts +23 -0
- package/dist/shared/dashboard-library-changes.d.ts.map +1 -0
- package/dist/shared/dashboard-library-changes.js +26 -0
- package/dist/shared/dashboard-library-changes.js.map +1 -0
- package/dist/shared/dashboard-owner-names.d.ts +17 -0
- package/dist/shared/dashboard-owner-names.d.ts.map +1 -0
- package/dist/shared/dashboard-owner-names.js +65 -0
- package/dist/shared/dashboard-owner-names.js.map +1 -0
- package/dist/shared/dashboard-recents.d.ts +34 -0
- package/dist/shared/dashboard-recents.d.ts.map +1 -0
- package/dist/shared/dashboard-recents.js +87 -0
- package/dist/shared/dashboard-recents.js.map +1 -0
- package/dist/shared/dashboards-app-install.d.ts +59 -0
- package/dist/shared/dashboards-app-install.d.ts.map +1 -0
- package/dist/shared/dashboards-app-install.js +97 -0
- package/dist/shared/dashboards-app-install.js.map +1 -0
- package/dist/shared/dashboards-app.helpers.d.ts +42 -0
- package/dist/shared/dashboards-app.helpers.d.ts.map +1 -0
- package/dist/shared/dashboards-app.helpers.js +143 -0
- package/dist/shared/dashboards-app.helpers.js.map +1 -0
- package/dist/work-queue-dashboards.module.d.ts +23 -0
- package/dist/work-queue-dashboards.module.d.ts.map +1 -0
- package/dist/work-queue-dashboards.module.js +92 -0
- package/dist/work-queue-dashboards.module.js.map +1 -0
- package/package.json +73 -66
|
@@ -0,0 +1,1022 @@
|
|
|
1
|
+
import { ChangeDetectorRef, Component, EventEmitter, HostBinding, Input, Output, ViewChild, ViewContainerRef, inject } from '@angular/core';
|
|
2
|
+
import { Subject, auditTime, filter, takeUntil } from 'rxjs';
|
|
3
|
+
import { LogError } from '@memberjunction/core';
|
|
4
|
+
import { DashboardEngine } from '@memberjunction/core-entities';
|
|
5
|
+
import { UUIDsEqual } from '@memberjunction/global';
|
|
6
|
+
import { BaseAngularComponent } from '@memberjunction/ng-base-types';
|
|
7
|
+
import { DashboardFavoritesService, HomeAppPinService, SafeDetectChanges } from '@memberjunction/ng-shared';
|
|
8
|
+
import { MJNotificationService } from '@memberjunction/ng-notifications';
|
|
9
|
+
import { MJConfirmService } from '@memberjunction/ng-ui-components';
|
|
10
|
+
import { ExtractPanelsFromLayout, RemovePartConfirmOptions } from '@memberjunction/ng-dashboard-viewer';
|
|
11
|
+
import { DashboardViewerFactory } from './dashboard-viewer-factory';
|
|
12
|
+
import { DASHBOARD_SAVE_IN_PROGRESS } from './dashboard-editor.types';
|
|
13
|
+
import * as i0 from "@angular/core";
|
|
14
|
+
import * as i1 from "@angular/common";
|
|
15
|
+
import * as i2 from "@angular/forms";
|
|
16
|
+
import * as i3 from "@memberjunction/ng-ui-components";
|
|
17
|
+
import * as i4 from "@memberjunction/ng-dashboard-viewer";
|
|
18
|
+
const _c0 = ["body"];
|
|
19
|
+
const _c1 = ["viewerHost"];
|
|
20
|
+
const _c2 = [[["", "headerTools", ""]], [["", "viewActions", ""]]];
|
|
21
|
+
const _c3 = ["[headerTools]", "[viewActions]"];
|
|
22
|
+
const _c4 = (a0, a1) => ({ $implicit: a0, Dashboard: a1 });
|
|
23
|
+
function DashboardEditorComponent_Conditional_0_Conditional_1_Template(rf, ctx) { if (rf & 1) {
|
|
24
|
+
i0.ɵɵelement(0, "mj-stat-badge", 7);
|
|
25
|
+
} }
|
|
26
|
+
function DashboardEditorComponent_Conditional_0_Conditional_3_Conditional_0_Template(rf, ctx) { if (rf & 1) {
|
|
27
|
+
i0.ɵɵelementContainer(0, 10);
|
|
28
|
+
} if (rf & 2) {
|
|
29
|
+
const dashboard_r1 = i0.ɵɵnextContext(2);
|
|
30
|
+
const ctx_r1 = i0.ɵɵnextContext();
|
|
31
|
+
i0.ɵɵproperty("ngTemplateOutlet", ctx_r1.TitleTemplate)("ngTemplateOutletContext", i0.ɵɵpureFunction2(2, _c4, dashboard_r1, dashboard_r1));
|
|
32
|
+
} }
|
|
33
|
+
function DashboardEditorComponent_Conditional_0_Conditional_3_Conditional_1_Conditional_2_Template(rf, ctx) { if (rf & 1) {
|
|
34
|
+
i0.ɵɵelement(0, "i", 14);
|
|
35
|
+
} }
|
|
36
|
+
function DashboardEditorComponent_Conditional_0_Conditional_3_Conditional_1_Template(rf, ctx) { if (rf & 1) {
|
|
37
|
+
i0.ɵɵelementStart(0, "h1", 11);
|
|
38
|
+
i0.ɵɵtext(1);
|
|
39
|
+
i0.ɵɵconditionalCreate(2, DashboardEditorComponent_Conditional_0_Conditional_3_Conditional_1_Conditional_2_Template, 1, 0, "i", 14);
|
|
40
|
+
i0.ɵɵelementEnd();
|
|
41
|
+
} if (rf & 2) {
|
|
42
|
+
const dashboard_r1 = i0.ɵɵnextContext(2);
|
|
43
|
+
const ctx_r1 = i0.ɵɵnextContext();
|
|
44
|
+
i0.ɵɵadvance();
|
|
45
|
+
i0.ɵɵtextInterpolate1(" ", dashboard_r1.Name, " ");
|
|
46
|
+
i0.ɵɵadvance();
|
|
47
|
+
i0.ɵɵconditional(ctx_r1.IsSharedWithUser ? 2 : -1);
|
|
48
|
+
} }
|
|
49
|
+
function DashboardEditorComponent_Conditional_0_Conditional_3_Conditional_2_Template(rf, ctx) { if (rf & 1) {
|
|
50
|
+
i0.ɵɵelementStart(0, "p", 12);
|
|
51
|
+
i0.ɵɵtext(1);
|
|
52
|
+
i0.ɵɵelementEnd();
|
|
53
|
+
} if (rf & 2) {
|
|
54
|
+
const dashboard_r1 = i0.ɵɵnextContext(2);
|
|
55
|
+
i0.ɵɵadvance();
|
|
56
|
+
i0.ɵɵtextInterpolate(dashboard_r1.Description);
|
|
57
|
+
} }
|
|
58
|
+
function DashboardEditorComponent_Conditional_0_Conditional_3_Conditional_3_Template(rf, ctx) { if (rf & 1) {
|
|
59
|
+
i0.ɵɵelementStart(0, "p", 13);
|
|
60
|
+
i0.ɵɵtext(1, "Add a description");
|
|
61
|
+
i0.ɵɵelementEnd();
|
|
62
|
+
} }
|
|
63
|
+
function DashboardEditorComponent_Conditional_0_Conditional_3_Template(rf, ctx) { if (rf & 1) {
|
|
64
|
+
i0.ɵɵconditionalCreate(0, DashboardEditorComponent_Conditional_0_Conditional_3_Conditional_0_Template, 1, 5, "ng-container", 10)(1, DashboardEditorComponent_Conditional_0_Conditional_3_Conditional_1_Template, 3, 2, "h1", 11);
|
|
65
|
+
i0.ɵɵconditionalCreate(2, DashboardEditorComponent_Conditional_0_Conditional_3_Conditional_2_Template, 2, 1, "p", 12)(3, DashboardEditorComponent_Conditional_0_Conditional_3_Conditional_3_Template, 2, 0, "p", 13);
|
|
66
|
+
} if (rf & 2) {
|
|
67
|
+
const dashboard_r1 = i0.ɵɵnextContext();
|
|
68
|
+
const ctx_r1 = i0.ɵɵnextContext();
|
|
69
|
+
i0.ɵɵconditional(ctx_r1.TitleTemplate ? 0 : 1);
|
|
70
|
+
i0.ɵɵadvance(2);
|
|
71
|
+
i0.ɵɵconditional(dashboard_r1.Description ? 2 : ctx_r1.CanEdit ? 3 : -1);
|
|
72
|
+
} }
|
|
73
|
+
function DashboardEditorComponent_Conditional_0_Conditional_4_Template(rf, ctx) { if (rf & 1) {
|
|
74
|
+
const _r3 = i0.ɵɵgetCurrentView();
|
|
75
|
+
i0.ɵɵelementStart(0, "input", 15);
|
|
76
|
+
i0.ɵɵtwoWayListener("ngModelChange", function DashboardEditorComponent_Conditional_0_Conditional_4_Template_input_ngModelChange_0_listener($event) { i0.ɵɵrestoreView(_r3); const ctx_r1 = i0.ɵɵnextContext(2); i0.ɵɵtwoWayBindingSet(ctx_r1.EditingName, $event) || (ctx_r1.EditingName = $event); return i0.ɵɵresetView($event); });
|
|
77
|
+
i0.ɵɵlistener("blur", function DashboardEditorComponent_Conditional_0_Conditional_4_Template_input_blur_0_listener() { i0.ɵɵrestoreView(_r3); const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.OnNameBlur()); });
|
|
78
|
+
i0.ɵɵelementEnd();
|
|
79
|
+
i0.ɵɵelementStart(1, "input", 16);
|
|
80
|
+
i0.ɵɵtwoWayListener("ngModelChange", function DashboardEditorComponent_Conditional_0_Conditional_4_Template_input_ngModelChange_1_listener($event) { i0.ɵɵrestoreView(_r3); const ctx_r1 = i0.ɵɵnextContext(2); i0.ɵɵtwoWayBindingSet(ctx_r1.EditingDescription, $event) || (ctx_r1.EditingDescription = $event); return i0.ɵɵresetView($event); });
|
|
81
|
+
i0.ɵɵelementEnd();
|
|
82
|
+
} if (rf & 2) {
|
|
83
|
+
const ctx_r1 = i0.ɵɵnextContext(2);
|
|
84
|
+
i0.ɵɵtwoWayProperty("ngModel", ctx_r1.EditingName);
|
|
85
|
+
i0.ɵɵadvance();
|
|
86
|
+
i0.ɵɵtwoWayProperty("ngModel", ctx_r1.EditingDescription);
|
|
87
|
+
} }
|
|
88
|
+
function DashboardEditorComponent_Conditional_0_Conditional_7_Conditional_0_Template(rf, ctx) { if (rf & 1) {
|
|
89
|
+
const _r4 = i0.ɵɵgetCurrentView();
|
|
90
|
+
i0.ɵɵelementStart(0, "button", 19);
|
|
91
|
+
i0.ɵɵlistener("click", function DashboardEditorComponent_Conditional_0_Conditional_7_Conditional_0_Template_button_click_0_listener() { i0.ɵɵrestoreView(_r4); const ctx_r1 = i0.ɵɵnextContext(3); return i0.ɵɵresetView(ctx_r1.ToggleFavorite()); });
|
|
92
|
+
i0.ɵɵelement(1, "i", 20);
|
|
93
|
+
i0.ɵɵelementEnd();
|
|
94
|
+
} if (rf & 2) {
|
|
95
|
+
const ctx_r1 = i0.ɵɵnextContext(3);
|
|
96
|
+
i0.ɵɵclassProp("is-favorite", ctx_r1.IsFavorite);
|
|
97
|
+
i0.ɵɵproperty("title", ctx_r1.IsFavorite ? "Remove from favorites" : "Add to favorites");
|
|
98
|
+
i0.ɵɵattribute("aria-label", ctx_r1.IsFavorite ? "Remove from favorites" : "Add to favorites");
|
|
99
|
+
i0.ɵɵadvance();
|
|
100
|
+
i0.ɵɵclassMap(ctx_r1.IsFavorite ? "fa-solid fa-star" : "fa-regular fa-star");
|
|
101
|
+
} }
|
|
102
|
+
function DashboardEditorComponent_Conditional_0_Conditional_7_Conditional_2_Template(rf, ctx) { if (rf & 1) {
|
|
103
|
+
const _r5 = i0.ɵɵgetCurrentView();
|
|
104
|
+
i0.ɵɵelementStart(0, "button", 21);
|
|
105
|
+
i0.ɵɵlistener("click", function DashboardEditorComponent_Conditional_0_Conditional_7_Conditional_2_Template_button_click_0_listener() { i0.ɵɵrestoreView(_r5); const dashboard_r1 = i0.ɵɵnextContext(2); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.OpenInDashboardsRequested.emit(dashboard_r1)); });
|
|
106
|
+
i0.ɵɵelement(1, "i", 22);
|
|
107
|
+
i0.ɵɵtext(2, " Open in Dashboards ");
|
|
108
|
+
i0.ɵɵelementEnd();
|
|
109
|
+
} }
|
|
110
|
+
function DashboardEditorComponent_Conditional_0_Conditional_7_Conditional_3_Template(rf, ctx) { if (rf & 1) {
|
|
111
|
+
const _r6 = i0.ɵɵgetCurrentView();
|
|
112
|
+
i0.ɵɵelement(0, "span", 23);
|
|
113
|
+
i0.ɵɵelementStart(1, "button", 24);
|
|
114
|
+
i0.ɵɵlistener("click", function DashboardEditorComponent_Conditional_0_Conditional_7_Conditional_3_Template_button_click_1_listener() { i0.ɵɵrestoreView(_r6); const ctx_r1 = i0.ɵɵnextContext(3); return i0.ɵɵresetView(ctx_r1.EnterEditMode()); });
|
|
115
|
+
i0.ɵɵelement(2, "i", 25);
|
|
116
|
+
i0.ɵɵtext(3, " Edit ");
|
|
117
|
+
i0.ɵɵelementEnd();
|
|
118
|
+
} if (rf & 2) {
|
|
119
|
+
const ctx_r1 = i0.ɵɵnextContext(3);
|
|
120
|
+
i0.ɵɵadvance();
|
|
121
|
+
i0.ɵɵproperty("disabled", ctx_r1.IsSaving);
|
|
122
|
+
} }
|
|
123
|
+
function DashboardEditorComponent_Conditional_0_Conditional_7_Template(rf, ctx) { if (rf & 1) {
|
|
124
|
+
i0.ɵɵconditionalCreate(0, DashboardEditorComponent_Conditional_0_Conditional_7_Conditional_0_Template, 2, 6, "button", 17);
|
|
125
|
+
i0.ɵɵprojection(1, 1);
|
|
126
|
+
i0.ɵɵconditionalCreate(2, DashboardEditorComponent_Conditional_0_Conditional_7_Conditional_2_Template, 3, 0, "button", 18);
|
|
127
|
+
i0.ɵɵconditionalCreate(3, DashboardEditorComponent_Conditional_0_Conditional_7_Conditional_3_Template, 4, 1);
|
|
128
|
+
} if (rf & 2) {
|
|
129
|
+
const ctx_r1 = i0.ɵɵnextContext(2);
|
|
130
|
+
i0.ɵɵconditional(ctx_r1.ShowFavorite ? 0 : -1);
|
|
131
|
+
i0.ɵɵadvance(2);
|
|
132
|
+
i0.ɵɵconditional(ctx_r1.ShowOpenInDashboards ? 2 : -1);
|
|
133
|
+
i0.ɵɵadvance();
|
|
134
|
+
i0.ɵɵconditional(ctx_r1.CanEdit ? 3 : -1);
|
|
135
|
+
} }
|
|
136
|
+
function DashboardEditorComponent_Conditional_0_Conditional_8_Template(rf, ctx) { if (rf & 1) {
|
|
137
|
+
const _r7 = i0.ɵɵgetCurrentView();
|
|
138
|
+
i0.ɵɵelementStart(0, "button", 26);
|
|
139
|
+
i0.ɵɵlistener("click", function DashboardEditorComponent_Conditional_0_Conditional_8_Template_button_click_0_listener() { i0.ɵɵrestoreView(_r7); const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.OpenAddPartDialog()); });
|
|
140
|
+
i0.ɵɵelement(1, "i", 27);
|
|
141
|
+
i0.ɵɵtext(2, " Add part ");
|
|
142
|
+
i0.ɵɵelementEnd();
|
|
143
|
+
i0.ɵɵelement(3, "span", 23);
|
|
144
|
+
i0.ɵɵelementStart(4, "button", 28);
|
|
145
|
+
i0.ɵɵlistener("click", function DashboardEditorComponent_Conditional_0_Conditional_8_Template_button_click_4_listener() { i0.ɵɵrestoreView(_r7); const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.SaveDashboard()); });
|
|
146
|
+
i0.ɵɵelement(5, "i", 20);
|
|
147
|
+
i0.ɵɵtext(6);
|
|
148
|
+
i0.ɵɵelementEnd();
|
|
149
|
+
i0.ɵɵelementStart(7, "button", 29);
|
|
150
|
+
i0.ɵɵlistener("click", function DashboardEditorComponent_Conditional_0_Conditional_8_Template_button_click_7_listener() { i0.ɵɵrestoreView(_r7); const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.CancelEdit()); });
|
|
151
|
+
i0.ɵɵtext(8, "Cancel");
|
|
152
|
+
i0.ɵɵelementEnd();
|
|
153
|
+
} if (rf & 2) {
|
|
154
|
+
const ctx_r1 = i0.ɵɵnextContext(2);
|
|
155
|
+
i0.ɵɵproperty("disabled", ctx_r1.IsSaving);
|
|
156
|
+
i0.ɵɵadvance(4);
|
|
157
|
+
i0.ɵɵproperty("disabled", ctx_r1.IsSaving);
|
|
158
|
+
i0.ɵɵadvance();
|
|
159
|
+
i0.ɵɵclassMap(ctx_r1.IsSaving ? "fa-solid fa-spinner fa-spin" : "fa-solid fa-check");
|
|
160
|
+
i0.ɵɵadvance();
|
|
161
|
+
i0.ɵɵtextInterpolate1(" ", ctx_r1.IsSaving ? "Saving..." : "Save", " ");
|
|
162
|
+
i0.ɵɵadvance();
|
|
163
|
+
i0.ɵɵproperty("disabled", ctx_r1.IsSaving);
|
|
164
|
+
} }
|
|
165
|
+
function DashboardEditorComponent_Conditional_0_Template(rf, ctx) { if (rf & 1) {
|
|
166
|
+
i0.ɵɵelementStart(0, "header", 6);
|
|
167
|
+
i0.ɵɵconditionalCreate(1, DashboardEditorComponent_Conditional_0_Conditional_1_Template, 1, 0, "mj-stat-badge", 7);
|
|
168
|
+
i0.ɵɵelementStart(2, "div", 8);
|
|
169
|
+
i0.ɵɵconditionalCreate(3, DashboardEditorComponent_Conditional_0_Conditional_3_Template, 4, 2)(4, DashboardEditorComponent_Conditional_0_Conditional_4_Template, 2, 2);
|
|
170
|
+
i0.ɵɵelementEnd();
|
|
171
|
+
i0.ɵɵelementStart(5, "div", 9);
|
|
172
|
+
i0.ɵɵprojection(6);
|
|
173
|
+
i0.ɵɵconditionalCreate(7, DashboardEditorComponent_Conditional_0_Conditional_7_Template, 4, 3)(8, DashboardEditorComponent_Conditional_0_Conditional_8_Template, 9, 6);
|
|
174
|
+
i0.ɵɵelementEnd()();
|
|
175
|
+
} if (rf & 2) {
|
|
176
|
+
const ctx_r1 = i0.ɵɵnextContext();
|
|
177
|
+
i0.ɵɵclassProp("is-editing", ctx_r1.IsEditing);
|
|
178
|
+
i0.ɵɵadvance();
|
|
179
|
+
i0.ɵɵconditional(ctx_r1.IsEditing ? 1 : -1);
|
|
180
|
+
i0.ɵɵadvance(2);
|
|
181
|
+
i0.ɵɵconditional(!ctx_r1.IsEditing ? 3 : 4);
|
|
182
|
+
i0.ɵɵadvance(4);
|
|
183
|
+
i0.ɵɵconditional(!ctx_r1.IsEditing ? 7 : 8);
|
|
184
|
+
} }
|
|
185
|
+
function DashboardEditorComponent_Conditional_6_Template(rf, ctx) { if (rf & 1) {
|
|
186
|
+
const _r8 = i0.ɵɵgetCurrentView();
|
|
187
|
+
i0.ɵɵelementStart(0, "mj-dashboard-part-dialog", 30);
|
|
188
|
+
i0.ɵɵlistener("Saved", function DashboardEditorComponent_Conditional_6_Template_mj_dashboard_part_dialog_Saved_0_listener($event) { i0.ɵɵrestoreView(_r8); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.OnPartDialogSaved($event)); })("Cancelled", function DashboardEditorComponent_Conditional_6_Template_mj_dashboard_part_dialog_Cancelled_0_listener() { i0.ɵɵrestoreView(_r8); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.ClosePartDialog()); });
|
|
189
|
+
i0.ɵɵelementEnd();
|
|
190
|
+
} if (rf & 2) {
|
|
191
|
+
const ctx_r1 = i0.ɵɵnextContext();
|
|
192
|
+
i0.ɵɵproperty("Visible", true)("Mode", ctx_r1.PartDialogMode)("PartTypes", ctx_r1.PartTypes)("Panel", ctx_r1.EditPartPanel)("Provider", ctx_r1.Provider);
|
|
193
|
+
} }
|
|
194
|
+
/** The entity of the dashboard records DashboardEngine caches. */
|
|
195
|
+
const DASHBOARDS_ENTITY_NAME = 'MJ: Dashboards';
|
|
196
|
+
/** How long Save waits for the dashboard screenshot. After that it saves without a new one. */
|
|
197
|
+
const SAVE_SCREENSHOT_TIMEOUT_MS = 1500;
|
|
198
|
+
/** Why a Save stopped: asked whether to replace a save made elsewhere, the user chose Keep editing. */
|
|
199
|
+
const KEPT_EDITING = 'The user kept editing: the dashboard was saved elsewhere.';
|
|
200
|
+
/** Why a Save stopped before the viewer saved: the editor shows another dashboard now. */
|
|
201
|
+
const DASHBOARD_CHANGED_BEFORE_SAVE = 'The editor shows another dashboard now. The dashboard was not saved.';
|
|
202
|
+
/** The part types offered while no viewer is open: one empty list, so change detection sees a stable value. */
|
|
203
|
+
const NO_PART_TYPES = [];
|
|
204
|
+
/** Permissions before a dashboard loads: nothing is allowed, so no Edit button flashes. */
|
|
205
|
+
const NO_PERMISSIONS = {
|
|
206
|
+
DashboardID: '', CanRead: false, CanEdit: false, CanDelete: false, CanShare: false, IsOwner: false, PermissionSource: 'none',
|
|
207
|
+
};
|
|
208
|
+
/**
|
|
209
|
+
* A Config dashboard with its header: view mode (title, description, favorite, host actions, Edit) and
|
|
210
|
+
* edit mode (Editing badge, name and description fields, Add part, Save, Cancel), the part dialog and the
|
|
211
|
+
* dashboard viewer. Made for the dashboard tab and Home's dashboard view, so both edit a dashboard the same
|
|
212
|
+
* way; Save asks before it replaces a save made elsewhere during the edit. It never navigates: hosts
|
|
213
|
+
* handle its events.
|
|
214
|
+
*
|
|
215
|
+
* Hosts project their own header buttons: `[headerTools]` shows in both modes, `[viewActions]` in view
|
|
216
|
+
* mode after the star.
|
|
217
|
+
*/
|
|
218
|
+
export class DashboardEditorComponent extends BaseAngularComponent {
|
|
219
|
+
cdr = inject(ChangeDetectorRef);
|
|
220
|
+
favoritesService = inject(DashboardFavoritesService);
|
|
221
|
+
pinService = inject(HomeAppPinService);
|
|
222
|
+
confirmService = inject(MJConfirmService);
|
|
223
|
+
viewerFactory = inject(DashboardViewerFactory);
|
|
224
|
+
// ── Inputs ────────────────────────────────────────────────
|
|
225
|
+
_dashboardId = null;
|
|
226
|
+
viewInitialized = false;
|
|
227
|
+
/**
|
|
228
|
+
* The Config dashboard to show. The editor reads it from DashboardEngine. The same ID again (in any
|
|
229
|
+
* case) changes nothing; another ID drops an edit in progress and shows that dashboard; null clears.
|
|
230
|
+
*/
|
|
231
|
+
set DashboardId(value) {
|
|
232
|
+
const id = value?.trim() || null;
|
|
233
|
+
if (UUIDsEqual(id, this._dashboardId))
|
|
234
|
+
return;
|
|
235
|
+
this._dashboardId = id;
|
|
236
|
+
if (this.viewInitialized)
|
|
237
|
+
void this.load();
|
|
238
|
+
}
|
|
239
|
+
get DashboardId() {
|
|
240
|
+
return this._dashboardId;
|
|
241
|
+
}
|
|
242
|
+
/** Enter edit mode when the dashboard finishes loading. Read once per load, then reset (StartInEditModeChange). */
|
|
243
|
+
StartInEditMode = false;
|
|
244
|
+
/** Emits false when the editor has read StartInEditMode, for a two-way binding. */
|
|
245
|
+
StartInEditModeChange = new EventEmitter();
|
|
246
|
+
/** Replaces the view-mode title, for example with Home's breadcrumb and dashboard switcher. */
|
|
247
|
+
TitleTemplate = null;
|
|
248
|
+
/** Shows "Open in Dashboards" in view mode. */
|
|
249
|
+
ShowOpenInDashboards = false;
|
|
250
|
+
/** Shows the favorite star in view mode. */
|
|
251
|
+
ShowFavorite = true;
|
|
252
|
+
/** 'fill': the dashboard fills the editor. 'card': a padded card body. */
|
|
253
|
+
BodyStyle = 'fill';
|
|
254
|
+
// ── Outputs ───────────────────────────────────────────────
|
|
255
|
+
/** The dashboard's layout is ready. Once for each DashboardId. */
|
|
256
|
+
Loaded = new EventEmitter();
|
|
257
|
+
/**
|
|
258
|
+
* The dashboard could not be shown: DashboardEngine does not hold it, it is a Code dashboard, or its
|
|
259
|
+
* layout failed. The editor then shows no dashboard.
|
|
260
|
+
*/
|
|
261
|
+
LoadFailed = new EventEmitter();
|
|
262
|
+
/** The editor entered (true) or left (false) edit mode. Not emitted when the editor is destroyed. */
|
|
263
|
+
EditingChange = new EventEmitter();
|
|
264
|
+
/** The viewer saved the dashboard (before the editor leaves edit mode). */
|
|
265
|
+
Saved = new EventEmitter();
|
|
266
|
+
/** The shown dashboard's saved name changed: this editor's Save, or a save elsewhere. Never empty. */
|
|
267
|
+
NameChanged = new EventEmitter();
|
|
268
|
+
/** The viewer's configuration changed (parts, layout). */
|
|
269
|
+
ConfigChanged = new EventEmitter();
|
|
270
|
+
/** The editor rebuilt the saved layout because the dashboard was saved elsewhere. */
|
|
271
|
+
ReloadedFromSaved = new EventEmitter();
|
|
272
|
+
/** The user starred (true) or unstarred (false) the dashboard. */
|
|
273
|
+
FavoriteChange = new EventEmitter();
|
|
274
|
+
/** A part asked to open something: a record, another dashboard, a query. */
|
|
275
|
+
NavigationRequested = new EventEmitter();
|
|
276
|
+
/** The user clicked "Open in Dashboards". */
|
|
277
|
+
OpenInDashboardsRequested = new EventEmitter();
|
|
278
|
+
/** In 'fill' style the editor takes its host's full height. */
|
|
279
|
+
get IsFill() { return this.BodyStyle === 'fill'; }
|
|
280
|
+
// ── View ──────────────────────────────────────────────────
|
|
281
|
+
bodyRef;
|
|
282
|
+
viewerHost;
|
|
283
|
+
// ── State ─────────────────────────────────────────────────
|
|
284
|
+
/** True while the editor is in edit mode. */
|
|
285
|
+
IsEditing = false;
|
|
286
|
+
/**
|
|
287
|
+
* True while a Save of the shown dashboard runs (the question about a save made elsewhere, the
|
|
288
|
+
* screenshot, then the save). Meanwhile Edit, Save, Cancel, Add part and part edits do nothing, and
|
|
289
|
+
* the header shows it.
|
|
290
|
+
*/
|
|
291
|
+
IsSaving = false;
|
|
292
|
+
/** The name field. */
|
|
293
|
+
EditingName = '';
|
|
294
|
+
/** The description field. */
|
|
295
|
+
EditingDescription = '';
|
|
296
|
+
/** The user's permissions for the shown dashboard. Nothing is allowed before a dashboard loads. */
|
|
297
|
+
Permissions = NO_PERMISSIONS;
|
|
298
|
+
/** Whether the part dialog is open to add a part or to edit one. Null while it is closed. */
|
|
299
|
+
PartDialogMode = null;
|
|
300
|
+
/** The part the part dialog edits in edit mode. */
|
|
301
|
+
EditPartPanel = null;
|
|
302
|
+
_dashboard = null;
|
|
303
|
+
viewer = null;
|
|
304
|
+
viewerRef = null;
|
|
305
|
+
/** Each load gets a number; a load whose number is no longer the latest stops at its next step. */
|
|
306
|
+
loadGeneration = 0;
|
|
307
|
+
/** Each edit session gets a number; a dialog answer from an ended session does nothing. */
|
|
308
|
+
editSession = 0;
|
|
309
|
+
/** The saved name and description when the current edit session began. */
|
|
310
|
+
editBaseline = null;
|
|
311
|
+
/** The saved name last reported through NameChanged (or the name at load). */
|
|
312
|
+
reportedName = null;
|
|
313
|
+
destroy$ = new Subject();
|
|
314
|
+
/** Ends the subscriptions to the current viewer's events. */
|
|
315
|
+
viewerChanged$ = new Subject();
|
|
316
|
+
// ── Read-only state for hosts ─────────────────────────────
|
|
317
|
+
/** The shown dashboard: the instance DashboardEngine holds. Null before a load. */
|
|
318
|
+
get Dashboard() { return this._dashboard; }
|
|
319
|
+
/** The viewer, for hosts that drive it (the dashboard tab's agent tools). Null before a load. */
|
|
320
|
+
get Viewer() { return this.viewer; }
|
|
321
|
+
/** The element that holds the dashboard: the Save screenshot, the pin thumbnail and the agent's screenshot capture it. */
|
|
322
|
+
get BodyElement() { return this.bodyRef.nativeElement; }
|
|
323
|
+
/** True when the user can edit the shown dashboard. */
|
|
324
|
+
get CanEdit() { return this.Permissions.CanEdit; }
|
|
325
|
+
/** The part types the part dialog offers: the viewer's part types. */
|
|
326
|
+
get PartTypes() { return this.viewer?.GetPartTypes() ?? NO_PART_TYPES; }
|
|
327
|
+
/** True when someone else owns the dashboard and shared it with the user. */
|
|
328
|
+
get IsSharedWithUser() { return !this.Permissions.IsOwner && this.Permissions.PermissionSource !== 'none'; }
|
|
329
|
+
/** True when the shown dashboard is one of the user's favorites. False when the favorites cannot be read. */
|
|
330
|
+
get IsFavorite() {
|
|
331
|
+
const id = this._dashboard?.ID;
|
|
332
|
+
if (!id)
|
|
333
|
+
return false;
|
|
334
|
+
try {
|
|
335
|
+
return this.favoritesService.IsFavorite(id);
|
|
336
|
+
}
|
|
337
|
+
catch {
|
|
338
|
+
return false;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
/** True while editing with changes the viewer or the fields have not saved. */
|
|
342
|
+
get HasUnsavedChanges() {
|
|
343
|
+
const dashboard = this._dashboard;
|
|
344
|
+
if (!this.IsEditing || !dashboard)
|
|
345
|
+
return false;
|
|
346
|
+
return (this.viewer?.HasUnsavedChanges ?? false)
|
|
347
|
+
|| this.EditingName !== dashboard.Name
|
|
348
|
+
|| this.EditingDescription !== (dashboard.Description ?? '');
|
|
349
|
+
}
|
|
350
|
+
// ── Lifecycle ─────────────────────────────────────────────
|
|
351
|
+
ngAfterViewInit() {
|
|
352
|
+
this.viewInitialized = true;
|
|
353
|
+
this.watchSavedDashboard();
|
|
354
|
+
this.watchFavorites();
|
|
355
|
+
if (this._dashboardId)
|
|
356
|
+
void this.load();
|
|
357
|
+
}
|
|
358
|
+
/** Stops a load in progress at its next step, ends the subscriptions and destroys the viewer. */
|
|
359
|
+
ngOnDestroy() {
|
|
360
|
+
this.loadGeneration++;
|
|
361
|
+
this.destroy$.next();
|
|
362
|
+
this.destroy$.complete();
|
|
363
|
+
this.dropViewer();
|
|
364
|
+
}
|
|
365
|
+
// ── Load ──────────────────────────────────────────────────
|
|
366
|
+
/**
|
|
367
|
+
* Shows the dashboard DashboardId names. Drops the dashboard shown before. When the dashboard cannot
|
|
368
|
+
* be shown, the editor shows none (an edit begun while the layout built ends) and emits LoadFailed.
|
|
369
|
+
*/
|
|
370
|
+
async load() {
|
|
371
|
+
const generation = ++this.loadGeneration;
|
|
372
|
+
this.teardownViewer();
|
|
373
|
+
const id = this._dashboardId;
|
|
374
|
+
if (!id) {
|
|
375
|
+
SafeDetectChanges(this.cdr);
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
try {
|
|
379
|
+
const dashboard = await this.findConfigDashboard(id);
|
|
380
|
+
if (generation !== this.loadGeneration)
|
|
381
|
+
return;
|
|
382
|
+
await this.showDashboard(dashboard, generation);
|
|
383
|
+
}
|
|
384
|
+
catch (error) {
|
|
385
|
+
if (generation !== this.loadGeneration)
|
|
386
|
+
return;
|
|
387
|
+
this.teardownViewer();
|
|
388
|
+
SafeDetectChanges(this.cdr);
|
|
389
|
+
LogError(`Dashboard editor: could not load dashboard ${id}: ${errorMessage(error)}`);
|
|
390
|
+
this.LoadFailed.emit({ DashboardId: id, Message: errorMessage(error) });
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
/** The Config dashboard with this ID in DashboardEngine. Rejects when the engine has none, or it is a Code dashboard. */
|
|
394
|
+
async findConfigDashboard(id) {
|
|
395
|
+
const provider = this.ProviderToUse;
|
|
396
|
+
await DashboardEngine.Instance.Config(false, provider.CurrentUser, provider);
|
|
397
|
+
const dashboard = DashboardEngine.Instance.Dashboards.find(d => UUIDsEqual(d.ID, id));
|
|
398
|
+
if (!dashboard)
|
|
399
|
+
throw new Error(`Dashboard with ID ${id} not found.`);
|
|
400
|
+
if (dashboard.Type === 'Code')
|
|
401
|
+
throw new Error(`"${dashboard.Name}" is a Code dashboard. The editor shows Config dashboards.`);
|
|
402
|
+
return dashboard;
|
|
403
|
+
}
|
|
404
|
+
/**
|
|
405
|
+
* Creates the viewer and shows the dashboard in it. The viewer is configured, and its events wired,
|
|
406
|
+
* before it gets the dashboard, because the dashboard starts the layout. Once the layout is ready the
|
|
407
|
+
* editor enters edit mode when StartInEditMode asks for it, then emits Loaded.
|
|
408
|
+
*/
|
|
409
|
+
async showDashboard(dashboard, generation) {
|
|
410
|
+
const viewer = this.createViewer();
|
|
411
|
+
this._dashboard = dashboard;
|
|
412
|
+
this.reportedName = dashboard.Name;
|
|
413
|
+
this.Permissions = this.readPermissions(dashboard);
|
|
414
|
+
this.configureViewer(viewer);
|
|
415
|
+
viewer.Dashboard = dashboard; // starts the layout
|
|
416
|
+
SafeDetectChanges(this.cdr); // the header shows while the layout builds
|
|
417
|
+
await viewer.WaitForLayoutReady();
|
|
418
|
+
if (generation !== this.loadGeneration)
|
|
419
|
+
return;
|
|
420
|
+
this.applyStartInEditMode();
|
|
421
|
+
SafeDetectChanges(this.cdr);
|
|
422
|
+
this.Loaded.emit(dashboard);
|
|
423
|
+
}
|
|
424
|
+
/** Creates the viewer in the body. Its host element fills the body: component CSS cannot reach a created host. */
|
|
425
|
+
createViewer() {
|
|
426
|
+
const ref = this.viewerFactory.Create(this.viewerHost);
|
|
427
|
+
const element = ref.location.nativeElement;
|
|
428
|
+
element.style.width = '100%';
|
|
429
|
+
element.style.height = '100%';
|
|
430
|
+
this.viewerRef = ref;
|
|
431
|
+
this.viewer = ref.instance;
|
|
432
|
+
return ref.instance;
|
|
433
|
+
}
|
|
434
|
+
/** Sets the viewer up for the editor, which shows the header, and passes the viewer's events on. */
|
|
435
|
+
configureViewer(viewer) {
|
|
436
|
+
viewer.Provider = this.Provider;
|
|
437
|
+
viewer.ShowToolbar = false;
|
|
438
|
+
viewer.ShowBreadcrumb = false;
|
|
439
|
+
viewer.ShowOpenInTabButton = false;
|
|
440
|
+
viewer.ShowEditButton = false;
|
|
441
|
+
viewer.CanEdit = this.Permissions.CanEdit;
|
|
442
|
+
viewer.Categories = DashboardEngine.Instance.DashboardCategories;
|
|
443
|
+
viewer.NavigationRequested.pipe(takeUntil(this.viewerChanged$)).subscribe(event => this.NavigationRequested.emit(event));
|
|
444
|
+
viewer.DashboardSaved.pipe(takeUntil(this.viewerChanged$)).subscribe(saved => this.onViewerSaved(saved));
|
|
445
|
+
viewer.error.pipe(takeUntil(this.viewerChanged$)).subscribe(e => LogError(`Dashboard editor: ${e.message}${e.error ? `: ${e.error.message}` : ''}`));
|
|
446
|
+
viewer.configChanged.pipe(takeUntil(this.viewerChanged$)).subscribe(() => this.ConfigChanged.emit());
|
|
447
|
+
viewer.PanelInteraction.pipe(takeUntil(this.viewerChanged$)).subscribe(event => this.onPanelInteraction(event));
|
|
448
|
+
}
|
|
449
|
+
/** Destroys the viewer and forgets the dashboard. An edit in progress is dropped, and EditingChange says so. */
|
|
450
|
+
teardownViewer() {
|
|
451
|
+
if (this.dropViewer())
|
|
452
|
+
this.EditingChange.emit(false);
|
|
453
|
+
}
|
|
454
|
+
/**
|
|
455
|
+
* Destroys the viewer and forgets the dashboard and its edit. A Save of that dashboard still running
|
|
456
|
+
* stops at its next step, and IsSaving is false for the next dashboard. Returns whether an edit was in
|
|
457
|
+
* progress.
|
|
458
|
+
*/
|
|
459
|
+
dropViewer() {
|
|
460
|
+
const wasEditing = this.IsEditing;
|
|
461
|
+
this.viewerChanged$.next();
|
|
462
|
+
this.closePartDialogs();
|
|
463
|
+
this.IsEditing = false;
|
|
464
|
+
this.IsSaving = false;
|
|
465
|
+
this.editSession++;
|
|
466
|
+
this.viewerRef?.destroy();
|
|
467
|
+
this.viewerRef = null;
|
|
468
|
+
this.viewer = null;
|
|
469
|
+
this._dashboard = null;
|
|
470
|
+
this.reportedName = null;
|
|
471
|
+
this.Permissions = NO_PERMISSIONS;
|
|
472
|
+
return wasEditing;
|
|
473
|
+
}
|
|
474
|
+
/** Enters edit mode once when the host asked for it with StartInEditMode, and resets the input. */
|
|
475
|
+
applyStartInEditMode() {
|
|
476
|
+
if (!this.StartInEditMode)
|
|
477
|
+
return;
|
|
478
|
+
this.StartInEditMode = false;
|
|
479
|
+
this.StartInEditModeChange.emit(false);
|
|
480
|
+
this.EnterEditMode();
|
|
481
|
+
}
|
|
482
|
+
/** Reads the user's permissions for the dashboard again, for example after the host's Share dialog saved. */
|
|
483
|
+
RefreshPermissions() {
|
|
484
|
+
this.Permissions = this.readPermissions(this._dashboard);
|
|
485
|
+
if (this.viewer)
|
|
486
|
+
this.viewer.CanEdit = this.Permissions.CanEdit;
|
|
487
|
+
SafeDetectChanges(this.cdr);
|
|
488
|
+
}
|
|
489
|
+
/** The user's permissions for a dashboard. None without a dashboard. */
|
|
490
|
+
readPermissions(dashboard) {
|
|
491
|
+
return dashboard
|
|
492
|
+
? DashboardEngine.Instance.GetDashboardPermissions(dashboard.ID, this.ProviderToUse.CurrentUser.ID)
|
|
493
|
+
: NO_PERMISSIONS;
|
|
494
|
+
}
|
|
495
|
+
// ── Edit mode ─────────────────────────────────────────────
|
|
496
|
+
/**
|
|
497
|
+
* Enters edit mode when the user can edit. Returns true when the editor is editing afterwards, and
|
|
498
|
+
* false, changing nothing, while a Save runs: that Save reads the name and description fields.
|
|
499
|
+
*/
|
|
500
|
+
EnterEditMode() {
|
|
501
|
+
if (this.IsSaving)
|
|
502
|
+
return false;
|
|
503
|
+
if (this.IsEditing)
|
|
504
|
+
return true;
|
|
505
|
+
if (!this.startEditing())
|
|
506
|
+
return false;
|
|
507
|
+
SafeDetectChanges(this.cdr);
|
|
508
|
+
this.EditingChange.emit(true);
|
|
509
|
+
return true;
|
|
510
|
+
}
|
|
511
|
+
/** Leaves edit mode without saving, or enters it. */
|
|
512
|
+
ToggleEditMode() {
|
|
513
|
+
if (this.IsEditing)
|
|
514
|
+
this.CancelEdit();
|
|
515
|
+
else
|
|
516
|
+
this.EnterEditMode();
|
|
517
|
+
}
|
|
518
|
+
/**
|
|
519
|
+
* Puts the editor and its viewer in edit mode. Returns false, and changes nothing, without a dashboard
|
|
520
|
+
* or when the user cannot edit it. Takes DashboardEngine's current copy first: when the dashboard was
|
|
521
|
+
* saved elsewhere since the viewer loaded it, the viewer reloads the saved layout, so this edit's save
|
|
522
|
+
* keeps it. Notes the saved name and description, so Save can tell a save made elsewhere during the
|
|
523
|
+
* edit (see savedElsewhereSinceEditStart).
|
|
524
|
+
*/
|
|
525
|
+
startEditing() {
|
|
526
|
+
if (!this._dashboard || !this.Permissions.CanEdit)
|
|
527
|
+
return false;
|
|
528
|
+
this.syncWithSavedDashboard();
|
|
529
|
+
const dashboard = this._dashboard; // the sync can take a newer copy
|
|
530
|
+
this.IsEditing = true;
|
|
531
|
+
this.editSession++;
|
|
532
|
+
this.EditingName = dashboard.Name;
|
|
533
|
+
this.EditingDescription = dashboard.Description || '';
|
|
534
|
+
this.editBaseline = { Name: dashboard.Name, Description: dashboard.Description ?? '' };
|
|
535
|
+
if (this.viewer)
|
|
536
|
+
this.viewer.IsEditing = true;
|
|
537
|
+
return true;
|
|
538
|
+
}
|
|
539
|
+
/**
|
|
540
|
+
* Leaves edit mode and drops the changes. The viewer reloads the saved layout before it leaves edit
|
|
541
|
+
* mode: it drops its layout at once, so leaving edit mode applies to the layout it rebuilds. Does
|
|
542
|
+
* nothing while a Save runs.
|
|
543
|
+
*/
|
|
544
|
+
CancelEdit() {
|
|
545
|
+
if (this.IsSaving || !this.IsEditing)
|
|
546
|
+
return;
|
|
547
|
+
this.IsEditing = false;
|
|
548
|
+
this.editSession++;
|
|
549
|
+
this.closePartDialogs();
|
|
550
|
+
this.reloadSavedDashboard();
|
|
551
|
+
if (this.viewer)
|
|
552
|
+
this.viewer.IsEditing = false;
|
|
553
|
+
SafeDetectChanges(this.cdr);
|
|
554
|
+
this.EditingChange.emit(false);
|
|
555
|
+
}
|
|
556
|
+
// ── Save ──────────────────────────────────────────────────
|
|
557
|
+
/**
|
|
558
|
+
* Saves the name, description and layout, then leaves edit mode. An empty name is never saved: the
|
|
559
|
+
* previous name is kept. When the save fails, the editor stays in edit mode (see onSaveFailed). One
|
|
560
|
+
* Save runs at a time (see IsSaving).
|
|
561
|
+
*/
|
|
562
|
+
async SaveDashboard() {
|
|
563
|
+
await this.saveAndLeaveEditMode();
|
|
564
|
+
}
|
|
565
|
+
/**
|
|
566
|
+
* Saves like SaveDashboard. Returns null when saved, also when the editor shows another dashboard by
|
|
567
|
+
* the time the viewer has saved; else why not. Outside edit mode it saves the dashboard's own name and
|
|
568
|
+
* description. `overrides` replace the fields first. While a Save runs it refuses at once and leaves
|
|
569
|
+
* the fields as they are.
|
|
570
|
+
*/
|
|
571
|
+
async Save(overrides = {}) {
|
|
572
|
+
if (this.IsSaving)
|
|
573
|
+
return DASHBOARD_SAVE_IN_PROGRESS;
|
|
574
|
+
const dashboard = this._dashboard;
|
|
575
|
+
if (dashboard && !this.IsEditing) {
|
|
576
|
+
this.EditingName = dashboard.Name;
|
|
577
|
+
this.EditingDescription = dashboard.Description || '';
|
|
578
|
+
}
|
|
579
|
+
if (overrides.Name !== undefined)
|
|
580
|
+
this.EditingName = overrides.Name;
|
|
581
|
+
if (overrides.Description !== undefined)
|
|
582
|
+
this.EditingDescription = overrides.Description;
|
|
583
|
+
return this.saveAndLeaveEditMode();
|
|
584
|
+
}
|
|
585
|
+
/**
|
|
586
|
+
* The work of SaveDashboard and Save. Returns null when saved, else why not: no dashboard is open, a
|
|
587
|
+
* Save runs, another save of the dashboard object runs (see stopWhileSavedElsewhere), the user kept
|
|
588
|
+
* editing rather than replace a save made elsewhere, the editor showed another dashboard before the
|
|
589
|
+
* viewer saved, or the save failed (the user has seen that reason). A Save belongs to the viewer it
|
|
590
|
+
* started with: once the editor shows another dashboard, the Save changes nothing in the editor and
|
|
591
|
+
* leaves IsSaving to that dashboard. A save the viewer already made still returns null. See
|
|
592
|
+
* prepareSave for the steps before the viewer saves.
|
|
593
|
+
*/
|
|
594
|
+
async saveAndLeaveEditMode() {
|
|
595
|
+
const dashboard = this._dashboard;
|
|
596
|
+
const viewer = this.viewer;
|
|
597
|
+
if (!dashboard || !viewer)
|
|
598
|
+
return 'No dashboard is open.';
|
|
599
|
+
if (this.IsSaving)
|
|
600
|
+
return DASHBOARD_SAVE_IN_PROGRESS;
|
|
601
|
+
const savingElsewhere = this.stopWhileSavedElsewhere(dashboard);
|
|
602
|
+
if (savingElsewhere)
|
|
603
|
+
return savingElsewhere;
|
|
604
|
+
this.IsSaving = true;
|
|
605
|
+
SafeDetectChanges(this.cdr);
|
|
606
|
+
try {
|
|
607
|
+
const stopped = await this.prepareSave(dashboard, viewer);
|
|
608
|
+
if (stopped)
|
|
609
|
+
return stopped;
|
|
610
|
+
if (!await viewer.save())
|
|
611
|
+
return this.onSaveFailed(dashboard);
|
|
612
|
+
if (viewer === this.viewer)
|
|
613
|
+
this.leaveEditModeAfterSave(viewer);
|
|
614
|
+
return null;
|
|
615
|
+
}
|
|
616
|
+
catch (error) {
|
|
617
|
+
return this.onSaveFailed(dashboard, error);
|
|
618
|
+
}
|
|
619
|
+
finally {
|
|
620
|
+
if (viewer === this.viewer) {
|
|
621
|
+
this.IsSaving = false;
|
|
622
|
+
SafeDetectChanges(this.cdr);
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
/**
|
|
627
|
+
* The steps of a Save before the viewer saves. It asks first when the dashboard was saved elsewhere
|
|
628
|
+
* since this edit began. Then it takes the screenshot and writes it, the name and the description to
|
|
629
|
+
* the dashboard; the fields are read after the screenshot, so a name typed meanwhile is saved. Returns
|
|
630
|
+
* why the Save stops, or null to save. When the editor shows another dashboard after a step, or another
|
|
631
|
+
* save of the dashboard object started meanwhile, it stops and writes nothing.
|
|
632
|
+
*/
|
|
633
|
+
async prepareSave(dashboard, viewer) {
|
|
634
|
+
const replace = !this.savedElsewhereSinceEditStart() || await this.confirmOverwrite();
|
|
635
|
+
if (viewer !== this.viewer)
|
|
636
|
+
return DASHBOARD_CHANGED_BEFORE_SAVE;
|
|
637
|
+
if (!replace)
|
|
638
|
+
return KEPT_EDITING;
|
|
639
|
+
// The screenshot goes on the dashboard (Thumbnail), so this same Save stores the picture the
|
|
640
|
+
// dashboard cards show. A failed Save reverts it with the other fields.
|
|
641
|
+
const thumbnail = await this.takeDashboardThumbnail(viewer);
|
|
642
|
+
if (viewer !== this.viewer)
|
|
643
|
+
return DASHBOARD_CHANGED_BEFORE_SAVE;
|
|
644
|
+
const savingElsewhere = this.stopWhileSavedElsewhere(dashboard);
|
|
645
|
+
if (savingElsewhere)
|
|
646
|
+
return savingElsewhere;
|
|
647
|
+
if (thumbnail !== undefined)
|
|
648
|
+
dashboard.Thumbnail = thumbnail;
|
|
649
|
+
this.restoreEmptyName();
|
|
650
|
+
dashboard.Name = this.EditingName;
|
|
651
|
+
dashboard.Description = this.EditingDescription;
|
|
652
|
+
return null;
|
|
653
|
+
}
|
|
654
|
+
/**
|
|
655
|
+
* When another save of the dashboard object runs (Home's rename, for example), tells the user and returns why this
|
|
656
|
+
* Save stops: BaseEntity's Save would join that save and write none of this Save's changes. The editor stays in edit
|
|
657
|
+
* mode with the user's changes. Null when no other save runs.
|
|
658
|
+
*/
|
|
659
|
+
stopWhileSavedElsewhere(dashboard) {
|
|
660
|
+
if (!dashboard.IsSaving)
|
|
661
|
+
return null;
|
|
662
|
+
const message = `"${dashboard.Name}" is being saved elsewhere. Save again in a moment.`;
|
|
663
|
+
MJNotificationService.Instance.CreateSimpleNotification(message, 'info', 3000);
|
|
664
|
+
return message;
|
|
665
|
+
}
|
|
666
|
+
/** Leaves edit mode after a successful save. A Save outside edit mode (an agent's request) emits no EditingChange. */
|
|
667
|
+
leaveEditModeAfterSave(viewer) {
|
|
668
|
+
const wasEditing = this.IsEditing;
|
|
669
|
+
this.IsEditing = false;
|
|
670
|
+
this.editSession++;
|
|
671
|
+
this.closePartDialogs();
|
|
672
|
+
viewer.IsEditing = false;
|
|
673
|
+
SafeDetectChanges(this.cdr);
|
|
674
|
+
if (wasEditing)
|
|
675
|
+
this.EditingChange.emit(false);
|
|
676
|
+
}
|
|
677
|
+
/**
|
|
678
|
+
* The dashboard's new Thumbnail: a small screenshot of the dashboard body, or null when the layout has
|
|
679
|
+
* no panels, so the dashboard cards show the icon. Undefined when no screenshot is ready in time (too
|
|
680
|
+
* many elements, no size, or the timeout): the current Thumbnail stays. Never throws.
|
|
681
|
+
*/
|
|
682
|
+
async takeDashboardThumbnail(viewer) {
|
|
683
|
+
try {
|
|
684
|
+
if (ExtractPanelsFromLayout(viewer.getConfig()?.layout ?? null).length === 0)
|
|
685
|
+
return null;
|
|
686
|
+
return (await this.pinService.CaptureThumbnail(this.BodyElement, SAVE_SCREENSHOT_TIMEOUT_MS)) || undefined;
|
|
687
|
+
}
|
|
688
|
+
catch (error) {
|
|
689
|
+
LogError(`Dashboard editor: could not take a screenshot of the dashboard: ${errorMessage(error)}`);
|
|
690
|
+
return undefined;
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
/** Keeps the previous name when the user leaves the name field empty or with only spaces. */
|
|
694
|
+
OnNameBlur() {
|
|
695
|
+
this.restoreEmptyName();
|
|
696
|
+
}
|
|
697
|
+
/** Puts the saved name back into an empty or spaces-only name field. */
|
|
698
|
+
restoreEmptyName() {
|
|
699
|
+
if (!this.EditingName.trim()) {
|
|
700
|
+
this.EditingName = this._dashboard?.Name || 'Untitled Dashboard';
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
/**
|
|
704
|
+
* Handles a save that did not complete. The editor stays in edit mode with the user's changes, the
|
|
705
|
+
* cached dashboard (which other pages read) goes back to its saved values, and the user sees why.
|
|
706
|
+
* Returns the message the user sees.
|
|
707
|
+
*/
|
|
708
|
+
onSaveFailed(dashboard, error) {
|
|
709
|
+
const reason = this.saveFailureReason(dashboard, error);
|
|
710
|
+
dashboard.Revert();
|
|
711
|
+
LogError(`Dashboard editor: could not save the dashboard: ${reason || 'no reason given'}`);
|
|
712
|
+
const message = reason ? `Could not save the dashboard: ${reason}` : 'Could not save the dashboard';
|
|
713
|
+
MJNotificationService.Instance.CreateSimpleNotification(message, 'error', 5000);
|
|
714
|
+
SafeDetectChanges(this.cdr);
|
|
715
|
+
return message;
|
|
716
|
+
}
|
|
717
|
+
/** Why a save failed: the thrown error, else the dashboard's failed save result. Empty when neither says. */
|
|
718
|
+
saveFailureReason(dashboard, error) {
|
|
719
|
+
if (error !== undefined)
|
|
720
|
+
return errorMessage(error);
|
|
721
|
+
const latest = dashboard.LatestResult;
|
|
722
|
+
return latest && !latest.Success ? latest.CompleteMessage : '';
|
|
723
|
+
}
|
|
724
|
+
/** Passes a save of the viewer on to the host, with the new name when it changed. */
|
|
725
|
+
onViewerSaved(saved) {
|
|
726
|
+
this.Saved.emit(saved);
|
|
727
|
+
this.reportNameIfChanged(saved.Name);
|
|
728
|
+
}
|
|
729
|
+
/** Emits NameChanged when the saved name differs from the one last reported. An empty name is never reported. */
|
|
730
|
+
reportNameIfChanged(name) {
|
|
731
|
+
if (!name || name === this.reportedName)
|
|
732
|
+
return;
|
|
733
|
+
this.reportedName = name;
|
|
734
|
+
this.NameChanged.emit(name);
|
|
735
|
+
}
|
|
736
|
+
// ── Saved elsewhere ───────────────────────────────────────
|
|
737
|
+
/**
|
|
738
|
+
* Follows DashboardEngine's changes to the shown dashboard until the editor is destroyed. A burst of
|
|
739
|
+
* changes is handled once, after the current task.
|
|
740
|
+
*/
|
|
741
|
+
watchSavedDashboard() {
|
|
742
|
+
DashboardEngine.Instance.DataChange$.pipe(filter(event => this.isChangeToShownDashboard(event)), auditTime(0), takeUntil(this.destroy$)).subscribe(() => this.onSavedDashboardChanged());
|
|
743
|
+
}
|
|
744
|
+
/** True for a change to the shown dashboard's record, or for a reload of all dashboards. */
|
|
745
|
+
isChangeToShownDashboard(event) {
|
|
746
|
+
const shown = this._dashboard;
|
|
747
|
+
if (!shown || event.config.EntityName?.trim().toLowerCase() !== DASHBOARDS_ENTITY_NAME.toLowerCase()) {
|
|
748
|
+
return false;
|
|
749
|
+
}
|
|
750
|
+
const affected = event.affectedEntity;
|
|
751
|
+
return !affected || affected === shown || affected.PrimaryKey.Equals(shown.PrimaryKey);
|
|
752
|
+
}
|
|
753
|
+
/**
|
|
754
|
+
* Brings an idle editor in step with the saved dashboard after a change to it, and reports a rename
|
|
755
|
+
* saved elsewhere, also while editing: the host's title follows the saved name, and the name the user
|
|
756
|
+
* typed stays in the field.
|
|
757
|
+
*/
|
|
758
|
+
onSavedDashboardChanged() {
|
|
759
|
+
if (!this.IsEditing && this.syncWithSavedDashboard() === 'reloaded') {
|
|
760
|
+
this.ReloadedFromSaved.emit();
|
|
761
|
+
}
|
|
762
|
+
this.reportNameIfChanged((this.savedDashboard() ?? this._dashboard)?.Name);
|
|
763
|
+
SafeDetectChanges(this.cdr);
|
|
764
|
+
}
|
|
765
|
+
/**
|
|
766
|
+
* Brings the shown dashboard in step with DashboardEngine's current copy of it. The viewer rebuilds
|
|
767
|
+
* only when the saved layout differs from the one it shows. When only the copy is new (DashboardEngine
|
|
768
|
+
* reloaded its dashboards), the editor and the viewer take it without a rebuild: a save writes every
|
|
769
|
+
* field of the entity, so saving an old copy would write its old values.
|
|
770
|
+
*/
|
|
771
|
+
syncWithSavedDashboard() {
|
|
772
|
+
const viewer = this.viewer;
|
|
773
|
+
const saved = this.savedDashboard();
|
|
774
|
+
if (!viewer || !saved)
|
|
775
|
+
return 'unchanged';
|
|
776
|
+
if (viewer.HasNewerSavedLayout(saved)) {
|
|
777
|
+
this.reloadSavedDashboard();
|
|
778
|
+
return 'reloaded';
|
|
779
|
+
}
|
|
780
|
+
if (saved !== this._dashboard && viewer.UseSavedCopy(saved)) {
|
|
781
|
+
this._dashboard = saved;
|
|
782
|
+
return 'took-copy';
|
|
783
|
+
}
|
|
784
|
+
return 'unchanged';
|
|
785
|
+
}
|
|
786
|
+
/** Shows the saved dashboard in the viewer: DashboardEngine's copy of it when the engine has one. */
|
|
787
|
+
reloadSavedDashboard() {
|
|
788
|
+
const viewer = this.viewer;
|
|
789
|
+
const saved = this.savedDashboard() ?? this._dashboard;
|
|
790
|
+
if (!viewer || !saved)
|
|
791
|
+
return;
|
|
792
|
+
this._dashboard = saved;
|
|
793
|
+
viewer.ReloadFromSaved(saved).catch((error) => {
|
|
794
|
+
LogError(`Dashboard editor: could not reload the saved dashboard: ${errorMessage(error)}`);
|
|
795
|
+
});
|
|
796
|
+
}
|
|
797
|
+
/** The shown dashboard as DashboardEngine holds it now. Null when the engine does not have it. */
|
|
798
|
+
savedDashboard() {
|
|
799
|
+
const shown = this._dashboard;
|
|
800
|
+
if (!shown)
|
|
801
|
+
return null;
|
|
802
|
+
try {
|
|
803
|
+
return DashboardEngine.Instance.Dashboards.find(d => UUIDsEqual(d.ID, shown.ID)) ?? null;
|
|
804
|
+
}
|
|
805
|
+
catch {
|
|
806
|
+
return null; // the user cannot read the dashboards cache
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
/**
|
|
810
|
+
* True when the dashboard was saved elsewhere after the current edit session began: another layout,
|
|
811
|
+
* name or description. False outside edit mode, where no edit session runs.
|
|
812
|
+
*/
|
|
813
|
+
savedElsewhereSinceEditStart() {
|
|
814
|
+
const saved = this.savedDashboard() ?? this._dashboard;
|
|
815
|
+
const baseline = this.editBaseline;
|
|
816
|
+
if (!this.IsEditing || !saved || !baseline || !this.viewer)
|
|
817
|
+
return false;
|
|
818
|
+
return this.viewer.HasNewerSavedLayout(saved)
|
|
819
|
+
|| saved.Name !== baseline.Name
|
|
820
|
+
|| (saved.Description ?? '') !== baseline.Description;
|
|
821
|
+
}
|
|
822
|
+
/** Asks whether to replace a save made elsewhere. Resolves true for Save anyway, false for Keep editing. */
|
|
823
|
+
confirmOverwrite() {
|
|
824
|
+
return this.confirmService.Confirm({
|
|
825
|
+
title: 'Replace changes saved elsewhere?',
|
|
826
|
+
message: 'This dashboard was saved in another place after you started editing. Saving now replaces those changes.',
|
|
827
|
+
type: 'warning',
|
|
828
|
+
confirmText: 'Save anyway',
|
|
829
|
+
cancelText: 'Keep editing',
|
|
830
|
+
});
|
|
831
|
+
}
|
|
832
|
+
// ── Parts ─────────────────────────────────────────────────
|
|
833
|
+
/** Routes the viewer's part requests. Outside edit mode, only Add part on an empty dashboard counts: it enters edit mode first. */
|
|
834
|
+
onPanelInteraction(event) {
|
|
835
|
+
if (event.interactionType !== 'custom' || this.IsSaving)
|
|
836
|
+
return;
|
|
837
|
+
const action = event.payload?.['action'];
|
|
838
|
+
if (!this.IsEditing && (action !== 'add-panel-requested' || !this.EnterEditMode()))
|
|
839
|
+
return;
|
|
840
|
+
switch (action) {
|
|
841
|
+
case 'add-panel-requested':
|
|
842
|
+
this.OpenAddPartDialog();
|
|
843
|
+
break;
|
|
844
|
+
case 'configure-part-requested':
|
|
845
|
+
this.openEditPartDialog(event.panelId);
|
|
846
|
+
break;
|
|
847
|
+
case 'remove-part-requested':
|
|
848
|
+
void this.confirmRemovePart(event.panelId);
|
|
849
|
+
break;
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
/** Opens the part dialog to add a part. Only while editing and not saving. */
|
|
853
|
+
OpenAddPartDialog() {
|
|
854
|
+
if (!this.IsEditing || this.IsSaving)
|
|
855
|
+
return;
|
|
856
|
+
this.EditPartPanel = null;
|
|
857
|
+
this.PartDialogMode = 'add';
|
|
858
|
+
SafeDetectChanges(this.cdr);
|
|
859
|
+
}
|
|
860
|
+
/** Opens the part dialog to edit the part with this panel ID. */
|
|
861
|
+
openEditPartDialog(panelId) {
|
|
862
|
+
const panel = this.viewer?.GetPanel(panelId) ?? null;
|
|
863
|
+
if (!panel)
|
|
864
|
+
return;
|
|
865
|
+
this.EditPartPanel = panel;
|
|
866
|
+
this.PartDialogMode = 'edit';
|
|
867
|
+
SafeDetectChanges(this.cdr);
|
|
868
|
+
}
|
|
869
|
+
/** Adds the part, or applies the edited part, from the part dialog. */
|
|
870
|
+
async OnPartDialogSaved(result) {
|
|
871
|
+
const mode = this.PartDialogMode;
|
|
872
|
+
const panel = this.EditPartPanel;
|
|
873
|
+
this.closePartDialogs();
|
|
874
|
+
if (mode === 'add') {
|
|
875
|
+
await this.applyPartChange('Could not add the part', async (v) => { await v.AddPanel(result.PartType.ID, result.Config, result.Title, result.Icon); });
|
|
876
|
+
}
|
|
877
|
+
else if (mode === 'edit' && panel) {
|
|
878
|
+
await this.applyPartChange('Could not update the part', v => v.UpdatePanelConfig(panel.id, result.Config, result.Title, result.Icon));
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
/** Closes the part dialog. */
|
|
882
|
+
ClosePartDialog() {
|
|
883
|
+
this.closePartDialogs();
|
|
884
|
+
SafeDetectChanges(this.cdr);
|
|
885
|
+
}
|
|
886
|
+
/** Closes every part dialog. A part dialog belongs to one edit session. */
|
|
887
|
+
closePartDialogs() {
|
|
888
|
+
this.PartDialogMode = null;
|
|
889
|
+
this.EditPartPanel = null;
|
|
890
|
+
}
|
|
891
|
+
/**
|
|
892
|
+
* Asks the user to confirm removing a part, then removes it. Changes nothing when the user cancels, or when the
|
|
893
|
+
* edit session, the viewer or the part changed while the confirm was open.
|
|
894
|
+
*/
|
|
895
|
+
async confirmRemovePart(panelId) {
|
|
896
|
+
const viewer = this.viewer;
|
|
897
|
+
const panel = viewer?.GetPanel(panelId);
|
|
898
|
+
if (!viewer || !panel)
|
|
899
|
+
return;
|
|
900
|
+
const session = this.editSession;
|
|
901
|
+
const confirmed = await this.confirmService.ConfirmDelete(RemovePartConfirmOptions(panel.title, viewer.GetPartTypeForPanel(panelId)?.Name));
|
|
902
|
+
if (!confirmed || !this.IsEditing || session !== this.editSession || viewer !== this.viewer || !viewer.GetPanel(panelId))
|
|
903
|
+
return;
|
|
904
|
+
await this.applyPartChange('Could not remove the part', v => v.ConfirmRemovePanel(panelId));
|
|
905
|
+
}
|
|
906
|
+
/**
|
|
907
|
+
* Applies one part change to the viewer, tells the user when it fails, and reports the change through
|
|
908
|
+
* ConfigChanged. The change is saved with the dashboard's Save. Does nothing while a Save runs, for
|
|
909
|
+
* example from a dialog that was open when the Save started.
|
|
910
|
+
*/
|
|
911
|
+
async applyPartChange(failureMessage, change) {
|
|
912
|
+
const viewer = this.viewer;
|
|
913
|
+
if (!viewer || this.IsSaving)
|
|
914
|
+
return;
|
|
915
|
+
try {
|
|
916
|
+
await change(viewer);
|
|
917
|
+
}
|
|
918
|
+
catch (error) {
|
|
919
|
+
LogError(`Dashboard editor: ${failureMessage}: ${errorMessage(error)}`);
|
|
920
|
+
MJNotificationService.Instance.CreateSimpleNotification(failureMessage, 'error', 3000);
|
|
921
|
+
}
|
|
922
|
+
this.ConfigChanged.emit();
|
|
923
|
+
}
|
|
924
|
+
// ── Favorite ──────────────────────────────────────────────
|
|
925
|
+
/** Shows the star's new state when any favorite changes, also on another page, until the editor is destroyed. */
|
|
926
|
+
watchFavorites() {
|
|
927
|
+
this.favoritesService.Changed$.pipe(takeUntil(this.destroy$)).subscribe(() => SafeDetectChanges(this.cdr));
|
|
928
|
+
}
|
|
929
|
+
/** Stars or unstars the shown dashboard, tells the user the new state, and emits it through FavoriteChange. */
|
|
930
|
+
async ToggleFavorite() {
|
|
931
|
+
const dashboard = this._dashboard;
|
|
932
|
+
if (!dashboard)
|
|
933
|
+
return;
|
|
934
|
+
try {
|
|
935
|
+
const isFavorite = await this.favoritesService.Toggle(dashboard.ID);
|
|
936
|
+
const message = isFavorite ? `Added "${dashboard.Name}" to favorites` : `Removed "${dashboard.Name}" from favorites`;
|
|
937
|
+
MJNotificationService.Instance.CreateSimpleNotification(message, 'success', 2000);
|
|
938
|
+
}
|
|
939
|
+
catch (error) {
|
|
940
|
+
LogError(`Dashboard editor: could not change the favorite: ${errorMessage(error)}`);
|
|
941
|
+
MJNotificationService.Instance.CreateSimpleNotification('Could not change the favorite', 'error', 3000);
|
|
942
|
+
}
|
|
943
|
+
this.FavoriteChange.emit(this.IsFavorite);
|
|
944
|
+
SafeDetectChanges(this.cdr);
|
|
945
|
+
}
|
|
946
|
+
static ɵfac = /*@__PURE__*/ (() => { let ɵDashboardEditorComponent_BaseFactory; return function DashboardEditorComponent_Factory(__ngFactoryType__) { return (ɵDashboardEditorComponent_BaseFactory || (ɵDashboardEditorComponent_BaseFactory = i0.ɵɵgetInheritedFactory(DashboardEditorComponent)))(__ngFactoryType__ || DashboardEditorComponent); }; })();
|
|
947
|
+
static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: DashboardEditorComponent, selectors: [["mj-dashboard-editor"]], viewQuery: function DashboardEditorComponent_Query(rf, ctx) { if (rf & 1) {
|
|
948
|
+
i0.ɵɵviewQuery(_c0, 7)(_c1, 7, ViewContainerRef);
|
|
949
|
+
} if (rf & 2) {
|
|
950
|
+
let _t;
|
|
951
|
+
i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.bodyRef = _t.first);
|
|
952
|
+
i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.viewerHost = _t.first);
|
|
953
|
+
} }, hostVars: 2, hostBindings: function DashboardEditorComponent_HostBindings(rf, ctx) { if (rf & 2) {
|
|
954
|
+
i0.ɵɵclassProp("dashboard-editor--fill", ctx.IsFill);
|
|
955
|
+
} }, inputs: { DashboardId: "DashboardId", StartInEditMode: "StartInEditMode", TitleTemplate: "TitleTemplate", ShowOpenInDashboards: "ShowOpenInDashboards", ShowFavorite: "ShowFavorite", BodyStyle: "BodyStyle" }, outputs: { StartInEditModeChange: "StartInEditModeChange", Loaded: "Loaded", LoadFailed: "LoadFailed", EditingChange: "EditingChange", Saved: "Saved", NameChanged: "NameChanged", ConfigChanged: "ConfigChanged", ReloadedFromSaved: "ReloadedFromSaved", FavoriteChange: "FavoriteChange", NavigationRequested: "NavigationRequested", OpenInDashboardsRequested: "OpenInDashboardsRequested" }, standalone: false, features: [i0.ɵɵInheritDefinitionFeature], ngContentSelectors: _c3, decls: 7, vars: 4, consts: [["body", ""], ["viewerHost", ""], [1, "dashboard-editor-header", 3, "is-editing"], [1, "dashboard-editor-main"], [1, "dashboard-editor-body"], [3, "Visible", "Mode", "PartTypes", "Panel", "Provider"], [1, "dashboard-editor-header"], ["Variant", "info", "Icon", "fa-solid fa-pen", "Label", "Editing", 1, "dashboard-editor-badge"], [1, "dashboard-editor-namewrap"], [1, "dashboard-editor-actions"], [3, "ngTemplateOutlet", "ngTemplateOutletContext"], [1, "dashboard-editor-title"], [1, "dashboard-editor-desc"], [1, "dashboard-editor-desc", "is-placeholder"], ["role", "img", "title", "Shared with you", "aria-label", "Shared with you", 1, "fa-solid", "fa-share-nodes", "dashboard-editor-shared"], ["type", "text", "aria-label", "Dashboard name", "placeholder", "Name this dashboard", 1, "mj-input", "dashboard-editor-name", 3, "ngModelChange", "blur", "ngModel"], ["type", "text", "aria-label", "Dashboard description", "placeholder", "Add a description", 1, "dashboard-editor-desc", "is-editable", 3, "ngModelChange", "ngModel"], ["type", "button", "mjButton", "", "Variant", "icon", "Size", "sm", 1, "dashboard-editor-favorite", 3, "is-favorite", "title"], ["type", "button", "mjButton", "", "Variant", "secondary", "Size", "sm", 1, "dashboard-editor-open-in"], ["type", "button", "mjButton", "", "Variant", "icon", "Size", "sm", 1, "dashboard-editor-favorite", 3, "click", "title"], ["aria-hidden", "true"], ["type", "button", "mjButton", "", "Variant", "secondary", "Size", "sm", 1, "dashboard-editor-open-in", 3, "click"], ["aria-hidden", "true", 1, "fa-solid", "fa-up-right-from-square"], ["aria-hidden", "true", 1, "dashboard-editor-divider"], ["type", "button", "mjButton", "", "Variant", "primary", "Size", "sm", 1, "dashboard-editor-edit", 3, "click", "disabled"], ["aria-hidden", "true", 1, "fa-solid", "fa-pen"], ["type", "button", "mjButton", "", "Variant", "secondary", "Size", "sm", 1, "dashboard-editor-add-part", 3, "click", "disabled"], ["aria-hidden", "true", 1, "fa-solid", "fa-plus"], ["type", "button", "mjButton", "", "Variant", "primary", "Size", "sm", 1, "dashboard-editor-save", 3, "click", "disabled"], ["type", "button", "mjButton", "", "Variant", "flat", "Size", "sm", 1, "dashboard-editor-cancel", 3, "click", "disabled"], [3, "Saved", "Cancelled", "Visible", "Mode", "PartTypes", "Panel", "Provider"]], template: function DashboardEditorComponent_Template(rf, ctx) { if (rf & 1) {
|
|
956
|
+
i0.ɵɵprojectionDef(_c2);
|
|
957
|
+
i0.ɵɵconditionalCreate(0, DashboardEditorComponent_Conditional_0_Template, 9, 5, "header", 2);
|
|
958
|
+
i0.ɵɵelementStart(1, "div", 3)(2, "div", 4, 0);
|
|
959
|
+
i0.ɵɵelementContainer(4, null, 1);
|
|
960
|
+
i0.ɵɵelementEnd()();
|
|
961
|
+
i0.ɵɵconditionalCreate(6, DashboardEditorComponent_Conditional_6_Template, 1, 5, "mj-dashboard-part-dialog", 5);
|
|
962
|
+
} if (rf & 2) {
|
|
963
|
+
let tmp_2_0;
|
|
964
|
+
i0.ɵɵconditional((tmp_2_0 = ctx.Dashboard) ? 0 : -1, tmp_2_0);
|
|
965
|
+
i0.ɵɵadvance();
|
|
966
|
+
i0.ɵɵclassProp("is-card", ctx.BodyStyle === "card");
|
|
967
|
+
i0.ɵɵadvance(5);
|
|
968
|
+
i0.ɵɵconditional(ctx.Dashboard && ctx.IsEditing && ctx.PartDialogMode ? 6 : -1);
|
|
969
|
+
} }, dependencies: [i1.NgTemplateOutlet, i2.DefaultValueAccessor, i2.NgControlStatus, i2.NgModel, i3.MJButtonDirective, i3.MJStatBadgeComponent, i4.DashboardPartDialogComponent], styles: ["[_nghost-%COMP%] { display: flex; flex-direction: column; min-width: 0; min-height: 0; }\n.dashboard-editor--fill[_nghost-%COMP%] { height: 100%; }\n\n.dashboard-editor-header[_ngcontent-%COMP%] {\n display: flex; align-items: center; gap: var(--mj-space-4); flex-shrink: 0;\n min-height: 72px; padding: var(--mj-space-3) var(--mj-space-8);\n background: var(--mj-bg-surface); border-bottom: 1px solid var(--mj-border-default);\n}\n.dashboard-editor-namewrap[_ngcontent-%COMP%] { display: flex; flex-direction: column; gap: 2px; flex: 1; min-width: 0; }\n.dashboard-editor-title[_ngcontent-%COMP%] {\n margin: 0; font-size: var(--mj-text-lg); font-weight: var(--mj-font-bold); letter-spacing: var(--mj-tracking-tight);\n color: var(--mj-text-primary); white-space: nowrap; overflow: hidden; text-overflow: ellipsis;\n}\n.dashboard-editor-shared[_ngcontent-%COMP%] { margin-left: var(--mj-space-2); font-size: var(--mj-text-xs); color: var(--mj-brand-primary); }\n\n\n.dashboard-editor-name[_ngcontent-%COMP%] { max-width: 520px; font-size: var(--mj-text-lg); font-weight: var(--mj-font-bold); letter-spacing: var(--mj-tracking-tight); }\n\n.dashboard-editor-desc[_ngcontent-%COMP%] {\n width: min(100%, 520px); margin: 0 0 0 -8px; padding: 3px 8px;\n font: inherit; font-size: var(--mj-text-xs); color: var(--mj-text-secondary);\n background: transparent; border: 1px solid transparent; border-radius: var(--mj-radius-md);\n}\n.dashboard-editor-desc.is-placeholder[_ngcontent-%COMP%], .dashboard-editor-desc[_ngcontent-%COMP%]::placeholder { color: var(--mj-text-muted); }\n.dashboard-editor-desc.is-editable[_ngcontent-%COMP%]:hover { border-color: var(--mj-border-default); background: var(--mj-bg-surface-card); }\n.dashboard-editor-desc.is-editable[_ngcontent-%COMP%]:focus {\n outline: none; border-color: var(--mj-border-focus); background: var(--mj-bg-surface); box-shadow: var(--mj-focus-ring);\n}\n\n.dashboard-editor-actions[_ngcontent-%COMP%] { display: flex; align-items: center; gap: var(--mj-space-2); flex-shrink: 0; }\n.dashboard-editor-divider[_ngcontent-%COMP%] { width: 1px; height: 22px; margin: 0 var(--mj-space-1); background: var(--mj-border-default); }\n\n.dashboard-editor-favorite.is-favorite[_ngcontent-%COMP%] i[_ngcontent-%COMP%] { color: var(--mj-status-warning); }\n\n.dashboard-editor-main[_ngcontent-%COMP%] { display: flex; flex-direction: column; flex: 1; min-height: 0; }\n.dashboard-editor-body[_ngcontent-%COMP%] { position: relative; flex: 1; min-height: 0; overflow: hidden; }\n\n\n.dashboard-editor-main.is-card[_ngcontent-%COMP%] { padding: var(--mj-space-4) var(--mj-space-8) var(--mj-space-8); }\n.dashboard-editor-main.is-card[_ngcontent-%COMP%] .dashboard-editor-body[_ngcontent-%COMP%] {\n flex: none; height: max(480px, calc(100vh - 230px));\n background: var(--mj-bg-surface); border: 1px solid var(--mj-border-default);\n border-radius: var(--mj-radius-lg); box-shadow: var(--mj-shadow-sm);\n}\n\n@media (max-width: 900px) {\n .dashboard-editor-header[_ngcontent-%COMP%] { flex-wrap: wrap; padding-left: var(--mj-space-4); padding-right: var(--mj-space-4); }\n .dashboard-editor-main.is-card[_ngcontent-%COMP%] { padding-left: var(--mj-space-4); padding-right: var(--mj-space-4); }\n}"] });
|
|
970
|
+
}
|
|
971
|
+
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(DashboardEditorComponent, [{
|
|
972
|
+
type: Component,
|
|
973
|
+
args: [{ standalone: false, selector: 'mj-dashboard-editor', template: "@if (Dashboard; as dashboard) {\n <header class=\"dashboard-editor-header\" [class.is-editing]=\"IsEditing\">\n @if (IsEditing) {\n <mj-stat-badge class=\"dashboard-editor-badge\" Variant=\"info\" Icon=\"fa-solid fa-pen\" Label=\"Editing\"></mj-stat-badge>\n }\n\n <div class=\"dashboard-editor-namewrap\">\n @if (!IsEditing) {\n @if (TitleTemplate) {\n <ng-container [ngTemplateOutlet]=\"TitleTemplate\" [ngTemplateOutletContext]=\"{ $implicit: dashboard, Dashboard: dashboard }\"></ng-container>\n } @else {\n <h1 class=\"dashboard-editor-title\">\n {{ dashboard.Name }}\n @if (IsSharedWithUser) {\n <i class=\"fa-solid fa-share-nodes dashboard-editor-shared\" role=\"img\" title=\"Shared with you\" aria-label=\"Shared with you\"></i>\n }\n </h1>\n }\n @if (dashboard.Description) {\n <p class=\"dashboard-editor-desc\">{{ dashboard.Description }}</p>\n } @else if (CanEdit) {\n <p class=\"dashboard-editor-desc is-placeholder\">Add a description</p>\n }\n } @else {\n <input type=\"text\" class=\"mj-input dashboard-editor-name\" aria-label=\"Dashboard name\" placeholder=\"Name this dashboard\"\n [(ngModel)]=\"EditingName\" (blur)=\"OnNameBlur()\">\n <input type=\"text\" class=\"dashboard-editor-desc is-editable\" aria-label=\"Dashboard description\" placeholder=\"Add a description\"\n [(ngModel)]=\"EditingDescription\">\n }\n </div>\n\n <div class=\"dashboard-editor-actions\">\n <ng-content select=\"[headerTools]\"></ng-content>\n @if (!IsEditing) {\n @if (ShowFavorite) {\n <button type=\"button\" mjButton Variant=\"icon\" Size=\"sm\" class=\"dashboard-editor-favorite\" [class.is-favorite]=\"IsFavorite\"\n [title]=\"IsFavorite ? 'Remove from favorites' : 'Add to favorites'\"\n [attr.aria-label]=\"IsFavorite ? 'Remove from favorites' : 'Add to favorites'\" (click)=\"ToggleFavorite()\">\n <i [class]=\"IsFavorite ? 'fa-solid fa-star' : 'fa-regular fa-star'\" aria-hidden=\"true\"></i>\n </button>\n }\n <ng-content select=\"[viewActions]\"></ng-content>\n @if (ShowOpenInDashboards) {\n <button type=\"button\" mjButton Variant=\"secondary\" Size=\"sm\" class=\"dashboard-editor-open-in\" (click)=\"OpenInDashboardsRequested.emit(dashboard)\">\n <i class=\"fa-solid fa-up-right-from-square\" aria-hidden=\"true\"></i> Open in Dashboards\n </button>\n }\n @if (CanEdit) {\n <span class=\"dashboard-editor-divider\" aria-hidden=\"true\"></span>\n <button type=\"button\" mjButton Variant=\"primary\" Size=\"sm\" class=\"dashboard-editor-edit\" [disabled]=\"IsSaving\" (click)=\"EnterEditMode()\">\n <i class=\"fa-solid fa-pen\" aria-hidden=\"true\"></i> Edit\n </button>\n }\n } @else {\n <button type=\"button\" mjButton Variant=\"secondary\" Size=\"sm\" class=\"dashboard-editor-add-part\" [disabled]=\"IsSaving\" (click)=\"OpenAddPartDialog()\">\n <i class=\"fa-solid fa-plus\" aria-hidden=\"true\"></i> Add part\n </button>\n <span class=\"dashboard-editor-divider\" aria-hidden=\"true\"></span>\n <button type=\"button\" mjButton Variant=\"primary\" Size=\"sm\" class=\"dashboard-editor-save\" [disabled]=\"IsSaving\" (click)=\"SaveDashboard()\">\n <i [class]=\"IsSaving ? 'fa-solid fa-spinner fa-spin' : 'fa-solid fa-check'\" aria-hidden=\"true\"></i>\n {{ IsSaving ? 'Saving...' : 'Save' }}\n </button>\n <button type=\"button\" mjButton Variant=\"flat\" Size=\"sm\" class=\"dashboard-editor-cancel\" [disabled]=\"IsSaving\" (click)=\"CancelEdit()\">Cancel</button>\n }\n </div>\n </header>\n}\n\n<div class=\"dashboard-editor-main\" [class.is-card]=\"BodyStyle === 'card'\">\n <div #body class=\"dashboard-editor-body\">\n <ng-container #viewerHost></ng-container>\n </div>\n</div>\n\n<!-- The part dialog belongs to one edit session -->\n@if (Dashboard && IsEditing && PartDialogMode) {\n <mj-dashboard-part-dialog [Visible]=\"true\" [Mode]=\"PartDialogMode\" [PartTypes]=\"PartTypes\"\n [Panel]=\"EditPartPanel\" [Provider]=\"Provider\"\n (Saved)=\"OnPartDialogSaved($event)\" (Cancelled)=\"ClosePartDialog()\"></mj-dashboard-part-dialog>\n}\n", styles: [":host { display: flex; flex-direction: column; min-width: 0; min-height: 0; }\n:host(.dashboard-editor--fill) { height: 100%; }\n\n.dashboard-editor-header {\n display: flex; align-items: center; gap: var(--mj-space-4); flex-shrink: 0;\n min-height: 72px; padding: var(--mj-space-3) var(--mj-space-8);\n background: var(--mj-bg-surface); border-bottom: 1px solid var(--mj-border-default);\n}\n.dashboard-editor-namewrap { display: flex; flex-direction: column; gap: 2px; flex: 1; min-width: 0; }\n.dashboard-editor-title {\n margin: 0; font-size: var(--mj-text-lg); font-weight: var(--mj-font-bold); letter-spacing: var(--mj-tracking-tight);\n color: var(--mj-text-primary); white-space: nowrap; overflow: hidden; text-overflow: ellipsis;\n}\n.dashboard-editor-shared { margin-left: var(--mj-space-2); font-size: var(--mj-text-xs); color: var(--mj-brand-primary); }\n\n/* .mj-input (global, ui-components input.scss) gives the border, radius, padding, hover and focus ring */\n.dashboard-editor-name { max-width: 520px; font-size: var(--mj-text-lg); font-weight: var(--mj-font-bold); letter-spacing: var(--mj-tracking-tight); }\n\n.dashboard-editor-desc {\n width: min(100%, 520px); margin: 0 0 0 -8px; padding: 3px 8px;\n font: inherit; font-size: var(--mj-text-xs); color: var(--mj-text-secondary);\n background: transparent; border: 1px solid transparent; border-radius: var(--mj-radius-md);\n}\n.dashboard-editor-desc.is-placeholder, .dashboard-editor-desc::placeholder { color: var(--mj-text-muted); }\n.dashboard-editor-desc.is-editable:hover { border-color: var(--mj-border-default); background: var(--mj-bg-surface-card); }\n.dashboard-editor-desc.is-editable:focus {\n outline: none; border-color: var(--mj-border-focus); background: var(--mj-bg-surface); box-shadow: var(--mj-focus-ring);\n}\n\n.dashboard-editor-actions { display: flex; align-items: center; gap: var(--mj-space-2); flex-shrink: 0; }\n.dashboard-editor-divider { width: 1px; height: 22px; margin: 0 var(--mj-space-1); background: var(--mj-border-default); }\n/* The colour goes on the icon, never on the button: button.scss owns the button */\n.dashboard-editor-favorite.is-favorite i { color: var(--mj-status-warning); }\n\n.dashboard-editor-main { display: flex; flex-direction: column; flex: 1; min-height: 0; }\n.dashboard-editor-body { position: relative; flex: 1; min-height: 0; overflow: hidden; }\n\n/* Card style: a padded body with the dashboard in a card. Golden Layout sizes itself from the body, so it needs a definite height. */\n.dashboard-editor-main.is-card { padding: var(--mj-space-4) var(--mj-space-8) var(--mj-space-8); }\n.dashboard-editor-main.is-card .dashboard-editor-body {\n flex: none; height: max(480px, calc(100vh - 230px));\n background: var(--mj-bg-surface); border: 1px solid var(--mj-border-default);\n border-radius: var(--mj-radius-lg); box-shadow: var(--mj-shadow-sm);\n}\n\n@media (max-width: 900px) {\n .dashboard-editor-header { flex-wrap: wrap; padding-left: var(--mj-space-4); padding-right: var(--mj-space-4); }\n .dashboard-editor-main.is-card { padding-left: var(--mj-space-4); padding-right: var(--mj-space-4); }\n}\n"] }]
|
|
974
|
+
}], null, { DashboardId: [{
|
|
975
|
+
type: Input
|
|
976
|
+
}], StartInEditMode: [{
|
|
977
|
+
type: Input
|
|
978
|
+
}], StartInEditModeChange: [{
|
|
979
|
+
type: Output
|
|
980
|
+
}], TitleTemplate: [{
|
|
981
|
+
type: Input
|
|
982
|
+
}], ShowOpenInDashboards: [{
|
|
983
|
+
type: Input
|
|
984
|
+
}], ShowFavorite: [{
|
|
985
|
+
type: Input
|
|
986
|
+
}], BodyStyle: [{
|
|
987
|
+
type: Input
|
|
988
|
+
}], Loaded: [{
|
|
989
|
+
type: Output
|
|
990
|
+
}], LoadFailed: [{
|
|
991
|
+
type: Output
|
|
992
|
+
}], EditingChange: [{
|
|
993
|
+
type: Output
|
|
994
|
+
}], Saved: [{
|
|
995
|
+
type: Output
|
|
996
|
+
}], NameChanged: [{
|
|
997
|
+
type: Output
|
|
998
|
+
}], ConfigChanged: [{
|
|
999
|
+
type: Output
|
|
1000
|
+
}], ReloadedFromSaved: [{
|
|
1001
|
+
type: Output
|
|
1002
|
+
}], FavoriteChange: [{
|
|
1003
|
+
type: Output
|
|
1004
|
+
}], NavigationRequested: [{
|
|
1005
|
+
type: Output
|
|
1006
|
+
}], OpenInDashboardsRequested: [{
|
|
1007
|
+
type: Output
|
|
1008
|
+
}], IsFill: [{
|
|
1009
|
+
type: HostBinding,
|
|
1010
|
+
args: ['class.dashboard-editor--fill']
|
|
1011
|
+
}], bodyRef: [{
|
|
1012
|
+
type: ViewChild,
|
|
1013
|
+
args: ['body', { static: true }]
|
|
1014
|
+
}], viewerHost: [{
|
|
1015
|
+
type: ViewChild,
|
|
1016
|
+
args: ['viewerHost', { read: ViewContainerRef, static: true }]
|
|
1017
|
+
}] }); })();
|
|
1018
|
+
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(DashboardEditorComponent, { className: "DashboardEditorComponent", filePath: "src/DashboardEditor/dashboard-editor.component.ts", lineNumber: 62 }); })();
|
|
1019
|
+
function errorMessage(error) {
|
|
1020
|
+
return error instanceof Error ? error.message : String(error);
|
|
1021
|
+
}
|
|
1022
|
+
//# sourceMappingURL=dashboard-editor.component.js.map
|