@memberjunction/ng-dashboards 6.2.0-edge.3 → 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/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 +26 -0
- package/dist/DataExplorer/data-explorer-dashboard.component.d.ts.map +1 -1
- package/dist/DataExplorer/data-explorer-dashboard.component.js +302 -219
- 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-dashboard.component.d.ts +1 -0
- package/dist/TelephonyOperations/telephony-operations-dashboard.component.d.ts.map +1 -1
- package/dist/TelephonyOperations/telephony-operations-dashboard.component.js +7 -0
- package/dist/TelephonyOperations/telephony-operations-dashboard.component.js.map +1 -1
- 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/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 -62
- package/dist/core-dashboards.module.d.ts.map +1 -1
- package/dist/core-dashboards.module.js +22 -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/public-api.d.ts +2 -0
- package/dist/public-api.d.ts.map +1 -1
- package/dist/public-api.js +3 -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/package.json +69 -68
|
@@ -4,168 +4,43 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
4
4
|
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
5
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
6
|
};
|
|
7
|
-
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
7
|
+
var DashboardBrowserResourceComponent_1;
|
|
8
|
+
import { Component, ChangeDetectionStrategy, inject } from '@angular/core';
|
|
9
|
+
import { skip, takeUntil } from 'rxjs/operators';
|
|
10
|
+
import { LogError } from '@memberjunction/core';
|
|
11
|
+
import { CountByUUID, GroupByUUID, IndexByUUID, RegisterClass, UUIDsEqual } from '@memberjunction/global';
|
|
12
|
+
import { BaseResourceComponent, DashboardFavoritesService } from '@memberjunction/ng-shared';
|
|
13
|
+
import { RecentAccessService } from '@memberjunction/ng-shared-generic';
|
|
14
|
+
import { ApplicationManager } from '@memberjunction/ng-base-application';
|
|
15
|
+
import { MJNotificationService } from '@memberjunction/ng-notifications';
|
|
16
|
+
import { DashboardEngine, UserInfoEngine } from '@memberjunction/core-entities';
|
|
12
17
|
import { BuildDashboardBrowserAgentContext, IsValidBrowserViewMode, } from './dashboard-browser-agent-context';
|
|
18
|
+
import { BuildLibraryRailSections, CountDashboardsByCategory, CountLibraryDashboards, DASHBOARD_LIBRARY_FILTERS, FilterDashboardsForLibrary, IsBrowsableDashboard, IsCategoryFolderLocation, IsDashboardLibraryFilter, LibraryEmptyState, LocationQueryParams, ParseRailItemId, RailActiveItemId, ResolveBrowseLocation, SameBrowseLocation, ToggleExpandedId, UNCATEGORIZED_CATEGORY_ID, VisibleLibraryDashboards, WithExpandedAncestors, } from './dashboard-library-filter';
|
|
19
|
+
import { LoadRecentDashboardIds, ObserveRecentDashboardIds } from '../shared/dashboard-recents';
|
|
20
|
+
import { ObserveDashboardLibraryChanges } from '../shared/dashboard-library-changes';
|
|
21
|
+
import { BuildOwnerLabels, LoadDashboardOwnerNames, OwnerIdsToLoad } from '../shared/dashboard-owner-names';
|
|
22
|
+
import { CreateBlankDashboard, DashboardNameMaxLength, DashboardsAppOpenOptions } from '../shared/dashboards-app.helpers';
|
|
13
23
|
import { ValidateStringParam, } from '../shared/agent-tool-validation';
|
|
14
|
-
import {
|
|
24
|
+
import { DASHBOARD_NAME_MAX_LENGTH } from '@memberjunction/ng-dashboard-viewer';
|
|
15
25
|
import * as i0 from "@angular/core";
|
|
16
|
-
import * as i1 from "@
|
|
26
|
+
import * as i1 from "@memberjunction/ng-ui-components";
|
|
17
27
|
import * as i2 from "@memberjunction/ng-dashboard-viewer";
|
|
18
|
-
import * as i3 from "./dashboard-share-dialog.component";
|
|
19
|
-
const _c0 = ["dashboardViewer"];
|
|
20
|
-
function DashboardBrowserResourceComponent_Conditional_1_Template(rf, ctx) { if (rf & 1) {
|
|
21
|
-
const _r1 = i0.ɵɵgetCurrentView();
|
|
22
|
-
i0.ɵɵelementStart(0, "mj-dashboard-browser", 3);
|
|
23
|
-
i0.ɵɵlistener("DashboardOpen", function DashboardBrowserResourceComponent_Conditional_1_Template_mj_dashboard_browser_DashboardOpen_0_listener($event) { i0.ɵɵrestoreView(_r1); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.onDashboardOpen($event)); })("DashboardEdit", function DashboardBrowserResourceComponent_Conditional_1_Template_mj_dashboard_browser_DashboardEdit_0_listener($event) { i0.ɵɵrestoreView(_r1); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.onDashboardEdit($event)); })("DashboardDelete", function DashboardBrowserResourceComponent_Conditional_1_Template_mj_dashboard_browser_DashboardDelete_0_listener($event) { i0.ɵɵrestoreView(_r1); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.onDashboardDelete($event)); })("DashboardMove", function DashboardBrowserResourceComponent_Conditional_1_Template_mj_dashboard_browser_DashboardMove_0_listener($event) { i0.ɵɵrestoreView(_r1); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.onDashboardMove($event)); })("DashboardCreate", function DashboardBrowserResourceComponent_Conditional_1_Template_mj_dashboard_browser_DashboardCreate_0_listener($event) { i0.ɵɵrestoreView(_r1); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.onDashboardCreate($event)); })("CategoryCreate", function DashboardBrowserResourceComponent_Conditional_1_Template_mj_dashboard_browser_CategoryCreate_0_listener($event) { i0.ɵɵrestoreView(_r1); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.onCategoryCreate($event)); })("CategoryDelete", function DashboardBrowserResourceComponent_Conditional_1_Template_mj_dashboard_browser_CategoryDelete_0_listener($event) { i0.ɵɵrestoreView(_r1); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.onCategoryDelete($event)); })("CategoryChange", function DashboardBrowserResourceComponent_Conditional_1_Template_mj_dashboard_browser_CategoryChange_0_listener($event) { i0.ɵɵrestoreView(_r1); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.onCategoryChange($event)); })("ViewPreferenceChange", function DashboardBrowserResourceComponent_Conditional_1_Template_mj_dashboard_browser_ViewPreferenceChange_0_listener($event) { i0.ɵɵrestoreView(_r1); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.onViewPreferenceChange($event)); });
|
|
24
|
-
i0.ɵɵelementEnd();
|
|
25
|
-
} if (rf & 2) {
|
|
26
|
-
const ctx_r1 = i0.ɵɵnextContext();
|
|
27
|
-
i0.ɵɵproperty("Dashboards", ctx_r1.dashboards)("Categories", ctx_r1.categories)("SelectedCategoryId", ctx_r1.selectedCategoryId)("ViewMode", ctx_r1.viewMode)("IsLoading", ctx_r1.isLoading)("ShowCreateButton", true)("AllowMultiSelect", true)("AllowDragDrop", true)("DashboardPermissions", ctx_r1.dashboardPermissionsMap)("EffectiveCategoryMap", ctx_r1.effectiveCategoryMap);
|
|
28
|
-
} }
|
|
29
|
-
function DashboardBrowserResourceComponent_Conditional_2_Conditional_0_Conditional_3_Template(rf, ctx) { if (rf & 1) {
|
|
30
|
-
i0.ɵɵelementStart(0, "span", 14);
|
|
31
|
-
i0.ɵɵelement(1, "i", 19);
|
|
32
|
-
i0.ɵɵtext(2, " Shared with you ");
|
|
33
|
-
i0.ɵɵelementEnd();
|
|
34
|
-
} }
|
|
35
|
-
function DashboardBrowserResourceComponent_Conditional_2_Conditional_0_Conditional_6_Template(rf, ctx) { if (rf & 1) {
|
|
36
|
-
const _r5 = i0.ɵɵgetCurrentView();
|
|
37
|
-
i0.ɵɵelementStart(0, "button", 20);
|
|
38
|
-
i0.ɵɵlistener("click", function DashboardBrowserResourceComponent_Conditional_2_Conditional_0_Conditional_6_Template_button_click_0_listener() { i0.ɵɵrestoreView(_r5); const ctx_r1 = i0.ɵɵnextContext(3); return i0.ɵɵresetView(ctx_r1.openShareDialog()); });
|
|
39
|
-
i0.ɵɵelement(1, "i", 21);
|
|
40
|
-
i0.ɵɵelementEnd();
|
|
41
|
-
} }
|
|
42
|
-
function DashboardBrowserResourceComponent_Conditional_2_Conditional_0_Conditional_7_Template(rf, ctx) { if (rf & 1) {
|
|
43
|
-
const _r6 = i0.ɵɵgetCurrentView();
|
|
44
|
-
i0.ɵɵelementStart(0, "button", 22);
|
|
45
|
-
i0.ɵɵlistener("click", function DashboardBrowserResourceComponent_Conditional_2_Conditional_0_Conditional_7_Template_button_click_0_listener() { i0.ɵɵrestoreView(_r6); const ctx_r1 = i0.ɵɵnextContext(3); return i0.ɵɵresetView(ctx_r1.toggleEditMode()); });
|
|
46
|
-
i0.ɵɵelement(1, "i", 23);
|
|
47
|
-
i0.ɵɵelementEnd();
|
|
48
|
-
} }
|
|
49
|
-
function DashboardBrowserResourceComponent_Conditional_2_Conditional_0_Template(rf, ctx) { if (rf & 1) {
|
|
50
|
-
const _r4 = i0.ɵɵgetCurrentView();
|
|
51
|
-
i0.ɵɵelementStart(0, "div", 4)(1, "mj-dashboard-breadcrumb", 12);
|
|
52
|
-
i0.ɵɵlistener("Navigate", function DashboardBrowserResourceComponent_Conditional_2_Conditional_0_Template_mj_dashboard_breadcrumb_Navigate_1_listener($event) { i0.ɵɵrestoreView(_r4); const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.onBreadcrumbNavigate($event)); });
|
|
53
|
-
i0.ɵɵelementEnd();
|
|
54
|
-
i0.ɵɵelementStart(2, "div", 13);
|
|
55
|
-
i0.ɵɵconditionalCreate(3, DashboardBrowserResourceComponent_Conditional_2_Conditional_0_Conditional_3_Template, 3, 0, "span", 14);
|
|
56
|
-
i0.ɵɵelementStart(4, "button", 15);
|
|
57
|
-
i0.ɵɵlistener("click", function DashboardBrowserResourceComponent_Conditional_2_Conditional_0_Template_button_click_4_listener() { i0.ɵɵrestoreView(_r4); const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.openInNewTab()); });
|
|
58
|
-
i0.ɵɵelement(5, "i", 16);
|
|
59
|
-
i0.ɵɵelementEnd();
|
|
60
|
-
i0.ɵɵconditionalCreate(6, DashboardBrowserResourceComponent_Conditional_2_Conditional_0_Conditional_6_Template, 2, 0, "button", 17);
|
|
61
|
-
i0.ɵɵconditionalCreate(7, DashboardBrowserResourceComponent_Conditional_2_Conditional_0_Conditional_7_Template, 2, 0, "button", 18);
|
|
62
|
-
i0.ɵɵelementEnd()();
|
|
63
|
-
} if (rf & 2) {
|
|
64
|
-
const ctx_r1 = i0.ɵɵnextContext(2);
|
|
65
|
-
i0.ɵɵadvance();
|
|
66
|
-
i0.ɵɵproperty("Categories", ctx_r1.categories)("CurrentCategoryId", (ctx_r1.selectedDashboard == null ? null : ctx_r1.selectedDashboard.CategoryID) || null)("CurrentDashboard", ctx_r1.selectedDashboard)("ShowDashboardName", true)("AllowDragDrop", false);
|
|
67
|
-
i0.ɵɵadvance(2);
|
|
68
|
-
i0.ɵɵconditional(!ctx_r1.selectedDashboardPermissions.IsOwner && ctx_r1.selectedDashboardPermissions.CanRead ? 3 : -1);
|
|
69
|
-
i0.ɵɵadvance(3);
|
|
70
|
-
i0.ɵɵconditional(ctx_r1.selectedDashboardPermissions.CanShare ? 6 : -1);
|
|
71
|
-
i0.ɵɵadvance();
|
|
72
|
-
i0.ɵɵconditional(ctx_r1.selectedDashboardPermissions.CanEdit ? 7 : -1);
|
|
73
|
-
} }
|
|
74
|
-
function DashboardBrowserResourceComponent_Conditional_2_Conditional_1_Template(rf, ctx) { if (rf & 1) {
|
|
75
|
-
const _r7 = i0.ɵɵgetCurrentView();
|
|
76
|
-
i0.ɵɵelementStart(0, "div", 5)(1, "div", 24)(2, "button", 25);
|
|
77
|
-
i0.ɵɵlistener("click", function DashboardBrowserResourceComponent_Conditional_2_Conditional_1_Template_button_click_2_listener() { i0.ɵɵrestoreView(_r7); const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.openAddPartDialog()); });
|
|
78
|
-
i0.ɵɵelement(3, "i", 26);
|
|
79
|
-
i0.ɵɵtext(4, " Add Part ");
|
|
80
|
-
i0.ɵɵelementEnd();
|
|
81
|
-
i0.ɵɵelement(5, "div", 27);
|
|
82
|
-
i0.ɵɵelementStart(6, "div", 28)(7, "input", 29);
|
|
83
|
-
i0.ɵɵtwoWayListener("ngModelChange", function DashboardBrowserResourceComponent_Conditional_2_Conditional_1_Template_input_ngModelChange_7_listener($event) { i0.ɵɵrestoreView(_r7); const ctx_r1 = i0.ɵɵnextContext(2); i0.ɵɵtwoWayBindingSet(ctx_r1.editingName, $event) || (ctx_r1.editingName = $event); return i0.ɵɵresetView($event); });
|
|
84
|
-
i0.ɵɵlistener("blur", function DashboardBrowserResourceComponent_Conditional_2_Conditional_1_Template_input_blur_7_listener() { i0.ɵɵrestoreView(_r7); const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.onNameBlur()); });
|
|
85
|
-
i0.ɵɵelementEnd();
|
|
86
|
-
i0.ɵɵelementStart(8, "input", 30);
|
|
87
|
-
i0.ɵɵtwoWayListener("ngModelChange", function DashboardBrowserResourceComponent_Conditional_2_Conditional_1_Template_input_ngModelChange_8_listener($event) { i0.ɵɵrestoreView(_r7); const ctx_r1 = i0.ɵɵnextContext(2); i0.ɵɵtwoWayBindingSet(ctx_r1.editingDescription, $event) || (ctx_r1.editingDescription = $event); return i0.ɵɵresetView($event); });
|
|
88
|
-
i0.ɵɵelementEnd()()();
|
|
89
|
-
i0.ɵɵelementStart(9, "div", 31)(10, "button", 32);
|
|
90
|
-
i0.ɵɵlistener("click", function DashboardBrowserResourceComponent_Conditional_2_Conditional_1_Template_button_click_10_listener() { i0.ɵɵrestoreView(_r7); const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.saveDashboard()); });
|
|
91
|
-
i0.ɵɵelement(11, "i", 33);
|
|
92
|
-
i0.ɵɵtext(12, " Save ");
|
|
93
|
-
i0.ɵɵelementEnd();
|
|
94
|
-
i0.ɵɵelementStart(13, "button", 34);
|
|
95
|
-
i0.ɵɵlistener("click", function DashboardBrowserResourceComponent_Conditional_2_Conditional_1_Template_button_click_13_listener() { i0.ɵɵrestoreView(_r7); const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.cancelEdit()); });
|
|
96
|
-
i0.ɵɵtext(14, " Cancel ");
|
|
97
|
-
i0.ɵɵelementEnd()()();
|
|
98
|
-
} if (rf & 2) {
|
|
99
|
-
const ctx_r1 = i0.ɵɵnextContext(2);
|
|
100
|
-
i0.ɵɵadvance(7);
|
|
101
|
-
i0.ɵɵtwoWayProperty("ngModel", ctx_r1.editingName);
|
|
102
|
-
i0.ɵɵadvance();
|
|
103
|
-
i0.ɵɵtwoWayProperty("ngModel", ctx_r1.editingDescription);
|
|
104
|
-
} }
|
|
105
|
-
function DashboardBrowserResourceComponent_Conditional_2_Conditional_5_Template(rf, ctx) { if (rf & 1) {
|
|
106
|
-
const _r8 = i0.ɵɵgetCurrentView();
|
|
107
|
-
i0.ɵɵelementStart(0, "mj-add-panel-dialog", 35);
|
|
108
|
-
i0.ɵɵlistener("panelAdded", function DashboardBrowserResourceComponent_Conditional_2_Conditional_5_Template_mj_add_panel_dialog_panelAdded_0_listener($event) { i0.ɵɵrestoreView(_r8); const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.onPanelAdded($event)); })("cancelled", function DashboardBrowserResourceComponent_Conditional_2_Conditional_5_Template_mj_add_panel_dialog_cancelled_0_listener() { i0.ɵɵrestoreView(_r8); const ctx_r1 = i0.ɵɵnextContext(2); return i0.ɵɵresetView(ctx_r1.onAddPanelCancelled()); });
|
|
109
|
-
i0.ɵɵelementEnd();
|
|
110
|
-
} if (rf & 2) {
|
|
111
|
-
i0.ɵɵnextContext();
|
|
112
|
-
const dashboardViewer_r9 = i0.ɵɵreference(4);
|
|
113
|
-
const ctx_r1 = i0.ɵɵnextContext();
|
|
114
|
-
i0.ɵɵproperty("visible", ctx_r1.showAddPanelDialog)("partTypes", dashboardViewer_r9.getPartTypes());
|
|
115
|
-
} }
|
|
116
|
-
function DashboardBrowserResourceComponent_Conditional_2_Template(rf, ctx) { if (rf & 1) {
|
|
117
|
-
const _r3 = i0.ɵɵgetCurrentView();
|
|
118
|
-
i0.ɵɵconditionalCreate(0, DashboardBrowserResourceComponent_Conditional_2_Conditional_0_Template, 8, 8, "div", 4);
|
|
119
|
-
i0.ɵɵconditionalCreate(1, DashboardBrowserResourceComponent_Conditional_2_Conditional_1_Template, 15, 2, "div", 5);
|
|
120
|
-
i0.ɵɵelementStart(2, "div", 6)(3, "mj-dashboard-viewer", 7, 0);
|
|
121
|
-
i0.ɵɵlistener("panelInteraction", function DashboardBrowserResourceComponent_Conditional_2_Template_mj_dashboard_viewer_panelInteraction_3_listener($event) { i0.ɵɵrestoreView(_r3); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.onPanelInteraction($event)); })("navigationRequested", function DashboardBrowserResourceComponent_Conditional_2_Template_mj_dashboard_viewer_navigationRequested_3_listener($event) { i0.ɵɵrestoreView(_r3); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.onNavigationRequested($event)); });
|
|
122
|
-
i0.ɵɵelementEnd()();
|
|
123
|
-
i0.ɵɵconditionalCreate(5, DashboardBrowserResourceComponent_Conditional_2_Conditional_5_Template, 1, 2, "mj-add-panel-dialog", 8);
|
|
124
|
-
i0.ɵɵelementStart(6, "mj-edit-part-dialog", 9);
|
|
125
|
-
i0.ɵɵlistener("Saved", function DashboardBrowserResourceComponent_Conditional_2_Template_mj_edit_part_dialog_Saved_6_listener($event) { i0.ɵɵrestoreView(_r3); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.onConfigDialogSaved($event)); })("Cancelled", function DashboardBrowserResourceComponent_Conditional_2_Template_mj_edit_part_dialog_Cancelled_6_listener() { i0.ɵɵrestoreView(_r3); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.onConfigDialogCancelled()); });
|
|
126
|
-
i0.ɵɵelementEnd();
|
|
127
|
-
i0.ɵɵelementStart(7, "mj-confirm-dialog", 10);
|
|
128
|
-
i0.ɵɵlistener("confirmed", function DashboardBrowserResourceComponent_Conditional_2_Template_mj_confirm_dialog_confirmed_7_listener() { i0.ɵɵrestoreView(_r3); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.onRemoveConfirmed()); })("cancelled", function DashboardBrowserResourceComponent_Conditional_2_Template_mj_confirm_dialog_cancelled_7_listener() { i0.ɵɵrestoreView(_r3); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.onRemoveCancelled()); });
|
|
129
|
-
i0.ɵɵelementEnd();
|
|
130
|
-
i0.ɵɵelementStart(8, "mj-dashboard-share-dialog", 11);
|
|
131
|
-
i0.ɵɵlistener("Result", function DashboardBrowserResourceComponent_Conditional_2_Template_mj_dashboard_share_dialog_Result_8_listener($event) { i0.ɵɵrestoreView(_r3); const ctx_r1 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r1.onShareDialogResult($event)); });
|
|
132
|
-
i0.ɵɵelementEnd();
|
|
133
|
-
} if (rf & 2) {
|
|
134
|
-
const dashboardViewer_r9 = i0.ɵɵreference(4);
|
|
135
|
-
const ctx_r1 = i0.ɵɵnextContext();
|
|
136
|
-
i0.ɵɵconditional(ctx_r1.mode === "view" ? 0 : -1);
|
|
137
|
-
i0.ɵɵadvance();
|
|
138
|
-
i0.ɵɵconditional(ctx_r1.mode === "edit" ? 1 : -1);
|
|
139
|
-
i0.ɵɵadvance(2);
|
|
140
|
-
i0.ɵɵproperty("dashboard", ctx_r1.selectedDashboard)("isEditing", ctx_r1.mode === "edit")("showToolbar", false)("showBreadcrumb", false);
|
|
141
|
-
i0.ɵɵadvance(2);
|
|
142
|
-
i0.ɵɵconditional(dashboardViewer_r9 ? 5 : -1);
|
|
143
|
-
i0.ɵɵadvance();
|
|
144
|
-
i0.ɵɵproperty("Visible", ctx_r1.showConfigDialog)("PartType", ctx_r1.configDialogPartType)("Panel", ctx_r1.configDialogPanel)("Config", (ctx_r1.configDialogPanel == null ? null : ctx_r1.configDialogPanel.config) || null);
|
|
145
|
-
i0.ɵɵadvance();
|
|
146
|
-
i0.ɵɵproperty("visible", ctx_r1.showConfirmDialog)("message", "Are you sure you want to remove '" + ctx_r1.confirmPanelTitle + "' from this dashboard?");
|
|
147
|
-
i0.ɵɵadvance();
|
|
148
|
-
i0.ɵɵproperty("Visible", ctx_r1.showShareDialog)("Dashboard", ctx_r1.selectedDashboard);
|
|
149
|
-
} }
|
|
150
28
|
/**
|
|
151
|
-
*
|
|
152
|
-
*
|
|
153
|
-
*
|
|
29
|
+
* Library page of the Dashboards app. A left rail picks a Library filter (All, Mine, Shared with
|
|
30
|
+
* me, Favorites, Recently opened), a category or Uncategorized; the generic
|
|
31
|
+
* DashboardBrowserComponent lists the result. Every list is flat, favorites first, then the
|
|
32
|
+
* dashboards opened last, then the rest; a category opens as a folder. Cards show a star the user
|
|
33
|
+
* can toggle and the owner ("You" or the owner's name). A click opens the dashboard in a tab of the
|
|
34
|
+
* Dashboards app, in place of the preview tab, so Back returns to the Library. A Shift, Ctrl or Cmd
|
|
35
|
+
* click opens it in a separate tab. New dashboard asks for the name first, then opens the new
|
|
36
|
+
* dashboard in edit mode.
|
|
154
37
|
*/
|
|
155
38
|
let DashboardBrowserResourceComponent = class DashboardBrowserResourceComponent extends BaseResourceComponent {
|
|
39
|
+
static { DashboardBrowserResourceComponent_1 = this; }
|
|
156
40
|
cdr;
|
|
157
41
|
// ========================================
|
|
158
42
|
// State
|
|
159
43
|
// ========================================
|
|
160
|
-
Mode = 'list';
|
|
161
|
-
/** @deprecated Use {@link Mode}. */
|
|
162
|
-
get mode() {
|
|
163
|
-
return this.Mode;
|
|
164
|
-
}
|
|
165
|
-
/** @deprecated Use {@link Mode}. */
|
|
166
|
-
set mode(value) {
|
|
167
|
-
this.Mode = value;
|
|
168
|
-
}
|
|
169
44
|
isLoading = false;
|
|
170
45
|
Dashboards = [];
|
|
171
46
|
/** @deprecated Use {@link Dashboards}. */
|
|
@@ -185,15 +60,6 @@ let DashboardBrowserResourceComponent = class DashboardBrowserResourceComponent
|
|
|
185
60
|
set categories(value) {
|
|
186
61
|
this.Categories = value;
|
|
187
62
|
}
|
|
188
|
-
SelectedDashboard = null;
|
|
189
|
-
/** @deprecated Use {@link SelectedDashboard}. */
|
|
190
|
-
get selectedDashboard() {
|
|
191
|
-
return this.SelectedDashboard;
|
|
192
|
-
}
|
|
193
|
-
/** @deprecated Use {@link SelectedDashboard}. */
|
|
194
|
-
set selectedDashboard(value) {
|
|
195
|
-
this.SelectedDashboard = value;
|
|
196
|
-
}
|
|
197
63
|
SelectedCategoryId = null;
|
|
198
64
|
/** @deprecated Use {@link SelectedCategoryId}. */
|
|
199
65
|
get selectedCategoryId() {
|
|
@@ -212,144 +78,12 @@ let DashboardBrowserResourceComponent = class DashboardBrowserResourceComponent
|
|
|
212
78
|
set viewMode(value) {
|
|
213
79
|
this.ViewMode = value;
|
|
214
80
|
}
|
|
215
|
-
ShowAddPanelDialog = false;
|
|
216
|
-
/** @deprecated Use {@link ShowAddPanelDialog}. */
|
|
217
|
-
get showAddPanelDialog() {
|
|
218
|
-
return this.ShowAddPanelDialog;
|
|
219
|
-
}
|
|
220
|
-
/** @deprecated Use {@link ShowAddPanelDialog}. */
|
|
221
|
-
set showAddPanelDialog(value) {
|
|
222
|
-
this.ShowAddPanelDialog = value;
|
|
223
|
-
}
|
|
224
81
|
/**
|
|
225
82
|
* Free-text search the agent has applied to the dashboard list (via the
|
|
226
83
|
* SearchDashboards tool). Reported in the agent context so the agent knows
|
|
227
84
|
* how the visible list is currently narrowed; cleared by ClearDashboardFilters.
|
|
228
85
|
*/
|
|
229
86
|
agentSearchText = '';
|
|
230
|
-
/**
|
|
231
|
-
* Which tool-set ('list' vs 'open') is currently registered with the agent.
|
|
232
|
-
* The Dashboard Browser exposes different tools depending on whether the user
|
|
233
|
-
* is browsing the list or has a dashboard open (mirrors the Data Explorer's
|
|
234
|
-
* mode-scoped tools). We only re-register on the flip — see
|
|
235
|
-
* {@link syncAgentToolsForMode}.
|
|
236
|
-
*/
|
|
237
|
-
lastRegisteredToolMode = null;
|
|
238
|
-
// Config dialog state
|
|
239
|
-
ShowConfigDialog = false;
|
|
240
|
-
/** @deprecated Use {@link ShowConfigDialog}. */
|
|
241
|
-
get showConfigDialog() {
|
|
242
|
-
return this.ShowConfigDialog;
|
|
243
|
-
}
|
|
244
|
-
/** @deprecated Use {@link ShowConfigDialog}. */
|
|
245
|
-
set showConfigDialog(value) {
|
|
246
|
-
this.ShowConfigDialog = value;
|
|
247
|
-
}
|
|
248
|
-
ConfigDialogPanel = null;
|
|
249
|
-
/** @deprecated Use {@link ConfigDialogPanel}. */
|
|
250
|
-
get configDialogPanel() {
|
|
251
|
-
return this.ConfigDialogPanel;
|
|
252
|
-
}
|
|
253
|
-
/** @deprecated Use {@link ConfigDialogPanel}. */
|
|
254
|
-
set configDialogPanel(value) {
|
|
255
|
-
this.ConfigDialogPanel = value;
|
|
256
|
-
}
|
|
257
|
-
ConfigDialogPartType = null;
|
|
258
|
-
/** @deprecated Use {@link ConfigDialogPartType}. */
|
|
259
|
-
get configDialogPartType() {
|
|
260
|
-
return this.ConfigDialogPartType;
|
|
261
|
-
}
|
|
262
|
-
/** @deprecated Use {@link ConfigDialogPartType}. */
|
|
263
|
-
set configDialogPartType(value) {
|
|
264
|
-
this.ConfigDialogPartType = value;
|
|
265
|
-
}
|
|
266
|
-
ConfigDialogClass = '';
|
|
267
|
-
/** @deprecated Use {@link ConfigDialogClass}. */
|
|
268
|
-
get configDialogClass() {
|
|
269
|
-
return this.ConfigDialogClass;
|
|
270
|
-
}
|
|
271
|
-
/** @deprecated Use {@link ConfigDialogClass}. */
|
|
272
|
-
set configDialogClass(value) {
|
|
273
|
-
this.ConfigDialogClass = value;
|
|
274
|
-
}
|
|
275
|
-
// Confirm dialog state
|
|
276
|
-
ShowConfirmDialog = false;
|
|
277
|
-
/** @deprecated Use {@link ShowConfirmDialog}. */
|
|
278
|
-
get showConfirmDialog() {
|
|
279
|
-
return this.ShowConfirmDialog;
|
|
280
|
-
}
|
|
281
|
-
/** @deprecated Use {@link ShowConfirmDialog}. */
|
|
282
|
-
set showConfirmDialog(value) {
|
|
283
|
-
this.ShowConfirmDialog = value;
|
|
284
|
-
}
|
|
285
|
-
ConfirmPanelId = '';
|
|
286
|
-
/** @deprecated Use {@link ConfirmPanelId}. */
|
|
287
|
-
get confirmPanelId() {
|
|
288
|
-
return this.ConfirmPanelId;
|
|
289
|
-
}
|
|
290
|
-
/** @deprecated Use {@link ConfirmPanelId}. */
|
|
291
|
-
set confirmPanelId(value) {
|
|
292
|
-
this.ConfirmPanelId = value;
|
|
293
|
-
}
|
|
294
|
-
ConfirmPanelTitle = '';
|
|
295
|
-
/** @deprecated Use {@link ConfirmPanelTitle}. */
|
|
296
|
-
get confirmPanelTitle() {
|
|
297
|
-
return this.ConfirmPanelTitle;
|
|
298
|
-
}
|
|
299
|
-
/** @deprecated Use {@link ConfirmPanelTitle}. */
|
|
300
|
-
set confirmPanelTitle(value) {
|
|
301
|
-
this.ConfirmPanelTitle = value;
|
|
302
|
-
}
|
|
303
|
-
// Share dialog state
|
|
304
|
-
ShowShareDialog = false;
|
|
305
|
-
/** @deprecated Use {@link ShowShareDialog}. */
|
|
306
|
-
get showShareDialog() {
|
|
307
|
-
return this.ShowShareDialog;
|
|
308
|
-
}
|
|
309
|
-
/** @deprecated Use {@link ShowShareDialog}. */
|
|
310
|
-
set showShareDialog(value) {
|
|
311
|
-
this.ShowShareDialog = value;
|
|
312
|
-
}
|
|
313
|
-
// Edit mode state for name/description
|
|
314
|
-
EditingName = '';
|
|
315
|
-
/** @deprecated Use {@link EditingName}. */
|
|
316
|
-
get editingName() {
|
|
317
|
-
return this.EditingName;
|
|
318
|
-
}
|
|
319
|
-
/** @deprecated Use {@link EditingName}. */
|
|
320
|
-
set editingName(value) {
|
|
321
|
-
this.EditingName = value;
|
|
322
|
-
}
|
|
323
|
-
EditingDescription = '';
|
|
324
|
-
/** @deprecated Use {@link EditingDescription}. */
|
|
325
|
-
get editingDescription() {
|
|
326
|
-
return this.EditingDescription;
|
|
327
|
-
}
|
|
328
|
-
/** @deprecated Use {@link EditingDescription}. */
|
|
329
|
-
set editingDescription(value) {
|
|
330
|
-
this.EditingDescription = value;
|
|
331
|
-
}
|
|
332
|
-
originalName = '';
|
|
333
|
-
originalDescription = '';
|
|
334
|
-
originalConfig = '';
|
|
335
|
-
// Permission state for selected dashboard
|
|
336
|
-
SelectedDashboardPermissions = {
|
|
337
|
-
DashboardID: '',
|
|
338
|
-
CanRead: true,
|
|
339
|
-
CanEdit: true,
|
|
340
|
-
CanDelete: true,
|
|
341
|
-
CanShare: true,
|
|
342
|
-
IsOwner: true,
|
|
343
|
-
PermissionSource: 'owner'
|
|
344
|
-
};
|
|
345
|
-
/** @deprecated Use {@link SelectedDashboardPermissions}. */
|
|
346
|
-
get selectedDashboardPermissions() {
|
|
347
|
-
return this.SelectedDashboardPermissions;
|
|
348
|
-
}
|
|
349
|
-
/** @deprecated Use {@link SelectedDashboardPermissions}. */
|
|
350
|
-
set selectedDashboardPermissions(value) {
|
|
351
|
-
this.SelectedDashboardPermissions = value;
|
|
352
|
-
}
|
|
353
87
|
// Permission map for all dashboards (used by browser component)
|
|
354
88
|
DashboardPermissionsMap = new Map();
|
|
355
89
|
/** @deprecated Use {@link DashboardPermissionsMap}. */
|
|
@@ -373,15 +107,48 @@ let DashboardBrowserResourceComponent = class DashboardBrowserResourceComponent
|
|
|
373
107
|
// Query params that arrived before the dashboard list finished loading (cold
|
|
374
108
|
// load / deep link / pin navigation). Applied once loadDashboards() completes.
|
|
375
109
|
_pendingQueryParams = null;
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
110
|
+
// True once a load has applied the tab's creation-time query params. That
|
|
111
|
+
// snapshot never changes, so later loads must not apply it again.
|
|
112
|
+
_initialQueryParamsApplied = false;
|
|
113
|
+
/** The ids of the dashboards the user opened, most recent first, as last read from the record logs. */
|
|
114
|
+
recentDashboardIds = [];
|
|
115
|
+
favorites = inject(DashboardFavoritesService);
|
|
116
|
+
recentAccess = inject(RecentAccessService);
|
|
117
|
+
appManager = inject(ApplicationManager);
|
|
118
|
+
notifications = inject(MJNotificationService);
|
|
119
|
+
/** The active Library filter. A category is only selected under `all`. */
|
|
120
|
+
LibraryFilter = 'all';
|
|
121
|
+
/** The dashboards given to the browser: the loaded list for the current location, in the Library order. */
|
|
122
|
+
VisibleDashboards = [];
|
|
123
|
+
/** IDs of the user's favorite dashboards, for the card stars. */
|
|
124
|
+
FavoriteIds = [];
|
|
125
|
+
/** Each dashboard's owner text ("You" or the owner's name), keyed by dashboard ID. */
|
|
126
|
+
OwnerLabels = new Map();
|
|
127
|
+
/** What an empty flat list shows at the current location. */
|
|
128
|
+
EmptyState = LibraryEmptyState({ Filter: 'all', CategoryId: null });
|
|
129
|
+
/** Owner names read so far, keyed by normalized user ID. '' = the read did not return the user. */
|
|
130
|
+
ownerNames = new Map();
|
|
131
|
+
/** Normalized IDs of the owners whose names are being read. */
|
|
132
|
+
ownerReadsInFlight = new Set();
|
|
133
|
+
/** How many dashboards the browser shows for each Library filter. */
|
|
134
|
+
LibraryCounts = { all: 0, mine: 0, shared: 0, favorites: 0, recent: 0 };
|
|
135
|
+
/** The rail's Library and Categories sections. */
|
|
136
|
+
RailSections = [];
|
|
137
|
+
/** Rail ids of the expanded category tree items. */
|
|
138
|
+
RailExpandedIds = [];
|
|
139
|
+
/** Whether the rail is collapsed to icons. Saved per user. */
|
|
140
|
+
RailCollapsed = false;
|
|
141
|
+
/** True while the New dashboard dialog is open. */
|
|
142
|
+
ShowNewDashboardDialog = false;
|
|
143
|
+
/** True while the dashboard named in the New dashboard dialog is being created. */
|
|
144
|
+
IsCreatingDashboard = false;
|
|
145
|
+
/** The longest name the New dashboard dialog accepts. */
|
|
146
|
+
NewDashboardNameMaxLength = DASHBOARD_NAME_MAX_LENGTH;
|
|
147
|
+
/** The category the dashboard named in the dialog is filed in; null for no category. */
|
|
148
|
+
newDashboardCategoryId = null;
|
|
149
|
+
static RAIL_COLLAPSED_SETTING = 'mj.dashboards.browseRailCollapsed';
|
|
150
|
+
// True once the top-level categories have been expanded on first load.
|
|
151
|
+
railExpansionInitialized = false;
|
|
385
152
|
// ========================================
|
|
386
153
|
// Constructor
|
|
387
154
|
// ========================================
|
|
@@ -397,9 +164,24 @@ let DashboardBrowserResourceComponent = class DashboardBrowserResourceComponent
|
|
|
397
164
|
// super.ngOnInit() wires up the query-param delivery. Any deep-link/pin params
|
|
398
165
|
// that arrive before loadDashboards() finishes are captured by OnQueryParamsChanged
|
|
399
166
|
// into _pendingQueryParams and applied once the dashboard list is available.
|
|
167
|
+
// Favorites and recents change while the Library is also a background tab. RecentItems emits
|
|
168
|
+
// right after a dashboard open is logged, and each emission reads the recently opened
|
|
169
|
+
// dashboards again. It also replays its current list on subscribe; loadDashboards() reads
|
|
170
|
+
// the first list, so that replay is skipped.
|
|
171
|
+
this.favorites.Changed$
|
|
172
|
+
.pipe(takeUntil(this.destroy$))
|
|
173
|
+
.subscribe(() => this.onLibrarySourcesChanged());
|
|
174
|
+
ObserveRecentDashboardIds(this.ProviderToUse, this.recentAccess.RecentItems.pipe(skip(1)))
|
|
175
|
+
.pipe(takeUntil(this.destroy$))
|
|
176
|
+
.subscribe(ids => this.onRecentDashboardIdsChanged(ids));
|
|
177
|
+
// The dashboard cache also changes while the Library is a background tab (saves and deletes
|
|
178
|
+
// in other tabs or sessions), so the Library re-reads it and refreshes the view.
|
|
179
|
+
ObserveDashboardLibraryChanges()
|
|
180
|
+
.pipe(takeUntil(this.destroy$))
|
|
181
|
+
.subscribe(() => this.onLibraryCacheChanged());
|
|
400
182
|
this.loadDashboards();
|
|
401
183
|
this.loadViewPreference();
|
|
402
|
-
this.
|
|
184
|
+
this.registerAgentTools();
|
|
403
185
|
this.emitAgentContext();
|
|
404
186
|
}
|
|
405
187
|
ngOnDestroy() {
|
|
@@ -415,10 +197,16 @@ let DashboardBrowserResourceComponent = class DashboardBrowserResourceComponent
|
|
|
415
197
|
* ActivatedRoute subscription could not reliably cover.
|
|
416
198
|
*/
|
|
417
199
|
OnQueryParamsChanged(params, _source) {
|
|
418
|
-
//
|
|
419
|
-
|
|
200
|
+
// The Library filter and category need no list lookup — apply them eagerly either way.
|
|
201
|
+
// Only a location change is reported to the agent, so the delivery that only removes the
|
|
202
|
+
// `dashboard` param (after a `?dashboard=` link opens its dashboard) reports nothing.
|
|
203
|
+
const previous = this.currentLocation;
|
|
204
|
+
this.applyLocation(ResolveBrowseLocation(params) ?? { Filter: 'all', CategoryId: null });
|
|
205
|
+
if (!SameBrowseLocation(previous, this.currentLocation)) {
|
|
206
|
+
this.emitAgentContext();
|
|
207
|
+
}
|
|
420
208
|
if (params['dashboard'] && this.Dashboards.length === 0) {
|
|
421
|
-
// List not loaded yet — defer the dashboard
|
|
209
|
+
// List not loaded yet — defer the dashboard open until it is.
|
|
422
210
|
this._pendingQueryParams = params;
|
|
423
211
|
this.cdr.detectChanges();
|
|
424
212
|
return;
|
|
@@ -426,129 +214,195 @@ let DashboardBrowserResourceComponent = class DashboardBrowserResourceComponent
|
|
|
426
214
|
this.applyDashboardSelectionFromParams(params);
|
|
427
215
|
}
|
|
428
216
|
/**
|
|
429
|
-
*
|
|
430
|
-
*
|
|
431
|
-
*
|
|
217
|
+
* Opens the dashboard named by the `dashboard` query param,
|
|
218
|
+
* then removes the param, so a reload does not open it again and the same
|
|
219
|
+
* link opens it next time. Requires the dashboard list to be loaded.
|
|
432
220
|
*/
|
|
433
221
|
applyDashboardSelectionFromParams(params) {
|
|
434
222
|
const dashboardId = params['dashboard'] || null;
|
|
435
|
-
const currentDashboardId = this.SelectedDashboard?.ID || null;
|
|
436
|
-
if (dashboardId === currentDashboardId) {
|
|
437
|
-
this.cdr.detectChanges();
|
|
438
|
-
return;
|
|
439
|
-
}
|
|
440
223
|
if (dashboardId) {
|
|
441
224
|
const dashboard = this.Dashboards.find(d => UUIDsEqual(d.ID, dashboardId));
|
|
442
225
|
if (dashboard) {
|
|
443
|
-
this.
|
|
226
|
+
this.openDashboard(dashboard);
|
|
444
227
|
}
|
|
228
|
+
this.clearDashboardQueryParam();
|
|
445
229
|
}
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
230
|
+
this.cdr.detectChanges();
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Removes the `dashboard` query param from this tab. Deferred to a microtask
|
|
234
|
+
* because UpdateQueryParams does nothing while OnQueryParamsChanged is being
|
|
235
|
+
* delivered.
|
|
236
|
+
*/
|
|
237
|
+
clearDashboardQueryParam() {
|
|
238
|
+
queueMicrotask(() => this.UpdateQueryParams({ dashboard: null }));
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Opens a dashboard in a tab of the Dashboards app (see DashboardsAppOpenOptions), in edit mode
|
|
242
|
+
* when `openInEditMode` is set, and in a separate tab when `openInNewTab` is set. The Library
|
|
243
|
+
* itself stays on the list.
|
|
244
|
+
*/
|
|
245
|
+
openDashboard(dashboard, openInEditMode = false, openInNewTab = false) {
|
|
246
|
+
this.navigationService.OpenDashboard(dashboard.ID, dashboard.Name, DashboardsAppOpenOptions(this.appManager, openInEditMode, openInNewTab));
|
|
449
247
|
}
|
|
450
248
|
// ========================================
|
|
451
249
|
// BaseResourceComponent Implementation
|
|
452
250
|
// ========================================
|
|
453
|
-
async GetResourceDisplayName(
|
|
454
|
-
return '
|
|
251
|
+
async GetResourceDisplayName(_data) {
|
|
252
|
+
return 'Library';
|
|
455
253
|
}
|
|
456
|
-
async GetResourceIconClass(
|
|
457
|
-
return 'fa-solid fa-
|
|
254
|
+
async GetResourceIconClass(_data) {
|
|
255
|
+
return 'fa-solid fa-layer-group';
|
|
256
|
+
}
|
|
257
|
+
// ========================================
|
|
258
|
+
// Library Rail
|
|
259
|
+
// ========================================
|
|
260
|
+
/** The rail item for the current Library filter or category. */
|
|
261
|
+
get RailActiveId() {
|
|
262
|
+
return RailActiveItemId(this.currentLocation, this.Categories);
|
|
263
|
+
}
|
|
264
|
+
/** True unless one category's folder is open: every other location is a flat list. */
|
|
265
|
+
get IsFlatView() {
|
|
266
|
+
return !IsCategoryFolderLocation(this.currentLocation);
|
|
267
|
+
}
|
|
268
|
+
/** Moves the Library to the Library filter, category or Uncategorized of the clicked rail item. */
|
|
269
|
+
OnRailItemClicked(item) {
|
|
270
|
+
const location = ParseRailItemId(item.id);
|
|
271
|
+
if (location) {
|
|
272
|
+
this.moveTo(location);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
/** Expands or collapses a category in the rail tree. */
|
|
276
|
+
OnRailItemToggled(item) {
|
|
277
|
+
this.RailExpandedIds = ToggleExpandedId(this.RailExpandedIds, item.id);
|
|
278
|
+
this.cdr.detectChanges();
|
|
279
|
+
}
|
|
280
|
+
/** Collapses or expands the whole rail and saves the choice for the user. */
|
|
281
|
+
OnRailCollapsedChange(collapsed) {
|
|
282
|
+
this.RailCollapsed = collapsed;
|
|
283
|
+
UserInfoEngine.Instance.SetSettingDebounced(DashboardBrowserResourceComponent_1.RAIL_COLLAPSED_SETTING, String(collapsed));
|
|
284
|
+
}
|
|
285
|
+
get currentLocation() {
|
|
286
|
+
return { Filter: this.LibraryFilter, CategoryId: this.SelectedCategoryId };
|
|
287
|
+
}
|
|
288
|
+
/** Applies a location the user chose, writes it to the URL, and reports it to the agent. */
|
|
289
|
+
moveTo(location) {
|
|
290
|
+
this.applyLocation(location);
|
|
291
|
+
this.updateUrlQueryParams();
|
|
292
|
+
this.emitAgentContext();
|
|
293
|
+
this.cdr.detectChanges();
|
|
294
|
+
}
|
|
295
|
+
/** Sets the Library filter and category, then recomputes what the browser and the rail show. */
|
|
296
|
+
applyLocation(location) {
|
|
297
|
+
this.LibraryFilter = location.Filter;
|
|
298
|
+
this.SelectedCategoryId = location.CategoryId;
|
|
299
|
+
this.refreshLibraryView();
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* Recomputes what the browser and the rail show from the current state: the visible dashboards,
|
|
303
|
+
* the stars, the owners, the empty state, the counts and the rail sections.
|
|
304
|
+
*/
|
|
305
|
+
refreshLibraryView() {
|
|
306
|
+
const context = this.libraryContext();
|
|
307
|
+
this.VisibleDashboards = VisibleLibraryDashboards(this.Dashboards, this.currentLocation, context, this.EffectiveCategoryMap);
|
|
308
|
+
this.FavoriteIds = context.FavoriteIds;
|
|
309
|
+
this.OwnerLabels = BuildOwnerLabels(this.Dashboards, context.CurrentUserId, this.ownerNames);
|
|
310
|
+
this.EmptyState = LibraryEmptyState(this.currentLocation);
|
|
311
|
+
this.LibraryCounts = CountLibraryDashboards(this.Dashboards, context);
|
|
312
|
+
const categoryCounts = CountDashboardsByCategory(this.Dashboards, this.Categories, this.EffectiveCategoryMap);
|
|
313
|
+
this.RailSections = BuildLibraryRailSections(this.LibraryCounts, this.Categories, categoryCounts.ByCategory, categoryCounts.Uncategorized);
|
|
314
|
+
this.refreshRailExpansion();
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* Expands the top-level categories the first time there are any, then keeps the user's
|
|
318
|
+
* choices, adding only the folders above the selected category so it stays visible.
|
|
319
|
+
*/
|
|
320
|
+
refreshRailExpansion() {
|
|
321
|
+
if (!this.railExpansionInitialized && this.Categories.length > 0) {
|
|
322
|
+
const topLevelItems = this.RailSections.flatMap(section => section.items);
|
|
323
|
+
this.RailExpandedIds = topLevelItems.filter(item => item.children?.length).map(item => item.id);
|
|
324
|
+
this.railExpansionInitialized = true;
|
|
325
|
+
}
|
|
326
|
+
this.RailExpandedIds = WithExpandedAncestors(this.RailExpandedIds, this.SelectedCategoryId, this.Categories);
|
|
327
|
+
}
|
|
328
|
+
libraryContext() {
|
|
329
|
+
return {
|
|
330
|
+
CurrentUserId: this.ProviderToUse.CurrentUser.ID,
|
|
331
|
+
FavoriteIds: this.favorites.FavoriteIds(),
|
|
332
|
+
RecentIds: this.recentDashboardIds,
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* Favorites or recents changed: recompute the view, which re-sorts it, and re-render. Does not
|
|
337
|
+
* report to the agent, because these changes also arrive while the Library is a background tab
|
|
338
|
+
* and the agent context belongs to the active tab.
|
|
339
|
+
*/
|
|
340
|
+
onLibrarySourcesChanged() {
|
|
341
|
+
this.refreshLibraryView();
|
|
342
|
+
this.cdr.detectChanges();
|
|
343
|
+
}
|
|
344
|
+
/** The recently opened dashboards were read again: keep them, then refresh as {@link onLibrarySourcesChanged} does. */
|
|
345
|
+
onRecentDashboardIdsChanged(ids) {
|
|
346
|
+
this.recentDashboardIds = ids;
|
|
347
|
+
this.onLibrarySourcesChanged();
|
|
348
|
+
}
|
|
349
|
+
/**
|
|
350
|
+
* The dashboard cache changed: re-read it, refresh the view and read the names of new owners.
|
|
351
|
+
* Does not report to the agent, because this also runs while the Library is a background tab.
|
|
352
|
+
*/
|
|
353
|
+
onLibraryCacheChanged() {
|
|
354
|
+
this.readLibraryFromEngine();
|
|
355
|
+
this.refreshLibraryView();
|
|
356
|
+
this.cdr.detectChanges();
|
|
357
|
+
void this.refreshOwnerNames();
|
|
458
358
|
}
|
|
459
359
|
// ========================================
|
|
460
360
|
// Agent Context & Client Tools
|
|
461
361
|
//
|
|
462
|
-
// 🔒 SAFETY BOUNDARY: the
|
|
463
|
-
// navigational tools to the AI agent
|
|
464
|
-
// the
|
|
465
|
-
//
|
|
466
|
-
//
|
|
467
|
-
// LIST mode only: SelectCategory, FilterByCategory, ClearDashboardFilters,
|
|
468
|
-
// SwitchViewMode.
|
|
469
|
-
// OPEN mode only (a dashboard is open): BackToList, GetDashboardPanels,
|
|
470
|
-
// GetDashboardDetail.
|
|
471
|
-
// READ-ONLY DETAIL (registered in both modes alongside the above):
|
|
472
|
-
// GetCategoryHierarchy, GetDashboardShares (and GetDashboardDetail).
|
|
362
|
+
// 🔒 SAFETY BOUNDARY: the Library page exposes ONLY read-only /
|
|
363
|
+
// navigational tools to the AI agent: SearchDashboards, OpenDashboard (opens
|
|
364
|
+
// the dashboard), RefreshDashboardList, SelectLibraryFilter,
|
|
365
|
+
// SelectCategory, FilterByCategory, ClearDashboardFilters, SwitchViewMode, and
|
|
366
|
+
// the read-only detail tools GetCategoryHierarchy and GetDashboardShares.
|
|
473
367
|
//
|
|
474
368
|
// Mutating operations — create / delete / save / share / move a dashboard,
|
|
475
|
-
// create / delete a category, add
|
|
476
|
-
//
|
|
477
|
-
//
|
|
478
|
-
//
|
|
479
|
-
//
|
|
480
|
-
// without revisiting this boundary.
|
|
369
|
+
// create / delete a category, add or remove a favorite — are intentionally
|
|
370
|
+
// NOT exposed. The agent helps the user find, open, and understand dashboards
|
|
371
|
+
// (the category tree and who a dashboard is shared with); the user performs
|
|
372
|
+
// every mutation from the UI, including the star on a card. Do NOT add a
|
|
373
|
+
// mutating tool here without revisiting this boundary.
|
|
481
374
|
// ========================================
|
|
482
375
|
/**
|
|
483
376
|
* Report the current browser state to the AI agent (async chat agent and
|
|
484
|
-
* realtime co-agent). Re-emit whenever
|
|
377
|
+
* realtime co-agent). Re-emit whenever the list, search, category filter,
|
|
485
378
|
* view mode, or loading state changes.
|
|
486
379
|
*/
|
|
487
380
|
emitAgentContext() {
|
|
488
|
-
|
|
489
|
-
// publish context, so the agent's tool manifest and context agree.
|
|
490
|
-
this.syncAgentToolsForMode();
|
|
491
|
-
const selectedCategory = this.SelectedCategoryId
|
|
492
|
-
? this.Categories.find(c => UUIDsEqual(c.ID, this.SelectedCategoryId)) ?? null
|
|
493
|
-
: null;
|
|
494
|
-
const dashboardOpen = this.Mode !== 'list' && this.SelectedDashboard !== null;
|
|
381
|
+
const context = this.libraryContext();
|
|
495
382
|
this.navigationService.SetAgentContext(this, BuildDashboardBrowserAgentContext({
|
|
496
|
-
|
|
497
|
-
SelectedDashboardId: this.SelectedDashboard?.ID ?? null,
|
|
498
|
-
SelectedDashboardName: this.SelectedDashboard?.Name ?? null,
|
|
499
|
-
VisibleDashboardNames: this.Dashboards.map(d => d.Name || '(untitled)'),
|
|
383
|
+
VisibleDashboardNames: this.VisibleDashboards.map(d => d.Name || '(untitled)'),
|
|
500
384
|
TotalDashboardCount: this.totalAccessibleDashboardCount,
|
|
501
|
-
FilteredDashboardCount: this.
|
|
385
|
+
FilteredDashboardCount: this.VisibleDashboards.length,
|
|
386
|
+
LibraryFilter: this.LibraryFilter,
|
|
387
|
+
LibraryCounts: this.LibraryCounts,
|
|
388
|
+
RecentlyOpenedNames: FilterDashboardsForLibrary(this.Dashboards.filter(IsBrowsableDashboard), 'recent', context).map(d => d.Name),
|
|
502
389
|
SearchText: this.agentSearchText,
|
|
503
390
|
AvailableCategoryNames: this.Categories.map(c => c.Name),
|
|
504
391
|
SelectedCategoryId: this.SelectedCategoryId,
|
|
505
|
-
SelectedCategoryName:
|
|
392
|
+
SelectedCategoryName: this.selectedCategoryName,
|
|
506
393
|
ViewMode: this.ViewMode,
|
|
507
394
|
IsLoading: this.isLoading,
|
|
508
|
-
// Opened-dashboard awareness (only meaningful when a dashboard is open)
|
|
509
|
-
OpenedDashboardName: dashboardOpen ? (this.SelectedDashboard?.Name ?? null) : null,
|
|
510
|
-
OpenedDashboardId: dashboardOpen ? (this.SelectedDashboard?.ID ?? null) : null,
|
|
511
|
-
OpenedDashboardIsEditing: this.Mode === 'edit',
|
|
512
|
-
OpenedDashboardCanEdit: dashboardOpen ? this.SelectedDashboardPermissions.CanEdit : false,
|
|
513
|
-
OpenedDashboardPanels: dashboardOpen ? this.readOpenedDashboardPanels() : [],
|
|
514
395
|
}));
|
|
515
396
|
}
|
|
516
|
-
/**
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
* panel in Golden Layout's componentState; {@link extractPanelsFromLayout}
|
|
521
|
-
* pulls them out. We resolve each panel's part-type name via the viewer's
|
|
522
|
-
* `getPartTypeForPanel`. Tolerant by design — if the viewer isn't mounted
|
|
523
|
-
* yet (e.g. context emitted before @ViewChild resolves) we return an empty
|
|
524
|
-
* list rather than throwing.
|
|
525
|
-
*
|
|
526
|
-
* @returns a descriptive panel summary list (never null)
|
|
527
|
-
*/
|
|
528
|
-
readOpenedDashboardPanels() {
|
|
529
|
-
const viewer = this.DashboardViewer;
|
|
530
|
-
if (!viewer)
|
|
531
|
-
return [];
|
|
532
|
-
try {
|
|
533
|
-
const config = viewer.getConfig();
|
|
534
|
-
const panels = extractPanelsFromLayout(config?.layout ?? null);
|
|
535
|
-
return panels.map(panel => {
|
|
536
|
-
const partType = viewer.getPartTypeForPanel(panel.id);
|
|
537
|
-
const summary = {
|
|
538
|
-
Title: panel.title || '(untitled panel)',
|
|
539
|
-
PartTypeName: partType?.Name || panel.config?.type || 'Unknown',
|
|
540
|
-
};
|
|
541
|
-
const icon = panel.icon || partType?.Icon || undefined;
|
|
542
|
-
if (icon) {
|
|
543
|
-
summary.Icon = icon;
|
|
544
|
-
}
|
|
545
|
-
return summary;
|
|
546
|
-
});
|
|
547
|
-
}
|
|
548
|
-
catch {
|
|
549
|
-
// Reading panels is best-effort; never let it break context emission.
|
|
550
|
-
return [];
|
|
397
|
+
/** The name of the selected category, "Uncategorized" for that list, or null for none. */
|
|
398
|
+
get selectedCategoryName() {
|
|
399
|
+
if (this.SelectedCategoryId === UNCATEGORIZED_CATEGORY_ID) {
|
|
400
|
+
return 'Uncategorized';
|
|
551
401
|
}
|
|
402
|
+
const selectedCategory = this.SelectedCategoryId
|
|
403
|
+
? this.Categories.find(c => UUIDsEqual(c.ID, this.SelectedCategoryId)) ?? null
|
|
404
|
+
: null;
|
|
405
|
+
return selectedCategory?.Name ?? null;
|
|
552
406
|
}
|
|
553
407
|
/**
|
|
554
408
|
* The total number of dashboards accessible to the current user, independent
|
|
@@ -561,30 +415,15 @@ let DashboardBrowserResourceComponent = class DashboardBrowserResourceComponent
|
|
|
561
415
|
return DashboardEngine.Instance.GetAccessibleDashboards(md.CurrentUser.ID).length;
|
|
562
416
|
}
|
|
563
417
|
/**
|
|
564
|
-
*
|
|
565
|
-
*
|
|
566
|
-
* The Dashboard Browser surfaces different tools depending on whether the
|
|
567
|
-
* user is browsing the list ('list') or has a dashboard open ('open' — the
|
|
568
|
-
* view/edit modes collapse to one tool-mode here). We compute the effective
|
|
569
|
-
* mode, and only when it differs from {@link lastRegisteredToolMode} do we
|
|
570
|
-
* call `SetAgentClientTools` with `[...common, ...(list ? list : open)]`.
|
|
571
|
-
* Guarding on the flip avoids re-registering on every context emission.
|
|
572
|
-
*
|
|
573
|
-
* Mirrors the Data Explorer's mode-scoped tool approach.
|
|
418
|
+
* Registers the agent client tools. The Library always shows the list, so the
|
|
419
|
+
* tool set never changes and is registered once.
|
|
574
420
|
*/
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
if (effective === this.lastRegisteredToolMode) {
|
|
578
|
-
return;
|
|
579
|
-
}
|
|
580
|
-
this.lastRegisteredToolMode = effective;
|
|
581
|
-
const scoped = effective === 'list' ? this.listModeTools() : this.openModeTools();
|
|
582
|
-
this.navigationService.SetAgentClientTools(this, [...this.commonTools(), ...scoped]);
|
|
421
|
+
registerAgentTools() {
|
|
422
|
+
this.navigationService.SetAgentClientTools(this, [...this.commonTools(), ...this.listModeTools()]);
|
|
583
423
|
}
|
|
584
424
|
/**
|
|
585
|
-
* Tools
|
|
586
|
-
*
|
|
587
|
-
* another dashboard without first going back to the list.
|
|
425
|
+
* Tools to find, open, and reload dashboards, plus the read-only detail
|
|
426
|
+
* tools (category tree, shares).
|
|
588
427
|
*/
|
|
589
428
|
commonTools() {
|
|
590
429
|
return [
|
|
@@ -601,7 +440,7 @@ let DashboardBrowserResourceComponent = class DashboardBrowserResourceComponent
|
|
|
601
440
|
},
|
|
602
441
|
{
|
|
603
442
|
Name: 'OpenDashboard',
|
|
604
|
-
Description: 'Open a dashboard
|
|
443
|
+
Description: 'Open a dashboard. Accepts either the dashboard NAME (as listed in VisibleDashboards) or its ID.',
|
|
605
444
|
ParameterSchema: { type: 'object', properties: { dashboard: { type: 'string', description: 'The dashboard name or ID to open.' }, dashboardId: { type: 'string', description: 'Deprecated alias for "dashboard" — the dashboard ID.' } } },
|
|
606
445
|
Handler: async (params) => {
|
|
607
446
|
const v = ValidateStringParam(params['dashboard'] ?? params['dashboardId'], 'dashboard');
|
|
@@ -631,8 +470,8 @@ let DashboardBrowserResourceComponent = class DashboardBrowserResourceComponent
|
|
|
631
470
|
},
|
|
632
471
|
{
|
|
633
472
|
Name: 'GetDashboardShares',
|
|
634
|
-
Description: 'List who a dashboard is shared with and their access level (read/edit/delete/share).
|
|
635
|
-
ParameterSchema: { type: 'object', properties: { dashboardId: { type: 'string', description: '
|
|
473
|
+
Description: 'List who a dashboard is shared with and their access level (read/edit/delete/share). Pass the dashboard ID or name. Read-only — returns no secrets.',
|
|
474
|
+
ParameterSchema: { type: 'object', properties: { dashboardId: { type: 'string', description: 'The dashboard ID or name.' } }, required: ['dashboardId'] },
|
|
636
475
|
Handler: async (params) => {
|
|
637
476
|
return this.agentGetDashboardShares(params['dashboardId']);
|
|
638
477
|
},
|
|
@@ -640,16 +479,23 @@ let DashboardBrowserResourceComponent = class DashboardBrowserResourceComponent
|
|
|
640
479
|
];
|
|
641
480
|
}
|
|
642
481
|
/**
|
|
643
|
-
* Tools
|
|
644
|
-
* filters, and view-mode toggling.
|
|
645
|
-
* dashboard is open, so they're scoped out of the OPEN tool-set.
|
|
482
|
+
* Tools that shape the list — the Library filter, category filtering, clear
|
|
483
|
+
* filters, and view-mode toggling.
|
|
646
484
|
*/
|
|
647
485
|
listModeTools() {
|
|
648
486
|
return [
|
|
487
|
+
{
|
|
488
|
+
Name: 'SelectLibraryFilter',
|
|
489
|
+
Description: 'Show one Library list from the rail: "all" (every dashboard: favorites first, then the ones opened last, then the rest), "mine" (dashboards the user owns), "shared" (dashboards shared with the user), "favorites", or "recent" (recently opened, the most recent first). Clears any category filter.',
|
|
490
|
+
ParameterSchema: { type: 'object', properties: { filter: { type: 'string', enum: [...DASHBOARD_LIBRARY_FILTERS] } }, required: ['filter'] },
|
|
491
|
+
Handler: async (params) => {
|
|
492
|
+
return this.agentSelectLibraryFilter(params['filter']);
|
|
493
|
+
},
|
|
494
|
+
},
|
|
649
495
|
{
|
|
650
496
|
Name: 'SelectCategory',
|
|
651
|
-
Description: 'Filter the dashboard list to a category. Accepts either the category NAME (as listed in AvailableCategories) or its ID. Pass an empty string to clear the category filter (show
|
|
652
|
-
ParameterSchema: { type: 'object', properties: { category: { type: 'string', description: 'The category name or ID to filter by. Empty string clears the filter.' } }, required: ['category'] },
|
|
497
|
+
Description: 'Filter the dashboard list to a category. Accepts either the category NAME (as listed in AvailableCategories) or its ID, or "Uncategorized" for the dashboards in no category. Pass an empty string to clear the category filter (show the whole list).',
|
|
498
|
+
ParameterSchema: { type: 'object', properties: { category: { type: 'string', description: 'The category name or ID to filter by, or "Uncategorized". Empty string clears the filter.' } }, required: ['category'] },
|
|
653
499
|
Handler: async (params) => {
|
|
654
500
|
const v = ValidateStringParam(params['category'], 'category');
|
|
655
501
|
if (!v.ok)
|
|
@@ -659,7 +505,7 @@ let DashboardBrowserResourceComponent = class DashboardBrowserResourceComponent
|
|
|
659
505
|
},
|
|
660
506
|
{
|
|
661
507
|
Name: 'FilterByCategory',
|
|
662
|
-
Description: 'Filter the dashboard list to a category by its ID. Pass an empty string to clear the category filter (show
|
|
508
|
+
Description: 'Filter the dashboard list to a category by its ID. Pass an empty string to clear the category filter (show the whole list). Prefer SelectCategory, which also accepts a category name.',
|
|
663
509
|
ParameterSchema: { type: 'object', properties: { categoryId: { type: 'string' } }, required: ['categoryId'] },
|
|
664
510
|
Handler: async (params) => {
|
|
665
511
|
const v = ValidateStringParam(params['categoryId'], 'categoryId');
|
|
@@ -670,7 +516,7 @@ let DashboardBrowserResourceComponent = class DashboardBrowserResourceComponent
|
|
|
670
516
|
},
|
|
671
517
|
{
|
|
672
518
|
Name: 'ClearDashboardFilters',
|
|
673
|
-
Description: 'Clear all active dashboard-list filters —
|
|
519
|
+
Description: 'Clear all active dashboard-list filters — the text search, the Library filter and the category filter — and return to the full list (All dashboards).',
|
|
674
520
|
ParameterSchema: { type: 'object', properties: {} },
|
|
675
521
|
Handler: async () => this.agentClearDashboardFilters(),
|
|
676
522
|
},
|
|
@@ -684,42 +530,6 @@ let DashboardBrowserResourceComponent = class DashboardBrowserResourceComponent
|
|
|
684
530
|
},
|
|
685
531
|
];
|
|
686
532
|
}
|
|
687
|
-
/**
|
|
688
|
-
* Tools available only while a dashboard is OPEN (view/edit) — go back to
|
|
689
|
-
* the list, inspect the open dashboard's panels, and read its detail
|
|
690
|
-
* (owner, dates, access level). Scoped out of the LIST tool-set because
|
|
691
|
-
* there's no open dashboard there.
|
|
692
|
-
*/
|
|
693
|
-
openModeTools() {
|
|
694
|
-
return [
|
|
695
|
-
{
|
|
696
|
-
Name: 'BackToList',
|
|
697
|
-
Description: 'Return from a dashboard view/edit back to the dashboard list.',
|
|
698
|
-
ParameterSchema: { type: 'object', properties: {} },
|
|
699
|
-
Handler: async () => {
|
|
700
|
-
this.BackToList();
|
|
701
|
-
this.emitAgentContext();
|
|
702
|
-
return { Success: true };
|
|
703
|
-
},
|
|
704
|
-
},
|
|
705
|
-
{
|
|
706
|
-
Name: 'GetDashboardPanels',
|
|
707
|
-
Description: 'List the panels/widgets on a dashboard — each panel\'s title, part-type, and icon. Defaults to the open dashboard; pass a dashboardId (or name) to inspect another accessible dashboard. Read-only.',
|
|
708
|
-
ParameterSchema: { type: 'object', properties: { dashboardId: { type: 'string', description: 'Optional dashboard ID or name. Defaults to the open dashboard.' } } },
|
|
709
|
-
Handler: async (params) => {
|
|
710
|
-
return this.agentGetDashboardPanels(params['dashboardId']);
|
|
711
|
-
},
|
|
712
|
-
},
|
|
713
|
-
{
|
|
714
|
-
Name: 'GetDashboardDetail',
|
|
715
|
-
Description: 'Get detail about a dashboard — owner, created/updated dates, category, and the current user\'s access level (CanRead/Edit/Delete/Share, IsOwner). Defaults to the open dashboard; pass a dashboardId (or name) for another accessible dashboard. Read-only.',
|
|
716
|
-
ParameterSchema: { type: 'object', properties: { dashboardId: { type: 'string', description: 'Optional dashboard ID or name. Defaults to the open dashboard.' } } },
|
|
717
|
-
Handler: async (params) => {
|
|
718
|
-
return this.agentGetDashboardDetail(params['dashboardId']);
|
|
719
|
-
},
|
|
720
|
-
},
|
|
721
|
-
];
|
|
722
|
-
}
|
|
723
533
|
/**
|
|
724
534
|
* Filter the dashboard list by a text query against name + description.
|
|
725
535
|
* The generic browser owns the category/view chrome; this surfaces a
|
|
@@ -727,94 +537,68 @@ let DashboardBrowserResourceComponent = class DashboardBrowserResourceComponent
|
|
|
727
537
|
* applicable, so we narrow the in-memory list the browser renders.
|
|
728
538
|
*/
|
|
729
539
|
agentSearchDashboards(query) {
|
|
730
|
-
const q = query.trim().toLowerCase();
|
|
731
|
-
const engine = DashboardEngine.Instance;
|
|
732
|
-
const md = this.ProviderToUse;
|
|
733
|
-
const all = [...engine.GetAccessibleDashboards(md.CurrentUser.ID)];
|
|
734
|
-
const matched = q
|
|
735
|
-
? all.filter(d => (d.Name || '').toLowerCase().includes(q) ||
|
|
736
|
-
(d.Description || '').toLowerCase().includes(q))
|
|
737
|
-
: all;
|
|
738
540
|
this.agentSearchText = query.trim();
|
|
739
|
-
this.Dashboards =
|
|
740
|
-
this.
|
|
741
|
-
this.SelectedDashboard = null;
|
|
541
|
+
this.Dashboards = this.readSearchedDashboards();
|
|
542
|
+
this.refreshLibraryView();
|
|
742
543
|
this.emitAgentContext();
|
|
743
544
|
this.cdr.detectChanges();
|
|
744
545
|
return { Success: true };
|
|
745
546
|
}
|
|
547
|
+
/** Show one Library list; clears any category filter. */
|
|
548
|
+
agentSelectLibraryFilter(rawFilter) {
|
|
549
|
+
if (!IsDashboardLibraryFilter(rawFilter)) {
|
|
550
|
+
return { Success: false, ErrorMessage: `filter must be one of: ${DASHBOARD_LIBRARY_FILTERS.join(', ')}.` };
|
|
551
|
+
}
|
|
552
|
+
this.moveTo({ Filter: rawFilter, CategoryId: null });
|
|
553
|
+
return { Success: true };
|
|
554
|
+
}
|
|
746
555
|
/**
|
|
747
|
-
* Apply a category filter by NAME or ID (empty string clears it)
|
|
748
|
-
*
|
|
749
|
-
*
|
|
750
|
-
*
|
|
556
|
+
* Apply a category filter by NAME or ID (empty string clears it). Resolves a
|
|
557
|
+
* supplied name (case-insensitive) to its id against the loaded, accessible
|
|
558
|
+
* category list — mirroring the Data Explorer's SelectView name→id resolution.
|
|
559
|
+
* "Uncategorized" (when no category has that name) shows the dashboards in no
|
|
560
|
+
* category. A category is shown under the All Library filter.
|
|
751
561
|
*/
|
|
752
562
|
agentSelectCategory(categoryNameOrId) {
|
|
753
563
|
const raw = categoryNameOrId.trim();
|
|
754
564
|
// Empty string clears the filter.
|
|
755
565
|
if (!raw) {
|
|
756
|
-
this.
|
|
757
|
-
this.Mode = 'list';
|
|
758
|
-
this.SelectedDashboard = null;
|
|
759
|
-
this.updateUrlQueryParams();
|
|
760
|
-
this.emitAgentContext();
|
|
761
|
-
this.cdr.detectChanges();
|
|
566
|
+
this.moveTo({ Filter: this.LibraryFilter, CategoryId: null });
|
|
762
567
|
return { Success: true };
|
|
763
568
|
}
|
|
764
569
|
// Prefer an exact id match, then fall back to a case-insensitive name match.
|
|
765
570
|
const lowered = raw.toLowerCase();
|
|
766
571
|
const match = this.Categories.find(c => UUIDsEqual(c.ID, raw)) ??
|
|
767
572
|
this.Categories.find(c => (c.Name || '').toLowerCase() === lowered);
|
|
573
|
+
if (!match && lowered === UNCATEGORIZED_CATEGORY_ID) {
|
|
574
|
+
this.moveTo({ Filter: 'all', CategoryId: UNCATEGORIZED_CATEGORY_ID });
|
|
575
|
+
return { Success: true };
|
|
576
|
+
}
|
|
768
577
|
if (!match) {
|
|
769
578
|
const available = this.Categories.map(c => c.Name).join(', ') || '(none)';
|
|
770
579
|
return { Success: false, ErrorMessage: `No accessible category named or identified by "${raw}". Available categories: ${available}.` };
|
|
771
580
|
}
|
|
772
|
-
this.
|
|
773
|
-
this.Mode = 'list';
|
|
774
|
-
this.SelectedDashboard = null;
|
|
775
|
-
this.updateUrlQueryParams();
|
|
776
|
-
this.emitAgentContext();
|
|
777
|
-
this.cdr.detectChanges();
|
|
581
|
+
this.moveTo({ Filter: 'all', CategoryId: match.ID });
|
|
778
582
|
return { Success: true };
|
|
779
583
|
}
|
|
780
|
-
/** Clear
|
|
584
|
+
/** Clear the text search, the Library filter and the category filter, returning to All dashboards. */
|
|
781
585
|
agentClearDashboardFilters() {
|
|
782
586
|
this.agentSearchText = '';
|
|
783
|
-
this.SelectedCategoryId = null;
|
|
784
|
-
this.Mode = 'list';
|
|
785
|
-
this.SelectedDashboard = null;
|
|
786
587
|
// Restore the full accessible list (the search may have narrowed this.dashboards).
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
this.Dashboards = [...engine.GetAccessibleDashboards(md.CurrentUser.ID)].sort((a, b) => new Date(b.__mj_UpdatedAt).getTime() - new Date(a.__mj_UpdatedAt).getTime());
|
|
790
|
-
this.updateUrlQueryParams();
|
|
791
|
-
this.emitAgentContext();
|
|
792
|
-
this.cdr.detectChanges();
|
|
588
|
+
this.Dashboards = this.readSearchedDashboards();
|
|
589
|
+
this.moveTo({ Filter: 'all', CategoryId: null });
|
|
793
590
|
return { Success: true };
|
|
794
591
|
}
|
|
795
592
|
/**
|
|
796
|
-
* Open a dashboard
|
|
797
|
-
*
|
|
798
|
-
*
|
|
593
|
+
* Open a dashboard by NAME or ID. Resolves against the full
|
|
594
|
+
* accessible list (see {@link resolveDashboardForTool}), so a prior search
|
|
595
|
+
* that narrowed the visible list does not hide it.
|
|
799
596
|
*/
|
|
800
597
|
agentOpenDashboard(dashboardNameOrId) {
|
|
801
|
-
const
|
|
802
|
-
if (!
|
|
803
|
-
return
|
|
804
|
-
|
|
805
|
-
// prior search may have narrowed) so the agent can open any dashboard by name.
|
|
806
|
-
const engine = DashboardEngine.Instance;
|
|
807
|
-
const md = this.ProviderToUse;
|
|
808
|
-
const accessible = engine.GetAccessibleDashboards(md.CurrentUser.ID);
|
|
809
|
-
const lowered = raw.toLowerCase();
|
|
810
|
-
const dashboard = accessible.find(d => UUIDsEqual(d.ID, raw)) ??
|
|
811
|
-
accessible.find(d => (d.Name || '').toLowerCase() === lowered);
|
|
812
|
-
if (!dashboard) {
|
|
813
|
-
const available = accessible.map(d => d.Name || '(untitled)').slice(0, 25).join(', ') || '(none)';
|
|
814
|
-
return { Success: false, ErrorMessage: `No accessible dashboard named or identified by "${raw}". Available dashboards include: ${available}.` };
|
|
815
|
-
}
|
|
816
|
-
this.OpenDashboard(dashboard);
|
|
817
|
-
this.emitAgentContext();
|
|
598
|
+
const resolved = this.resolveDashboardForTool(dashboardNameOrId);
|
|
599
|
+
if (!resolved.ok)
|
|
600
|
+
return resolved.result;
|
|
601
|
+
this.openDashboard(resolved.dashboard);
|
|
818
602
|
return { Success: true };
|
|
819
603
|
}
|
|
820
604
|
/** Switch and persist the list view mode (cards | list). */
|
|
@@ -829,31 +613,24 @@ let DashboardBrowserResourceComponent = class DashboardBrowserResourceComponent
|
|
|
829
613
|
return { Success: true };
|
|
830
614
|
}
|
|
831
615
|
// ========================================
|
|
832
|
-
// Read-only detail tools (
|
|
833
|
-
// GetCategoryHierarchy / GetDashboardShares).
|
|
616
|
+
// Read-only detail tools (GetCategoryHierarchy / GetDashboardShares).
|
|
834
617
|
// These return descriptive data only — no mutations, never throw.
|
|
835
618
|
// ========================================
|
|
836
619
|
/**
|
|
837
|
-
* Resolve
|
|
838
|
-
*
|
|
839
|
-
*
|
|
840
|
-
*
|
|
620
|
+
* Resolve a tool's dashboard param (ID **or** name) to an accessible
|
|
621
|
+
* dashboard: an exact ID match first, then a case-insensitive name match.
|
|
622
|
+
* Returns a tolerant error result when the param is missing or nothing
|
|
623
|
+
* matches.
|
|
841
624
|
*/
|
|
842
625
|
resolveDashboardForTool(rawParam) {
|
|
843
|
-
const engine = DashboardEngine.Instance;
|
|
844
|
-
const md = this.ProviderToUse;
|
|
845
|
-
const accessible = engine.GetAccessibleDashboards(md.CurrentUser.ID);
|
|
846
626
|
const raw = typeof rawParam === 'string' ? rawParam.trim() : '';
|
|
847
|
-
// No param → default to the open dashboard.
|
|
848
627
|
if (!raw) {
|
|
849
|
-
|
|
850
|
-
return { ok: true, dashboard: this.SelectedDashboard };
|
|
851
|
-
}
|
|
852
|
-
return {
|
|
853
|
-
ok: false,
|
|
854
|
-
result: { Success: false, ErrorMessage: 'No dashboard is open. Provide a dashboardId (or name) to inspect a specific dashboard.' },
|
|
855
|
-
};
|
|
628
|
+
return { ok: false, result: { Success: false, ErrorMessage: 'A dashboard ID or name is required.' } };
|
|
856
629
|
}
|
|
630
|
+
// Search the full accessible set (not this.Dashboards, which a prior
|
|
631
|
+
// search may have narrowed).
|
|
632
|
+
const md = this.ProviderToUse;
|
|
633
|
+
const accessible = DashboardEngine.Instance.GetAccessibleDashboards(md.CurrentUser.ID);
|
|
857
634
|
const lowered = raw.toLowerCase();
|
|
858
635
|
const dashboard = accessible.find(d => UUIDsEqual(d.ID, raw)) ??
|
|
859
636
|
accessible.find(d => (d.Name || '').toLowerCase() === lowered);
|
|
@@ -866,96 +643,6 @@ let DashboardBrowserResourceComponent = class DashboardBrowserResourceComponent
|
|
|
866
643
|
}
|
|
867
644
|
return { ok: true, dashboard };
|
|
868
645
|
}
|
|
869
|
-
/**
|
|
870
|
-
* Return the panel list for the open (or named) dashboard. For the open
|
|
871
|
-
* dashboard we read live panels from the viewer; for another dashboard we
|
|
872
|
-
* parse its persisted UIConfigDetails. Read-only.
|
|
873
|
-
*/
|
|
874
|
-
agentGetDashboardPanels(rawDashboardId) {
|
|
875
|
-
const resolved = this.resolveDashboardForTool(rawDashboardId);
|
|
876
|
-
if (!resolved.ok)
|
|
877
|
-
return resolved.result;
|
|
878
|
-
const dashboard = resolved.dashboard;
|
|
879
|
-
// If this is the currently-open dashboard, read the viewer's live panels
|
|
880
|
-
// (reflects any in-session, unsaved layout edits).
|
|
881
|
-
const isOpen = this.SelectedDashboard !== null && UUIDsEqual(dashboard.ID, this.SelectedDashboard.ID);
|
|
882
|
-
const panels = isOpen
|
|
883
|
-
? this.readOpenedDashboardPanels()
|
|
884
|
-
: this.readPanelsFromPersistedConfig(dashboard);
|
|
885
|
-
return {
|
|
886
|
-
Success: true,
|
|
887
|
-
Data: {
|
|
888
|
-
DashboardId: dashboard.ID,
|
|
889
|
-
DashboardName: dashboard.Name,
|
|
890
|
-
PanelCount: panels.length,
|
|
891
|
-
Panels: panels,
|
|
892
|
-
},
|
|
893
|
-
};
|
|
894
|
-
}
|
|
895
|
-
/**
|
|
896
|
-
* Parse a dashboard's persisted UIConfigDetails into a descriptive panel
|
|
897
|
-
* summary list, resolving each panel's part-type name from the engine's
|
|
898
|
-
* cached part types. Tolerant — returns [] on missing/invalid config.
|
|
899
|
-
*/
|
|
900
|
-
readPanelsFromPersistedConfig(dashboard) {
|
|
901
|
-
const raw = dashboard.UIConfigDetails;
|
|
902
|
-
if (!raw)
|
|
903
|
-
return [];
|
|
904
|
-
try {
|
|
905
|
-
const parsed = JSON.parse(raw);
|
|
906
|
-
const panels = extractPanelsFromLayout(parsed?.layout ?? null);
|
|
907
|
-
const partTypes = DashboardEngine.Instance.DashboardPartTypes;
|
|
908
|
-
return panels.map(panel => {
|
|
909
|
-
const partType = partTypes.find(pt => UUIDsEqual(pt.ID, panel.partTypeId)) ?? null;
|
|
910
|
-
const summary = {
|
|
911
|
-
Title: panel.title || '(untitled panel)',
|
|
912
|
-
PartTypeName: partType?.Name || panel.config?.type || 'Unknown',
|
|
913
|
-
};
|
|
914
|
-
const icon = panel.icon || partType?.Icon || undefined;
|
|
915
|
-
if (icon) {
|
|
916
|
-
summary.Icon = icon;
|
|
917
|
-
}
|
|
918
|
-
return summary;
|
|
919
|
-
});
|
|
920
|
-
}
|
|
921
|
-
catch {
|
|
922
|
-
return [];
|
|
923
|
-
}
|
|
924
|
-
}
|
|
925
|
-
/**
|
|
926
|
-
* Return owner / dates / category / access-level detail for the open (or
|
|
927
|
-
* named) dashboard. Access level comes from the DashboardEngine permissions
|
|
928
|
-
* already computed by the browser. Read-only.
|
|
929
|
-
*/
|
|
930
|
-
agentGetDashboardDetail(rawDashboardId) {
|
|
931
|
-
const resolved = this.resolveDashboardForTool(rawDashboardId);
|
|
932
|
-
if (!resolved.ok)
|
|
933
|
-
return resolved.result;
|
|
934
|
-
const dashboard = resolved.dashboard;
|
|
935
|
-
const md = this.ProviderToUse;
|
|
936
|
-
const perms = DashboardEngine.Instance.GetDashboardPermissions(dashboard.ID, md.CurrentUser.ID);
|
|
937
|
-
return {
|
|
938
|
-
Success: true,
|
|
939
|
-
Data: {
|
|
940
|
-
DashboardId: dashboard.ID,
|
|
941
|
-
DashboardName: dashboard.Name,
|
|
942
|
-
Description: dashboard.Description ?? null,
|
|
943
|
-
Owner: dashboard.User ?? null,
|
|
944
|
-
CategoryName: dashboard.Category ?? null,
|
|
945
|
-
CategoryId: dashboard.CategoryID ?? null,
|
|
946
|
-
CreatedAt: this.toIsoOrNull(dashboard.__mj_CreatedAt),
|
|
947
|
-
UpdatedAt: this.toIsoOrNull(dashboard.__mj_UpdatedAt),
|
|
948
|
-
Access: {
|
|
949
|
-
CanRead: perms.CanRead,
|
|
950
|
-
CanEdit: perms.CanEdit,
|
|
951
|
-
CanDelete: perms.CanDelete,
|
|
952
|
-
CanShare: perms.CanShare,
|
|
953
|
-
IsOwner: perms.IsOwner,
|
|
954
|
-
PermissionSource: perms.PermissionSource,
|
|
955
|
-
},
|
|
956
|
-
},
|
|
957
|
-
};
|
|
958
|
-
}
|
|
959
646
|
/**
|
|
960
647
|
* Return the accessible category tree (name, ID, parent ID, and per-category
|
|
961
648
|
* count of accessible dashboards filed directly under it). Read-only.
|
|
@@ -965,11 +652,12 @@ let DashboardBrowserResourceComponent = class DashboardBrowserResourceComponent
|
|
|
965
652
|
const engine = DashboardEngine.Instance;
|
|
966
653
|
const categories = engine.GetAccessibleCategories(md.CurrentUser.ID);
|
|
967
654
|
const accessible = engine.GetAccessibleDashboards(md.CurrentUser.ID);
|
|
655
|
+
const dashboardCountByCategory = CountByUUID(accessible.filter(d => d.CategoryID), d => d.CategoryID);
|
|
968
656
|
const tree = categories.map(cat => ({
|
|
969
657
|
Name: cat.Name,
|
|
970
658
|
Id: cat.ID,
|
|
971
659
|
ParentId: cat.ParentID ?? null,
|
|
972
|
-
DashboardCount:
|
|
660
|
+
DashboardCount: dashboardCountByCategory.Get(cat.ID) ?? 0,
|
|
973
661
|
}));
|
|
974
662
|
// Dashboards with no (accessible) category sit at the root.
|
|
975
663
|
const rootDashboardCount = accessible.filter(d => !d.CategoryID).length;
|
|
@@ -1012,46 +700,25 @@ let DashboardBrowserResourceComponent = class DashboardBrowserResourceComponent
|
|
|
1012
700
|
},
|
|
1013
701
|
};
|
|
1014
702
|
}
|
|
1015
|
-
/** Format a date to ISO, tolerant of null/invalid values. */
|
|
1016
|
-
toIsoOrNull(value) {
|
|
1017
|
-
if (!value)
|
|
1018
|
-
return null;
|
|
1019
|
-
const time = new Date(value).getTime();
|
|
1020
|
-
return Number.isFinite(time) ? new Date(time).toISOString() : null;
|
|
1021
|
-
}
|
|
1022
703
|
// ========================================
|
|
1023
704
|
// Event Handlers from Generic Browser
|
|
1024
705
|
// ========================================
|
|
1025
706
|
/**
|
|
1026
|
-
* Handle dashboard open request from generic browser
|
|
707
|
+
* Handle dashboard open request from generic browser: open the dashboard, in a separate tab for a
|
|
708
|
+
* Shift, Ctrl or Cmd click.
|
|
1027
709
|
*/
|
|
1028
710
|
OnDashboardOpen(event) {
|
|
1029
|
-
|
|
1030
|
-
// Open in a dedicated Explorer tab via NavigationService
|
|
1031
|
-
this.navigationService.OpenDashboard(event.Dashboard.ID, event.Dashboard.Name, { forceNewTab: true });
|
|
1032
|
-
}
|
|
1033
|
-
else {
|
|
1034
|
-
// Open inline in the browser's view pane
|
|
1035
|
-
this.OpenDashboard(event.Dashboard);
|
|
1036
|
-
}
|
|
711
|
+
this.openDashboard(event.Dashboard, false, event.OpenInNewTab);
|
|
1037
712
|
}
|
|
1038
713
|
/** @deprecated Use {@link OnDashboardOpen}. */
|
|
1039
714
|
onDashboardOpen(event) {
|
|
1040
715
|
return this.OnDashboardOpen(event);
|
|
1041
716
|
}
|
|
1042
717
|
/**
|
|
1043
|
-
*
|
|
1044
|
-
*/
|
|
1045
|
-
openInNewTab() {
|
|
1046
|
-
if (this.SelectedDashboard) {
|
|
1047
|
-
this.navigationService.OpenDashboard(this.SelectedDashboard.ID, this.SelectedDashboard.Name, { forceNewTab: true });
|
|
1048
|
-
}
|
|
1049
|
-
}
|
|
1050
|
-
/**
|
|
1051
|
-
* Handle dashboard edit request from generic browser
|
|
718
|
+
* Handle dashboard edit request from generic browser: open it in edit mode.
|
|
1052
719
|
*/
|
|
1053
720
|
OnDashboardEdit(event) {
|
|
1054
|
-
this.
|
|
721
|
+
this.openDashboard(event.Dashboard, true);
|
|
1055
722
|
}
|
|
1056
723
|
/** @deprecated Use {@link OnDashboardEdit}. */
|
|
1057
724
|
onDashboardEdit(event) {
|
|
@@ -1077,6 +744,7 @@ let DashboardBrowserResourceComponent = class DashboardBrowserResourceComponent
|
|
|
1077
744
|
if (await tg.Submit()) {
|
|
1078
745
|
const deletedIds = new Set(event.Dashboards.map(d => d.ID));
|
|
1079
746
|
this.Dashboards = this.Dashboards.filter(d => !deletedIds.has(d.ID));
|
|
747
|
+
this.refreshLibraryView();
|
|
1080
748
|
}
|
|
1081
749
|
else {
|
|
1082
750
|
console.error('Failed to delete dashboards — all changes rolled back');
|
|
@@ -1087,6 +755,7 @@ let DashboardBrowserResourceComponent = class DashboardBrowserResourceComponent
|
|
|
1087
755
|
}
|
|
1088
756
|
finally {
|
|
1089
757
|
this.isLoading = false;
|
|
758
|
+
this.emitAgentContext();
|
|
1090
759
|
this.cdr.detectChanges();
|
|
1091
760
|
}
|
|
1092
761
|
}
|
|
@@ -1109,6 +778,9 @@ let DashboardBrowserResourceComponent = class DashboardBrowserResourceComponent
|
|
|
1109
778
|
const currentUserId = md.CurrentUser.ID;
|
|
1110
779
|
const tg = await md.CreateTransactionGroup();
|
|
1111
780
|
const sharedDashboardIds = [];
|
|
781
|
+
// The user's links, grouped once. Links created below are queued in the transaction group,
|
|
782
|
+
// not added to the engine's list, so the grouping matches the per-dashboard filter it replaces.
|
|
783
|
+
const userLinksByDashboard = GroupByUUID(DashboardEngine.Instance.DashboardCategoryLinks.filter(link => UUIDsEqual(link.UserID, currentUserId)), link => link.DashboardID);
|
|
1112
784
|
for (const dashboard of event.Dashboards) {
|
|
1113
785
|
const permissions = DashboardEngine.Instance.GetDashboardPermissions(dashboard.ID, currentUserId);
|
|
1114
786
|
if (permissions.IsOwner) {
|
|
@@ -1119,7 +791,7 @@ let DashboardBrowserResourceComponent = class DashboardBrowserResourceComponent
|
|
|
1119
791
|
}
|
|
1120
792
|
else {
|
|
1121
793
|
// Non-owner: create or update a category link instead
|
|
1122
|
-
const existingLinks =
|
|
794
|
+
const existingLinks = userLinksByDashboard.Get(dashboard.ID) ?? [];
|
|
1123
795
|
let link;
|
|
1124
796
|
if (existingLinks.length > 0) {
|
|
1125
797
|
link = existingLinks[0];
|
|
@@ -1143,7 +815,8 @@ let DashboardBrowserResourceComponent = class DashboardBrowserResourceComponent
|
|
|
1143
815
|
}
|
|
1144
816
|
this.EffectiveCategoryMap = new Map(this.EffectiveCategoryMap);
|
|
1145
817
|
this.Dashboards = [...this.Dashboards];
|
|
1146
|
-
|
|
818
|
+
// The folder view follows the moved dashboards; a flat Library list still shows them.
|
|
819
|
+
this.applyLocation(this.IsFlatView ? this.currentLocation : { Filter: 'all', CategoryId: event.TargetCategoryId });
|
|
1147
820
|
this.updateUrlQueryParams();
|
|
1148
821
|
}
|
|
1149
822
|
else {
|
|
@@ -1155,6 +828,7 @@ let DashboardBrowserResourceComponent = class DashboardBrowserResourceComponent
|
|
|
1155
828
|
}
|
|
1156
829
|
finally {
|
|
1157
830
|
this.isLoading = false;
|
|
831
|
+
this.emitAgentContext();
|
|
1158
832
|
this.cdr.detectChanges();
|
|
1159
833
|
}
|
|
1160
834
|
}
|
|
@@ -1162,23 +836,35 @@ let DashboardBrowserResourceComponent = class DashboardBrowserResourceComponent
|
|
|
1162
836
|
async onDashboardMove(event) {
|
|
1163
837
|
return this.OnDashboardMove(event);
|
|
1164
838
|
}
|
|
1165
|
-
/**
|
|
1166
|
-
* Handle create dashboard request from generic browser
|
|
1167
|
-
*/
|
|
839
|
+
/** The generic browser asks for a new dashboard (its New menu or an empty state): asks for the name first. */
|
|
1168
840
|
async OnDashboardCreate(event) {
|
|
1169
|
-
|
|
841
|
+
this.OpenNewDashboardDialog(event.CategoryId);
|
|
1170
842
|
}
|
|
1171
843
|
/** @deprecated Use {@link OnDashboardCreate}. */
|
|
1172
844
|
async onDashboardCreate(event) {
|
|
1173
845
|
return this.OnDashboardCreate(event);
|
|
1174
846
|
}
|
|
847
|
+
/** Stars or unstars a dashboard from its card. Changed$ then re-sorts the Library. */
|
|
848
|
+
async OnDashboardFavoriteToggle(event) {
|
|
849
|
+
const dashboard = event.Dashboard;
|
|
850
|
+
try {
|
|
851
|
+
const isFavorite = await this.favorites.Toggle(dashboard.ID);
|
|
852
|
+
const message = isFavorite ? `Added "${dashboard.Name}" to favorites` : `Removed "${dashboard.Name}" from favorites`;
|
|
853
|
+
this.notifications.CreateSimpleNotification(message, 'success', 2000);
|
|
854
|
+
this.emitAgentContext();
|
|
855
|
+
}
|
|
856
|
+
catch (error) {
|
|
857
|
+
LogError(`Dashboards Library: could not change the favorite: ${error instanceof Error ? error.message : String(error)}`);
|
|
858
|
+
this.notifications.CreateSimpleNotification('Could not change the favorite', 'error', 3000);
|
|
859
|
+
}
|
|
860
|
+
}
|
|
1175
861
|
/**
|
|
1176
|
-
* Handle
|
|
862
|
+
* Handle folder navigation in the generic browser and update the URL. Entering a
|
|
863
|
+
* folder shows it under the All Library filter; going back to the root shows the
|
|
864
|
+
* current Library filter's flat list.
|
|
1177
865
|
*/
|
|
1178
866
|
OnCategoryChange(event) {
|
|
1179
|
-
this.
|
|
1180
|
-
this.updateUrlQueryParams();
|
|
1181
|
-
this.emitAgentContext();
|
|
867
|
+
this.moveTo({ Filter: event.CategoryId ? 'all' : this.LibraryFilter, CategoryId: event.CategoryId });
|
|
1182
868
|
}
|
|
1183
869
|
/** @deprecated Use {@link OnCategoryChange}. */
|
|
1184
870
|
onCategoryChange(event) {
|
|
@@ -1241,6 +927,7 @@ let DashboardBrowserResourceComponent = class DashboardBrowserResourceComponent
|
|
|
1241
927
|
// Add to local array - engine will self-update
|
|
1242
928
|
this.Categories.push(category);
|
|
1243
929
|
this.Categories = [...this.Categories].sort((a, b) => a.Name.localeCompare(b.Name));
|
|
930
|
+
this.refreshLibraryView();
|
|
1244
931
|
}
|
|
1245
932
|
else {
|
|
1246
933
|
const errorMessage = category.LatestResult?.Message || 'Unknown error saving category';
|
|
@@ -1256,6 +943,7 @@ let DashboardBrowserResourceComponent = class DashboardBrowserResourceComponent
|
|
|
1256
943
|
}
|
|
1257
944
|
finally {
|
|
1258
945
|
this.isLoading = false;
|
|
946
|
+
this.emitAgentContext();
|
|
1259
947
|
this.cdr.detectChanges();
|
|
1260
948
|
}
|
|
1261
949
|
}
|
|
@@ -1296,6 +984,7 @@ let DashboardBrowserResourceComponent = class DashboardBrowserResourceComponent
|
|
|
1296
984
|
if (await tg.Submit()) {
|
|
1297
985
|
this.Categories = this.Categories.filter(c => !categoryIds.has(c.ID));
|
|
1298
986
|
this.Dashboards = [...this.Dashboards];
|
|
987
|
+
this.refreshLibraryView();
|
|
1299
988
|
}
|
|
1300
989
|
else {
|
|
1301
990
|
console.error('[DashboardBrowserResource] Failed to delete categories — all changes rolled back');
|
|
@@ -1306,6 +995,7 @@ let DashboardBrowserResourceComponent = class DashboardBrowserResourceComponent
|
|
|
1306
995
|
}
|
|
1307
996
|
finally {
|
|
1308
997
|
this.isLoading = false;
|
|
998
|
+
this.emitAgentContext();
|
|
1309
999
|
this.cdr.detectChanges();
|
|
1310
1000
|
}
|
|
1311
1001
|
}
|
|
@@ -1313,585 +1003,215 @@ let DashboardBrowserResourceComponent = class DashboardBrowserResourceComponent
|
|
|
1313
1003
|
async onCategoryDelete(event) {
|
|
1314
1004
|
return this.OnCategoryDelete(event);
|
|
1315
1005
|
}
|
|
1316
|
-
/**
|
|
1317
|
-
* Handle breadcrumb navigation event
|
|
1318
|
-
* Navigates back to list view with optional category selection
|
|
1319
|
-
*/
|
|
1320
|
-
OnBreadcrumbNavigate(event) {
|
|
1321
|
-
console.debug('[DashboardBrowserResource] Breadcrumb navigate:', event);
|
|
1322
|
-
// CategoryId is null for root, or a category ID string
|
|
1323
|
-
this.SelectedCategoryId = event.CategoryId;
|
|
1324
|
-
this.BackToList();
|
|
1325
|
-
this.updateUrlQueryParams();
|
|
1326
|
-
}
|
|
1327
|
-
/** @deprecated Use {@link OnBreadcrumbNavigate}. */
|
|
1328
|
-
onBreadcrumbNavigate(event) {
|
|
1329
|
-
return this.OnBreadcrumbNavigate(event);
|
|
1330
|
-
}
|
|
1331
1006
|
// ========================================
|
|
1332
|
-
// Public Methods -
|
|
1007
|
+
// Public Methods - Dashboard CRUD
|
|
1333
1008
|
// ========================================
|
|
1334
1009
|
/**
|
|
1335
|
-
*
|
|
1010
|
+
* Opens the New dashboard dialog. The dashboard is filed in `categoryId`, else in the open
|
|
1011
|
+
* category folder, else in no category.
|
|
1336
1012
|
*/
|
|
1337
|
-
|
|
1338
|
-
this.
|
|
1339
|
-
this.
|
|
1340
|
-
|
|
1341
|
-
const md = this.ProviderToUse;
|
|
1342
|
-
this.SelectedDashboardPermissions = DashboardEngine.Instance.GetDashboardPermissions(dashboard.ID, md.CurrentUser.ID);
|
|
1343
|
-
this.updateUrlQueryParams();
|
|
1344
|
-
this.NotifyDisplayNameChanged(dashboard.Name || 'Dashboard');
|
|
1345
|
-
this.emitAgentContext();
|
|
1013
|
+
OpenNewDashboardDialog(categoryId) {
|
|
1014
|
+
this.newDashboardCategoryId = categoryId ?? (IsCategoryFolderLocation(this.currentLocation) ? this.SelectedCategoryId : null);
|
|
1015
|
+
this.NewDashboardNameMaxLength = DashboardNameMaxLength(this.ProviderToUse);
|
|
1016
|
+
this.ShowNewDashboardDialog = true;
|
|
1346
1017
|
this.cdr.detectChanges();
|
|
1347
1018
|
}
|
|
1348
|
-
/**
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
}
|
|
1352
|
-
/**
|
|
1353
|
-
* Open a dashboard for editing
|
|
1354
|
-
*/
|
|
1355
|
-
EditDashboard(dashboard) {
|
|
1356
|
-
// Check if user has edit permission
|
|
1357
|
-
const md = this.ProviderToUse;
|
|
1358
|
-
const permissions = DashboardEngine.Instance.GetDashboardPermissions(dashboard.ID, md.CurrentUser.ID);
|
|
1359
|
-
if (!permissions.CanEdit) {
|
|
1360
|
-
console.warn('User does not have permission to edit this dashboard');
|
|
1361
|
-
return;
|
|
1362
|
-
}
|
|
1363
|
-
this.SelectedDashboard = dashboard;
|
|
1364
|
-
this.SelectedDashboardPermissions = permissions;
|
|
1365
|
-
this.Mode = 'edit';
|
|
1366
|
-
// Initialize editing fields
|
|
1367
|
-
this.EditingName = dashboard.Name;
|
|
1368
|
-
this.EditingDescription = dashboard.Description || '';
|
|
1369
|
-
// Store originals for cancel
|
|
1370
|
-
this.originalName = dashboard.Name;
|
|
1371
|
-
this.originalDescription = dashboard.Description || '';
|
|
1372
|
-
this.originalConfig = dashboard.UIConfigDetails || '';
|
|
1373
|
-
this.emitAgentContext();
|
|
1019
|
+
/** Closes the New dashboard dialog and creates nothing. */
|
|
1020
|
+
OnNewDashboardCancelled() {
|
|
1021
|
+
this.ShowNewDashboardDialog = false;
|
|
1374
1022
|
this.cdr.detectChanges();
|
|
1375
1023
|
}
|
|
1376
|
-
/** @deprecated Use {@link EditDashboard}. */
|
|
1377
|
-
editDashboard(dashboard) {
|
|
1378
|
-
return this.EditDashboard(dashboard);
|
|
1379
|
-
}
|
|
1380
1024
|
/**
|
|
1381
|
-
*
|
|
1025
|
+
* Creates the dashboard named in the dialog, reloads the cache the dashboard tab reads from, puts
|
|
1026
|
+
* the dashboard first in the list, and opens it in edit mode in a tab of the Dashboards app. When
|
|
1027
|
+
* the save fails, the dialog stays open with the name and the user is told. A second call while
|
|
1028
|
+
* the dashboard is being created does nothing.
|
|
1382
1029
|
*/
|
|
1383
|
-
|
|
1384
|
-
this.
|
|
1385
|
-
this.Mode = 'list';
|
|
1386
|
-
this.updateUrlQueryParams();
|
|
1387
|
-
this.NotifyDisplayNameChanged('Dashboards');
|
|
1388
|
-
this.emitAgentContext();
|
|
1389
|
-
this.cdr.detectChanges();
|
|
1390
|
-
}
|
|
1391
|
-
/** @deprecated Use {@link BackToList}. */
|
|
1392
|
-
backToList() {
|
|
1393
|
-
return this.BackToList();
|
|
1394
|
-
}
|
|
1395
|
-
/**
|
|
1396
|
-
* Toggle edit mode for current dashboard
|
|
1397
|
-
*/
|
|
1398
|
-
ToggleEditMode() {
|
|
1399
|
-
if (this.Mode === 'view' && this.SelectedDashboard) {
|
|
1400
|
-
this.EditDashboard(this.SelectedDashboard);
|
|
1401
|
-
}
|
|
1402
|
-
else if (this.Mode === 'edit') {
|
|
1403
|
-
this.Mode = 'view';
|
|
1404
|
-
this.cdr.detectChanges();
|
|
1405
|
-
}
|
|
1406
|
-
}
|
|
1407
|
-
/** @deprecated Use {@link ToggleEditMode}. */
|
|
1408
|
-
toggleEditMode() {
|
|
1409
|
-
return this.ToggleEditMode();
|
|
1410
|
-
}
|
|
1411
|
-
/**
|
|
1412
|
-
* Open the share dialog for the current dashboard
|
|
1413
|
-
*/
|
|
1414
|
-
OpenShareDialog() {
|
|
1415
|
-
if (!this.SelectedDashboard)
|
|
1416
|
-
return;
|
|
1417
|
-
// Verify user has share permission
|
|
1418
|
-
if (!this.SelectedDashboardPermissions.CanShare) {
|
|
1419
|
-
console.warn('User does not have permission to share this dashboard');
|
|
1030
|
+
async OnNewDashboardNamed(name) {
|
|
1031
|
+
if (this.IsCreatingDashboard)
|
|
1420
1032
|
return;
|
|
1033
|
+
this.setCreatingDashboard(true);
|
|
1034
|
+
try {
|
|
1035
|
+
const dashboard = await CreateBlankDashboard(this.ProviderToUse, name, this.newDashboardCategoryId);
|
|
1036
|
+
if (!dashboard) {
|
|
1037
|
+
this.notifications.CreateSimpleNotification('Could not create the dashboard', 'error', 3000);
|
|
1038
|
+
return;
|
|
1039
|
+
}
|
|
1040
|
+
await this.reloadDashboardCache();
|
|
1041
|
+
this.ShowNewDashboardDialog = false;
|
|
1042
|
+
this.Dashboards = [dashboard, ...this.Dashboards.filter(d => !UUIDsEqual(d.ID, dashboard.ID))];
|
|
1043
|
+
this.refreshLibraryView();
|
|
1044
|
+
this.emitAgentContext();
|
|
1045
|
+
this.openDashboard(dashboard, true);
|
|
1046
|
+
}
|
|
1047
|
+
finally {
|
|
1048
|
+
this.setCreatingDashboard(false);
|
|
1421
1049
|
}
|
|
1422
|
-
this.ShowShareDialog = true;
|
|
1423
|
-
this.cdr.detectChanges();
|
|
1424
|
-
}
|
|
1425
|
-
/** @deprecated Use {@link OpenShareDialog}. */
|
|
1426
|
-
openShareDialog() {
|
|
1427
|
-
return this.OpenShareDialog();
|
|
1428
1050
|
}
|
|
1429
|
-
/**
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
CloseShareDialog() {
|
|
1433
|
-
this.ShowShareDialog = false;
|
|
1434
|
-
this.cdr.detectChanges();
|
|
1051
|
+
/** @deprecated Use {@link OpenNewDashboardDialog}. */
|
|
1052
|
+
async CreateDashboard(categoryId) {
|
|
1053
|
+
this.OpenNewDashboardDialog(categoryId);
|
|
1435
1054
|
}
|
|
1436
|
-
/** @deprecated Use {@link
|
|
1437
|
-
|
|
1438
|
-
return this.
|
|
1055
|
+
/** @deprecated Use {@link OpenNewDashboardDialog}. */
|
|
1056
|
+
async createDashboard(categoryId) {
|
|
1057
|
+
return this.CreateDashboard(categoryId);
|
|
1439
1058
|
}
|
|
1440
|
-
/**
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
OnShareDialogResult(result) {
|
|
1444
|
-
this.ShowShareDialog = false;
|
|
1445
|
-
if (result.Action === 'save' && this.SelectedDashboard) {
|
|
1446
|
-
// Recompute permissions after sharing changes
|
|
1447
|
-
const md = this.ProviderToUse;
|
|
1448
|
-
this.SelectedDashboardPermissions = DashboardEngine.Instance.GetDashboardPermissions(this.SelectedDashboard.ID, md.CurrentUser.ID);
|
|
1449
|
-
}
|
|
1059
|
+
/** Sets whether the named dashboard is being created, and re-renders the dialog. */
|
|
1060
|
+
setCreatingDashboard(creating) {
|
|
1061
|
+
this.IsCreatingDashboard = creating;
|
|
1450
1062
|
this.cdr.detectChanges();
|
|
1451
1063
|
}
|
|
1452
|
-
/** @deprecated Use {@link OnShareDialogResult}. */
|
|
1453
|
-
onShareDialogResult(result) {
|
|
1454
|
-
return this.OnShareDialogResult(result);
|
|
1455
|
-
}
|
|
1456
1064
|
// ========================================
|
|
1457
|
-
//
|
|
1065
|
+
// Private Methods - Data Loading
|
|
1458
1066
|
// ========================================
|
|
1459
|
-
/**
|
|
1460
|
-
|
|
1461
|
-
*/
|
|
1462
|
-
async CreateDashboard(categoryId) {
|
|
1067
|
+
/** Reloads the dashboard cache before the new dashboard's tab opens: the tab reads the dashboard from it. A failure is only logged. */
|
|
1068
|
+
async reloadDashboardCache() {
|
|
1463
1069
|
try {
|
|
1464
|
-
this.
|
|
1465
|
-
this.cdr.detectChanges();
|
|
1466
|
-
const md = this.ProviderToUse;
|
|
1467
|
-
const dashboard = await md.GetEntityObject('MJ: Dashboards');
|
|
1468
|
-
dashboard.Name = 'New Dashboard';
|
|
1469
|
-
dashboard.Description = '';
|
|
1470
|
-
dashboard.UserID = md.CurrentUser.ID;
|
|
1471
|
-
dashboard.UIConfigDetails = JSON.stringify(createDefaultDashboardConfig());
|
|
1472
|
-
if (categoryId) {
|
|
1473
|
-
dashboard.CategoryID = categoryId;
|
|
1474
|
-
}
|
|
1475
|
-
else if (this.SelectedCategoryId) {
|
|
1476
|
-
dashboard.CategoryID = this.SelectedCategoryId;
|
|
1477
|
-
}
|
|
1478
|
-
const saved = await dashboard.Save();
|
|
1479
|
-
if (saved) {
|
|
1480
|
-
this.Dashboards.unshift(dashboard);
|
|
1481
|
-
this.Dashboards = [...this.Dashboards];
|
|
1482
|
-
this.EditDashboard(dashboard);
|
|
1483
|
-
}
|
|
1484
|
-
else {
|
|
1485
|
-
console.error('Failed to save dashboard:', dashboard.LatestResult);
|
|
1486
|
-
}
|
|
1487
|
-
}
|
|
1488
|
-
catch (err) {
|
|
1489
|
-
console.error('Failed to create dashboard:', err);
|
|
1070
|
+
await DashboardEngine.Instance.Config(true, this.ProviderToUse.CurrentUser, this.ProviderToUse);
|
|
1490
1071
|
}
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
this.cdr.detectChanges();
|
|
1072
|
+
catch (error) {
|
|
1073
|
+
LogError(`Dashboards Library: could not reload the dashboards: ${error instanceof Error ? error.message : String(error)}`);
|
|
1494
1074
|
}
|
|
1495
1075
|
}
|
|
1496
|
-
|
|
1497
|
-
async createDashboard(categoryId) {
|
|
1498
|
-
return this.CreateDashboard(categoryId);
|
|
1499
|
-
}
|
|
1500
|
-
/**
|
|
1501
|
-
* Save the current dashboard
|
|
1502
|
-
*/
|
|
1503
|
-
async SaveDashboard() {
|
|
1504
|
-
if (!this.SelectedDashboard)
|
|
1505
|
-
return;
|
|
1076
|
+
async loadDashboards() {
|
|
1506
1077
|
try {
|
|
1507
1078
|
this.isLoading = true;
|
|
1508
1079
|
this.cdr.detectChanges();
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1080
|
+
// Use DashboardEngine for consistent cached data. Recently opened reads the user's dashboard
|
|
1081
|
+
// record logs (LoadRecentDashboardIds); RecentAccessService is only its change trigger.
|
|
1082
|
+
const engine = DashboardEngine.Instance;
|
|
1083
|
+
const [, recentDashboardIds] = await Promise.all([
|
|
1084
|
+
engine.Config(false),
|
|
1085
|
+
LoadRecentDashboardIds(this.ProviderToUse),
|
|
1086
|
+
]);
|
|
1087
|
+
this.recentDashboardIds = recentDashboardIds;
|
|
1088
|
+
this.readLibraryFromEngine();
|
|
1089
|
+
console.debug('[DashboardBrowserResource] Loaded from DashboardEngine:', {
|
|
1090
|
+
dashboardCount: this.Dashboards.length,
|
|
1091
|
+
categoryCount: this.Categories.length,
|
|
1092
|
+
sharedDashboardsInEffectiveMap: this.EffectiveCategoryMap.size,
|
|
1093
|
+
categories: this.Categories.map(c => ({ id: c.ID, name: c.Name, parentId: c.ParentID }))
|
|
1094
|
+
});
|
|
1095
|
+
// List is now loaded — apply any query-param selection. Use params deferred by
|
|
1096
|
+
// OnQueryParamsChanged during the load if present. Otherwise the first load reads
|
|
1097
|
+
// the tab's creation-time params (covers hosts that give no tab id); later loads
|
|
1098
|
+
// skip that snapshot, which would reopen a dashboard that was already opened.
|
|
1099
|
+
const params = this._pendingQueryParams ?? (this._initialQueryParamsApplied ? null : this.GetQueryParams());
|
|
1100
|
+
this._pendingQueryParams = null;
|
|
1101
|
+
this._initialQueryParamsApplied = true;
|
|
1102
|
+
// Params without `lib` or `category` keep the current location.
|
|
1103
|
+
this.applyLocation((params && ResolveBrowseLocation(params)) ?? this.currentLocation);
|
|
1104
|
+
if (params && Object.keys(params).length > 0) {
|
|
1105
|
+
this.applyDashboardSelectionFromParams(params);
|
|
1513
1106
|
}
|
|
1514
|
-
this.
|
|
1515
|
-
|
|
1516
|
-
this.originalConfig = this.SelectedDashboard.UIConfigDetails || '';
|
|
1517
|
-
// Update the dashboard in the list
|
|
1518
|
-
this.Dashboards = [...this.Dashboards];
|
|
1519
|
-
this.Mode = 'view';
|
|
1107
|
+
this.emitAgentContext();
|
|
1108
|
+
void this.refreshOwnerNames();
|
|
1520
1109
|
}
|
|
1521
1110
|
catch (err) {
|
|
1522
|
-
console.error('Failed to
|
|
1111
|
+
console.error('Failed to load dashboards:', err);
|
|
1523
1112
|
}
|
|
1524
1113
|
finally {
|
|
1525
1114
|
this.isLoading = false;
|
|
1115
|
+
this.NotifyLoadComplete();
|
|
1526
1116
|
this.cdr.detectChanges();
|
|
1527
1117
|
}
|
|
1528
1118
|
}
|
|
1529
|
-
/** @deprecated Use {@link SaveDashboard}. */
|
|
1530
|
-
async saveDashboard() {
|
|
1531
|
-
return this.SaveDashboard();
|
|
1532
|
-
}
|
|
1533
|
-
/**
|
|
1534
|
-
* Cancel editing and revert changes
|
|
1535
|
-
*/
|
|
1536
|
-
CancelEdit() {
|
|
1537
|
-
if (!this.SelectedDashboard) {
|
|
1538
|
-
this.BackToList();
|
|
1539
|
-
return;
|
|
1540
|
-
}
|
|
1541
|
-
this.SelectedDashboard.Name = this.originalName;
|
|
1542
|
-
this.SelectedDashboard.Description = this.originalDescription;
|
|
1543
|
-
this.SelectedDashboard.UIConfigDetails = this.originalConfig;
|
|
1544
|
-
this.EditingName = '';
|
|
1545
|
-
this.EditingDescription = '';
|
|
1546
|
-
this.Mode = 'view';
|
|
1547
|
-
this.cdr.detectChanges();
|
|
1548
|
-
}
|
|
1549
|
-
/** @deprecated Use {@link CancelEdit}. */
|
|
1550
|
-
cancelEdit() {
|
|
1551
|
-
return this.CancelEdit();
|
|
1552
|
-
}
|
|
1553
|
-
/**
|
|
1554
|
-
* Handle name input blur - validate name is not empty
|
|
1555
|
-
*/
|
|
1556
|
-
OnNameBlur() {
|
|
1557
|
-
if (!this.EditingName.trim()) {
|
|
1558
|
-
this.EditingName = this.originalName || 'Untitled Dashboard';
|
|
1559
|
-
}
|
|
1560
|
-
}
|
|
1561
|
-
/** @deprecated Use {@link OnNameBlur}. */
|
|
1562
|
-
onNameBlur() {
|
|
1563
|
-
return this.OnNameBlur();
|
|
1564
|
-
}
|
|
1565
|
-
// ========================================
|
|
1566
|
-
// Public Methods - Part Management
|
|
1567
|
-
// ========================================
|
|
1568
|
-
/**
|
|
1569
|
-
* Open the Add Part dialog
|
|
1570
|
-
*/
|
|
1571
|
-
OpenAddPartDialog() {
|
|
1572
|
-
this.ShowAddPanelDialog = true;
|
|
1573
|
-
this.cdr.detectChanges();
|
|
1574
|
-
}
|
|
1575
|
-
/** @deprecated Use {@link OpenAddPartDialog}. */
|
|
1576
|
-
openAddPartDialog() {
|
|
1577
|
-
return this.OpenAddPartDialog();
|
|
1578
|
-
}
|
|
1579
|
-
/**
|
|
1580
|
-
* Handle panel interaction events from the viewer
|
|
1581
|
-
*/
|
|
1582
|
-
OnPanelInteraction(event) {
|
|
1583
|
-
if (event.interactionType !== 'custom')
|
|
1584
|
-
return;
|
|
1585
|
-
const action = event.payload?.['action'];
|
|
1586
|
-
switch (action) {
|
|
1587
|
-
case 'add-panel-requested':
|
|
1588
|
-
this.OpenAddPartDialog();
|
|
1589
|
-
break;
|
|
1590
|
-
case 'configure-part-requested':
|
|
1591
|
-
this.OpenConfigDialog(event.panelId);
|
|
1592
|
-
break;
|
|
1593
|
-
case 'remove-part-requested':
|
|
1594
|
-
this.OpenRemoveConfirmDialog(event.panelId, event.payload?.['panelTitle'] || 'this part');
|
|
1595
|
-
break;
|
|
1596
|
-
}
|
|
1597
|
-
}
|
|
1598
|
-
/** @deprecated Use {@link OnPanelInteraction}. */
|
|
1599
|
-
onPanelInteraction(event) {
|
|
1600
|
-
return this.OnPanelInteraction(event);
|
|
1601
|
-
}
|
|
1602
|
-
/**
|
|
1603
|
-
* Handle navigation events from panels
|
|
1604
|
-
*/
|
|
1605
|
-
OnNavigationRequested(event) {
|
|
1606
|
-
const request = event.request;
|
|
1607
|
-
const openInNewTab = request.openInNewTab || false;
|
|
1608
|
-
switch (request.type) {
|
|
1609
|
-
case 'OpenEntityRecord': {
|
|
1610
|
-
// Navigate to entity record
|
|
1611
|
-
// recordId is either a full "F1|v1||F2|v2" segment or a bare value; a bare value maps onto
|
|
1612
|
-
// the entity's real key column, whatever it is called
|
|
1613
|
-
const entity = this.ProviderToUse.Entities.find(e => e.Name === request.entityName);
|
|
1614
|
-
const compositeKey = CompositeKey.FromURLSegment(entity, request.recordId);
|
|
1615
|
-
this.navigationService.OpenEntityRecord(request.entityName, compositeKey, { forceNewTab: openInNewTab });
|
|
1616
|
-
break;
|
|
1617
|
-
}
|
|
1618
|
-
case 'OpenDashboard': {
|
|
1619
|
-
// Navigate to another dashboard
|
|
1620
|
-
const targetDashboard = this.Dashboards.find(d => UUIDsEqual(d.ID, request.dashboardId));
|
|
1621
|
-
if (targetDashboard) {
|
|
1622
|
-
if (openInNewTab) {
|
|
1623
|
-
this.navigationService.OpenDashboard(targetDashboard.ID, targetDashboard.Name, { forceNewTab: true });
|
|
1624
|
-
}
|
|
1625
|
-
else {
|
|
1626
|
-
this.OpenDashboard(targetDashboard);
|
|
1627
|
-
}
|
|
1628
|
-
}
|
|
1629
|
-
break;
|
|
1630
|
-
}
|
|
1631
|
-
case 'OpenQuery': {
|
|
1632
|
-
// Navigate to query viewer
|
|
1633
|
-
const md = this.ProviderToUse;
|
|
1634
|
-
const queryInfo = md.Queries.find(q => UUIDsEqual(q.ID, request.queryId));
|
|
1635
|
-
if (queryInfo) {
|
|
1636
|
-
this.navigationService.OpenQuery(request.queryId, queryInfo.Name, { forceNewTab: openInNewTab });
|
|
1637
|
-
}
|
|
1638
|
-
break;
|
|
1639
|
-
}
|
|
1640
|
-
case 'OpenNavItem': {
|
|
1641
|
-
// Navigate to a specific nav item within an application
|
|
1642
|
-
const appId = request.appName ? this.resolveAppId(request.appName) : undefined;
|
|
1643
|
-
this.navigationService.OpenNavItemByName(request.navItemName, undefined, appId, {
|
|
1644
|
-
queryParams: request.queryParams
|
|
1645
|
-
});
|
|
1646
|
-
break;
|
|
1647
|
-
}
|
|
1648
|
-
}
|
|
1649
|
-
}
|
|
1650
|
-
/** @deprecated Use {@link OnNavigationRequested}. */
|
|
1651
|
-
onNavigationRequested(event) {
|
|
1652
|
-
return this.OnNavigationRequested(event);
|
|
1653
|
-
}
|
|
1654
|
-
/**
|
|
1655
|
-
* Resolve an application name to its ID
|
|
1656
|
-
*/
|
|
1657
|
-
resolveAppId(appName) {
|
|
1658
|
-
const md = this.ProviderToUse;
|
|
1659
|
-
const app = md.Applications.find(a => a.Name.toLowerCase() === appName.toLowerCase());
|
|
1660
|
-
return app?.ID;
|
|
1661
|
-
}
|
|
1662
1119
|
/**
|
|
1663
|
-
*
|
|
1120
|
+
* Reads the library from the engine cache: the dashboards the user can access (narrowed by an
|
|
1121
|
+
* agent search), the categories owned by or shared with the user (by name), and the permission
|
|
1122
|
+
* and effective-category maps.
|
|
1664
1123
|
*/
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
this.
|
|
1670
|
-
|
|
1671
|
-
this.emitAgentContext();
|
|
1672
|
-
this.cdr.detectChanges();
|
|
1673
|
-
}
|
|
1674
|
-
/** @deprecated Use {@link OnPanelAdded}. */
|
|
1675
|
-
async onPanelAdded(result) {
|
|
1676
|
-
return this.OnPanelAdded(result);
|
|
1124
|
+
readLibraryFromEngine() {
|
|
1125
|
+
const engine = DashboardEngine.Instance;
|
|
1126
|
+
const currentUserId = this.ProviderToUse.CurrentUser.ID;
|
|
1127
|
+
this.Dashboards = this.readSearchedDashboards();
|
|
1128
|
+
this.Categories = [...engine.GetAccessibleCategories(currentUserId)].sort((a, b) => a.Name.localeCompare(b.Name));
|
|
1129
|
+
this.buildPermissionMaps(engine, currentUserId);
|
|
1677
1130
|
}
|
|
1678
1131
|
/**
|
|
1679
|
-
*
|
|
1132
|
+
* The dashboards the user can access whose name or description contains the agent's search text
|
|
1133
|
+
* (all when there is none). The Library order is applied when the view is refreshed.
|
|
1680
1134
|
*/
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
this.
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1135
|
+
readSearchedDashboards() {
|
|
1136
|
+
const accessible = DashboardEngine.Instance.GetAccessibleDashboards(this.ProviderToUse.CurrentUser.ID);
|
|
1137
|
+
const q = this.agentSearchText.toLowerCase();
|
|
1138
|
+
return q
|
|
1139
|
+
? accessible.filter(d => (d.Name || '').toLowerCase().includes(q) ||
|
|
1140
|
+
(d.Description || '').toLowerCase().includes(q))
|
|
1141
|
+
: [...accessible];
|
|
1688
1142
|
}
|
|
1689
|
-
// ========================================
|
|
1690
|
-
// Public Methods - Config Dialog
|
|
1691
|
-
// ========================================
|
|
1692
1143
|
/**
|
|
1693
|
-
*
|
|
1144
|
+
* Reads the names of owners not read yet and not being read, then shows them on the cards.
|
|
1145
|
+
* Never throws.
|
|
1694
1146
|
*/
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
const
|
|
1699
|
-
|
|
1700
|
-
if (
|
|
1701
|
-
console.warn('Could not find panel or part type for config dialog');
|
|
1147
|
+
async refreshOwnerNames() {
|
|
1148
|
+
const provider = this.ProviderToUse;
|
|
1149
|
+
const userId = provider.CurrentUser.ID;
|
|
1150
|
+
const missing = OwnerIdsToLoad(DashboardEngine.Instance.GetAccessibleDashboards(userId), userId, this.ownerNames)
|
|
1151
|
+
.filter(id => !this.ownerReadsInFlight.has(id));
|
|
1152
|
+
if (missing.length === 0)
|
|
1702
1153
|
return;
|
|
1154
|
+
missing.forEach(id => this.ownerReadsInFlight.add(id));
|
|
1155
|
+
try {
|
|
1156
|
+
const loaded = await LoadDashboardOwnerNames(provider, missing);
|
|
1157
|
+
for (const id of missing)
|
|
1158
|
+
this.ownerNames.set(id, loaded.get(id) ?? '');
|
|
1159
|
+
this.refreshLibraryView();
|
|
1160
|
+
this.cdr.detectChanges();
|
|
1703
1161
|
}
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
this.ConfigDialogClass = partType.ConfigDialogClass || '';
|
|
1707
|
-
this.ShowConfigDialog = true;
|
|
1708
|
-
this.cdr.detectChanges();
|
|
1709
|
-
}
|
|
1710
|
-
/** @deprecated Use {@link OpenConfigDialog}. */
|
|
1711
|
-
openConfigDialog(panelId) {
|
|
1712
|
-
return this.OpenConfigDialog(panelId);
|
|
1713
|
-
}
|
|
1714
|
-
/**
|
|
1715
|
-
* Handle config dialog save
|
|
1716
|
-
*/
|
|
1717
|
-
OnConfigDialogSaved(result) {
|
|
1718
|
-
if (this.DashboardViewer && this.ConfigDialogPanel) {
|
|
1719
|
-
this.DashboardViewer.updatePanelConfig(this.ConfigDialogPanel.id, result.Config, result.Title, result.Icon);
|
|
1162
|
+
catch (error) {
|
|
1163
|
+
LogError(`Dashboards Library: could not read the dashboard owners: ${error instanceof Error ? error.message : String(error)}`);
|
|
1720
1164
|
}
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
this.emitAgentContext();
|
|
1724
|
-
}
|
|
1725
|
-
/** @deprecated Use {@link OnConfigDialogSaved}. */
|
|
1726
|
-
onConfigDialogSaved(result) {
|
|
1727
|
-
return this.OnConfigDialogSaved(result);
|
|
1728
|
-
}
|
|
1729
|
-
/**
|
|
1730
|
-
* Handle config dialog cancel
|
|
1731
|
-
*/
|
|
1732
|
-
OnConfigDialogCancelled() {
|
|
1733
|
-
this.closeConfigDialog();
|
|
1734
|
-
}
|
|
1735
|
-
/** @deprecated Use {@link OnConfigDialogCancelled}. */
|
|
1736
|
-
onConfigDialogCancelled() {
|
|
1737
|
-
return this.OnConfigDialogCancelled();
|
|
1738
|
-
}
|
|
1739
|
-
/**
|
|
1740
|
-
* Close the config dialog
|
|
1741
|
-
*/
|
|
1742
|
-
closeConfigDialog() {
|
|
1743
|
-
this.ShowConfigDialog = false;
|
|
1744
|
-
this.ConfigDialogPanel = null;
|
|
1745
|
-
this.ConfigDialogPartType = null;
|
|
1746
|
-
this.ConfigDialogClass = '';
|
|
1747
|
-
this.cdr.detectChanges();
|
|
1748
|
-
}
|
|
1749
|
-
// ========================================
|
|
1750
|
-
// Public Methods - Remove Confirm Dialog
|
|
1751
|
-
// ========================================
|
|
1752
|
-
/**
|
|
1753
|
-
* Open the remove confirmation dialog
|
|
1754
|
-
*/
|
|
1755
|
-
OpenRemoveConfirmDialog(panelId, panelTitle) {
|
|
1756
|
-
this.ConfirmPanelId = panelId;
|
|
1757
|
-
this.ConfirmPanelTitle = panelTitle;
|
|
1758
|
-
this.ShowConfirmDialog = true;
|
|
1759
|
-
this.cdr.detectChanges();
|
|
1760
|
-
}
|
|
1761
|
-
/** @deprecated Use {@link OpenRemoveConfirmDialog}. */
|
|
1762
|
-
openRemoveConfirmDialog(panelId, panelTitle) {
|
|
1763
|
-
return this.OpenRemoveConfirmDialog(panelId, panelTitle);
|
|
1764
|
-
}
|
|
1765
|
-
/**
|
|
1766
|
-
* Handle remove confirmation
|
|
1767
|
-
*/
|
|
1768
|
-
OnRemoveConfirmed() {
|
|
1769
|
-
if (this.DashboardViewer && this.ConfirmPanelId) {
|
|
1770
|
-
this.DashboardViewer.confirmRemovePanel(this.ConfirmPanelId);
|
|
1165
|
+
finally {
|
|
1166
|
+
missing.forEach(id => this.ownerReadsInFlight.delete(id));
|
|
1771
1167
|
}
|
|
1772
|
-
this.closeRemoveConfirmDialog();
|
|
1773
|
-
// Panel set changed — refresh opened-dashboard context for the agent.
|
|
1774
|
-
this.emitAgentContext();
|
|
1775
|
-
}
|
|
1776
|
-
/** @deprecated Use {@link OnRemoveConfirmed}. */
|
|
1777
|
-
onRemoveConfirmed() {
|
|
1778
|
-
return this.OnRemoveConfirmed();
|
|
1779
|
-
}
|
|
1780
|
-
/**
|
|
1781
|
-
* Handle remove cancel
|
|
1782
|
-
*/
|
|
1783
|
-
OnRemoveCancelled() {
|
|
1784
|
-
this.closeRemoveConfirmDialog();
|
|
1785
|
-
}
|
|
1786
|
-
/** @deprecated Use {@link OnRemoveCancelled}. */
|
|
1787
|
-
onRemoveCancelled() {
|
|
1788
|
-
return this.OnRemoveCancelled();
|
|
1789
1168
|
}
|
|
1790
1169
|
/**
|
|
1791
|
-
*
|
|
1170
|
+
* Builds the permission map, and the effective category of each shared dashboard, for every
|
|
1171
|
+
* dashboard the user can access. An agent search that narrows the list does not narrow the maps.
|
|
1792
1172
|
*/
|
|
1793
|
-
|
|
1794
|
-
this.
|
|
1795
|
-
this.
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
// Filter dashboards to only those accessible to the current user
|
|
1813
|
-
this.Dashboards = [...engine.GetAccessibleDashboards(currentUserId)].sort((a, b) => new Date(b.__mj_UpdatedAt).getTime() - new Date(a.__mj_UpdatedAt).getTime());
|
|
1814
|
-
// Filter categories to only those owned by or shared with the current user
|
|
1815
|
-
this.Categories = [...engine.GetAccessibleCategories(currentUserId)].sort((a, b) => a.Name.localeCompare(b.Name));
|
|
1816
|
-
// Build permissions map and effective category map for all dashboards
|
|
1817
|
-
this.DashboardPermissionsMap = new Map();
|
|
1818
|
-
this.EffectiveCategoryMap = new Map();
|
|
1819
|
-
// Get category links for current user (from engine's cached data)
|
|
1820
|
-
const userCategoryLinks = engine.DashboardCategoryLinks.filter(link => UUIDsEqual(link.UserID, currentUserId));
|
|
1821
|
-
for (const dashboard of this.Dashboards) {
|
|
1822
|
-
const perms = engine.GetDashboardPermissions(dashboard.ID, currentUserId);
|
|
1823
|
-
this.DashboardPermissionsMap.set(dashboard.ID, perms);
|
|
1824
|
-
// For shared dashboards (not owned), determine effective category
|
|
1825
|
-
if (!perms.IsOwner) {
|
|
1826
|
-
// Look for a category link for this dashboard
|
|
1827
|
-
const categoryLink = userCategoryLinks.find(link => UUIDsEqual(link.DashboardID, dashboard.ID));
|
|
1828
|
-
if (categoryLink) {
|
|
1829
|
-
// User has explicitly organized this shared dashboard
|
|
1830
|
-
this.EffectiveCategoryMap.set(dashboard.ID, categoryLink.DashboardCategoryID);
|
|
1831
|
-
}
|
|
1832
|
-
else {
|
|
1833
|
-
// No link exists - show in root (null category)
|
|
1834
|
-
this.EffectiveCategoryMap.set(dashboard.ID, null);
|
|
1835
|
-
}
|
|
1173
|
+
buildPermissionMaps(engine, currentUserId) {
|
|
1174
|
+
this.DashboardPermissionsMap = new Map();
|
|
1175
|
+
this.EffectiveCategoryMap = new Map();
|
|
1176
|
+
// Get category links for current user (from engine's cached data)
|
|
1177
|
+
const userCategoryLinksByDashboard = IndexByUUID(engine.DashboardCategoryLinks.filter(link => UUIDsEqual(link.UserID, currentUserId)), link => link.DashboardID);
|
|
1178
|
+
for (const dashboard of engine.GetAccessibleDashboards(currentUserId)) {
|
|
1179
|
+
const perms = engine.GetDashboardPermissions(dashboard.ID, currentUserId);
|
|
1180
|
+
this.DashboardPermissionsMap.set(dashboard.ID, perms);
|
|
1181
|
+
// For shared dashboards (not owned), determine effective category
|
|
1182
|
+
if (!perms.IsOwner) {
|
|
1183
|
+
// Look for a category link for this dashboard
|
|
1184
|
+
const categoryLink = userCategoryLinksByDashboard.Get(dashboard.ID);
|
|
1185
|
+
if (categoryLink) {
|
|
1186
|
+
// User has explicitly organized this shared dashboard
|
|
1187
|
+
this.EffectiveCategoryMap.set(dashboard.ID, categoryLink.DashboardCategoryID);
|
|
1188
|
+
}
|
|
1189
|
+
else {
|
|
1190
|
+
// No link exists - show in root (null category)
|
|
1191
|
+
this.EffectiveCategoryMap.set(dashboard.ID, null);
|
|
1836
1192
|
}
|
|
1837
|
-
// For owned dashboards, we don't add to effectiveCategoryMap
|
|
1838
|
-
// so the browser will use the dashboard's actual CategoryID
|
|
1839
|
-
}
|
|
1840
|
-
console.debug('[DashboardBrowserResource] Loaded from DashboardEngine:', {
|
|
1841
|
-
dashboardCount: this.Dashboards.length,
|
|
1842
|
-
categoryCount: this.Categories.length,
|
|
1843
|
-
sharedDashboardsInEffectiveMap: this.EffectiveCategoryMap.size,
|
|
1844
|
-
categories: this.Categories.map(c => ({ id: c.ID, name: c.Name, parentId: c.ParentID }))
|
|
1845
|
-
});
|
|
1846
|
-
// List is now loaded — apply any query-param selection. Use params deferred by
|
|
1847
|
-
// OnQueryParamsChanged during the cold load if present, otherwise read the tab's
|
|
1848
|
-
// current params (covers deep links and Home pin navigation).
|
|
1849
|
-
const params = this._pendingQueryParams ?? this.GetQueryParams();
|
|
1850
|
-
this._pendingQueryParams = null;
|
|
1851
|
-
if (params && Object.keys(params).length > 0) {
|
|
1852
|
-
this.SelectedCategoryId = params['category'] || this.SelectedCategoryId;
|
|
1853
|
-
this.applyDashboardSelectionFromParams(params);
|
|
1854
1193
|
}
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
}
|
|
1858
|
-
catch (err) {
|
|
1859
|
-
console.error('Failed to load dashboards:', err);
|
|
1860
|
-
}
|
|
1861
|
-
finally {
|
|
1862
|
-
this.isLoading = false;
|
|
1863
|
-
this.cdr.detectChanges();
|
|
1194
|
+
// For owned dashboards, we don't add to effectiveCategoryMap
|
|
1195
|
+
// so the browser will use the dashboard's actual CategoryID
|
|
1864
1196
|
}
|
|
1865
1197
|
}
|
|
1866
1198
|
// ========================================
|
|
1867
1199
|
// Private Methods - URL Query Params
|
|
1868
1200
|
// ========================================
|
|
1869
1201
|
/**
|
|
1870
|
-
* Update the URL query params for the current tab
|
|
1202
|
+
* Update the URL query params for the current tab: the Library filter (`lib`,
|
|
1203
|
+
* omitted for All), the category filter, and no `dashboard` param (the Library
|
|
1204
|
+
* never shows a dashboard itself).
|
|
1871
1205
|
* Routes through BaseResourceComponent.UpdateQueryParams, which updates the tab
|
|
1872
1206
|
* configuration (triggering the shell's URL sync while respecting app-scoped
|
|
1873
1207
|
* routes) and is auto-suppressed while delivering OnQueryParamsChanged so
|
|
1874
1208
|
* reflecting URL → state never loops back into a redundant URL push.
|
|
1875
1209
|
*/
|
|
1876
1210
|
updateUrlQueryParams() {
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
}
|
|
1882
|
-
else {
|
|
1883
|
-
queryParams['category'] = null;
|
|
1884
|
-
}
|
|
1885
|
-
// Track dashboard (for browser back/forward support)
|
|
1886
|
-
if (this.SelectedDashboard) {
|
|
1887
|
-
queryParams['dashboard'] = this.SelectedDashboard.ID;
|
|
1888
|
-
}
|
|
1889
|
-
else {
|
|
1890
|
-
queryParams['dashboard'] = null;
|
|
1891
|
-
}
|
|
1892
|
-
// Push via the base-class method (auto-suppressed during OnQueryParamsChanged
|
|
1893
|
-
// delivery) so the URL update respects app-scoped routes without loop-back.
|
|
1894
|
-
this.UpdateQueryParams(queryParams);
|
|
1211
|
+
this.UpdateQueryParams({
|
|
1212
|
+
...LocationQueryParams(this.currentLocation),
|
|
1213
|
+
dashboard: null,
|
|
1214
|
+
});
|
|
1895
1215
|
}
|
|
1896
1216
|
// ========================================
|
|
1897
1217
|
// Private Methods - Category Helpers
|
|
@@ -1917,39 +1237,46 @@ let DashboardBrowserResourceComponent = class DashboardBrowserResourceComponent
|
|
|
1917
1237
|
if (stored === 'cards' || stored === 'list') {
|
|
1918
1238
|
this.ViewMode = stored;
|
|
1919
1239
|
}
|
|
1240
|
+
this.RailCollapsed = UserInfoEngine.Instance.GetSetting(DashboardBrowserResourceComponent_1.RAIL_COLLAPSED_SETTING) === 'true';
|
|
1920
1241
|
}
|
|
1921
1242
|
saveViewPreference(mode) {
|
|
1922
1243
|
// TODO: Save to User Settings entity
|
|
1923
1244
|
localStorage.setItem('dashboard-browser-view-mode', mode);
|
|
1924
1245
|
}
|
|
1925
1246
|
static ɵfac = function DashboardBrowserResourceComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || DashboardBrowserResourceComponent)(i0.ɵɵdirectiveInject(i0.ChangeDetectorRef)); };
|
|
1926
|
-
static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: DashboardBrowserResourceComponent, selectors: [["mj-dashboard-browser-resource"]],
|
|
1927
|
-
i0.ɵɵ
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
i0.ɵɵ
|
|
1931
|
-
|
|
1932
|
-
i0.ɵɵ
|
|
1933
|
-
i0.ɵɵ
|
|
1934
|
-
i0.ɵɵ
|
|
1247
|
+
static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: DashboardBrowserResourceComponent, selectors: [["mj-dashboard-browser-resource"]], standalone: false, features: [i0.ɵɵInheritDefinitionFeature], decls: 9, vars: 30, consts: [["Title", "Library", "Icon", "fa-solid fa-layer-group", "Subtitle", "Every dashboard you own or that is shared with you"], ["meta", ""], ["Label", "dashboards", 3, "Count", "Total"], ["Direction", "row", 3, "Flex", "Padding"], ["MobileTitle", "Dashboards", 3, "CollapsedChange", "ItemClicked", "ItemToggled", "Sections", "ActiveId", "ExpandedIds", "Collapsible", "Collapsed"], [3, "DashboardOpen", "DashboardEdit", "DashboardDelete", "DashboardMove", "DashboardCreate", "DashboardFavoriteToggle", "CategoryCreate", "CategoryDelete", "CategoryChange", "ViewPreferenceChange", "Dashboards", "Categories", "SelectedCategoryId", "FlatMode", "ViewMode", "IsLoading", "ShowCreateButton", "AllowMultiSelect", "AllowDragDrop", "DashboardPermissions", "EffectiveCategoryMap", "FavoriteIds", "ShowFavorites", "OwnerLabels", "FlatEmptyIcon", "FlatEmptyTitle", "FlatEmptyMessage", "FlatEmptyWelcome"], [3, "Confirmed", "Cancelled", "Visible", "MaxLength", "Processing"]], template: function DashboardBrowserResourceComponent_Template(rf, ctx) { if (rf & 1) {
|
|
1248
|
+
i0.ɵɵelementStart(0, "mj-page-layout")(1, "mj-page-header", 0)(2, "div", 1);
|
|
1249
|
+
i0.ɵɵelement(3, "mj-stat-badge", 2);
|
|
1250
|
+
i0.ɵɵelementEnd()();
|
|
1251
|
+
i0.ɵɵelementStart(4, "mj-page-body", 3)(5, "mj-left-nav", 4);
|
|
1252
|
+
i0.ɵɵlistener("CollapsedChange", function DashboardBrowserResourceComponent_Template_mj_left_nav_CollapsedChange_5_listener($event) { return ctx.OnRailCollapsedChange($event); })("ItemClicked", function DashboardBrowserResourceComponent_Template_mj_left_nav_ItemClicked_5_listener($event) { return ctx.OnRailItemClicked($event); })("ItemToggled", function DashboardBrowserResourceComponent_Template_mj_left_nav_ItemToggled_5_listener($event) { return ctx.OnRailItemToggled($event); });
|
|
1253
|
+
i0.ɵɵelementEnd();
|
|
1254
|
+
i0.ɵɵelementStart(6, "mj-left-nav-content")(7, "mj-dashboard-browser", 5);
|
|
1255
|
+
i0.ɵɵlistener("DashboardOpen", function DashboardBrowserResourceComponent_Template_mj_dashboard_browser_DashboardOpen_7_listener($event) { return ctx.OnDashboardOpen($event); })("DashboardEdit", function DashboardBrowserResourceComponent_Template_mj_dashboard_browser_DashboardEdit_7_listener($event) { return ctx.OnDashboardEdit($event); })("DashboardDelete", function DashboardBrowserResourceComponent_Template_mj_dashboard_browser_DashboardDelete_7_listener($event) { return ctx.OnDashboardDelete($event); })("DashboardMove", function DashboardBrowserResourceComponent_Template_mj_dashboard_browser_DashboardMove_7_listener($event) { return ctx.OnDashboardMove($event); })("DashboardCreate", function DashboardBrowserResourceComponent_Template_mj_dashboard_browser_DashboardCreate_7_listener($event) { return ctx.OnDashboardCreate($event); })("DashboardFavoriteToggle", function DashboardBrowserResourceComponent_Template_mj_dashboard_browser_DashboardFavoriteToggle_7_listener($event) { return ctx.OnDashboardFavoriteToggle($event); })("CategoryCreate", function DashboardBrowserResourceComponent_Template_mj_dashboard_browser_CategoryCreate_7_listener($event) { return ctx.OnCategoryCreate($event); })("CategoryDelete", function DashboardBrowserResourceComponent_Template_mj_dashboard_browser_CategoryDelete_7_listener($event) { return ctx.OnCategoryDelete($event); })("CategoryChange", function DashboardBrowserResourceComponent_Template_mj_dashboard_browser_CategoryChange_7_listener($event) { return ctx.OnCategoryChange($event); })("ViewPreferenceChange", function DashboardBrowserResourceComponent_Template_mj_dashboard_browser_ViewPreferenceChange_7_listener($event) { return ctx.OnViewPreferenceChange($event); });
|
|
1256
|
+
i0.ɵɵelementEnd()()()();
|
|
1257
|
+
i0.ɵɵelementStart(8, "mj-dashboard-name-dialog", 6);
|
|
1258
|
+
i0.ɵɵlistener("Confirmed", function DashboardBrowserResourceComponent_Template_mj_dashboard_name_dialog_Confirmed_8_listener($event) { return ctx.OnNewDashboardNamed($event); })("Cancelled", function DashboardBrowserResourceComponent_Template_mj_dashboard_name_dialog_Cancelled_8_listener() { return ctx.OnNewDashboardCancelled(); });
|
|
1935
1259
|
i0.ɵɵelementEnd();
|
|
1936
1260
|
} if (rf & 2) {
|
|
1261
|
+
i0.ɵɵadvance(3);
|
|
1262
|
+
i0.ɵɵproperty("Count", ctx.LibraryCounts[ctx.LibraryFilter])("Total", ctx.LibraryCounts.all);
|
|
1263
|
+
i0.ɵɵadvance();
|
|
1264
|
+
i0.ɵɵproperty("Flex", true)("Padding", false);
|
|
1937
1265
|
i0.ɵɵadvance();
|
|
1938
|
-
i0.ɵɵ
|
|
1266
|
+
i0.ɵɵproperty("Sections", ctx.RailSections)("ActiveId", ctx.RailActiveId)("ExpandedIds", ctx.RailExpandedIds)("Collapsible", true)("Collapsed", ctx.RailCollapsed);
|
|
1267
|
+
i0.ɵɵadvance(2);
|
|
1268
|
+
i0.ɵɵproperty("Dashboards", ctx.VisibleDashboards)("Categories", ctx.Categories)("SelectedCategoryId", ctx.IsFlatView ? null : ctx.SelectedCategoryId)("FlatMode", ctx.IsFlatView)("ViewMode", ctx.ViewMode)("IsLoading", ctx.isLoading)("ShowCreateButton", true)("AllowMultiSelect", true)("AllowDragDrop", true)("DashboardPermissions", ctx.DashboardPermissionsMap)("EffectiveCategoryMap", ctx.EffectiveCategoryMap)("FavoriteIds", ctx.FavoriteIds)("ShowFavorites", true)("OwnerLabels", ctx.OwnerLabels)("FlatEmptyIcon", ctx.EmptyState.Icon)("FlatEmptyTitle", ctx.EmptyState.Title)("FlatEmptyMessage", ctx.EmptyState.Message)("FlatEmptyWelcome", ctx.EmptyState.Welcome);
|
|
1939
1269
|
i0.ɵɵadvance();
|
|
1940
|
-
i0.ɵɵ
|
|
1941
|
-
} }, dependencies: [i1.
|
|
1270
|
+
i0.ɵɵproperty("Visible", ctx.ShowNewDashboardDialog)("MaxLength", ctx.NewDashboardNameMaxLength)("Processing", ctx.IsCreatingDashboard);
|
|
1271
|
+
} }, dependencies: [i1.MJPageHeaderComponent, i1.MJPageLayoutComponent, i1.MJPageBodyComponent, i1.MJStatBadgeComponent, i1.MJLeftNavComponent, i1.MJLeftNavContentComponent, i2.DashboardBrowserComponent, i2.DashboardNameDialogComponent], styles: ["\n\n\n\n[_nghost-%COMP%] {\n display: block;\n height: 100%;\n width: 100%;\n}"], changeDetection: 0 });
|
|
1942
1272
|
};
|
|
1943
|
-
DashboardBrowserResourceComponent = __decorate([
|
|
1273
|
+
DashboardBrowserResourceComponent = DashboardBrowserResourceComponent_1 = __decorate([
|
|
1944
1274
|
RegisterClass(BaseResourceComponent, 'DashboardBrowserResource')
|
|
1945
1275
|
], DashboardBrowserResourceComponent);
|
|
1946
1276
|
export { DashboardBrowserResourceComponent };
|
|
1947
1277
|
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(DashboardBrowserResourceComponent, [{
|
|
1948
1278
|
type: Component,
|
|
1949
|
-
args: [{ standalone: false, selector: 'mj-dashboard-browser-resource', changeDetection: ChangeDetectionStrategy.OnPush, template: "<!-- Dashboard Browser Resource -->\n<div class=\"dashboard-browser-resource\">\n <!-- List Mode - Uses Generic DashboardBrowserComponent -->\n @if (mode === 'list') {\n <mj-dashboard-browser\n [Dashboards]=\"dashboards\"\n [Categories]=\"categories\"\n [SelectedCategoryId]=\"selectedCategoryId\"\n [ViewMode]=\"viewMode\"\n [IsLoading]=\"isLoading\"\n [ShowCreateButton]=\"true\"\n [AllowMultiSelect]=\"true\"\n [AllowDragDrop]=\"true\"\n [DashboardPermissions]=\"dashboardPermissionsMap\"\n [EffectiveCategoryMap]=\"effectiveCategoryMap\"\n (DashboardOpen)=\"onDashboardOpen($event)\"\n (DashboardEdit)=\"onDashboardEdit($event)\"\n (DashboardDelete)=\"onDashboardDelete($event)\"\n (DashboardMove)=\"onDashboardMove($event)\"\n (DashboardCreate)=\"onDashboardCreate($event)\"\n (CategoryCreate)=\"onCategoryCreate($event)\"\n (CategoryDelete)=\"onCategoryDelete($event)\"\n (CategoryChange)=\"onCategoryChange($event)\"\n (ViewPreferenceChange)=\"onViewPreferenceChange($event)\">\n </mj-dashboard-browser>\n }\n\n <!-- View/Edit Mode -->\n @if (mode === 'view' || mode === 'edit') {\n <!-- View Mode Toolbar - matches browser toolbar structure -->\n @if (mode === 'view') {\n <div class=\"viewer-toolbar\">\n <!-- Breadcrumb navigation (includes its own padding/styling) -->\n <mj-dashboard-breadcrumb\n [Categories]=\"categories\"\n [CurrentCategoryId]=\"selectedDashboard?.CategoryID || null\"\n [CurrentDashboard]=\"selectedDashboard\"\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 <!-- Right-side Actions -->\n <div class=\"toolbar-actions\">\n <!-- Shared indicator for non-owned dashboards -->\n @if (!selectedDashboardPermissions.IsOwner && selectedDashboardPermissions.CanRead) {\n <span class=\"shared-indicator\">\n <i class=\"fa-solid fa-share-nodes\"></i>\n Shared with you\n </span>\n }\n <button\n class=\"btn-icon\"\n title=\"Open in its own tab\"\n (click)=\"openInNewTab()\">\n <i class=\"fa-solid fa-up-right-from-square\"></i>\n </button>\n <!-- Share button - only shown if user can share -->\n @if (selectedDashboardPermissions.CanShare) {\n <button\n class=\"btn-icon\"\n title=\"Share Dashboard\"\n (click)=\"openShareDialog()\">\n <i class=\"fa-solid fa-user-plus\"></i>\n </button>\n }\n <!-- Edit button - only shown if user can edit -->\n @if (selectedDashboardPermissions.CanEdit) {\n <button\n class=\"btn-icon\"\n title=\"Edit Dashboard\"\n (click)=\"toggleEditMode()\">\n <i class=\"fa-solid fa-edit\"></i>\n </button>\n }\n </div>\n </div>\n }\n <!-- Edit Mode Header - different layout -->\n @if (mode === 'edit') {\n <div class=\"viewer-header editing\">\n <div class=\"header-left\">\n <!-- Add Part button on the left -->\n <button\n class=\"btn-add-part\"\n (click)=\"openAddPartDialog()\">\n <i class=\"fa-solid fa-plus\"></i>\n Add Part\n </button>\n <!-- Separator -->\n <div class=\"header-separator\"></div>\n <!-- Editable name/description -->\n <div class=\"dashboard-info-edit\">\n <input\n type=\"text\"\n class=\"dashboard-name-input\"\n [(ngModel)]=\"editingName\"\n placeholder=\"Dashboard name\"\n (blur)=\"onNameBlur()\">\n <input\n type=\"text\"\n class=\"dashboard-description-input\"\n [(ngModel)]=\"editingDescription\"\n placeholder=\"Add a description...\">\n </div>\n </div>\n <div class=\"header-right\">\n <!-- Save, Cancel buttons -->\n <button\n class=\"btn-primary\"\n (click)=\"saveDashboard()\">\n <i class=\"fa-solid fa-save\"></i>\n Save\n </button>\n <button\n class=\"btn-cancel\"\n (click)=\"cancelEdit()\">\n Cancel\n </button>\n </div>\n </div>\n }\n <!-- Dashboard Viewer -->\n <div class=\"viewer-content\">\n <mj-dashboard-viewer\n #dashboardViewer\n [dashboard]=\"selectedDashboard\"\n [isEditing]=\"mode === 'edit'\"\n [showToolbar]=\"false\"\n [showBreadcrumb]=\"false\"\n (panelInteraction)=\"onPanelInteraction($event)\"\n (navigationRequested)=\"onNavigationRequested($event)\">\n </mj-dashboard-viewer>\n </div>\n <!-- Add Panel Dialog -->\n @if (dashboardViewer) {\n <mj-add-panel-dialog\n [visible]=\"showAddPanelDialog\"\n [partTypes]=\"dashboardViewer.getPartTypes()\"\n (panelAdded)=\"onPanelAdded($event)\"\n (cancelled)=\"onAddPanelCancelled()\">\n </mj-add-panel-dialog>\n }\n <!-- Generic Edit Part Dialog - dynamically loads config panels via ClassFactory -->\n <mj-edit-part-dialog\n [Visible]=\"showConfigDialog\"\n [PartType]=\"configDialogPartType\"\n [Panel]=\"configDialogPanel\"\n [Config]=\"configDialogPanel?.config || null\"\n (Saved)=\"onConfigDialogSaved($event)\"\n (Cancelled)=\"onConfigDialogCancelled()\">\n </mj-edit-part-dialog>\n <!-- Remove Part Confirmation Dialog -->\n <mj-confirm-dialog\n [visible]=\"showConfirmDialog\"\n type=\"danger\"\n title=\"Remove Part\"\n [message]=\"'Are you sure you want to remove \\'' + confirmPanelTitle + '\\' from this dashboard?'\"\n confirmText=\"Remove\"\n cancelText=\"Cancel\"\n (confirmed)=\"onRemoveConfirmed()\"\n (cancelled)=\"onRemoveCancelled()\">\n </mj-confirm-dialog>\n <!-- Share Dashboard Dialog -->\n <mj-dashboard-share-dialog\n [Visible]=\"showShareDialog\"\n [Dashboard]=\"selectedDashboard\"\n (Result)=\"onShareDialogResult($event)\">\n </mj-dashboard-share-dialog>\n }\n</div>\n", styles: ["/* Dashboard Browser Resource Styles */\n/* This is a thin wrapper - list mode styles are in the generic DashboardBrowserComponent */\n\n:host {\n display: block;\n height: 100%;\n width: 100%;\n}\n\n.dashboard-browser-resource {\n display: flex;\n flex-direction: column;\n height: 100%;\n width: 100%;\n background: var(--mj-bg-surface-card);\n position: relative;\n}\n\n/* Generic browser fills the container in list mode */\n.dashboard-browser-resource mj-dashboard-browser {\n display: block;\n height: 100%;\n width: 100%;\n}\n\n/* ========================================\n Viewer Toolbar (View Mode) - Matches Browser Toolbar\n ======================================== */\n\n.viewer-toolbar {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 0 24px 0 0;\n background: var(--mj-bg-surface);\n border-bottom: 1px solid var(--mj-border-default);\n gap: 16px;\n}\n\n.viewer-toolbar .toolbar-actions {\n display: flex;\n align-items: center;\n gap: 12px;\n}\n\n/* Shared with you indicator */\n.shared-indicator {\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 6px 12px;\n background: var(--mj-bg-info);\n border-radius: 16px;\n font-size: 12px;\n font-weight: 500;\n color: var(--mj-brand-primary);\n white-space: nowrap;\n}\n\n.shared-indicator i {\n font-size: 11px;\n}\n\n/* ========================================\n Viewer Header (Edit Mode)\n ======================================== */\n\n.viewer-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 12px 24px;\n background: var(--mj-bg-surface);\n border-bottom: 1px solid var(--mj-border-default);\n transition: background 0.2s, border-color 0.2s;\n}\n\n/* Edit mode header styling */\n.viewer-header.editing {\n background: var(--mj-bg-brand);\n border-bottom: 2px solid var(--mj-brand-primary);\n}\n\n.viewer-header .header-left {\n display: flex;\n align-items: center;\n gap: 12px;\n flex: 1;\n}\n\n.viewer-header h2 {\n margin: 0;\n font-size: 18px;\n font-weight: 500;\n color: var(--mj-text-primary);\n}\n\n.viewer-header .header-right {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.back-btn {\n width: 36px;\n height: 36px;\n display: flex;\n align-items: center;\n justify-content: center;\n border: none;\n border-radius: 6px;\n background: var(--mj-bg-surface-card);\n color: var(--mj-text-secondary);\n cursor: pointer;\n transition: all 0.2s;\n}\n\n.back-btn:hover {\n background: var(--mj-border-default);\n color: var(--mj-text-primary);\n}\n\n/* Add Part button (left side in edit mode) */\n.btn-add-part {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 8px 16px;\n border: none;\n border-radius: 6px;\n background: var(--mj-brand-primary);\n color: var(--mj-text-inverse);\n font-size: 13px;\n font-weight: 500;\n cursor: pointer;\n transition: background 0.2s, transform 0.1s;\n box-shadow: 0 2px 4px color-mix(in srgb, var(--mj-brand-primary) 30%, transparent);\n}\n\n.btn-add-part:hover {\n background: var(--mj-brand-primary-hover);\n transform: translateY(-1px);\n box-shadow: 0 3px 6px color-mix(in srgb, var(--mj-brand-primary) 40%, transparent);\n}\n\n.btn-add-part i {\n font-size: 12px;\n}\n\n/* Header separator */\n.header-separator {\n width: 1px;\n height: 28px;\n background: color-mix(in srgb, var(--mj-brand-primary) 30%, transparent);\n margin: 0 4px;\n}\n\n/* ========================================\n Buttons\n ======================================== */\n\n.btn-primary {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 10px 18px;\n border: none;\n border-radius: 6px;\n background: var(--mj-brand-primary);\n color: var(--mj-text-inverse);\n font-size: 14px;\n font-weight: 500;\n cursor: pointer;\n transition: background 0.2s;\n}\n\n.btn-primary:hover {\n background: var(--mj-brand-primary-hover);\n}\n\n.btn-icon {\n width: 36px;\n height: 36px;\n display: flex;\n align-items: center;\n justify-content: center;\n border: 1px solid var(--mj-border-default);\n border-radius: 6px;\n background: var(--mj-bg-surface);\n color: var(--mj-text-secondary);\n cursor: pointer;\n transition: all 0.2s;\n}\n\n.btn-icon:hover {\n background: var(--mj-bg-surface-card);\n}\n\n.btn-icon.active {\n background: var(--mj-bg-info);\n border-color: var(--mj-brand-primary);\n color: var(--mj-brand-primary);\n}\n\n/* Cancel button styling */\n.btn-cancel {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 10px 18px;\n border: 1px solid var(--mj-border-strong);\n border-radius: 6px;\n background: var(--mj-bg-surface);\n color: var(--mj-text-secondary);\n font-size: 14px;\n cursor: pointer;\n transition: all 0.2s;\n}\n\n.btn-cancel:hover {\n background: var(--mj-bg-surface-card);\n border-color: var(--mj-border-strong);\n color: var(--mj-text-primary);\n}\n\n/* ========================================\n Dashboard Info Edit (Edit Mode)\n ======================================== */\n\n.dashboard-info-edit {\n display: flex;\n align-items: center;\n gap: 16px;\n flex: 1;\n}\n\n.dashboard-name-input {\n border: 1px solid transparent;\n border-radius: 4px;\n padding: 6px 12px;\n font-size: 16px;\n font-weight: 500;\n color: var(--mj-text-primary);\n background: color-mix(in srgb, var(--mj-bg-surface) 70%, transparent);\n outline: none;\n min-width: 200px;\n max-width: 300px;\n transition: border-color 0.2s, background 0.2s, box-shadow 0.2s;\n}\n\n.dashboard-name-input:hover {\n background: color-mix(in srgb, var(--mj-bg-surface) 90%, transparent);\n}\n\n.dashboard-name-input:focus {\n background: var(--mj-bg-surface);\n border-color: var(--mj-brand-primary);\n box-shadow: 0 0 0 2px color-mix(in srgb, var(--mj-brand-primary) 20%, transparent);\n}\n\n.dashboard-description-input {\n border: 1px solid transparent;\n border-radius: 4px;\n padding: 6px 12px;\n font-size: 13px;\n color: var(--mj-text-secondary);\n background: color-mix(in srgb, var(--mj-bg-surface) 50%, transparent);\n outline: none;\n flex: 1;\n min-width: 150px;\n max-width: 400px;\n transition: border-color 0.2s, background 0.2s, box-shadow 0.2s;\n}\n\n.dashboard-description-input:hover {\n background: color-mix(in srgb, var(--mj-bg-surface) 80%, transparent);\n}\n\n.dashboard-description-input:focus {\n background: var(--mj-bg-surface);\n border-color: var(--mj-brand-primary);\n box-shadow: 0 0 0 2px color-mix(in srgb, var(--mj-brand-primary) 20%, transparent);\n}\n\n.dashboard-description-input::placeholder {\n color: var(--mj-text-disabled);\n font-style: normal;\n}\n\n/* ========================================\n Viewer Content\n ======================================== */\n\n.viewer-content {\n flex: 1;\n overflow: hidden;\n min-height: 0;\n}\n\n.viewer-content mj-dashboard-viewer {\n display: block;\n width: 100%;\n height: 100%;\n}\n\n/* ========================================\n Responsive\n ======================================== */\n\n@media (max-width: 768px) {\n .viewer-header {\n flex-direction: column;\n gap: 12px;\n align-items: stretch;\n }\n\n .viewer-header .header-left {\n flex-wrap: wrap;\n }\n\n .dashboard-info-edit {\n flex-direction: column;\n align-items: stretch;\n }\n\n .dashboard-name-input,\n .dashboard-description-input {\n max-width: none;\n }\n}\n"] }]
|
|
1950
|
-
}], () => [{ type: i0.ChangeDetectorRef }],
|
|
1951
|
-
|
|
1952
|
-
args: ['dashboardViewer']
|
|
1953
|
-
}] }); })();
|
|
1954
|
-
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(DashboardBrowserResourceComponent, { className: "DashboardBrowserResourceComponent", filePath: "src/DashboardBrowser/dashboard-browser-resource.component.ts", lineNumber: 77 }); })();
|
|
1279
|
+
args: [{ standalone: false, selector: 'mj-dashboard-browser-resource', changeDetection: ChangeDetectionStrategy.OnPush, template: "<!-- Library page of the Dashboards app -->\n<mj-page-layout>\n <mj-page-header Title=\"Library\" Icon=\"fa-solid fa-layer-group\" Subtitle=\"Every dashboard you own or that is shared with you\">\n <div meta>\n <mj-stat-badge [Count]=\"LibraryCounts[LibraryFilter]\" [Total]=\"LibraryCounts.all\" Label=\"dashboards\"></mj-stat-badge>\n </div>\n </mj-page-header>\n\n <mj-page-body [Flex]=\"true\" [Padding]=\"false\" Direction=\"row\">\n <!-- Library filters and the category tree -->\n <mj-left-nav\n MobileTitle=\"Dashboards\"\n [Sections]=\"RailSections\"\n [ActiveId]=\"RailActiveId\"\n [ExpandedIds]=\"RailExpandedIds\"\n [Collapsible]=\"true\"\n [Collapsed]=\"RailCollapsed\"\n (CollapsedChange)=\"OnRailCollapsedChange($event)\"\n (ItemClicked)=\"OnRailItemClicked($event)\"\n (ItemToggled)=\"OnRailItemToggled($event)\">\n </mj-left-nav>\n\n <mj-left-nav-content>\n <!-- Generic browser: a flat list for every Library filter and Uncategorized, a folder for a category. A click opens the dashboard in the preview tab; a Shift, Ctrl or Cmd click opens it in a separate tab. -->\n <mj-dashboard-browser\n [Dashboards]=\"VisibleDashboards\"\n [Categories]=\"Categories\"\n [SelectedCategoryId]=\"IsFlatView ? null : SelectedCategoryId\"\n [FlatMode]=\"IsFlatView\"\n [ViewMode]=\"ViewMode\"\n [IsLoading]=\"isLoading\"\n [ShowCreateButton]=\"true\"\n [AllowMultiSelect]=\"true\"\n [AllowDragDrop]=\"true\"\n [DashboardPermissions]=\"DashboardPermissionsMap\"\n [EffectiveCategoryMap]=\"EffectiveCategoryMap\"\n [FavoriteIds]=\"FavoriteIds\"\n [ShowFavorites]=\"true\"\n [OwnerLabels]=\"OwnerLabels\"\n [FlatEmptyIcon]=\"EmptyState.Icon\"\n [FlatEmptyTitle]=\"EmptyState.Title\"\n [FlatEmptyMessage]=\"EmptyState.Message\"\n [FlatEmptyWelcome]=\"EmptyState.Welcome\"\n (DashboardOpen)=\"OnDashboardOpen($event)\"\n (DashboardEdit)=\"OnDashboardEdit($event)\"\n (DashboardDelete)=\"OnDashboardDelete($event)\"\n (DashboardMove)=\"OnDashboardMove($event)\"\n (DashboardCreate)=\"OnDashboardCreate($event)\"\n (DashboardFavoriteToggle)=\"OnDashboardFavoriteToggle($event)\"\n (CategoryCreate)=\"OnCategoryCreate($event)\"\n (CategoryDelete)=\"OnCategoryDelete($event)\"\n (CategoryChange)=\"OnCategoryChange($event)\"\n (ViewPreferenceChange)=\"OnViewPreferenceChange($event)\">\n </mj-dashboard-browser>\n </mj-left-nav-content>\n </mj-page-body>\n</mj-page-layout>\n\n<!-- New dashboard: asks for the name first; Cancel creates nothing -->\n<mj-dashboard-name-dialog\n [Visible]=\"ShowNewDashboardDialog\"\n [MaxLength]=\"NewDashboardNameMaxLength\"\n [Processing]=\"IsCreatingDashboard\"\n (Confirmed)=\"OnNewDashboardNamed($event)\"\n (Cancelled)=\"OnNewDashboardCancelled()\">\n</mj-dashboard-name-dialog>\n", styles: ["/* Dashboard Browser Resource Styles */\n/* The page chrome, the rail and the generic browser own their layout. The host fills the tab.\n <mj-page-body Direction=\"row\"> stacks the rail above the content at narrow widths. */\n\n:host {\n display: block;\n height: 100%;\n width: 100%;\n}\n"] }]
|
|
1280
|
+
}], () => [{ type: i0.ChangeDetectorRef }], null); })();
|
|
1281
|
+
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(DashboardBrowserResourceComponent, { className: "DashboardBrowserResourceComponent", filePath: "src/DashboardBrowser/dashboard-browser-resource.component.ts", lineNumber: 101 }); })();
|
|
1955
1282
|
//# sourceMappingURL=dashboard-browser-resource.component.js.map
|