@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.
Files changed (177) hide show
  1. package/dist/AI/components/analytics/realtime/realtime-transcripts-data.d.ts +4 -3
  2. package/dist/AI/components/analytics/realtime/realtime-transcripts-data.d.ts.map +1 -1
  3. package/dist/AI/components/analytics/realtime/realtime-transcripts-data.js +8 -5
  4. package/dist/AI/components/analytics/realtime/realtime-transcripts-data.js.map +1 -1
  5. package/dist/AI/components/duplicates/compare-value.component.d.ts +67 -0
  6. package/dist/AI/components/duplicates/compare-value.component.d.ts.map +1 -0
  7. package/dist/AI/components/duplicates/compare-value.component.js +168 -0
  8. package/dist/AI/components/duplicates/compare-value.component.js.map +1 -0
  9. package/dist/AI/components/duplicates/duplicate-detection-resource.component.d.ts +49 -3
  10. package/dist/AI/components/duplicates/duplicate-detection-resource.component.d.ts.map +1 -1
  11. package/dist/AI/components/duplicates/duplicate-detection-resource.component.js +287 -169
  12. package/dist/AI/components/duplicates/duplicate-detection-resource.component.js.map +1 -1
  13. package/dist/AI/components/duplicates/duplicate-detection-run-scope.d.ts +77 -0
  14. package/dist/AI/components/duplicates/duplicate-detection-run-scope.d.ts.map +1 -0
  15. package/dist/AI/components/duplicates/duplicate-detection-run-scope.js +116 -0
  16. package/dist/AI/components/duplicates/duplicate-detection-run-scope.js.map +1 -0
  17. package/dist/AI/components/prompts/model-prompt-priority-matrix.component.d.ts +1 -0
  18. package/dist/AI/components/prompts/model-prompt-priority-matrix.component.d.ts.map +1 -1
  19. package/dist/AI/components/prompts/model-prompt-priority-matrix.component.js +16 -4
  20. package/dist/AI/components/prompts/model-prompt-priority-matrix.component.js.map +1 -1
  21. package/dist/AI/components/tags/tags-resource.component.d.ts +2 -0
  22. package/dist/AI/components/tags/tags-resource.component.d.ts.map +1 -1
  23. package/dist/AI/components/tags/tags-resource.component.js +12 -6
  24. package/dist/AI/components/tags/tags-resource.component.js.map +1 -1
  25. package/dist/AI/components/vectors/vector-index-auto.d.ts +29 -0
  26. package/dist/AI/components/vectors/vector-index-auto.d.ts.map +1 -0
  27. package/dist/AI/components/vectors/vector-index-auto.js +59 -0
  28. package/dist/AI/components/vectors/vector-index-auto.js.map +1 -0
  29. package/dist/AI/components/vectors/vector-management-resource.component.d.ts +9 -0
  30. package/dist/AI/components/vectors/vector-management-resource.component.d.ts.map +1 -1
  31. package/dist/AI/components/vectors/vector-management-resource.component.js +65 -20
  32. package/dist/AI/components/vectors/vector-management-resource.component.js.map +1 -1
  33. package/dist/Actions/components/actions-overview.component.d.ts.map +1 -1
  34. package/dist/Actions/components/actions-overview.component.js +7 -3
  35. package/dist/Actions/components/actions-overview.component.js.map +1 -1
  36. package/dist/Actions/components/categories-list-view.component.d.ts.map +1 -1
  37. package/dist/Actions/components/categories-list-view.component.js +3 -2
  38. package/dist/Actions/components/categories-list-view.component.js.map +1 -1
  39. package/dist/ComponentStudio/component-studio-dashboard.component.d.ts.map +1 -1
  40. package/dist/ComponentStudio/component-studio-dashboard.component.js +1 -0
  41. package/dist/ComponentStudio/component-studio-dashboard.component.js.map +1 -1
  42. package/dist/ComponentStudio/components/ai-assistant/ai-assistant-panel.component.d.ts +0 -3
  43. package/dist/ComponentStudio/components/ai-assistant/ai-assistant-panel.component.d.ts.map +1 -1
  44. package/dist/ComponentStudio/components/ai-assistant/ai-assistant-panel.component.js +5 -14
  45. package/dist/ComponentStudio/components/ai-assistant/ai-assistant-panel.component.js.map +1 -1
  46. package/dist/ComponentStudio/components/artifact-load-dialog.component.d.ts.map +1 -1
  47. package/dist/ComponentStudio/components/artifact-load-dialog.component.js +1 -0
  48. package/dist/ComponentStudio/components/artifact-load-dialog.component.js.map +1 -1
  49. package/dist/ComponentStudio/components/artifact-selection-dialog.component.d.ts.map +1 -1
  50. package/dist/ComponentStudio/components/artifact-selection-dialog.component.js +1 -0
  51. package/dist/ComponentStudio/components/artifact-selection-dialog.component.js.map +1 -1
  52. package/dist/Credentials/components/credentials-list-resource.component.d.ts +2 -0
  53. package/dist/Credentials/components/credentials-list-resource.component.d.ts.map +1 -1
  54. package/dist/Credentials/components/credentials-list-resource.component.js +12 -10
  55. package/dist/Credentials/components/credentials-list-resource.component.js.map +1 -1
  56. package/dist/DashboardBrowser/dashboard-browser-agent-context.d.ts +14 -43
  57. package/dist/DashboardBrowser/dashboard-browser-agent-context.d.ts.map +1 -1
  58. package/dist/DashboardBrowser/dashboard-browser-agent-context.js +6 -29
  59. package/dist/DashboardBrowser/dashboard-browser-agent-context.js.map +1 -1
  60. package/dist/DashboardBrowser/dashboard-browser-resource.component.d.ts +174 -321
  61. package/dist/DashboardBrowser/dashboard-browser-resource.component.d.ts.map +1 -1
  62. package/dist/DashboardBrowser/dashboard-browser-resource.component.js +522 -1195
  63. package/dist/DashboardBrowser/dashboard-browser-resource.component.js.map +1 -1
  64. package/dist/DashboardBrowser/dashboard-library-filter.d.ts +116 -0
  65. package/dist/DashboardBrowser/dashboard-library-filter.d.ts.map +1 -0
  66. package/dist/DashboardBrowser/dashboard-library-filter.js +277 -0
  67. package/dist/DashboardBrowser/dashboard-library-filter.js.map +1 -0
  68. package/dist/DashboardBrowser/dashboard-share-dialog.component.d.ts +1 -1
  69. package/dist/DashboardBrowser/dashboard-share-dialog.component.js.map +1 -1
  70. package/dist/DashboardBrowser/dashboards-agent-tools.d.ts +26 -0
  71. package/dist/DashboardBrowser/dashboards-agent-tools.d.ts.map +1 -0
  72. package/dist/DashboardBrowser/dashboards-agent-tools.js +32 -0
  73. package/dist/DashboardBrowser/dashboards-agent-tools.js.map +1 -0
  74. package/dist/DashboardBrowser/dashboards-categories-resource.component.d.ts +58 -0
  75. package/dist/DashboardBrowser/dashboards-categories-resource.component.d.ts.map +1 -0
  76. package/dist/DashboardBrowser/dashboards-categories-resource.component.js +391 -0
  77. package/dist/DashboardBrowser/dashboards-categories-resource.component.js.map +1 -0
  78. package/dist/DashboardBrowser/dashboards-categories.helpers.d.ts +37 -0
  79. package/dist/DashboardBrowser/dashboards-categories.helpers.d.ts.map +1 -0
  80. package/dist/DashboardBrowser/dashboards-categories.helpers.js +68 -0
  81. package/dist/DashboardBrowser/dashboards-categories.helpers.js.map +1 -0
  82. package/dist/DashboardEditor/dashboard-editor.component.d.ts +295 -0
  83. package/dist/DashboardEditor/dashboard-editor.component.d.ts.map +1 -0
  84. package/dist/DashboardEditor/dashboard-editor.component.js +1022 -0
  85. package/dist/DashboardEditor/dashboard-editor.component.js.map +1 -0
  86. package/dist/DashboardEditor/dashboard-editor.types.d.ts +23 -0
  87. package/dist/DashboardEditor/dashboard-editor.types.d.ts.map +1 -0
  88. package/dist/DashboardEditor/dashboard-editor.types.js +3 -0
  89. package/dist/DashboardEditor/dashboard-editor.types.js.map +1 -0
  90. package/dist/DashboardEditor/dashboard-viewer-factory.d.ts +11 -0
  91. package/dist/DashboardEditor/dashboard-viewer-factory.d.ts.map +1 -0
  92. package/dist/DashboardEditor/dashboard-viewer-factory.js +17 -0
  93. package/dist/DashboardEditor/dashboard-viewer-factory.js.map +1 -0
  94. package/dist/DataExplorer/data-explorer-dashboard.component.d.ts +26 -0
  95. package/dist/DataExplorer/data-explorer-dashboard.component.d.ts.map +1 -1
  96. package/dist/DataExplorer/data-explorer-dashboard.component.js +302 -219
  97. package/dist/DataExplorer/data-explorer-dashboard.component.js.map +1 -1
  98. package/dist/DataExplorer/services/explorer-state.service.d.ts +1 -1
  99. package/dist/DataExplorer/services/explorer-state.service.js +2 -5
  100. package/dist/DataExplorer/services/explorer-state.service.js.map +1 -1
  101. package/dist/FormBuilder/form-builder-resource.component.d.ts +0 -14
  102. package/dist/FormBuilder/form-builder-resource.component.d.ts.map +1 -1
  103. package/dist/FormBuilder/form-builder-resource.component.js +7 -29
  104. package/dist/FormBuilder/form-builder-resource.component.js.map +1 -1
  105. package/dist/Home/home-agent-context.d.ts +29 -10
  106. package/dist/Home/home-agent-context.d.ts.map +1 -1
  107. package/dist/Home/home-agent-context.js +50 -24
  108. package/dist/Home/home-agent-context.js.map +1 -1
  109. package/dist/Home/home-dashboard-switcher.component.d.ts +95 -0
  110. package/dist/Home/home-dashboard-switcher.component.d.ts.map +1 -0
  111. package/dist/Home/home-dashboard-switcher.component.js +442 -0
  112. package/dist/Home/home-dashboard-switcher.component.js.map +1 -0
  113. package/dist/Home/home-dashboard.component.d.ts +330 -17
  114. package/dist/Home/home-dashboard.component.d.ts.map +1 -1
  115. package/dist/Home/home-dashboard.component.js +1702 -1014
  116. package/dist/Home/home-dashboard.component.js.map +1 -1
  117. package/dist/Home/home-menus.d.ts +38 -0
  118. package/dist/Home/home-menus.d.ts.map +1 -0
  119. package/dist/Home/home-menus.js +53 -0
  120. package/dist/Home/home-menus.js.map +1 -0
  121. package/dist/Home/home-pinned-dashboards.d.ts +33 -0
  122. package/dist/Home/home-pinned-dashboards.d.ts.map +1 -0
  123. package/dist/Home/home-pinned-dashboards.js +76 -0
  124. package/dist/Home/home-pinned-dashboards.js.map +1 -0
  125. package/dist/PredictiveStudio/components/ps-production.component.d.ts +5 -1
  126. package/dist/PredictiveStudio/components/ps-production.component.d.ts.map +1 -1
  127. package/dist/PredictiveStudio/components/ps-production.component.js +64 -41
  128. package/dist/PredictiveStudio/components/ps-production.component.js.map +1 -1
  129. package/dist/QueryBrowser/query-browser-resource.component.d.ts.map +1 -1
  130. package/dist/QueryBrowser/query-browser-resource.component.js +3 -2
  131. package/dist/QueryBrowser/query-browser-resource.component.js.map +1 -1
  132. package/dist/RealtimeRecordings/realtime-recordings-dashboard.component.js +2 -2
  133. package/dist/RealtimeRecordings/realtime-recordings-dashboard.component.js.map +1 -1
  134. package/dist/TelephonyOperations/telephony-operations-dashboard.component.d.ts +1 -0
  135. package/dist/TelephonyOperations/telephony-operations-dashboard.component.d.ts.map +1 -1
  136. package/dist/TelephonyOperations/telephony-operations-dashboard.component.js +7 -0
  137. package/dist/TelephonyOperations/telephony-operations-dashboard.component.js.map +1 -1
  138. package/dist/Testing/components/testing-explorer.component.d.ts.map +1 -1
  139. package/dist/Testing/components/testing-explorer.component.js +7 -7
  140. package/dist/Testing/components/testing-explorer.component.js.map +1 -1
  141. package/dist/ai-dashboards.module.d.ts +21 -20
  142. package/dist/ai-dashboards.module.d.ts.map +1 -1
  143. package/dist/ai-dashboards.module.js +4 -1
  144. package/dist/ai-dashboards.module.js.map +1 -1
  145. package/dist/core-dashboards.module.d.ts +69 -62
  146. package/dist/core-dashboards.module.d.ts.map +1 -1
  147. package/dist/core-dashboards.module.js +22 -2
  148. package/dist/core-dashboards.module.js.map +1 -1
  149. package/dist/data-explorer-dashboards.module.d.ts +1 -1
  150. package/dist/data-explorer-dashboards.module.d.ts.map +1 -1
  151. package/dist/data-explorer-dashboards.module.js +3 -1
  152. package/dist/data-explorer-dashboards.module.js.map +1 -1
  153. package/dist/public-api.d.ts +2 -0
  154. package/dist/public-api.d.ts.map +1 -1
  155. package/dist/public-api.js +3 -0
  156. package/dist/public-api.js.map +1 -1
  157. package/dist/shared/dashboard-library-changes.d.ts +23 -0
  158. package/dist/shared/dashboard-library-changes.d.ts.map +1 -0
  159. package/dist/shared/dashboard-library-changes.js +26 -0
  160. package/dist/shared/dashboard-library-changes.js.map +1 -0
  161. package/dist/shared/dashboard-owner-names.d.ts +17 -0
  162. package/dist/shared/dashboard-owner-names.d.ts.map +1 -0
  163. package/dist/shared/dashboard-owner-names.js +65 -0
  164. package/dist/shared/dashboard-owner-names.js.map +1 -0
  165. package/dist/shared/dashboard-recents.d.ts +34 -0
  166. package/dist/shared/dashboard-recents.d.ts.map +1 -0
  167. package/dist/shared/dashboard-recents.js +87 -0
  168. package/dist/shared/dashboard-recents.js.map +1 -0
  169. package/dist/shared/dashboards-app-install.d.ts +59 -0
  170. package/dist/shared/dashboards-app-install.d.ts.map +1 -0
  171. package/dist/shared/dashboards-app-install.js +97 -0
  172. package/dist/shared/dashboards-app-install.js.map +1 -0
  173. package/dist/shared/dashboards-app.helpers.d.ts +42 -0
  174. package/dist/shared/dashboards-app.helpers.d.ts.map +1 -0
  175. package/dist/shared/dashboards-app.helpers.js +143 -0
  176. package/dist/shared/dashboards-app.helpers.js.map +1 -0
  177. 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
- import { Component, ChangeDetectionStrategy, ViewChild } from '@angular/core';
8
- import { RegisterClass, UUIDsEqual } from '@memberjunction/global';
9
- import { CompositeKey } from '@memberjunction/core';
10
- import { BaseResourceComponent } from '@memberjunction/ng-shared';
11
- import { DashboardEngine } from '@memberjunction/core-entities';
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 { createDefaultDashboardConfig, extractPanelsFromLayout } from '@memberjunction/ng-dashboard-viewer';
24
+ import { DASHBOARD_NAME_MAX_LENGTH } from '@memberjunction/ng-dashboard-viewer';
15
25
  import * as i0 from "@angular/core";
16
- import * as i1 from "@angular/forms";
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
- * Resource component for browsing, creating, and editing dashboards.
152
- * Uses the generic DashboardBrowserComponent for list mode and handles
153
- * view/edit mode internally with routing integration.
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
- DashboardViewer;
377
- /** @deprecated Use {@link DashboardViewer}. */
378
- get dashboardViewer() {
379
- return this.DashboardViewer;
380
- }
381
- /** @deprecated Use {@link DashboardViewer}. */
382
- set dashboardViewer(value) {
383
- this.DashboardViewer = value;
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.syncAgentToolsForMode();
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
- // Category is a cheap assignment with no list lookup — apply it eagerly either way.
419
- this.SelectedCategoryId = params['category'] || null;
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 selection until it is.
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
- * Open/close the dashboard to match the `dashboard` query param. Requires the
430
- * dashboard list to be loaded. URL pushes from openDashboard()/backToList() are
431
- * auto-suppressed while delivering, so this reflects URL → state without looping.
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.OpenDashboard(dashboard);
226
+ this.openDashboard(dashboard);
444
227
  }
228
+ this.clearDashboardQueryParam();
445
229
  }
446
- else {
447
- this.BackToList();
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(data) {
454
- return 'Dashboards';
251
+ async GetResourceDisplayName(_data) {
252
+ return 'Library';
455
253
  }
456
- async GetResourceIconClass(data) {
457
- return 'fa-solid fa-gauge-high';
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 Dashboard Browser exposes ONLY read-only /
463
- // navigational tools to the AI agent. The tool-set is MODE-SCOPED (mirrors
464
- // the Data Explorer):
465
- // COMMON (both modes): SearchDashboards, OpenDashboard (by name/id —
466
- // still useful while viewing, to switch dashboards), RefreshDashboardList.
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 / remove / configure a panel — are
476
- // intentionally NOT exposed. The agent helps the user find, open, and
477
- // understand dashboards (including the panels on the open one, its owner,
478
- // dates, access level, the category tree, and who it's shared with); the
479
- // user performs every mutation from the UI. Do NOT add a mutating tool here
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 mode, selection, category filter,
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
- // Keep the registered tool-set aligned with the current mode before we
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
- Mode: this.Mode,
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.Dashboards.length,
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: selectedCategory?.Name ?? null,
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
- * Read the panels/widgets on the currently-open dashboard from the viewer.
518
- *
519
- * The viewer holds the live `DashboardConfig` whose `layout` embeds each
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
- * Re-register the agent client tools when the effective tool-mode flips.
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
- syncAgentToolsForMode() {
576
- const effective = this.Mode === 'list' ? 'list' : 'open';
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 available in BOTH modes — find, switch to, and reload dashboards.
586
- * OpenDashboard stays available while viewing so the agent can switch to
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 for viewing (inline). Accepts either the dashboard NAME (as listed in VisibleDashboards) or its ID. Works from the list and while another dashboard is open (to switch).',
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). Defaults to the open dashboard; pass a dashboardId (or name) to inspect another accessible dashboard. Read-only — returns no secrets.',
635
- ParameterSchema: { type: 'object', properties: { dashboardId: { type: 'string', description: 'Optional dashboard ID or name. Defaults to the open dashboard.' } } },
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 available only while browsing the LIST — category filtering, clear
644
- * filters, and view-mode toggling. These have no meaning while a single
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 root).',
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 root). Prefer SelectCategory, which also accepts a category name.',
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 — both the text search and the category filter — and return to the full list at the root category.',
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 = matched.sort((a, b) => new Date(b.__mj_UpdatedAt).getTime() - new Date(a.__mj_UpdatedAt).getTime());
740
- this.Mode = 'list';
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) and return
748
- * to the list. Resolves a supplied name (case-insensitive) to its id against
749
- * the loaded, accessible category list — mirroring the Data Explorer's
750
- * SelectView name→id resolution.
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.SelectedCategoryId = null;
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.SelectedCategoryId = match.ID;
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 both the text search and the category filter, returning to the full root list. */
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
- const engine = DashboardEngine.Instance;
788
- const md = this.ProviderToUse;
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 for inline viewing by NAME or ID. Resolves a supplied name
797
- * (case-insensitive) to its dashboard against the loaded, accessible list —
798
- * mirroring the Data Explorer's SelectView name→id resolution.
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 raw = dashboardNameOrId.trim();
802
- if (!raw)
803
- return { Success: false, ErrorMessage: 'A dashboard name or ID is required.' };
804
- // Search against the full accessible set (not just this.dashboards, which a
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 (GetDashboardPanels / GetDashboardDetail /
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 an optional `dashboardId` tool param (ID **or** name, or omitted)
838
- * to a concrete accessible dashboard. When omitted/empty, defaults to the
839
- * currently-open dashboard. Returns null with a tolerant error result when
840
- * nothing matches.
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
- if (this.SelectedDashboard) {
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: accessible.filter(d => d.CategoryID && UUIDsEqual(d.CategoryID, cat.ID)).length,
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
- if (event.OpenInNewTab) {
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
- * Open the current dashboard in its own dedicated Explorer tab
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.EditDashboard(event.Dashboard);
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 = DashboardEngine.Instance.DashboardCategoryLinks.filter(link => UUIDsEqual(link.DashboardID, dashboard.ID) && UUIDsEqual(link.UserID, currentUserId));
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
- this.SelectedCategoryId = event.TargetCategoryId;
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
- await this.CreateDashboard(event.CategoryId);
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 category change from generic browser - update URL
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.SelectedCategoryId = event.CategoryId;
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 - Navigation
1007
+ // Public Methods - Dashboard CRUD
1333
1008
  // ========================================
1334
1009
  /**
1335
- * Open a dashboard for viewing
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
- OpenDashboard(dashboard) {
1338
- this.SelectedDashboard = dashboard;
1339
- this.Mode = 'view';
1340
- // Compute permissions for the selected dashboard
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
- /** @deprecated Use {@link OpenDashboard}. */
1349
- openDashboard(dashboard) {
1350
- return this.OpenDashboard(dashboard);
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
- * Go back to list view
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
- BackToList() {
1384
- this.SelectedDashboard = null;
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
- * Close the share dialog
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 CloseShareDialog}. */
1437
- closeShareDialog() {
1438
- return this.CloseShareDialog();
1055
+ /** @deprecated Use {@link OpenNewDashboardDialog}. */
1056
+ async createDashboard(categoryId) {
1057
+ return this.CreateDashboard(categoryId);
1439
1058
  }
1440
- /**
1441
- * Handle share dialog result
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
- // Public Methods - Dashboard CRUD
1065
+ // Private Methods - Data Loading
1458
1066
  // ========================================
1459
- /**
1460
- * Create a new dashboard
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.isLoading = true;
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
- finally {
1492
- this.isLoading = false;
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
- /** @deprecated Use {@link CreateDashboard}. */
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
- this.SelectedDashboard.Name = this.EditingName;
1510
- this.SelectedDashboard.Description = this.EditingDescription;
1511
- if (this.DashboardViewer) {
1512
- await this.DashboardViewer.save();
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.originalName = this.EditingName;
1515
- this.originalDescription = this.EditingDescription;
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 save dashboard:', err);
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
- * Handle add panel dialog result
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
- async OnPanelAdded(result) {
1666
- if (this.DashboardViewer) {
1667
- await this.DashboardViewer.addPanel(result.PartType.ID, result.Config, result.Title, result.Icon);
1668
- }
1669
- this.ShowAddPanelDialog = false;
1670
- // Panel set changed — refresh opened-dashboard context for the agent.
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
- * Handle add panel dialog cancel
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
- OnAddPanelCancelled() {
1682
- this.ShowAddPanelDialog = false;
1683
- this.cdr.detectChanges();
1684
- }
1685
- /** @deprecated Use {@link OnAddPanelCancelled}. */
1686
- onAddPanelCancelled() {
1687
- return this.OnAddPanelCancelled();
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
- * Open the config dialog for a panel
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
- OpenConfigDialog(panelId) {
1696
- if (!this.DashboardViewer)
1697
- return;
1698
- const panel = this.DashboardViewer.getPanel(panelId);
1699
- const partType = this.DashboardViewer.getPartTypeForPanel(panelId);
1700
- if (!panel || !partType) {
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
- this.ConfigDialogPanel = panel;
1705
- this.ConfigDialogPartType = partType;
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
- this.closeConfigDialog();
1722
- // A panel's title/icon/config may have changed — refresh agent context.
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
- * Close the remove confirm dialog
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
- closeRemoveConfirmDialog() {
1794
- this.ShowConfirmDialog = false;
1795
- this.ConfirmPanelId = '';
1796
- this.ConfirmPanelTitle = '';
1797
- this.cdr.detectChanges();
1798
- }
1799
- // ========================================
1800
- // Private Methods - Data Loading
1801
- // ========================================
1802
- async loadDashboards() {
1803
- try {
1804
- this.isLoading = true;
1805
- this.cdr.detectChanges();
1806
- // Use DashboardEngine for consistent cached data
1807
- const engine = DashboardEngine.Instance;
1808
- await engine.Config(false); // Wait for engine to load data
1809
- const md = this.ProviderToUse;
1810
- const currentUserId = md.CurrentUser.ID;
1811
- // Get data from engine - sort dashboards by updated date, categories by name
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
- this.emitAgentContext();
1856
- this.NotifyLoadComplete();
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
- const queryParams = {};
1878
- // Track category
1879
- if (this.SelectedCategoryId) {
1880
- queryParams['category'] = this.SelectedCategoryId;
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"]], viewQuery: function DashboardBrowserResourceComponent_Query(rf, ctx) { if (rf & 1) {
1927
- i0.ɵɵviewQuery(_c0, 5);
1928
- } if (rf & 2) {
1929
- let _t;
1930
- i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.DashboardViewer = _t.first);
1931
- } }, standalone: false, features: [i0.ɵɵInheritDefinitionFeature], decls: 3, vars: 2, consts: [["dashboardViewer", ""], [1, "dashboard-browser-resource"], [3, "Dashboards", "Categories", "SelectedCategoryId", "ViewMode", "IsLoading", "ShowCreateButton", "AllowMultiSelect", "AllowDragDrop", "DashboardPermissions", "EffectiveCategoryMap"], [3, "DashboardOpen", "DashboardEdit", "DashboardDelete", "DashboardMove", "DashboardCreate", "CategoryCreate", "CategoryDelete", "CategoryChange", "ViewPreferenceChange", "Dashboards", "Categories", "SelectedCategoryId", "ViewMode", "IsLoading", "ShowCreateButton", "AllowMultiSelect", "AllowDragDrop", "DashboardPermissions", "EffectiveCategoryMap"], [1, "viewer-toolbar"], [1, "viewer-header", "editing"], [1, "viewer-content"], [3, "panelInteraction", "navigationRequested", "dashboard", "isEditing", "showToolbar", "showBreadcrumb"], [3, "visible", "partTypes"], [3, "Saved", "Cancelled", "Visible", "PartType", "Panel", "Config"], ["type", "danger", "title", "Remove Part", "confirmText", "Remove", "cancelText", "Cancel", 3, "confirmed", "cancelled", "visible", "message"], [3, "Result", "Visible", "Dashboard"], ["Size", "large", "RootIcon", "fa-solid fa-gauge-high", "RootLabel", "Dashboards", 3, "Navigate", "Categories", "CurrentCategoryId", "CurrentDashboard", "ShowDashboardName", "AllowDragDrop"], [1, "toolbar-actions"], [1, "shared-indicator"], ["title", "Open in its own tab", 1, "btn-icon", 3, "click"], [1, "fa-solid", "fa-up-right-from-square"], ["title", "Share Dashboard", 1, "btn-icon"], ["title", "Edit Dashboard", 1, "btn-icon"], [1, "fa-solid", "fa-share-nodes"], ["title", "Share Dashboard", 1, "btn-icon", 3, "click"], [1, "fa-solid", "fa-user-plus"], ["title", "Edit Dashboard", 1, "btn-icon", 3, "click"], [1, "fa-solid", "fa-edit"], [1, "header-left"], [1, "btn-add-part", 3, "click"], [1, "fa-solid", "fa-plus"], [1, "header-separator"], [1, "dashboard-info-edit"], ["type", "text", "placeholder", "Dashboard name", 1, "dashboard-name-input", 3, "ngModelChange", "blur", "ngModel"], ["type", "text", "placeholder", "Add a description...", 1, "dashboard-description-input", 3, "ngModelChange", "ngModel"], [1, "header-right"], [1, "btn-primary", 3, "click"], [1, "fa-solid", "fa-save"], [1, "btn-cancel", 3, "click"], [3, "panelAdded", "cancelled", "visible", "partTypes"]], template: function DashboardBrowserResourceComponent_Template(rf, ctx) { if (rf & 1) {
1932
- i0.ɵɵelementStart(0, "div", 1);
1933
- i0.ɵɵconditionalCreate(1, DashboardBrowserResourceComponent_Conditional_1_Template, 1, 10, "mj-dashboard-browser", 2);
1934
- i0.ɵɵconditionalCreate(2, DashboardBrowserResourceComponent_Conditional_2_Template, 9, 15);
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.ɵɵconditional(ctx.mode === "list" ? 1 : -1);
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.ɵɵconditional(ctx.mode === "view" || ctx.mode === "edit" ? 2 : -1);
1941
- } }, dependencies: [i1.DefaultValueAccessor, i1.NgControlStatus, i1.NgModel, i2.DashboardViewerComponent, i2.DashboardBrowserComponent, i2.DashboardBreadcrumbComponent, i2.AddPanelDialogComponent, i2.EditPartDialogComponent, i2.ConfirmDialogComponent, i3.DashboardShareDialogComponent], styles: ["\n\n\n[_nghost-%COMP%] {\n display: block;\n height: 100%;\n width: 100%;\n}\n\n.dashboard-browser-resource[_ngcontent-%COMP%] {\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\n.dashboard-browser-resource[_ngcontent-%COMP%] mj-dashboard-browser[_ngcontent-%COMP%] {\n display: block;\n height: 100%;\n width: 100%;\n}\n\n\n\n\n\n.viewer-toolbar[_ngcontent-%COMP%] {\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[_ngcontent-%COMP%] .toolbar-actions[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 12px;\n}\n\n\n.shared-indicator[_ngcontent-%COMP%] {\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[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 11px;\n}\n\n\n\n\n\n.viewer-header[_ngcontent-%COMP%] {\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\n.viewer-header.editing[_ngcontent-%COMP%] {\n background: var(--mj-bg-brand);\n border-bottom: 2px solid var(--mj-brand-primary);\n}\n\n.viewer-header[_ngcontent-%COMP%] .header-left[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 12px;\n flex: 1;\n}\n\n.viewer-header[_ngcontent-%COMP%] h2[_ngcontent-%COMP%] {\n margin: 0;\n font-size: 18px;\n font-weight: 500;\n color: var(--mj-text-primary);\n}\n\n.viewer-header[_ngcontent-%COMP%] .header-right[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.back-btn[_ngcontent-%COMP%] {\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[_ngcontent-%COMP%]:hover {\n background: var(--mj-border-default);\n color: var(--mj-text-primary);\n}\n\n\n.btn-add-part[_ngcontent-%COMP%] {\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[_ngcontent-%COMP%]: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[_ngcontent-%COMP%] i[_ngcontent-%COMP%] {\n font-size: 12px;\n}\n\n\n.header-separator[_ngcontent-%COMP%] {\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\n\n\n.btn-primary[_ngcontent-%COMP%] {\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[_ngcontent-%COMP%]:hover {\n background: var(--mj-brand-primary-hover);\n}\n\n.btn-icon[_ngcontent-%COMP%] {\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[_ngcontent-%COMP%]:hover {\n background: var(--mj-bg-surface-card);\n}\n\n.btn-icon.active[_ngcontent-%COMP%] {\n background: var(--mj-bg-info);\n border-color: var(--mj-brand-primary);\n color: var(--mj-brand-primary);\n}\n\n\n.btn-cancel[_ngcontent-%COMP%] {\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[_ngcontent-%COMP%]: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\n\n\n.dashboard-info-edit[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 16px;\n flex: 1;\n}\n\n.dashboard-name-input[_ngcontent-%COMP%] {\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[_ngcontent-%COMP%]:hover {\n background: color-mix(in srgb, var(--mj-bg-surface) 90%, transparent);\n}\n\n.dashboard-name-input[_ngcontent-%COMP%]: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[_ngcontent-%COMP%] {\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[_ngcontent-%COMP%]:hover {\n background: color-mix(in srgb, var(--mj-bg-surface) 80%, transparent);\n}\n\n.dashboard-description-input[_ngcontent-%COMP%]: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[_ngcontent-%COMP%]::placeholder {\n color: var(--mj-text-disabled);\n font-style: normal;\n}\n\n\n\n\n\n.viewer-content[_ngcontent-%COMP%] {\n flex: 1;\n overflow: hidden;\n min-height: 0;\n}\n\n.viewer-content[_ngcontent-%COMP%] mj-dashboard-viewer[_ngcontent-%COMP%] {\n display: block;\n width: 100%;\n height: 100%;\n}\n\n\n\n\n\n@media (max-width: 768px) {\n .viewer-header[_ngcontent-%COMP%] {\n flex-direction: column;\n gap: 12px;\n align-items: stretch;\n }\n\n .viewer-header[_ngcontent-%COMP%] .header-left[_ngcontent-%COMP%] {\n flex-wrap: wrap;\n }\n\n .dashboard-info-edit[_ngcontent-%COMP%] {\n flex-direction: column;\n align-items: stretch;\n }\n\n .dashboard-name-input[_ngcontent-%COMP%], \n .dashboard-description-input[_ngcontent-%COMP%] {\n max-width: none;\n }\n}"], changeDetection: 0 });
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 }], { DashboardViewer: [{
1951
- type: ViewChild,
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